1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <string> 6 7 #include "base/command_line.h" 8 #include "base/files/file_path.h" 9 #include "base/files/file_util.h" 10 #include "base/files/scoped_temp_dir.h" 11 #include "base/run_loop.h" 12 #include "base/test/simple_test_clock.h" 13 #include "base/time/time.h" 14 #include "chrome/browser/extensions/activity_log/activity_action_constants.h" 15 #include "chrome/browser/extensions/activity_log/activity_database.h" 16 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" 17 #include "chrome/browser/extensions/extension_service.h" 18 #include "chrome/browser/extensions/test_extension_system.h" 19 #include "chrome/common/chrome_constants.h" 20 #include "chrome/common/chrome_switches.h" 21 #include "chrome/test/base/chrome_render_view_host_test_harness.h" 22 #include "chrome/test/base/testing_profile.h" 23 #include "content/public/browser/web_contents.h" 24 #include "content/public/test/test_browser_thread.h" 25 #include "extensions/common/dom_action_types.h" 26 #include "sql/statement.h" 27 #include "testing/gtest/include/gtest/gtest.h" 28 29 #if defined(OS_CHROMEOS) 30 #include "chrome/browser/chromeos/login/users/mock_user_manager.h" 31 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h" 32 #include "chrome/browser/chromeos/settings/cros_settings.h" 33 #include "chrome/browser/chromeos/settings/device_settings_service.h" 34 #include "chromeos/chromeos_switches.h" 35 #endif 36 37 using content::BrowserThread; 38 39 namespace constants = activity_log_constants; 40 41 namespace extensions { 42 43 // A dummy implementation of ActivityDatabase::Delegate, sufficient for 44 // the unit tests. 45 class ActivityDatabaseTestPolicy : public ActivityDatabase::Delegate { 46 public: 47 ActivityDatabaseTestPolicy() {}; 48 49 static const char kTableName[]; 50 static const char* kTableContentFields[]; 51 static const char* kTableFieldTypes[]; 52 53 virtual void Record(ActivityDatabase* db, scoped_refptr<Action> action); 54 55 protected: 56 virtual bool InitDatabase(sql::Connection* db) OVERRIDE; 57 virtual bool FlushDatabase(sql::Connection*) OVERRIDE; 58 virtual void OnDatabaseFailure() OVERRIDE {} 59 virtual void OnDatabaseClose() OVERRIDE { delete this; } 60 61 std::vector<scoped_refptr<Action> > queue_; 62 }; 63 64 const char ActivityDatabaseTestPolicy::kTableName[] = "actions"; 65 const char* ActivityDatabaseTestPolicy::kTableContentFields[] = { 66 "extension_id", "time", "action_type", "api_name"}; 67 const char* ActivityDatabaseTestPolicy::kTableFieldTypes[] = { 68 "LONGVARCHAR NOT NULL", "INTEGER", "INTEGER", "LONGVARCHAR"}; 69 70 bool ActivityDatabaseTestPolicy::InitDatabase(sql::Connection* db) { 71 return ActivityDatabase::InitializeTable(db, 72 kTableName, 73 kTableContentFields, 74 kTableFieldTypes, 75 arraysize(kTableContentFields)); 76 } 77 78 bool ActivityDatabaseTestPolicy::FlushDatabase(sql::Connection* db) { 79 std::string sql_str = 80 "INSERT INTO " + std::string(kTableName) + 81 " (extension_id, time, action_type, api_name) VALUES (?,?,?,?)"; 82 83 std::vector<scoped_refptr<Action> >::size_type i; 84 for (i = 0; i < queue_.size(); i++) { 85 const Action& action = *queue_[i].get(); 86 sql::Statement statement(db->GetCachedStatement( 87 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 88 statement.BindString(0, action.extension_id()); 89 statement.BindInt64(1, action.time().ToInternalValue()); 90 statement.BindInt(2, static_cast<int>(action.action_type())); 91 statement.BindString(3, action.api_name()); 92 if (!statement.Run()) { 93 LOG(ERROR) << "Activity log database I/O failed: " << sql_str; 94 return false; 95 } 96 } 97 98 queue_.clear(); 99 return true; 100 } 101 102 void ActivityDatabaseTestPolicy::Record(ActivityDatabase* db, 103 scoped_refptr<Action> action) { 104 queue_.push_back(action); 105 db->AdviseFlush(queue_.size()); 106 } 107 108 class ActivityDatabaseTest : public ChromeRenderViewHostTestHarness { 109 protected: 110 virtual void SetUp() OVERRIDE { 111 ChromeRenderViewHostTestHarness::SetUp(); 112 #if defined OS_CHROMEOS 113 test_user_manager_.reset(new chromeos::ScopedTestUserManager()); 114 #endif 115 CommandLine command_line(CommandLine::NO_PROGRAM); 116 CommandLine::ForCurrentProcess()->AppendSwitch( 117 switches::kEnableExtensionActivityLogTesting); 118 } 119 120 virtual void TearDown() OVERRIDE { 121 #if defined OS_CHROMEOS 122 test_user_manager_.reset(); 123 #endif 124 ChromeRenderViewHostTestHarness::TearDown(); 125 } 126 127 // Creates a test database and initializes the table schema. 128 ActivityDatabase* OpenDatabase(const base::FilePath& db_file) { 129 db_delegate_ = new ActivityDatabaseTestPolicy(); 130 ActivityDatabase* activity_db = new ActivityDatabase(db_delegate_); 131 activity_db->Init(db_file); 132 CHECK(activity_db->is_db_valid()); 133 return activity_db; 134 } 135 136 scoped_refptr<Action> CreateAction(const base::Time& time, 137 const std::string& api_name) const { 138 scoped_refptr<Action> action( 139 new Action("punky", time, Action::ACTION_API_CALL, api_name)); 140 return action; 141 } 142 143 void Record(ActivityDatabase* db, scoped_refptr<Action> action) { 144 db_delegate_->Record(db, action); 145 } 146 147 int CountActions(sql::Connection* db, const std::string& api_name_pattern) { 148 if (!db->DoesTableExist(ActivityDatabaseTestPolicy::kTableName)) 149 return -1; 150 std::string sql_str = "SELECT COUNT(*) FROM " + 151 std::string(ActivityDatabaseTestPolicy::kTableName) + 152 " WHERE api_name LIKE ?"; 153 sql::Statement statement(db->GetCachedStatement( 154 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 155 statement.BindString(0, api_name_pattern); 156 if (!statement.Step()) 157 return -1; 158 return statement.ColumnInt(0); 159 } 160 161 private: 162 #if defined OS_CHROMEOS 163 chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; 164 chromeos::ScopedTestCrosSettings test_cros_settings_; 165 scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_; 166 #endif 167 168 ActivityDatabaseTestPolicy* db_delegate_; 169 }; 170 171 // Check that the database is initialized properly. 172 TEST_F(ActivityDatabaseTest, Init) { 173 base::ScopedTempDir temp_dir; 174 base::FilePath db_file; 175 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 176 db_file = temp_dir.path().AppendASCII("ActivityInit.db"); 177 base::DeleteFile(db_file, false); 178 179 ActivityDatabase* activity_db = OpenDatabase(db_file); 180 activity_db->Close(); 181 182 sql::Connection db; 183 ASSERT_TRUE(db.Open(db_file)); 184 ASSERT_TRUE(db.DoesTableExist(ActivityDatabaseTestPolicy::kTableName)); 185 db.Close(); 186 } 187 188 // Check that actions are recorded in the db. 189 TEST_F(ActivityDatabaseTest, RecordAction) { 190 base::ScopedTempDir temp_dir; 191 base::FilePath db_file; 192 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 193 db_file = temp_dir.path().AppendASCII("ActivityRecord.db"); 194 base::DeleteFile(db_file, false); 195 196 ActivityDatabase* activity_db = OpenDatabase(db_file); 197 activity_db->SetBatchModeForTesting(false); 198 scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster"); 199 Record(activity_db, action); 200 activity_db->Close(); 201 202 sql::Connection db; 203 ASSERT_TRUE(db.Open(db_file)); 204 205 ASSERT_EQ(1, CountActions(&db, "brewster")); 206 } 207 208 TEST_F(ActivityDatabaseTest, BatchModeOff) { 209 base::ScopedTempDir temp_dir; 210 base::FilePath db_file; 211 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 212 db_file = temp_dir.path().AppendASCII("ActivityRecord.db"); 213 base::DeleteFile(db_file, false); 214 215 // Record some actions 216 ActivityDatabase* activity_db = OpenDatabase(db_file); 217 activity_db->SetBatchModeForTesting(false); 218 219 scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster"); 220 Record(activity_db, action); 221 ASSERT_EQ(1, CountActions(&activity_db->db_, "brewster")); 222 223 activity_db->Close(); 224 } 225 226 TEST_F(ActivityDatabaseTest, BatchModeOn) { 227 base::ScopedTempDir temp_dir; 228 base::FilePath db_file; 229 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 230 db_file = temp_dir.path().AppendASCII("ActivityRecord.db"); 231 base::DeleteFile(db_file, false); 232 233 // Record some actions 234 ActivityDatabase* activity_db = OpenDatabase(db_file); 235 activity_db->SetBatchModeForTesting(true); 236 scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster"); 237 Record(activity_db, action); 238 ASSERT_EQ(0, CountActions(&activity_db->db_, "brewster")); 239 240 // Artificially trigger and then stop the timer. 241 activity_db->SetTimerForTesting(0); 242 base::MessageLoop::current()->RunUntilIdle(); 243 ASSERT_EQ(1, CountActions(&activity_db->db_, "brewster")); 244 245 activity_db->Close(); 246 } 247 248 TEST_F(ActivityDatabaseTest, BatchModeFlush) { 249 base::ScopedTempDir temp_dir; 250 base::FilePath db_file; 251 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 252 db_file = temp_dir.path().AppendASCII("ActivityFlush.db"); 253 base::DeleteFile(db_file, false); 254 255 // Record some actions 256 ActivityDatabase* activity_db = OpenDatabase(db_file); 257 activity_db->SetBatchModeForTesting(true); 258 scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster"); 259 Record(activity_db, action); 260 ASSERT_EQ(0, CountActions(&activity_db->db_, "brewster")); 261 262 // Request an immediate database flush. 263 activity_db->AdviseFlush(ActivityDatabase::kFlushImmediately); 264 ASSERT_EQ(1, CountActions(&activity_db->db_, "brewster")); 265 266 activity_db->Close(); 267 } 268 269 // Check that nothing explodes if the DB isn't initialized. 270 TEST_F(ActivityDatabaseTest, InitFailure) { 271 base::ScopedTempDir temp_dir; 272 base::FilePath db_file; 273 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 274 db_file = temp_dir.path().AppendASCII("ActivityRecord.db"); 275 base::DeleteFile(db_file, false); 276 277 ActivityDatabaseTestPolicy* delegate = new ActivityDatabaseTestPolicy(); 278 ActivityDatabase* activity_db = new ActivityDatabase(delegate); 279 scoped_refptr<Action> action = new Action( 280 "punky", base::Time::Now(), Action::ACTION_API_CALL, "brewster"); 281 action->mutable_args()->AppendString("woof"); 282 delegate->Record(activity_db, action); 283 activity_db->Close(); 284 } 285 286 } // namespace extensions 287