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