// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/activity_log/counting_policy.h" #include <stddef.h> #include <stdint.h> #include <utility> #include "base/cancelable_callback.h" #include "base/command_line.h" #include "base/location.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/test/simple_test_clock.h" #include "base/test/test_timeouts.h" #include "base/thread_task_runner_handle.h" #include "build/build_config.h" #include "chrome/browser/extensions/activity_log/activity_log.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_profile.h" #include "content/public/test/test_browser_thread_bundle.h" #include "extensions/common/extension_builder.h" #include "sql/statement.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #endif using content::BrowserThread; namespace extensions { class CountingPolicyTest : public testing::Test { public: CountingPolicyTest() : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), saved_cmdline_(base::CommandLine::NO_PROGRAM) { #if defined OS_CHROMEOS test_user_manager_.reset(new chromeos::ScopedTestUserManager()); #endif base::CommandLine command_line(base::CommandLine::NO_PROGRAM); saved_cmdline_ = *base::CommandLine::ForCurrentProcess(); profile_.reset(new TestingProfile()); base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExtensionActivityLogging); extension_service_ = static_cast<TestExtensionSystem*>( ExtensionSystem::Get(profile_.get()))->CreateExtensionService (&command_line, base::FilePath(), false); } ~CountingPolicyTest() override { #if defined OS_CHROMEOS test_user_manager_.reset(); #endif base::RunLoop().RunUntilIdle(); profile_.reset(NULL); base::RunLoop().RunUntilIdle(); // Restore the original command line and undo the affects of SetUp(). *base::CommandLine::ForCurrentProcess() = saved_cmdline_; } // Wait for the task queue for the specified thread to empty. void WaitOnThread(const BrowserThread::ID& thread) { BrowserThread::PostTaskAndReply( thread, FROM_HERE, base::Bind(&base::DoNothing), base::MessageLoop::current()->QuitWhenIdleClosure()); base::MessageLoop::current()->Run(); } // A wrapper function for CheckReadFilteredData, so that we don't need to // enter empty string values for parameters we don't care about. void CheckReadData( ActivityLogDatabasePolicy* policy, const std::string& extension_id, int day, const base::Callback<void(scoped_ptr<Action::ActionVector>)>& checker) { CheckReadFilteredData( policy, extension_id, Action::ACTION_ANY, "", "", "", day, checker); } // A helper function to call ReadFilteredData on a policy object and wait for // the results to be processed. void CheckReadFilteredData( ActivityLogDatabasePolicy* policy, const std::string& extension_id, const Action::ActionType type, const std::string& api_name, const std::string& page_url, const std::string& arg_url, int day, const base::Callback<void(scoped_ptr<Action::ActionVector>)>& checker) { // Submit a request to the policy to read back some data, and call the // checker function when results are available. This will happen on the // database thread. policy->ReadFilteredData( extension_id, type, api_name, page_url, arg_url, day, base::Bind(&CountingPolicyTest::CheckWrapper, checker, base::MessageLoop::current()->QuitWhenIdleClosure())); // Set up a timeout for receiving results; if we haven't received anything // when the timeout triggers then assume that the test is broken. base::CancelableClosure timeout( base::Bind(&CountingPolicyTest::TimeoutCallback)); base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, timeout.callback(), TestTimeouts::action_timeout()); // Wait for results; either the checker or the timeout callbacks should // cause the main loop to exit. base::MessageLoop::current()->Run(); timeout.Cancel(); } // A helper function which verifies that the string_ids and url_ids tables in // the database have the specified sizes. static void CheckStringTableSizes(CountingPolicy* policy, int string_size, int url_size) { sql::Connection* db = policy->GetDatabaseConnection(); sql::Statement statement1(db->GetCachedStatement( sql::StatementID(SQL_FROM_HERE), "SELECT COUNT(*) FROM string_ids")); ASSERT_TRUE(statement1.Step()); ASSERT_EQ(string_size, statement1.ColumnInt(0)); sql::Statement statement2(db->GetCachedStatement( sql::StatementID(SQL_FROM_HERE), "SELECT COUNT(*) FROM url_ids")); ASSERT_TRUE(statement2.Step()); ASSERT_EQ(url_size, statement2.ColumnInt(0)); } // Checks that the number of queued actions to be written out does not exceed // kSizeThresholdForFlush. Runs on the database thread. static void CheckQueueSize(CountingPolicy* policy) { // This should be updated if kSizeThresholdForFlush in activity_database.cc // changes. ASSERT_LE(policy->queued_actions_.size(), 200U); } static void CheckWrapper( const base::Callback<void(scoped_ptr<Action::ActionVector>)>& checker, const base::Closure& done, scoped_ptr<Action::ActionVector> results) { checker.Run(std::move(results)); done.Run(); } static void TimeoutCallback() { base::MessageLoop::current()->QuitWhenIdle(); FAIL() << "Policy test timed out waiting for results"; } static void RetrieveActions_FetchFilteredActions0( scoped_ptr<std::vector<scoped_refptr<Action> > > i) { ASSERT_EQ(0, static_cast<int>(i->size())); } static void RetrieveActions_FetchFilteredActions1( scoped_ptr<std::vector<scoped_refptr<Action> > > i) { ASSERT_EQ(1, static_cast<int>(i->size())); } static void RetrieveActions_FetchFilteredActions2( scoped_ptr<std::vector<scoped_refptr<Action> > > i) { ASSERT_EQ(2, static_cast<int>(i->size())); } static void RetrieveActions_FetchFilteredActions300( scoped_ptr<std::vector<scoped_refptr<Action> > > i) { ASSERT_EQ(300, static_cast<int>(i->size())); } static void Arguments_Stripped(scoped_ptr<Action::ActionVector> i) { scoped_refptr<Action> last = i->front(); CheckAction(*last.get(), "odlameecjipmbmbejkplpemijjgpljce", Action::ACTION_API_CALL, "extension.connect", "[\"hello\",\"world\"]", "", "", "", 1); } static void Arguments_GetSinglesAction( scoped_ptr<Action::ActionVector> actions) { ASSERT_EQ(1, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "http://www.google.com/", "", "", 1); } static void Arguments_GetTodaysActions( scoped_ptr<Action::ActionVector> actions) { ASSERT_EQ(3, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_API_CALL, "brewster", "", "", "", "", 2); CheckAction(*actions->at(1).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "http://www.google.com/", "", "", 1); CheckAction(*actions->at(2).get(), "punky", Action::ACTION_API_CALL, "extension.sendMessage", "[\"not\",\"stripped\"]", "", "", "", 1); } static void Arguments_GetOlderActions( scoped_ptr<Action::ActionVector> actions) { ASSERT_EQ(2, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "http://www.google.com/", "", "", 1); CheckAction(*actions->at(1).get(), "punky", Action::ACTION_API_CALL, "brewster", "", "", "", "", 1); } static void Arguments_CheckMergeCount( int count, scoped_ptr<Action::ActionVector> actions) { if (count > 0) { ASSERT_EQ(1u, actions->size()); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_API_CALL, "brewster", "", "", "", "", count); } else { ASSERT_EQ(0u, actions->size()); } } static void Arguments_CheckMergeCountAndTime( int count, const base::Time& time, scoped_ptr<Action::ActionVector> actions) { if (count > 0) { ASSERT_EQ(1u, actions->size()); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_API_CALL, "brewster", "", "", "", "", count); ASSERT_EQ(time, actions->at(0)->time()); } else { ASSERT_EQ(0u, actions->size()); } } static void AllURLsRemoved(scoped_ptr<Action::ActionVector> actions) { ASSERT_EQ(2, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "", "", "", 1); CheckAction(*actions->at(1).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "", "", "", 1); } static void SomeURLsRemoved(scoped_ptr<Action::ActionVector> actions) { // These will be in the vector in reverse time order. ASSERT_EQ(5, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "http://www.google.com/", "Google", "http://www.args-url.com/", 1); CheckAction(*actions->at(1).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "http://www.google.com/", "Google", "", 1); CheckAction(*actions->at(2).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "", "", "", 1); CheckAction(*actions->at(3).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "", "", "http://www.google.com/", 1); CheckAction(*actions->at(4).get(), "punky", Action::ACTION_DOM_ACCESS, "lets", "", "", "", "", 1); } static void CheckDuplicates(scoped_ptr<Action::ActionVector> actions) { ASSERT_EQ(2u, actions->size()); int total_count = 0; for (size_t i = 0; i < actions->size(); i++) { total_count += actions->at(i)->count(); } ASSERT_EQ(3, total_count); } static void CheckAction(const Action& action, const std::string& expected_id, const Action::ActionType& expected_type, const std::string& expected_api_name, const std::string& expected_args_str, const std::string& expected_page_url, const std::string& expected_page_title, const std::string& expected_arg_url, int expected_count) { ASSERT_EQ(expected_id, action.extension_id()); ASSERT_EQ(expected_type, action.action_type()); ASSERT_EQ(expected_api_name, action.api_name()); ASSERT_EQ(expected_args_str, ActivityLogPolicy::Util::Serialize(action.args())); ASSERT_EQ(expected_page_url, action.SerializePageUrl()); ASSERT_EQ(expected_page_title, action.page_title()); ASSERT_EQ(expected_arg_url, action.SerializeArgUrl()); ASSERT_EQ(expected_count, action.count()); ASSERT_NE(-1, action.action_id()); } // A helper function initializes the policy with a number of actions, calls // RemoveActions on a policy object and then checks the result of the // deletion. void CheckRemoveActions( ActivityLogDatabasePolicy* policy, const std::vector<int64_t>& action_ids, const base::Callback<void(scoped_ptr<Action::ActionVector>)>& checker) { // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions scoped_refptr<Action> action = new Action("punky1", mock_clock->Now() - base::TimeDelta::FromMinutes(40), Action::ACTION_DOM_ACCESS, "lets1"); action->mutable_args()->AppendString("vamoose1"); action->set_page_url(GURL("http://www.google1.com")); action->set_page_title("Google1"); action->set_arg_url(GURL("http://www.args-url1.com")); policy->ProcessAction(action); // Record the same action twice, so there are multiple entries in the // database. policy->ProcessAction(action); action = new Action("punky2", mock_clock->Now() - base::TimeDelta::FromMinutes(30), Action::ACTION_API_CALL, "lets2"); action->mutable_args()->AppendString("vamoose2"); action->set_page_url(GURL("http://www.google2.com")); action->set_page_title("Google2"); action->set_arg_url(GURL("http://www.args-url2.com")); policy->ProcessAction(action); // Record the same action twice, so there are multiple entries in the // database. policy->ProcessAction(action); // Submit a request to delete actions. policy->RemoveActions(action_ids); // Check the result of the deletion. The checker function gets all // activities in the database. CheckReadData(policy, "", -1, checker); // Clean database. policy->DeleteDatabase(); } static void AllActionsDeleted(scoped_ptr<Action::ActionVector> actions) { ASSERT_EQ(0, static_cast<int>(actions->size())); } static void NoActionsDeleted(scoped_ptr<Action::ActionVector> actions) { // These will be in the vector in reverse time order. ASSERT_EQ(2, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky2", Action::ACTION_API_CALL, "lets2", "", "http://www.google2.com/", "Google2", "http://www.args-url2.com/", 2); ASSERT_EQ(2, actions->at(0)->action_id()); CheckAction(*actions->at(1).get(), "punky1", Action::ACTION_DOM_ACCESS, "lets1", "", "http://www.google1.com/", "Google1", "http://www.args-url1.com/", 2); ASSERT_EQ(1, actions->at(1)->action_id()); } static void Action1Deleted(scoped_ptr<Action::ActionVector> actions) { // These will be in the vector in reverse time order. ASSERT_EQ(1, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky2", Action::ACTION_API_CALL, "lets2", "", "http://www.google2.com/", "Google2", "http://www.args-url2.com/", 2); ASSERT_EQ(2, actions->at(0)->action_id()); } static void Action2Deleted(scoped_ptr<Action::ActionVector> actions) { // These will be in the vector in reverse time order. ASSERT_EQ(1, static_cast<int>(actions->size())); CheckAction(*actions->at(0).get(), "punky1", Action::ACTION_DOM_ACCESS, "lets1", "", "http://www.google1.com/", "Google1", "http://www.args-url1.com/", 2); ASSERT_EQ(1, actions->at(0)->action_id()); } protected: ExtensionService* extension_service_; scoped_ptr<TestingProfile> profile_; content::TestBrowserThreadBundle thread_bundle_; // Used to preserve a copy of the original command line. // The test framework will do this itself as well. However, by then, // it is too late to call ActivityLog::RecomputeLoggingIsEnabled() in // TearDown(). base::CommandLine saved_cmdline_; #if defined OS_CHROMEOS chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; chromeos::ScopedTestCrosSettings test_cros_settings_; scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_; #endif }; TEST_F(CountingPolicyTest, Construct) { ActivityLogDatabasePolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); scoped_refptr<const Extension> extension = ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "Test extension") .Set("version", "1.0.0") .Set("manifest_version", 2) .Build()) .Build(); extension_service_->AddExtension(extension.get()); scoped_ptr<base::ListValue> args(new base::ListValue()); scoped_refptr<Action> action = new Action(extension->id(), base::Time::Now(), Action::ACTION_API_CALL, "tabs.testMethod"); action->set_args(std::move(args)); policy->ProcessAction(action); policy->Close(); } TEST_F(CountingPolicyTest, LogWithStrippedArguments) { ActivityLogDatabasePolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); scoped_refptr<const Extension> extension = ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "Test extension") .Set("version", "1.0.0") .Set("manifest_version", 2) .Build()) .Build(); extension_service_->AddExtension(extension.get()); scoped_ptr<base::ListValue> args(new base::ListValue()); args->Set(0, new base::StringValue("hello")); args->Set(1, new base::StringValue("world")); scoped_refptr<Action> action = new Action(extension->id(), base::Time::Now(), Action::ACTION_API_CALL, "extension.connect"); action->set_args(std::move(args)); policy->ProcessAction(action); CheckReadData(policy, extension->id(), 0, base::Bind(&CountingPolicyTest::Arguments_Stripped)); policy->Close(); } TEST_F(CountingPolicyTest, GetTodaysActions) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Disable row expiration for this test by setting a time before any actions // we generate. policy->set_retention_time(base::TimeDelta::FromDays(14)); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. Note: Ownership is passed // to the policy, but we still keep a pointer locally. The policy will take // care of destruction; this is safe since the policy outlives all our // accesses to the mock clock. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions scoped_refptr<Action> action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromMinutes(40), Action::ACTION_API_CALL, "brewster"); action->mutable_args()->AppendString("woof"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromMinutes(30), Action::ACTION_API_CALL, "brewster"); action->mutable_args()->AppendString("meow"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromMinutes(20), Action::ACTION_API_CALL, "extension.sendMessage"); action->mutable_args()->AppendString("not"); action->mutable_args()->AppendString("stripped"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); action = new Action( "scoobydoo", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); CheckReadData( policy, "punky", 0, base::Bind(&CountingPolicyTest::Arguments_GetTodaysActions)); policy->Close(); } // Check that we can read back less recent actions in the db. TEST_F(CountingPolicyTest, GetOlderActions) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); policy->set_retention_time(base::TimeDelta::FromDays(14)); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions scoped_refptr<Action> action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(3) - base::TimeDelta::FromMinutes(40), Action::ACTION_API_CALL, "brewster"); action->mutable_args()->AppendString("woof"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(3), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("too new"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(7), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("too old"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); CheckReadData( policy, "punky", 3, base::Bind(&CountingPolicyTest::Arguments_GetOlderActions)); policy->Close(); } TEST_F(CountingPolicyTest, LogAndFetchFilteredActions) { ActivityLogDatabasePolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); scoped_refptr<const Extension> extension = ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "Test extension") .Set("version", "1.0.0") .Set("manifest_version", 2) .Build()) .Build(); extension_service_->AddExtension(extension.get()); GURL gurl("http://www.google.com"); // Write some API calls scoped_refptr<Action> action_api = new Action(extension->id(), base::Time::Now(), Action::ACTION_API_CALL, "tabs.testMethod"); action_api->set_args(make_scoped_ptr(new base::ListValue())); policy->ProcessAction(action_api); scoped_refptr<Action> action_dom = new Action(extension->id(), base::Time::Now(), Action::ACTION_DOM_ACCESS, "document.write"); action_dom->set_args(make_scoped_ptr(new base::ListValue())); action_dom->set_page_url(gurl); policy->ProcessAction(action_dom); CheckReadFilteredData( policy, extension->id(), Action::ACTION_API_CALL, "tabs.testMethod", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions1)); CheckReadFilteredData( policy, "", Action::ACTION_DOM_ACCESS, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions1)); CheckReadFilteredData( policy, "", Action::ACTION_DOM_ACCESS, "", "http://www.google.com/", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions1)); CheckReadFilteredData( policy, "", Action::ACTION_DOM_ACCESS, "", "http://www.google.com", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions1)); CheckReadFilteredData( policy, "", Action::ACTION_DOM_ACCESS, "", "http://www.goo", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions1)); CheckReadFilteredData( policy, extension->id(), Action::ACTION_ANY, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions2)); policy->Close(); } // Check that merging of actions only occurs within the same day, not across // days, and that old data can be expired from the database. TEST_F(CountingPolicyTest, MergingAndExpiring) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Initially disable expiration by setting a retention time before any // actions we generate. policy->set_retention_time(base::TimeDelta::FromDays(14)); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // The first two actions should be merged; the last one is on a separate day // and should not be. scoped_refptr<Action> action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(3) - base::TimeDelta::FromMinutes(40), Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(3) - base::TimeDelta::FromMinutes(20), Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(2) - base::TimeDelta::FromMinutes(20), Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); CheckReadData(policy, "punky", 3, base::Bind(&CountingPolicyTest::Arguments_CheckMergeCount, 2)); CheckReadData(policy, "punky", 2, base::Bind(&CountingPolicyTest::Arguments_CheckMergeCount, 1)); // Clean actions before midnight two days ago. Force expiration to run by // clearing last_database_cleaning_time_ and submitting a new action. policy->set_retention_time(base::TimeDelta::FromDays(2)); policy->last_database_cleaning_time_ = base::Time(); action = new Action("punky", mock_clock->Now(), Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); CheckReadData(policy, "punky", 3, base::Bind(&CountingPolicyTest::Arguments_CheckMergeCount, 0)); CheckReadData(policy, "punky", 2, base::Bind(&CountingPolicyTest::Arguments_CheckMergeCount, 1)); policy->Close(); } // Test cleaning of old data in the string and URL tables. TEST_F(CountingPolicyTest, StringTableCleaning) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Initially disable expiration by setting a retention time before any // actions we generate. policy->set_retention_time(base::TimeDelta::FromDays(14)); base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now()); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Insert an action; this should create entries in both the string table (for // the extension and API name) and the URL table (for page_url). scoped_refptr<Action> action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromDays(7), Action::ACTION_API_CALL, "brewster"); action->set_page_url(GURL("http://www.google.com/")); policy->ProcessAction(action); // Add an action which will not be expired, so that some strings will remain // in use. action = new Action( "punky", mock_clock->Now(), Action::ACTION_API_CALL, "tabs.create"); policy->ProcessAction(action); // There should now be three strings ("punky", "brewster", "tabs.create") and // one URL in the tables. policy->Flush(); policy->ScheduleAndForget(policy, &CountingPolicyTest::CheckStringTableSizes, 3, 1); WaitOnThread(BrowserThread::DB); // Trigger a cleaning. The oldest action is expired when we submit a // duplicate of the newer action. After this, there should be two strings // and no URLs. policy->set_retention_time(base::TimeDelta::FromDays(2)); policy->last_database_cleaning_time_ = base::Time(); policy->ProcessAction(action); policy->Flush(); policy->ScheduleAndForget(policy, &CountingPolicyTest::CheckStringTableSizes, 2, 0); WaitOnThread(BrowserThread::DB); policy->Close(); } // A stress test for memory- and database-based merging of actions. Submit // multiple items, not in chronological order, spanning a few days. Check that // items are merged properly and final timestamps are correct. TEST_F(CountingPolicyTest, MoreMerging) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); policy->set_retention_time(base::TimeDelta::FromDays(14)); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Create an action 2 days ago, then 1 day ago, then 2 days ago. Make sure // that we end up with two merged records (one for each day), and each has // the appropriate timestamp. These merges should happen in the database // since the date keeps changing. base::Time time1 = mock_clock->Now() - base::TimeDelta::FromDays(2) - base::TimeDelta::FromMinutes(40); base::Time time2 = mock_clock->Now() - base::TimeDelta::FromDays(1) - base::TimeDelta::FromMinutes(40); base::Time time3 = mock_clock->Now() - base::TimeDelta::FromDays(2) - base::TimeDelta::FromMinutes(20); scoped_refptr<Action> action = new Action("punky", time1, Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); action = new Action("punky", time2, Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); action = new Action("punky", time3, Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); CheckReadData( policy, "punky", 2, base::Bind( &CountingPolicyTest::Arguments_CheckMergeCountAndTime, 2, time3)); CheckReadData( policy, "punky", 1, base::Bind( &CountingPolicyTest::Arguments_CheckMergeCountAndTime, 1, time2)); // Create three actions today, where the merges should happen in memory. // Again these are not chronological; timestamp time5 should win out since it // is the latest. base::Time time4 = mock_clock->Now() - base::TimeDelta::FromMinutes(60); base::Time time5 = mock_clock->Now() - base::TimeDelta::FromMinutes(20); base::Time time6 = mock_clock->Now() - base::TimeDelta::FromMinutes(40); action = new Action("punky", time4, Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); action = new Action("punky", time5, Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); action = new Action("punky", time6, Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); CheckReadData( policy, "punky", 0, base::Bind( &CountingPolicyTest::Arguments_CheckMergeCountAndTime, 3, time5)); policy->Close(); } // Check that actions are flushed to disk before letting too many accumulate in // memory. TEST_F(CountingPolicyTest, EarlyFlush) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); for (int i = 0; i < 500; i++) { scoped_refptr<Action> action = new Action("punky", base::Time::Now(), Action::ACTION_API_CALL, base::StringPrintf("apicall_%d", i)); policy->ProcessAction(action); } policy->ScheduleAndForget(policy, &CountingPolicyTest::CheckQueueSize); WaitOnThread(BrowserThread::DB); policy->Close(); } TEST_F(CountingPolicyTest, CapReturns) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); for (int i = 0; i < 305; i++) { scoped_refptr<Action> action = new Action("punky", base::Time::Now(), Action::ACTION_API_CALL, base::StringPrintf("apicall_%d", i)); policy->ProcessAction(action); } policy->Flush(); WaitOnThread(BrowserThread::DB); CheckReadFilteredData( policy, "punky", Action::ACTION_ANY, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions300)); policy->Close(); } TEST_F(CountingPolicyTest, RemoveAllURLs) { ActivityLogDatabasePolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions scoped_refptr<Action> action = new Action("punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.args-url.com")); policy->ProcessAction(action); mock_clock->Advance(base::TimeDelta::FromSeconds(1)); action = new Action( "punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google2.com")); action->set_page_title("Google"); // Deliberately no arg url set to make sure it stills works if there is no arg // url. policy->ProcessAction(action); // Clean all the URLs. std::vector<GURL> no_url_restrictions; policy->RemoveURLs(no_url_restrictions); CheckReadData( policy, "punky", 0, base::Bind(&CountingPolicyTest::AllURLsRemoved)); policy->Close(); } TEST_F(CountingPolicyTest, RemoveSpecificURLs) { ActivityLogDatabasePolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions // This should have the page url and args url cleared. scoped_refptr<Action> action = new Action("punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google1.com")); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.google1.com")); policy->ProcessAction(action); // This should have the page url cleared but not args url. mock_clock->Advance(base::TimeDelta::FromSeconds(1)); action = new Action( "punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google1.com")); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.google.com")); policy->ProcessAction(action); // This should have the page url cleared. The args url is deliberately not // set to make sure this doesn't cause any issues. mock_clock->Advance(base::TimeDelta::FromSeconds(1)); action = new Action( "punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google2.com")); action->set_page_title("Google"); policy->ProcessAction(action); // This should have the args url cleared but not the page url or page title. mock_clock->Advance(base::TimeDelta::FromSeconds(1)); action = new Action( "punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.google1.com")); policy->ProcessAction(action); // This should have neither cleared. mock_clock->Advance(base::TimeDelta::FromSeconds(1)); action = new Action( "punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.args-url.com")); action->set_count(5); policy->ProcessAction(action); // Clean some URLs. std::vector<GURL> urls; urls.push_back(GURL("http://www.google1.com")); urls.push_back(GURL("http://www.google2.com")); urls.push_back(GURL("http://www.url_not_in_db.com")); policy->RemoveURLs(urls); CheckReadData( policy, "punky", 0, base::Bind(&CountingPolicyTest::SomeURLsRemoved)); policy->Close(); } TEST_F(CountingPolicyTest, RemoveExtensionData) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions scoped_refptr<Action> action = new Action("deleteextensiondata", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.google.com")); policy->ProcessAction(action); policy->ProcessAction(action); policy->ProcessAction(action); scoped_refptr<Action> action2 = new Action("dontdelete", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_title("Google"); action->set_arg_url(GURL("http://www.google.com")); policy->ProcessAction(action2); policy->Flush(); policy->RemoveExtensionData("deleteextensiondata"); CheckReadFilteredData( policy, "deleteextensiondata", Action::ACTION_ANY, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions0)); CheckReadFilteredData( policy, "dontdelete", Action::ACTION_ANY, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions1)); policy->Close(); } TEST_F(CountingPolicyTest, DeleteDatabase) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); // Disable row expiration for this test by setting a time before any actions // we generate. policy->set_retention_time(base::TimeDelta::FromDays(14)); // Use a mock clock to ensure that events are not recorded on the wrong day // when the test is run close to local midnight. Note: Ownership is passed // to the policy, but we still keep a pointer locally. The policy will take // care of destruction; this is safe since the policy outlives all our // accesses to the mock clock. base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record some actions scoped_refptr<Action> action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromMinutes(40), Action::ACTION_API_CALL, "brewster"); action->mutable_args()->AppendString("woof"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromMinutes(30), Action::ACTION_API_CALL, "brewster"); action->mutable_args()->AppendString("meow"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now() - base::TimeDelta::FromMinutes(20), Action::ACTION_API_CALL, "extension.sendMessage"); action->mutable_args()->AppendString("not"); action->mutable_args()->AppendString("stripped"); policy->ProcessAction(action); action = new Action("punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); action = new Action( "scoobydoo", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); CheckReadData( policy, "punky", 0, base::Bind(&CountingPolicyTest::Arguments_GetTodaysActions)); policy->DeleteDatabase(); CheckReadFilteredData( policy, "", Action::ACTION_ANY, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions0)); // The following code tests that the caches of url and string tables were // cleared by the deletion above. // https://code.google.com/p/chromium/issues/detail?id=341674. action = new Action("punky", mock_clock->Now(), Action::ACTION_DOM_ACCESS, "lets"); action->mutable_args()->AppendString("vamoose"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); CheckReadData( policy, "", -1, base::Bind(&CountingPolicyTest::Arguments_GetSinglesAction)); policy->DeleteDatabase(); CheckReadFilteredData( policy, "", Action::ACTION_ANY, "", "", "", -1, base::Bind( &CountingPolicyTest::RetrieveActions_FetchFilteredActions0)); policy->Close(); } // Tests that duplicate rows in the activity log database are handled properly // when updating counts. TEST_F(CountingPolicyTest, DuplicateRows) { CountingPolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); base::SimpleTestClock* mock_clock = new base::SimpleTestClock(); mock_clock->SetNow(base::Time::Now().LocalMidnight() + base::TimeDelta::FromHours(12)); policy->SetClockForTesting(scoped_ptr<base::Clock>(mock_clock)); // Record two actions with distinct URLs. scoped_refptr<Action> action; action = new Action( "punky", mock_clock->Now(), Action::ACTION_API_CALL, "brewster"); action->set_page_url(GURL("http://www.google.com")); policy->ProcessAction(action); action = new Action( "punky", mock_clock->Now(), Action::ACTION_API_CALL, "brewster"); action->set_page_url(GURL("http://www.google.co.uk")); policy->ProcessAction(action); // Manipulate the database to clear the URLs, so that we end up with // duplicate rows. std::vector<GURL> no_url_restrictions; policy->RemoveURLs(no_url_restrictions); // Record one more action, with no URL. This should increment the count on // one, and exactly one, of the existing rows. action = new Action( "punky", mock_clock->Now(), Action::ACTION_API_CALL, "brewster"); policy->ProcessAction(action); CheckReadData( policy, "punky", 0, base::Bind(&CountingPolicyTest::CheckDuplicates)); policy->Close(); } TEST_F(CountingPolicyTest, RemoveActions) { ActivityLogDatabasePolicy* policy = new CountingPolicy(profile_.get()); policy->Init(); std::vector<int64_t> action_ids; CheckRemoveActions( policy, action_ids, base::Bind(&CountingPolicyTest::NoActionsDeleted)); action_ids.push_back(-1); action_ids.push_back(-10); action_ids.push_back(0); action_ids.push_back(5); action_ids.push_back(10); CheckRemoveActions( policy, action_ids, base::Bind(&CountingPolicyTest::NoActionsDeleted)); action_ids.clear(); for (int i = 0; i < 50; i++) { action_ids.push_back(i + 3); } CheckRemoveActions( policy, action_ids, base::Bind(&CountingPolicyTest::NoActionsDeleted)); action_ids.clear(); // CheckRemoveActions pushes two actions to the Activity Log database with IDs // 1 and 2. action_ids.push_back(1); action_ids.push_back(2); CheckRemoveActions( policy, action_ids, base::Bind(&CountingPolicyTest::AllActionsDeleted)); action_ids.clear(); action_ids.push_back(1); CheckRemoveActions( policy, action_ids, base::Bind(&CountingPolicyTest::Action1Deleted)); action_ids.clear(); action_ids.push_back(2); CheckRemoveActions( policy, action_ids, base::Bind(&CountingPolicyTest::Action2Deleted)); action_ids.clear(); policy->Close(); } } // namespace extensions