Home | History | Annotate | Download | only in activity_log
      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