| // Copyright (c) 2012 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 "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/api/storage/leveldb_settings_storage_factory.h" |
| #include "chrome/browser/extensions/api/storage/settings_frontend.h" |
| #include "chrome/browser/extensions/api/storage/settings_namespace.h" |
| #include "chrome/browser/extensions/api/storage/settings_test_util.h" |
| #include "chrome/browser/value_store/value_store.h" |
| #include "content/public/test/test_browser_thread.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using content::BrowserThread; |
| |
| namespace extensions { |
| |
| namespace settings = settings_namespace; |
| namespace util = settings_test_util; |
| |
| namespace { |
| |
| // To save typing ValueStore::DEFAULTS everywhere. |
| const ValueStore::WriteOptions DEFAULTS = ValueStore::DEFAULTS; |
| |
| // Creates a kilobyte of data. |
| scoped_ptr<Value> CreateKilobyte() { |
| std::string kilobyte_string; |
| for (int i = 0; i < 1024; ++i) { |
| kilobyte_string += "a"; |
| } |
| return scoped_ptr<Value>(new base::StringValue(kilobyte_string)); |
| } |
| |
| // Creates a megabyte of data. |
| scoped_ptr<Value> CreateMegabyte() { |
| base::ListValue* megabyte = new base::ListValue(); |
| for (int i = 0; i < 1000; ++i) { |
| megabyte->Append(CreateKilobyte().release()); |
| } |
| return scoped_ptr<Value>(megabyte); |
| } |
| |
| } // namespace |
| |
| class ExtensionSettingsFrontendTest : public testing::Test { |
| public: |
| ExtensionSettingsFrontendTest() |
| : storage_factory_(new util::ScopedSettingsStorageFactory()), |
| ui_thread_(BrowserThread::UI, base::MessageLoop::current()), |
| file_thread_(BrowserThread::FILE, base::MessageLoop::current()) {} |
| |
| virtual void SetUp() OVERRIDE { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| profile_.reset(new util::MockProfile(temp_dir_.path())); |
| ResetFrontend(); |
| } |
| |
| virtual void TearDown() OVERRIDE { |
| frontend_.reset(); |
| profile_.reset(); |
| // Execute any pending deletion tasks. |
| message_loop_.RunUntilIdle(); |
| } |
| |
| protected: |
| void ResetFrontend() { |
| storage_factory_->Reset(new LeveldbSettingsStorageFactory()); |
| frontend_.reset( |
| SettingsFrontend::Create(storage_factory_.get(), profile_.get())); |
| } |
| |
| base::ScopedTempDir temp_dir_; |
| scoped_ptr<util::MockProfile> profile_; |
| scoped_ptr<SettingsFrontend> frontend_; |
| scoped_refptr<util::ScopedSettingsStorageFactory> storage_factory_; |
| |
| private: |
| base::MessageLoop message_loop_; |
| content::TestBrowserThread ui_thread_; |
| content::TestBrowserThread file_thread_; |
| }; |
| |
| // Get a semblance of coverage for both extension and app settings by |
| // alternating in each test. |
| // TODO(kalman): explicitly test the two interact correctly. |
| |
| TEST_F(ExtensionSettingsFrontendTest, SettingsPreservedAcrossReconstruction) { |
| const std::string id = "ext"; |
| ExtensionServiceInterface* esi = |
| extensions::ExtensionSystem::Get(profile_.get())->extension_service(); |
| static_cast<extensions::settings_test_util::MockExtensionService*>(esi)-> |
| AddExtensionWithId(id, Manifest::TYPE_EXTENSION); |
| |
| ValueStore* storage = util::GetStorage(id, frontend_.get()); |
| |
| // The correctness of Get/Set/Remove/Clear is tested elsewhere so no need to |
| // be too rigorous. |
| { |
| StringValue bar("bar"); |
| ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar); |
| ASSERT_FALSE(result->HasError()); |
| } |
| |
| { |
| ValueStore::ReadResult result = storage->Get(); |
| ASSERT_FALSE(result->HasError()); |
| EXPECT_FALSE(result->settings().empty()); |
| } |
| |
| ResetFrontend(); |
| storage = util::GetStorage(id, frontend_.get()); |
| |
| { |
| ValueStore::ReadResult result = storage->Get(); |
| ASSERT_FALSE(result->HasError()); |
| EXPECT_FALSE(result->settings().empty()); |
| } |
| } |
| |
| TEST_F(ExtensionSettingsFrontendTest, SettingsClearedOnUninstall) { |
| const std::string id = "ext"; |
| ExtensionServiceInterface* esi = |
| extensions::ExtensionSystem::Get(profile_.get())->extension_service(); |
| static_cast<extensions::settings_test_util::MockExtensionService*>(esi)-> |
| AddExtensionWithId(id, Manifest::TYPE_LEGACY_PACKAGED_APP); |
| |
| ValueStore* storage = util::GetStorage(id, frontend_.get()); |
| |
| { |
| StringValue bar("bar"); |
| ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar); |
| ASSERT_FALSE(result->HasError()); |
| } |
| |
| // This would be triggered by extension uninstall via a DataDeleter. |
| frontend_->DeleteStorageSoon(id); |
| base::MessageLoop::current()->RunUntilIdle(); |
| |
| // The storage area may no longer be valid post-uninstall, so re-request. |
| storage = util::GetStorage(id, frontend_.get()); |
| { |
| ValueStore::ReadResult result = storage->Get(); |
| ASSERT_FALSE(result->HasError()); |
| EXPECT_TRUE(result->settings().empty()); |
| } |
| } |
| |
| TEST_F(ExtensionSettingsFrontendTest, LeveldbDatabaseDeletedFromDiskOnClear) { |
| const std::string id = "ext"; |
| ExtensionServiceInterface* esi = |
| extensions::ExtensionSystem::Get(profile_.get())->extension_service(); |
| static_cast<extensions::settings_test_util::MockExtensionService*>(esi)-> |
| AddExtensionWithId(id, Manifest::TYPE_EXTENSION); |
| |
| ValueStore* storage = util::GetStorage(id, frontend_.get()); |
| |
| { |
| StringValue bar("bar"); |
| ValueStore::WriteResult result = storage->Set(DEFAULTS, "foo", bar); |
| ASSERT_FALSE(result->HasError()); |
| EXPECT_TRUE(base::PathExists(temp_dir_.path())); |
| } |
| |
| // Should need to both clear the database and delete the frontend for the |
| // leveldb database to be deleted from disk. |
| { |
| ValueStore::WriteResult result = storage->Clear(); |
| ASSERT_FALSE(result->HasError()); |
| EXPECT_TRUE(base::PathExists(temp_dir_.path())); |
| } |
| |
| frontend_.reset(); |
| base::MessageLoop::current()->RunUntilIdle(); |
| // TODO(kalman): Figure out why this fails, despite appearing to work. |
| // Leaving this commented out rather than disabling the whole test so that the |
| // deletion code paths are at least exercised. |
| //EXPECT_FALSE(base::PathExists(temp_dir_.path())); |
| } |
| |
| TEST_F(ExtensionSettingsFrontendTest, |
| QuotaLimitsEnforcedCorrectlyForSyncAndLocal) { |
| const std::string id = "ext"; |
| ExtensionServiceInterface* esi = |
| extensions::ExtensionSystem::Get(profile_.get())->extension_service(); |
| static_cast<extensions::settings_test_util::MockExtensionService*>(esi)-> |
| AddExtensionWithId(id, Manifest::TYPE_EXTENSION); |
| |
| ValueStore* sync_storage = |
| util::GetStorage(id, settings::SYNC, frontend_.get()); |
| ValueStore* local_storage = |
| util::GetStorage(id, settings::LOCAL, frontend_.get()); |
| |
| // Sync storage should run out after ~100K. |
| scoped_ptr<Value> kilobyte = CreateKilobyte(); |
| for (int i = 0; i < 100; ++i) { |
| sync_storage->Set( |
| ValueStore::DEFAULTS, base::StringPrintf("%d", i), *kilobyte); |
| } |
| |
| EXPECT_TRUE(sync_storage->Set( |
| ValueStore::DEFAULTS, "WillError", *kilobyte)->HasError()); |
| |
| // Local storage shouldn't run out after ~100K. |
| for (int i = 0; i < 100; ++i) { |
| local_storage->Set( |
| ValueStore::DEFAULTS, base::StringPrintf("%d", i), *kilobyte); |
| } |
| |
| EXPECT_FALSE(local_storage->Set( |
| ValueStore::DEFAULTS, "WontError", *kilobyte)->HasError()); |
| |
| // Local storage should run out after ~5MB. |
| scoped_ptr<Value> megabyte = CreateMegabyte(); |
| for (int i = 0; i < 5; ++i) { |
| local_storage->Set( |
| ValueStore::DEFAULTS, base::StringPrintf("%d", i), *megabyte); |
| } |
| |
| EXPECT_TRUE(local_storage->Set( |
| ValueStore::DEFAULTS, "WillError", *megabyte)->HasError()); |
| } |
| |
| // In other tests, we assume that the result of GetStorage is a pointer to the |
| // a Storage owned by a Frontend object, but for the unlimitedStorage case, this |
| // might not be true. So, write the tests in a "callback" style. |
| // We should really rewrite all tests to be asynchronous in this way. |
| |
| static void UnlimitedSyncStorageTestCallback(ValueStore* sync_storage) { |
| // Sync storage should still run out after ~100K; the unlimitedStorage |
| // permission can't apply to sync. |
| scoped_ptr<Value> kilobyte = CreateKilobyte(); |
| for (int i = 0; i < 100; ++i) { |
| sync_storage->Set( |
| ValueStore::DEFAULTS, base::StringPrintf("%d", i), *kilobyte); |
| } |
| |
| EXPECT_TRUE(sync_storage->Set( |
| ValueStore::DEFAULTS, "WillError", *kilobyte)->HasError()); |
| } |
| |
| static void UnlimitedLocalStorageTestCallback(ValueStore* local_storage) { |
| // Local storage should never run out. |
| scoped_ptr<Value> megabyte = CreateMegabyte(); |
| for (int i = 0; i < 7; ++i) { |
| local_storage->Set( |
| ValueStore::DEFAULTS, base::StringPrintf("%d", i), *megabyte); |
| } |
| |
| EXPECT_FALSE(local_storage->Set( |
| ValueStore::DEFAULTS, "WontError", *megabyte)->HasError()); |
| } |
| |
| #if defined(OS_WIN) |
| // See: http://crbug.com/227296 |
| #define MAYBE_UnlimitedStorageForLocalButNotSync \ |
| DISABLED_UnlimitedStorageForLocalButNotSync |
| #else |
| #define MAYBE_UnlimitedStorageForLocalButNotSync \ |
| UnlimitedStorageForLocalButNotSync |
| #endif |
| |
| TEST_F(ExtensionSettingsFrontendTest, |
| MAYBE_UnlimitedStorageForLocalButNotSync) { |
| const std::string id = "ext"; |
| std::set<std::string> permissions; |
| permissions.insert("unlimitedStorage"); |
| ExtensionServiceInterface* esi = |
| extensions::ExtensionSystem::Get(profile_.get())->extension_service(); |
| static_cast<extensions::settings_test_util::MockExtensionService*>(esi)-> |
| AddExtensionWithIdAndPermissions(id, Manifest::TYPE_EXTENSION, |
| permissions); |
| |
| frontend_->RunWithStorage( |
| id, settings::SYNC, base::Bind(&UnlimitedSyncStorageTestCallback)); |
| frontend_->RunWithStorage( |
| id, settings::LOCAL, base::Bind(&UnlimitedLocalStorageTestCallback)); |
| |
| base::MessageLoop::current()->RunUntilIdle(); |
| } |
| |
| } // namespace extensions |