Home | History | Annotate | Download | only in storage
      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 "base/bind.h"
      6 #include "base/file_util.h"
      7 #include "base/files/scoped_temp_dir.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/strings/stringprintf.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/extensions/api/storage/leveldb_settings_storage_factory.h"
     13 #include "chrome/browser/extensions/api/storage/settings_frontend.h"
     14 #include "chrome/browser/extensions/api/storage/settings_namespace.h"
     15 #include "chrome/browser/extensions/api/storage/settings_test_util.h"
     16 #include "chrome/browser/value_store/value_store.h"
     17 #include "content/public/test/test_browser_thread.h"
     18 #include "testing/gtest/include/gtest/gtest.h"
     19 
     20 using content::BrowserThread;
     21 
     22 namespace extensions {
     23 
     24 namespace settings = settings_namespace;
     25 namespace util = settings_test_util;
     26 
     27 namespace {
     28 
     29 // To save typing ValueStore::DEFAULTS everywhere.
     30 const ValueStore::WriteOptions DEFAULTS = ValueStore::DEFAULTS;
     31 
     32 // Creates a kilobyte of data.
     33 scoped_ptr<Value> CreateKilobyte() {
     34   std::string kilobyte_string;
     35   for (int i = 0; i < 1024; ++i) {
     36     kilobyte_string += "a";
     37   }
     38   return scoped_ptr<Value>(Value::CreateStringValue(kilobyte_string));
     39 }
     40 
     41 // Creates a megabyte of data.
     42 scoped_ptr<Value> CreateMegabyte() {
     43   base::ListValue* megabyte = new base::ListValue();
     44   for (int i = 0; i < 1000; ++i) {
     45     megabyte->Append(CreateKilobyte().release());
     46   }
     47   return scoped_ptr<Value>(megabyte);
     48 }
     49 
     50 }
     51 
     52 class ExtensionSettingsFrontendTest : public testing::Test {
     53  public:
     54   ExtensionSettingsFrontendTest()
     55       : storage_factory_(new util::ScopedSettingsStorageFactory()),
     56         ui_thread_(BrowserThread::UI, base::MessageLoop::current()),
     57         file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {}
     58 
     59   virtual void SetUp() OVERRIDE {
     60     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     61     profile_.reset(new util::MockProfile(temp_dir_.path()));
     62     ResetFrontend();
     63   }
     64 
     65   virtual void TearDown() OVERRIDE {
     66     frontend_.reset();
     67     profile_.reset();
     68     // Execute any pending deletion tasks.
     69     message_loop_.RunUntilIdle();
     70   }
     71 
     72  protected:
     73   void ResetFrontend() {
     74     storage_factory_->Reset(new LeveldbSettingsStorageFactory());
     75     frontend_.reset(
     76         SettingsFrontend::Create(storage_factory_.get(), profile_.get()));
     77   }
     78 
     79   base::ScopedTempDir temp_dir_;
     80   scoped_ptr<util::MockProfile> profile_;
     81   scoped_ptr<SettingsFrontend> frontend_;
     82   scoped_refptr<util::ScopedSettingsStorageFactory> storage_factory_;
     83 
     84  private:
     85   base::MessageLoop message_loop_;
     86   content::TestBrowserThread ui_thread_;
     87   content::TestBrowserThread file_thread_;
     88 };
     89 
     90 // Get a semblance of coverage for both extension and app settings by
     91 // alternating in each test.
     92 // TODO(kalman): explicitly test the two interact correctly.
     93 
     94 TEST_F(ExtensionSettingsFrontendTest, SettingsPreservedAcrossReconstruction) {
     95   const std::string id = "ext";
     96   ExtensionServiceInterface* esi =
     97       extensions::ExtensionSystem::Get(profile_.get())->extension_service();
     98   static_cast<extensions::settings_test_util::MockExtensionService*>(esi)->
     99       AddExtensionWithId(id, Manifest::TYPE_EXTENSION);
    100 
    101   ValueStore* storage = util::GetStorage(id, frontend_.get());
    102 
    103   // The correctness of Get/Set/Remove/Clear is tested elsewhere so no need to
    104   // be too rigorous.
    105   {
    106     StringValue bar("bar");
    107     ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar);
    108     ASSERT_FALSE(result->HasError());
    109   }
    110 
    111   {
    112     ValueStore::ReadResult result = storage->Get();
    113     ASSERT_FALSE(result->HasError());
    114     EXPECT_FALSE(result->settings()->empty());
    115   }
    116 
    117   ResetFrontend();
    118   storage = util::GetStorage(id, frontend_.get());
    119 
    120   {
    121     ValueStore::ReadResult result = storage->Get();
    122     ASSERT_FALSE(result->HasError());
    123     EXPECT_FALSE(result->settings()->empty());
    124   }
    125 }
    126 
    127 TEST_F(ExtensionSettingsFrontendTest, SettingsClearedOnUninstall) {
    128   const std::string id = "ext";
    129   ExtensionServiceInterface* esi =
    130       extensions::ExtensionSystem::Get(profile_.get())->extension_service();
    131   static_cast<extensions::settings_test_util::MockExtensionService*>(esi)->
    132       AddExtensionWithId(id, Manifest::TYPE_LEGACY_PACKAGED_APP);
    133 
    134   ValueStore* storage = util::GetStorage(id, frontend_.get());
    135 
    136   {
    137     StringValue bar("bar");
    138     ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar);
    139     ASSERT_FALSE(result->HasError());
    140   }
    141 
    142   // This would be triggered by extension uninstall via a DataDeleter.
    143   frontend_->DeleteStorageSoon(id);
    144   base::MessageLoop::current()->RunUntilIdle();
    145 
    146   // The storage area may no longer be valid post-uninstall, so re-request.
    147   storage = util::GetStorage(id, frontend_.get());
    148   {
    149     ValueStore::ReadResult result = storage->Get();
    150     ASSERT_FALSE(result->HasError());
    151     EXPECT_TRUE(result->settings()->empty());
    152   }
    153 }
    154 
    155 TEST_F(ExtensionSettingsFrontendTest, LeveldbDatabaseDeletedFromDiskOnClear) {
    156   const std::string id = "ext";
    157   ExtensionServiceInterface* esi =
    158       extensions::ExtensionSystem::Get(profile_.get())->extension_service();
    159   static_cast<extensions::settings_test_util::MockExtensionService*>(esi)->
    160       AddExtensionWithId(id, Manifest::TYPE_EXTENSION);
    161 
    162   ValueStore* storage = util::GetStorage(id, frontend_.get());
    163 
    164   {
    165     StringValue bar("bar");
    166     ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar);
    167     ASSERT_FALSE(result->HasError());
    168     EXPECT_TRUE(base::PathExists(temp_dir_.path()));
    169   }
    170 
    171   // Should need to both clear the database and delete the frontend for the
    172   // leveldb database to be deleted from disk.
    173   {
    174     ValueStore::WriteResult result = storage->Clear();
    175     ASSERT_FALSE(result->HasError());
    176     EXPECT_TRUE(base::PathExists(temp_dir_.path()));
    177   }
    178 
    179   frontend_.reset();
    180   base::MessageLoop::current()->RunUntilIdle();
    181   // TODO(kalman): Figure out why this fails, despite appearing to work.
    182   // Leaving this commented out rather than disabling the whole test so that the
    183   // deletion code paths are at least exercised.
    184   //EXPECT_FALSE(base::PathExists(temp_dir_.path()));
    185 }
    186 
    187 TEST_F(ExtensionSettingsFrontendTest,
    188        QuotaLimitsEnforcedCorrectlyForSyncAndLocal) {
    189   const std::string id = "ext";
    190   ExtensionServiceInterface* esi =
    191       extensions::ExtensionSystem::Get(profile_.get())->extension_service();
    192   static_cast<extensions::settings_test_util::MockExtensionService*>(esi)->
    193       AddExtensionWithId(id, Manifest::TYPE_EXTENSION);
    194 
    195   ValueStore* sync_storage =
    196       util::GetStorage(id, settings::SYNC, frontend_.get());
    197   ValueStore* local_storage =
    198       util::GetStorage(id, settings::LOCAL, frontend_.get());
    199 
    200   // Sync storage should run out after ~100K.
    201   scoped_ptr<Value> kilobyte = CreateKilobyte();
    202   for (int i = 0; i < 100; ++i) {
    203     sync_storage->Set(
    204         ValueStore::DEFAULTS, base::StringPrintf("%d", i), *kilobyte);
    205   }
    206 
    207   EXPECT_TRUE(sync_storage->Set(
    208       ValueStore::DEFAULTS, "WillError", *kilobyte)->HasError());
    209 
    210   // Local storage shouldn't run out after ~100K.
    211   for (int i = 0; i < 100; ++i) {
    212     local_storage->Set(
    213         ValueStore::DEFAULTS, base::StringPrintf("%d", i), *kilobyte);
    214   }
    215 
    216   EXPECT_FALSE(local_storage->Set(
    217       ValueStore::DEFAULTS, "WontError", *kilobyte)->HasError());
    218 
    219   // Local storage should run out after ~5MB.
    220   scoped_ptr<Value> megabyte = CreateMegabyte();
    221   for (int i = 0; i < 5; ++i) {
    222     local_storage->Set(
    223         ValueStore::DEFAULTS, base::StringPrintf("%d", i), *megabyte);
    224   }
    225 
    226   EXPECT_TRUE(local_storage->Set(
    227       ValueStore::DEFAULTS, "WillError", *megabyte)->HasError());
    228 }
    229 
    230 // In other tests, we assume that the result of GetStorage is a pointer to the
    231 // a Storage owned by a Frontend object, but for the unlimitedStorage case, this
    232 // might not be true. So, write the tests in a "callback" style.
    233 // We should really rewrite all tests to be asynchronous in this way.
    234 
    235 static void UnlimitedSyncStorageTestCallback(ValueStore* sync_storage) {
    236   // Sync storage should still run out after ~100K; the unlimitedStorage
    237   // permission can't apply to sync.
    238   scoped_ptr<Value> kilobyte = CreateKilobyte();
    239   for (int i = 0; i < 100; ++i) {
    240     sync_storage->Set(
    241         ValueStore::DEFAULTS, base::StringPrintf("%d", i), *kilobyte);
    242   }
    243 
    244   EXPECT_TRUE(sync_storage->Set(
    245       ValueStore::DEFAULTS, "WillError", *kilobyte)->HasError());
    246 }
    247 
    248 static void UnlimitedLocalStorageTestCallback(ValueStore* local_storage) {
    249   // Local storage should never run out.
    250   scoped_ptr<Value> megabyte = CreateMegabyte();
    251   for (int i = 0; i < 7; ++i) {
    252     local_storage->Set(
    253         ValueStore::DEFAULTS, base::StringPrintf("%d", i), *megabyte);
    254   }
    255 
    256   EXPECT_FALSE(local_storage->Set(
    257       ValueStore::DEFAULTS, "WontError", *megabyte)->HasError());
    258 }
    259 
    260 #if defined(OS_WIN)
    261 // See: http://crbug.com/227296
    262 #define MAYBE_UnlimitedStorageForLocalButNotSync \
    263     DISABLED_UnlimitedStorageForLocalButNotSync
    264 #else
    265 #define MAYBE_UnlimitedStorageForLocalButNotSync \
    266     UnlimitedStorageForLocalButNotSync
    267 #endif
    268 
    269 TEST_F(ExtensionSettingsFrontendTest,
    270        MAYBE_UnlimitedStorageForLocalButNotSync) {
    271   const std::string id = "ext";
    272   std::set<std::string> permissions;
    273   permissions.insert("unlimitedStorage");
    274   ExtensionServiceInterface* esi =
    275       extensions::ExtensionSystem::Get(profile_.get())->extension_service();
    276   static_cast<extensions::settings_test_util::MockExtensionService*>(esi)->
    277       AddExtensionWithIdAndPermissions(id, Manifest::TYPE_EXTENSION,
    278           permissions);
    279 
    280   frontend_->RunWithStorage(
    281       id, settings::SYNC, base::Bind(&UnlimitedSyncStorageTestCallback));
    282   frontend_->RunWithStorage(
    283       id, settings::LOCAL, base::Bind(&UnlimitedLocalStorageTestCallback));
    284 
    285   base::MessageLoop::current()->RunUntilIdle();
    286 }
    287 
    288 }  // namespace extensions
    289