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.
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"
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"
37 using content::BrowserThread
;
39 namespace constants
= activity_log_constants
;
41 namespace extensions
{
43 // A dummy implementation of ActivityDatabase::Delegate, sufficient for
45 class ActivityDatabaseTestPolicy
: public ActivityDatabase::Delegate
{
47 ActivityDatabaseTestPolicy() {}
49 static const char kTableName
[];
50 static const char* const kTableContentFields
[];
51 static const char* const kTableFieldTypes
[];
53 virtual void Record(ActivityDatabase
* db
, scoped_refptr
<Action
> action
);
56 bool InitDatabase(sql::Connection
* db
) override
;
57 bool FlushDatabase(sql::Connection
*) override
;
58 void OnDatabaseFailure() override
{}
59 void OnDatabaseClose() override
{ delete this; }
61 std::vector
<scoped_refptr
<Action
> > queue_
;
64 const char ActivityDatabaseTestPolicy::kTableName
[] = "actions";
65 const char* const ActivityDatabaseTestPolicy::kTableContentFields
[] = {
66 "extension_id", "time", "action_type", "api_name"};
67 const char* const ActivityDatabaseTestPolicy::kTableFieldTypes
[] = {
68 "LONGVARCHAR NOT NULL", "INTEGER", "INTEGER", "LONGVARCHAR"};
70 bool ActivityDatabaseTestPolicy::InitDatabase(sql::Connection
* db
) {
71 return ActivityDatabase::InitializeTable(db
,
75 arraysize(kTableContentFields
));
78 bool ActivityDatabaseTestPolicy::FlushDatabase(sql::Connection
* db
) {
80 "INSERT INTO " + std::string(kTableName
) +
81 " (extension_id, time, action_type, api_name) VALUES (?,?,?,?)";
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
;
102 void ActivityDatabaseTestPolicy::Record(ActivityDatabase
* db
,
103 scoped_refptr
<Action
> action
) {
104 queue_
.push_back(action
);
105 db
->AdviseFlush(queue_
.size());
108 class ActivityDatabaseTest
: public ChromeRenderViewHostTestHarness
{
110 void SetUp() override
{
111 ChromeRenderViewHostTestHarness::SetUp();
112 #if defined OS_CHROMEOS
113 test_user_manager_
.reset(new chromeos::ScopedTestUserManager());
115 base::CommandLine
command_line(base::CommandLine::NO_PROGRAM
);
116 base::CommandLine::ForCurrentProcess()->AppendSwitch(
117 switches::kEnableExtensionActivityLogTesting
);
120 void TearDown() override
{
121 #if defined OS_CHROMEOS
122 test_user_manager_
.reset();
124 ChromeRenderViewHostTestHarness::TearDown();
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());
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
));
143 void Record(ActivityDatabase
* db
, scoped_refptr
<Action
> action
) {
144 db_delegate_
->Record(db
, action
);
147 int CountActions(sql::Connection
* db
, const std::string
& api_name_pattern
) {
148 if (!db
->DoesTableExist(ActivityDatabaseTestPolicy::kTableName
))
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())
158 return statement
.ColumnInt(0);
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_
;
168 ActivityDatabaseTestPolicy
* db_delegate_
;
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 sql::Connection::Delete(db_file
);
179 ActivityDatabase
* activity_db
= OpenDatabase(db_file
);
180 activity_db
->Close();
183 ASSERT_TRUE(db
.Open(db_file
));
184 ASSERT_TRUE(db
.DoesTableExist(ActivityDatabaseTestPolicy::kTableName
));
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 sql::Connection::Delete(db_file
);
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();
203 ASSERT_TRUE(db
.Open(db_file
));
205 ASSERT_EQ(1, CountActions(&db
, "brewster"));
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 sql::Connection::Delete(db_file
);
215 // Record some actions
216 ActivityDatabase
* activity_db
= OpenDatabase(db_file
);
217 activity_db
->SetBatchModeForTesting(false);
219 scoped_refptr
<Action
> action
= CreateAction(base::Time::Now(), "brewster");
220 Record(activity_db
, action
);
221 ASSERT_EQ(1, CountActions(&activity_db
->db_
, "brewster"));
223 activity_db
->Close();
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 sql::Connection::Delete(db_file
);
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"));
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"));
245 activity_db
->Close();
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 sql::Connection::Delete(db_file
);
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"));
262 // Request an immediate database flush.
263 activity_db
->AdviseFlush(ActivityDatabase::kFlushImmediately
);
264 ASSERT_EQ(1, CountActions(&activity_db
->db_
, "brewster"));
266 activity_db
->Close();
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 sql::Connection::Delete(db_file
);
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();
286 } // namespace extensions