diff options
author | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-13 19:57:41 +0000 |
---|---|---|
committer | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-13 19:57:41 +0000 |
commit | 3a4f74af6084db9d35b298f889d00557445e8bab (patch) | |
tree | d784934185d8a9aa82d728191375c4863ee3ab4b /extensions/browser/api | |
parent | 2f018d6f8564359c10abf57999a787142713c631 (diff) | |
download | chromium_src-3a4f74af6084db9d35b298f889d00557445e8bab.zip chromium_src-3a4f74af6084db9d35b298f889d00557445e8bab.tar.gz chromium_src-3a4f74af6084db9d35b298f889d00557445e8bab.tar.bz2 |
Move storage API interface description to src/extensions
The extensions local-storage API is moving into src/extensions so it can be
used by app_shell. This finishes that move.
* Move storage.json to extensions/common/api
* Move storage functions into core_api namespace
* Remove ExtensionService references from StorageApiUnittest
BUG=348058
TEST=unit_tests *Storage* and browser_tests *Storage*
Review URL: https://codereview.chromium.org/197883002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256901 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions/browser/api')
-rw-r--r-- | extensions/browser/api/storage/local_value_store_cache.cc | 6 | ||||
-rw-r--r-- | extensions/browser/api/storage/settings_frontend.cc | 4 | ||||
-rw-r--r-- | extensions/browser/api/storage/storage_api.cc | 304 | ||||
-rw-r--r-- | extensions/browser/api/storage/storage_api.h | 143 | ||||
-rw-r--r-- | extensions/browser/api/storage/storage_api_unittest.cc | 117 |
5 files changed, 569 insertions, 5 deletions
diff --git a/extensions/browser/api/storage/local_value_store_cache.cc b/extensions/browser/api/storage/local_value_store_cache.cc index 7c7ce58..6d05ca2 100644 --- a/extensions/browser/api/storage/local_value_store_cache.cc +++ b/extensions/browser/api/storage/local_value_store_cache.cc @@ -9,12 +9,12 @@ #include "base/bind.h" #include "base/callback.h" #include "base/files/file_path.h" -#include "chrome/common/extensions/api/storage.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/api/storage/settings_storage_factory.h" #include "extensions/browser/api/storage/settings_storage_quota_enforcer.h" #include "extensions/browser/api/storage/weak_unlimited_settings_storage.h" #include "extensions/browser/value_store/value_store.h" +#include "extensions/common/api/storage.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/permissions/api_permission.h" @@ -26,10 +26,10 @@ namespace extensions { namespace { // Returns the quota limit for local storage, taken from the schema in -// chrome/common/extensions/api/storage.json. +// extensions/common/api/storage.json. SettingsStorageQuotaEnforcer::Limits GetLocalQuotaLimits() { SettingsStorageQuotaEnforcer::Limits limits = { - static_cast<size_t>(api::storage::local::QUOTA_BYTES), + static_cast<size_t>(core_api::storage::local::QUOTA_BYTES), std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max() }; diff --git a/extensions/browser/api/storage/settings_frontend.cc b/extensions/browser/api/storage/settings_frontend.cc index 6fa3337..87dc8e9 100644 --- a/extensions/browser/api/storage/settings_frontend.cc +++ b/extensions/browser/api/storage/settings_frontend.cc @@ -9,7 +9,6 @@ #include "base/files/file_path.h" #include "base/json/json_reader.h" #include "base/lazy_instance.h" -#include "chrome/common/extensions/api/storage.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/api/extensions_api_client.h" @@ -18,6 +17,7 @@ #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" +#include "extensions/common/api/storage.h" using content::BrowserContext; using content::BrowserThread; @@ -48,7 +48,7 @@ class DefaultObserver : public SettingsObserver { args->Append(new base::StringValue(settings_namespace::ToString( settings_namespace))); scoped_ptr<Event> event(new Event( - api::storage::OnChanged::kEventName, args.Pass())); + core_api::storage::OnChanged::kEventName, args.Pass())); ExtensionSystem::Get(browser_context_)->event_router()-> DispatchEventToExtension(extension_id, event.Pass()); } diff --git a/extensions/browser/api/storage/storage_api.cc b/extensions/browser/api/storage/storage_api.cc new file mode 100644 index 0000000..939a75a --- /dev/null +++ b/extensions/browser/api/storage/storage_api.cc @@ -0,0 +1,304 @@ +// Copyright 2014 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 "extensions/browser/api/storage/storage_api.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" +#include "extensions/browser/api/storage/settings_frontend.h" +#include "extensions/browser/quota_service.h" +#include "extensions/common/api/storage.h" + +namespace extensions { + +using content::BrowserThread; + +// SettingsFunction + +SettingsFunction::SettingsFunction() + : settings_namespace_(settings_namespace::INVALID), + tried_restoring_storage_(false) {} + +SettingsFunction::~SettingsFunction() {} + +bool SettingsFunction::ShouldSkipQuotaLimiting() const { + // Only apply quota if this is for sync storage. + std::string settings_namespace_string; + if (!args_->GetString(0, &settings_namespace_string)) { + // This should be EXTENSION_FUNCTION_VALIDATE(false) but there is no way + // to signify that from this function. It will be caught in RunImpl(). + return false; + } + return settings_namespace_string != "sync"; +} + +bool SettingsFunction::RunImpl() { + std::string settings_namespace_string; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &settings_namespace_string)); + args_->Remove(0, NULL); + settings_namespace_ = + settings_namespace::FromString(settings_namespace_string); + EXTENSION_FUNCTION_VALIDATE( + settings_namespace_ != settings_namespace::INVALID); + + SettingsFrontend* frontend = SettingsFrontend::Get(browser_context()); + if (!frontend->IsStorageEnabled(settings_namespace_)) { + error_ = base::StringPrintf( + "\"%s\" is not available in this instance of Chrome", + settings_namespace_string.c_str()); + return false; + } + + observers_ = frontend->GetObservers(); + frontend->RunWithStorage( + GetExtension(), + settings_namespace_, + base::Bind(&SettingsFunction::AsyncRunWithStorage, this)); + return true; +} + +void SettingsFunction::AsyncRunWithStorage(ValueStore* storage) { + bool success = RunWithStorage(storage); + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&SettingsFunction::SendResponse, this, success)); +} + +bool SettingsFunction::UseReadResult(ValueStore::ReadResult result, + ValueStore* storage) { + if (result->HasError()) + return HandleError(result->error(), storage); + + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->Swap(&result->settings()); + SetResult(dict); + return true; +} + +bool SettingsFunction::UseWriteResult(ValueStore::WriteResult result, + ValueStore* storage) { + if (result->HasError()) + return HandleError(result->error(), storage); + + if (!result->changes().empty()) { + observers_->Notify( + &SettingsObserver::OnSettingsChanged, + extension_id(), + settings_namespace_, + ValueStoreChange::ToJson(result->changes())); + } + + return true; +} + +bool SettingsFunction::HandleError(const ValueStore::Error& error, + ValueStore* storage) { + // If the method failed due to corruption, and we haven't tried to fix it, we + // can try to restore the storage and re-run it. Otherwise, the method has + // failed. + if (error.code == ValueStore::CORRUPTION && !tried_restoring_storage_) { + tried_restoring_storage_ = true; + + // If the corruption is on a particular key, try to restore that key and + // re-run. + if (error.key.get() && storage->RestoreKey(*error.key)) + return RunWithStorage(storage); + + // If the full database is corrupted, try to restore the whole thing and + // re-run. + if (storage->Restore()) + return RunWithStorage(storage); + } + + error_ = error.message; + return false; +} + +// Concrete settings functions + +namespace { + +// Adds all StringValues from a ListValue to a vector of strings. +void AddAllStringValues(const base::ListValue& from, + std::vector<std::string>* to) { + DCHECK(to->empty()); + std::string as_string; + for (base::ListValue::const_iterator it = from.begin(); + it != from.end(); ++it) { + if ((*it)->GetAsString(&as_string)) { + to->push_back(as_string); + } + } +} + +// Gets the keys of a DictionaryValue. +std::vector<std::string> GetKeys(const base::DictionaryValue& dict) { + std::vector<std::string> keys; + for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { + keys.push_back(it.key()); + } + return keys; +} + +// Creates quota heuristics for settings modification. +void GetModificationQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) { + QuotaLimitHeuristic::Config longLimitConfig = { + // See storage.json for current value. + core_api::storage::sync::MAX_WRITE_OPERATIONS_PER_HOUR, + base::TimeDelta::FromHours(1) + }; + heuristics->push_back(new QuotaService::TimedLimit( + longLimitConfig, + new QuotaLimitHeuristic::SingletonBucketMapper(), + "MAX_WRITE_OPERATIONS_PER_HOUR")); + + // A max of 10 operations per minute, sustained over 10 minutes. + QuotaLimitHeuristic::Config shortLimitConfig = { + // See storage.json for current value. + core_api::storage::sync::MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE, + base::TimeDelta::FromMinutes(1) + }; + heuristics->push_back(new QuotaService::SustainedLimit( + base::TimeDelta::FromMinutes(10), + shortLimitConfig, + new QuotaLimitHeuristic::SingletonBucketMapper(), + "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE")); +}; + +} // namespace + +bool StorageStorageAreaGetFunction::RunWithStorage(ValueStore* storage) { + base::Value* input = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &input)); + + switch (input->GetType()) { + case base::Value::TYPE_NULL: + return UseReadResult(storage->Get(), storage); + + case base::Value::TYPE_STRING: { + std::string as_string; + input->GetAsString(&as_string); + return UseReadResult(storage->Get(as_string), storage); + } + + case base::Value::TYPE_LIST: { + std::vector<std::string> as_string_list; + AddAllStringValues(*static_cast<base::ListValue*>(input), + &as_string_list); + return UseReadResult(storage->Get(as_string_list), storage); + } + + case base::Value::TYPE_DICTIONARY: { + base::DictionaryValue* as_dict = + static_cast<base::DictionaryValue*>(input); + ValueStore::ReadResult result = storage->Get(GetKeys(*as_dict)); + if (result->HasError()) { + return UseReadResult(result.Pass(), storage); + } + + base::DictionaryValue* with_default_values = as_dict->DeepCopy(); + with_default_values->MergeDictionary(&result->settings()); + return UseReadResult( + ValueStore::MakeReadResult(make_scoped_ptr(with_default_values)), + storage); + } + + default: + EXTENSION_FUNCTION_VALIDATE(false); + return false; + } +} + +bool StorageStorageAreaGetBytesInUseFunction::RunWithStorage( + ValueStore* storage) { + base::Value* input = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &input)); + + size_t bytes_in_use = 0; + + switch (input->GetType()) { + case base::Value::TYPE_NULL: + bytes_in_use = storage->GetBytesInUse(); + break; + + case base::Value::TYPE_STRING: { + std::string as_string; + input->GetAsString(&as_string); + bytes_in_use = storage->GetBytesInUse(as_string); + break; + } + + case base::Value::TYPE_LIST: { + std::vector<std::string> as_string_list; + AddAllStringValues(*static_cast<base::ListValue*>(input), + &as_string_list); + bytes_in_use = storage->GetBytesInUse(as_string_list); + break; + } + + default: + EXTENSION_FUNCTION_VALIDATE(false); + return false; + } + + SetResult(new base::FundamentalValue(static_cast<int>(bytes_in_use))); + return true; +} + +bool StorageStorageAreaSetFunction::RunWithStorage(ValueStore* storage) { + base::DictionaryValue* input = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &input)); + return UseWriteResult(storage->Set(ValueStore::DEFAULTS, *input), storage); +} + +void StorageStorageAreaSetFunction::GetQuotaLimitHeuristics( + QuotaLimitHeuristics* heuristics) const { + GetModificationQuotaLimitHeuristics(heuristics); +} + +bool StorageStorageAreaRemoveFunction::RunWithStorage(ValueStore* storage) { + base::Value* input = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &input)); + + switch (input->GetType()) { + case base::Value::TYPE_STRING: { + std::string as_string; + input->GetAsString(&as_string); + return UseWriteResult(storage->Remove(as_string), storage); + } + + case base::Value::TYPE_LIST: { + std::vector<std::string> as_string_list; + AddAllStringValues(*static_cast<base::ListValue*>(input), + &as_string_list); + return UseWriteResult(storage->Remove(as_string_list), storage); + } + + default: + EXTENSION_FUNCTION_VALIDATE(false); + return false; + }; +} + +void StorageStorageAreaRemoveFunction::GetQuotaLimitHeuristics( + QuotaLimitHeuristics* heuristics) const { + GetModificationQuotaLimitHeuristics(heuristics); +} + +bool StorageStorageAreaClearFunction::RunWithStorage(ValueStore* storage) { + return UseWriteResult(storage->Clear(), storage); +} + +void StorageStorageAreaClearFunction::GetQuotaLimitHeuristics( + QuotaLimitHeuristics* heuristics) const { + GetModificationQuotaLimitHeuristics(heuristics); +} + +} // namespace extensions diff --git a/extensions/browser/api/storage/storage_api.h b/extensions/browser/api/storage/storage_api.h new file mode 100644 index 0000000..f573235 --- /dev/null +++ b/extensions/browser/api/storage/storage_api.h @@ -0,0 +1,143 @@ +// Copyright 2014 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. + +#ifndef EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_ +#define EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "extensions/browser/api/storage/settings_namespace.h" +#include "extensions/browser/api/storage/settings_observer.h" +#include "extensions/browser/extension_function.h" +#include "extensions/browser/value_store/value_store.h" + +namespace extensions { + +// Superclass of all settings functions. +class SettingsFunction : public UIThreadExtensionFunction { + protected: + SettingsFunction(); + virtual ~SettingsFunction(); + + // ExtensionFunction: + virtual bool ShouldSkipQuotaLimiting() const OVERRIDE; + virtual bool RunImpl() OVERRIDE; + + // Extension settings function implementations should do their work here. + // The SettingsFrontend makes sure this is posted to the appropriate thread. + // Implementations should fill in args themselves, though (like RunImpl) + // may return false to imply failure. + virtual bool RunWithStorage(ValueStore* storage) = 0; + + // Handles the |result| of a read function. + // - If the result succeeded, this will set |result_| and return. + // - If |result| failed with a ValueStore::CORRUPTION error, this will call + // RestoreStorageAndRetry(), and return that result. + // - If the |result| failed with a different error, this will set |error_| + // and return. + bool UseReadResult(ValueStore::ReadResult result, ValueStore* storage); + + // Handles the |result| of a write function. + // - If the result succeeded, this will set |result_| and return. + // - If |result| failed with a ValueStore::CORRUPTION error, this will call + // RestoreStorageAndRetry(), and return that result. + // - If the |result| failed with a different error, this will set |error_| + // and return. + // This will also send out a change notification, if appropriate. + bool UseWriteResult(ValueStore::WriteResult result, ValueStore* storage); + + private: + // Called via PostTask from RunImpl. Calls RunWithStorage and then + // SendResponse with its success value. + void AsyncRunWithStorage(ValueStore* storage); + + // Called if we encounter a ValueStore error. If the error is due to + // corruption, tries to restore the ValueStore and re-run the API function. + // If the storage cannot be restored or was due to some other error, then sets + // error and returns. This also sets the |tried_restoring_storage_| flag to + // ensure we don't enter a loop. + bool HandleError(const ValueStore::Error& error, ValueStore* storage); + + // The settings namespace the call was for. For example, SYNC if the API + // call was chrome.settings.experimental.sync..., LOCAL if .local, etc. + settings_namespace::Namespace settings_namespace_; + + // A flag indicating whether or not we have tried to restore storage. We + // should only ever try once (per API call) in order to avoid entering a loop. + bool tried_restoring_storage_; + + // Observers, cached so that it's only grabbed from the UI thread. + scoped_refptr<SettingsObserverList> observers_; +}; + +class StorageStorageAreaGetFunction : public SettingsFunction { + public: + DECLARE_EXTENSION_FUNCTION("storage.get", STORAGE_GET) + + protected: + virtual ~StorageStorageAreaGetFunction() {} + + // SettingsFunction: + virtual bool RunWithStorage(ValueStore* storage) OVERRIDE; +}; + +class StorageStorageAreaSetFunction : public SettingsFunction { + public: + DECLARE_EXTENSION_FUNCTION("storage.set", STORAGE_SET) + + protected: + virtual ~StorageStorageAreaSetFunction() {} + + // SettingsFunction: + virtual bool RunWithStorage(ValueStore* storage) OVERRIDE; + + // ExtensionFunction: + virtual void GetQuotaLimitHeuristics( + QuotaLimitHeuristics* heuristics) const OVERRIDE; +}; + +class StorageStorageAreaRemoveFunction : public SettingsFunction { + public: + DECLARE_EXTENSION_FUNCTION("storage.remove", STORAGE_REMOVE) + + protected: + virtual ~StorageStorageAreaRemoveFunction() {} + + // SettingsFunction: + virtual bool RunWithStorage(ValueStore* storage) OVERRIDE; + + // ExtensionFunction: + virtual void GetQuotaLimitHeuristics( + QuotaLimitHeuristics* heuristics) const OVERRIDE; +}; + +class StorageStorageAreaClearFunction : public SettingsFunction { + public: + DECLARE_EXTENSION_FUNCTION("storage.clear", STORAGE_CLEAR) + + protected: + virtual ~StorageStorageAreaClearFunction() {} + + // SettingsFunction: + virtual bool RunWithStorage(ValueStore* storage) OVERRIDE; + + // ExtensionFunction: + virtual void GetQuotaLimitHeuristics( + QuotaLimitHeuristics* heuristics) const OVERRIDE; +}; + +class StorageStorageAreaGetBytesInUseFunction : public SettingsFunction { + public: + DECLARE_EXTENSION_FUNCTION("storage.getBytesInUse", STORAGE_GETBYTESINUSE) + + protected: + virtual ~StorageStorageAreaGetBytesInUseFunction() {} + + // SettingsFunction: + virtual bool RunWithStorage(ValueStore* storage) OVERRIDE; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_STORAGE_STORAGE_API_H_ diff --git a/extensions/browser/api/storage/storage_api_unittest.cc b/extensions/browser/api/storage/storage_api_unittest.cc new file mode 100644 index 0000000..2fe5269 --- /dev/null +++ b/extensions/browser/api/storage/storage_api_unittest.cc @@ -0,0 +1,117 @@ +// Copyright 2014 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/command_line.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/strings/stringprintf.h" +#include "chrome/browser/extensions/extension_api_unittest.h" +#include "chrome/browser/extensions/test_extension_system.h" +#include "extensions/browser/api/storage/leveldb_settings_storage_factory.h" +#include "extensions/browser/api/storage/settings_frontend.h" +#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h" +#include "extensions/browser/api/storage/settings_test_util.h" +#include "extensions/browser/api/storage/storage_api.h" +#include "extensions/browser/event_router.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/value_store/leveldb_value_store.h" +#include "extensions/browser/value_store/value_store.h" +#include "extensions/common/id_util.h" +#include "extensions/common/manifest.h" +#include "extensions/common/test_util.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" + +namespace extensions { + +namespace { + +// Caller owns the returned object. +KeyedService* CreateSettingsFrontendForTesting( + content::BrowserContext* context) { + return SettingsFrontend::CreateForTesting(new LeveldbSettingsStorageFactory(), + context); +} + +} // namespace + +class StorageApiUnittest : public ExtensionApiUnittest { + public: + virtual void SetUp() OVERRIDE { + ExtensionApiUnittest::SetUp(); + TestExtensionSystem* extension_system = + static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile())); + // SettingsFrontend requires an EventRouter. + extension_system->SetEventRouter(scoped_ptr<EventRouter>( + new EventRouter(profile(), ExtensionPrefs::Get(profile())))); + } + + protected: + // Runs the storage.set() API function with local storage. + void RunSetFunction(const std::string& key, const std::string& value) { + RunFunction( + new StorageStorageAreaSetFunction(), + base::StringPrintf( + "[\"local\", {\"%s\": \"%s\"}]", key.c_str(), value.c_str())); + } + + // Runs the storage.get() API function with the local storage, and populates + // |value| with the string result. + testing::AssertionResult RunGetFunction(const std::string& key, + std::string* value) { + scoped_ptr<base::Value> result = RunFunctionAndReturnValue( + new StorageStorageAreaGetFunction(), + base::StringPrintf("[\"local\", \"%s\"]", key.c_str())); + base::DictionaryValue* dict = NULL; + if (!result->GetAsDictionary(&dict)) + return testing::AssertionFailure() << result << " was not a dictionary."; + if (!dict->GetString(key, value)) { + return testing::AssertionFailure() << " could not retrieve a string from" + << dict << " at " << key; + } + return testing::AssertionSuccess(); + } +}; + +TEST_F(StorageApiUnittest, RestoreCorruptedStorage) { + // Ensure a SettingsFrontend can be created on demand. The SettingsFrontend + // will be owned by the KeyedService system. + SettingsFrontend::GetFactoryInstance()->SetTestingFactory( + profile(), &CreateSettingsFrontendForTesting); + + const char kKey[] = "key"; + const char kValue[] = "value"; + std::string result; + + // Do a simple set/get combo to make sure the API works. + RunSetFunction(kKey, kValue); + EXPECT_TRUE(RunGetFunction(kKey, &result)); + EXPECT_EQ(kValue, result); + + // Corrupt the store. This is not as pretty as ideal, because we use knowledge + // of the underlying structure, but there's no real good way to corrupt a + // store other than directly modifying the files. + ValueStore* store = + settings_test_util::GetStorage(extension_ref(), + settings_namespace::LOCAL, + SettingsFrontend::Get(profile())); + ASSERT_TRUE(store); + SettingsStorageQuotaEnforcer* quota_store = + static_cast<SettingsStorageQuotaEnforcer*>(store); + LeveldbValueStore* leveldb_store = + static_cast<LeveldbValueStore*>(quota_store->get_delegate_for_test()); + leveldb::WriteBatch batch; + batch.Put(kKey, "[{(.*+\"\'\\"); + EXPECT_TRUE(leveldb_store->WriteToDbForTest(&batch)); + EXPECT_TRUE(leveldb_store->Get(kKey)->IsCorrupted()); + + // Running another set should end up working (even though it will restore the + // store behind the scenes). + RunSetFunction(kKey, kValue); + EXPECT_TRUE(RunGetFunction(kKey, &result)); + EXPECT_EQ(kValue, result); +} + +} // namespace extensions |