| // Copyright 2016 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 "content/browser/leveldb_wrapper_impl.h" |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "components/leveldb/public/cpp/util.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/test/mock_leveldb_database.h" |
| #include "mojo/public/cpp/bindings/associated_binding.h" |
| #include "mojo/public/cpp/bindings/strong_associated_binding.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using leveldb::StdStringToUint8Vector; |
| using leveldb::Uint8VectorToStdString; |
| |
| namespace content { |
| |
| namespace { |
| |
| const char* kTestPrefix = "abc"; |
| const char* kTestSource = "source"; |
| const size_t kTestSizeLimit = 512; |
| |
| class GetAllCallback : public mojom::LevelDBWrapperGetAllCallback { |
| public: |
| static mojom::LevelDBWrapperGetAllCallbackAssociatedPtrInfo CreateAndBind( |
| bool* result, |
| const base::Closure& callback) { |
| mojom::LevelDBWrapperGetAllCallbackAssociatedPtrInfo ptr_info; |
| auto request = mojo::MakeRequest(&ptr_info); |
| mojo::MakeStrongAssociatedBinding( |
| base::WrapUnique(new GetAllCallback(result, callback)), |
| std::move(request)); |
| return ptr_info; |
| } |
| |
| private: |
| GetAllCallback(bool* result, const base::Closure& callback) |
| : m_result(result), m_callback(callback) {} |
| void Complete(bool success) override { |
| *m_result = success; |
| m_callback.Run(); |
| } |
| |
| bool* m_result; |
| base::Closure m_callback; |
| }; |
| |
| class MockDelegate : public LevelDBWrapperImpl::Delegate { |
| public: |
| void OnNoBindings() override {} |
| std::vector<leveldb::mojom::BatchedOperationPtr> PrepareToCommit() override { |
| return std::vector<leveldb::mojom::BatchedOperationPtr>(); |
| } |
| void DidCommit(leveldb::mojom::DatabaseError error) override {} |
| void OnMapLoaded(leveldb::mojom::DatabaseError error) override { |
| map_load_count_++; |
| } |
| |
| int map_load_count() const { return map_load_count_; } |
| |
| private: |
| int map_load_count_ = 0; |
| }; |
| |
| void GetCallback(const base::Closure& callback, |
| bool* success_out, |
| std::vector<uint8_t>* value_out, |
| bool success, |
| const std::vector<uint8_t>& value) { |
| *success_out = success; |
| *value_out = value; |
| callback.Run(); |
| } |
| |
| void SuccessCallback(const base::Closure& callback, |
| bool* success_out, |
| bool success) { |
| *success_out = success; |
| callback.Run(); |
| } |
| |
| void NoOpSuccessCallback(bool success) {} |
| |
| } // namespace |
| |
| class LevelDBWrapperImplTest : public testing::Test, |
| public mojom::LevelDBObserver { |
| public: |
| struct Observation { |
| enum { kAdd, kChange, kDelete, kDeleteAll } type; |
| std::string key; |
| std::string old_value; |
| std::string new_value; |
| std::string source; |
| }; |
| |
| LevelDBWrapperImplTest() |
| : db_(&mock_data_), |
| level_db_wrapper_(&db_, |
| kTestPrefix, |
| kTestSizeLimit, |
| base::TimeDelta::FromSeconds(5), |
| 10 * 1024 * 1024 /* max_bytes_per_hour */, |
| 60 /* max_commits_per_hour */, |
| &delegate_), |
| observer_binding_(this) { |
| set_mock_data(std::string(kTestPrefix) + "def", "defdata"); |
| set_mock_data(std::string(kTestPrefix) + "123", "123data"); |
| set_mock_data("123", "baddata"); |
| |
| level_db_wrapper_.Bind(mojo::MakeRequest(&level_db_wrapper_ptr_)); |
| mojom::LevelDBObserverAssociatedPtrInfo ptr_info; |
| observer_binding_.Bind(mojo::MakeRequest(&ptr_info)); |
| level_db_wrapper_ptr_->AddObserver(std::move(ptr_info)); |
| } |
| |
| void set_mock_data(const std::string& key, const std::string& value) { |
| mock_data_[StdStringToUint8Vector(key)] = StdStringToUint8Vector(value); |
| } |
| |
| void set_mock_data(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& value) { |
| mock_data_[key] = value; |
| } |
| |
| bool has_mock_data(const std::string& key) { |
| return mock_data_.find(StdStringToUint8Vector(key)) != mock_data_.end(); |
| } |
| |
| std::string get_mock_data(const std::string& key) { |
| return has_mock_data(key) |
| ? Uint8VectorToStdString(mock_data_[StdStringToUint8Vector(key)]) |
| : ""; |
| } |
| |
| void clear_mock_data() { mock_data_.clear(); } |
| |
| mojom::LevelDBWrapper* wrapper() { return level_db_wrapper_ptr_.get(); } |
| LevelDBWrapperImpl* wrapper_impl() { return &level_db_wrapper_; } |
| |
| bool GetSync(const std::vector<uint8_t>& key, std::vector<uint8_t>* result) { |
| base::RunLoop run_loop; |
| bool success = false; |
| wrapper()->Get(key, base::BindOnce(&GetCallback, run_loop.QuitClosure(), |
| &success, result)); |
| run_loop.Run(); |
| return success; |
| } |
| |
| bool PutSync(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& value, |
| std::string source = kTestSource) { |
| base::RunLoop run_loop; |
| bool success = false; |
| wrapper()->Put( |
| key, value, source, |
| base::BindOnce(&SuccessCallback, run_loop.QuitClosure(), &success)); |
| run_loop.Run(); |
| return success; |
| } |
| |
| bool DeleteSync(const std::vector<uint8_t>& key) { |
| base::RunLoop run_loop; |
| bool success = false; |
| wrapper()->Delete( |
| key, kTestSource, |
| base::BindOnce(&SuccessCallback, run_loop.QuitClosure(), &success)); |
| run_loop.Run(); |
| return success; |
| } |
| |
| bool DeleteAllSync() { |
| base::RunLoop run_loop; |
| bool success = false; |
| wrapper()->DeleteAll( |
| kTestSource, |
| base::BindOnce(&SuccessCallback, run_loop.QuitClosure(), &success)); |
| run_loop.Run(); |
| return success; |
| } |
| |
| void CommitChanges() { level_db_wrapper_.ScheduleImmediateCommit(); } |
| |
| const std::vector<Observation>& observations() { return observations_; } |
| |
| MockDelegate* delegate() { return &delegate_; } |
| |
| private: |
| // LevelDBObserver: |
| void KeyAdded(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& value, |
| const std::string& source) override { |
| observations_.push_back({Observation::kAdd, Uint8VectorToStdString(key), "", |
| Uint8VectorToStdString(value), source}); |
| } |
| void KeyChanged(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& new_value, |
| const std::vector<uint8_t>& old_value, |
| const std::string& source) override { |
| observations_.push_back({Observation::kChange, Uint8VectorToStdString(key), |
| Uint8VectorToStdString(old_value), |
| Uint8VectorToStdString(new_value), source}); |
| } |
| void KeyDeleted(const std::vector<uint8_t>& key, |
| const std::vector<uint8_t>& old_value, |
| const std::string& source) override { |
| observations_.push_back({Observation::kDelete, Uint8VectorToStdString(key), |
| Uint8VectorToStdString(old_value), "", source}); |
| } |
| void AllDeleted(const std::string& source) override { |
| observations_.push_back({Observation::kDeleteAll, "", "", "", source}); |
| } |
| |
| TestBrowserThreadBundle thread_bundle_; |
| std::map<std::vector<uint8_t>, std::vector<uint8_t>> mock_data_; |
| MockLevelDBDatabase db_; |
| MockDelegate delegate_; |
| LevelDBWrapperImpl level_db_wrapper_; |
| mojom::LevelDBWrapperPtr level_db_wrapper_ptr_; |
| mojo::AssociatedBinding<mojom::LevelDBObserver> observer_binding_; |
| std::vector<Observation> observations_; |
| }; |
| |
| TEST_F(LevelDBWrapperImplTest, GetLoadedFromMap) { |
| std::vector<uint8_t> result; |
| EXPECT_TRUE(GetSync(StdStringToUint8Vector("123"), &result)); |
| EXPECT_EQ(StdStringToUint8Vector("123data"), result); |
| |
| EXPECT_FALSE(GetSync(StdStringToUint8Vector("x"), &result)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, GetFromPutOverwrite) { |
| std::vector<uint8_t> key = StdStringToUint8Vector("123"); |
| std::vector<uint8_t> value = StdStringToUint8Vector("foo"); |
| |
| EXPECT_TRUE(PutSync(key, value)); |
| |
| std::vector<uint8_t> result; |
| EXPECT_TRUE(GetSync(key, &result)); |
| EXPECT_EQ(value, result); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, GetFromPutNewKey) { |
| std::vector<uint8_t> key = StdStringToUint8Vector("newkey"); |
| std::vector<uint8_t> value = StdStringToUint8Vector("foo"); |
| |
| EXPECT_TRUE(PutSync(key, value)); |
| |
| std::vector<uint8_t> result; |
| EXPECT_TRUE(GetSync(key, &result)); |
| EXPECT_EQ(value, result); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, GetAll) { |
| leveldb::mojom::DatabaseError status; |
| std::vector<mojom::KeyValuePtr> data; |
| base::RunLoop run_loop; |
| bool result = false; |
| EXPECT_TRUE(wrapper()->GetAll( |
| GetAllCallback::CreateAndBind(&result, run_loop.QuitClosure()), &status, |
| &data)); |
| EXPECT_EQ(leveldb::mojom::DatabaseError::OK, status); |
| EXPECT_EQ(2u, data.size()); |
| EXPECT_FALSE(result); |
| run_loop.Run(); |
| EXPECT_TRUE(result); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, CommitPutToDB) { |
| std::string key1 = "123"; |
| std::string value1 = "foo"; |
| std::string key2 = "abc"; |
| std::string value2 = "data abc"; |
| |
| EXPECT_TRUE( |
| PutSync(StdStringToUint8Vector(key1), StdStringToUint8Vector(value1))); |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key2), |
| StdStringToUint8Vector("old value"))); |
| EXPECT_TRUE( |
| PutSync(StdStringToUint8Vector(key2), StdStringToUint8Vector(value2))); |
| |
| EXPECT_FALSE(has_mock_data(kTestPrefix + key2)); |
| |
| CommitChanges(); |
| EXPECT_TRUE(has_mock_data(kTestPrefix + key1)); |
| EXPECT_EQ(value1, get_mock_data(kTestPrefix + key1)); |
| EXPECT_TRUE(has_mock_data(kTestPrefix + key2)); |
| EXPECT_EQ(value2, get_mock_data(kTestPrefix + key2)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, PutObservations) { |
| std::string key = "new_key"; |
| std::string value1 = "foo"; |
| std::string value2 = "data abc"; |
| std::string source1 = "source1"; |
| std::string source2 = "source2"; |
| |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), |
| StdStringToUint8Vector(value1), source1)); |
| ASSERT_EQ(1u, observations().size()); |
| EXPECT_EQ(Observation::kAdd, observations()[0].type); |
| EXPECT_EQ(key, observations()[0].key); |
| EXPECT_EQ(value1, observations()[0].new_value); |
| EXPECT_EQ(source1, observations()[0].source); |
| |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), |
| StdStringToUint8Vector(value2), source2)); |
| ASSERT_EQ(2u, observations().size()); |
| EXPECT_EQ(Observation::kChange, observations()[1].type); |
| EXPECT_EQ(key, observations()[1].key); |
| EXPECT_EQ(value1, observations()[1].old_value); |
| EXPECT_EQ(value2, observations()[1].new_value); |
| EXPECT_EQ(source2, observations()[1].source); |
| |
| // Same put should not cause another observation. |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), |
| StdStringToUint8Vector(value2), source2)); |
| ASSERT_EQ(2u, observations().size()); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, DeleteNonExistingKey) { |
| EXPECT_TRUE(DeleteSync(StdStringToUint8Vector("doesn't exist"))); |
| EXPECT_EQ(0u, observations().size()); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, DeleteExistingKey) { |
| std::string key = "newkey"; |
| std::string value = "foo"; |
| set_mock_data(kTestPrefix + key, value); |
| |
| EXPECT_TRUE(DeleteSync(StdStringToUint8Vector(key))); |
| ASSERT_EQ(1u, observations().size()); |
| EXPECT_EQ(Observation::kDelete, observations()[0].type); |
| EXPECT_EQ(key, observations()[0].key); |
| EXPECT_EQ(value, observations()[0].old_value); |
| EXPECT_EQ(kTestSource, observations()[0].source); |
| |
| EXPECT_TRUE(has_mock_data(kTestPrefix + key)); |
| |
| CommitChanges(); |
| EXPECT_FALSE(has_mock_data(kTestPrefix + key)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, DeleteAllWithoutLoadedMap) { |
| std::string key = "newkey"; |
| std::string value = "foo"; |
| std::string dummy_key = "foobar"; |
| set_mock_data(dummy_key, value); |
| set_mock_data(kTestPrefix + key, value); |
| |
| EXPECT_TRUE(DeleteAllSync()); |
| ASSERT_EQ(1u, observations().size()); |
| EXPECT_EQ(Observation::kDeleteAll, observations()[0].type); |
| EXPECT_EQ(kTestSource, observations()[0].source); |
| |
| EXPECT_TRUE(has_mock_data(kTestPrefix + key)); |
| EXPECT_TRUE(has_mock_data(dummy_key)); |
| |
| CommitChanges(); |
| EXPECT_FALSE(has_mock_data(kTestPrefix + key)); |
| EXPECT_TRUE(has_mock_data(dummy_key)); |
| |
| // Deleting all again should still work, but not cause an observation. |
| EXPECT_TRUE(DeleteAllSync()); |
| ASSERT_EQ(1u, observations().size()); |
| |
| // And now we've deleted all, writing something the quota size should work. |
| EXPECT_TRUE(PutSync(std::vector<uint8_t>(kTestSizeLimit, 'b'), |
| std::vector<uint8_t>())); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, DeleteAllWithLoadedMap) { |
| std::string key = "newkey"; |
| std::string value = "foo"; |
| std::string dummy_key = "foobar"; |
| set_mock_data(dummy_key, value); |
| |
| EXPECT_TRUE( |
| PutSync(StdStringToUint8Vector(key), StdStringToUint8Vector(value))); |
| |
| EXPECT_TRUE(DeleteAllSync()); |
| ASSERT_EQ(2u, observations().size()); |
| EXPECT_EQ(Observation::kDeleteAll, observations()[1].type); |
| EXPECT_EQ(kTestSource, observations()[1].source); |
| |
| EXPECT_TRUE(has_mock_data(dummy_key)); |
| |
| CommitChanges(); |
| EXPECT_FALSE(has_mock_data(kTestPrefix + key)); |
| EXPECT_TRUE(has_mock_data(dummy_key)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, DeleteAllWithPendingMapLoad) { |
| std::string key = "newkey"; |
| std::string value = "foo"; |
| std::string dummy_key = "foobar"; |
| set_mock_data(dummy_key, value); |
| |
| wrapper()->Put(StdStringToUint8Vector(key), StdStringToUint8Vector(value), |
| kTestSource, base::BindOnce(&NoOpSuccessCallback)); |
| |
| EXPECT_TRUE(DeleteAllSync()); |
| ASSERT_EQ(2u, observations().size()); |
| EXPECT_EQ(Observation::kDeleteAll, observations()[1].type); |
| EXPECT_EQ(kTestSource, observations()[1].source); |
| |
| EXPECT_TRUE(has_mock_data(dummy_key)); |
| |
| CommitChanges(); |
| EXPECT_FALSE(has_mock_data(kTestPrefix + key)); |
| EXPECT_TRUE(has_mock_data(dummy_key)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, DeleteAllWithoutLoadedEmptyMap) { |
| clear_mock_data(); |
| |
| EXPECT_TRUE(DeleteAllSync()); |
| ASSERT_EQ(0u, observations().size()); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, PutOverQuotaLargeValue) { |
| std::vector<uint8_t> key = StdStringToUint8Vector("newkey"); |
| std::vector<uint8_t> value(kTestSizeLimit, 4); |
| |
| EXPECT_FALSE(PutSync(key, value)); |
| |
| value.resize(kTestSizeLimit / 2); |
| EXPECT_TRUE(PutSync(key, value)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, PutOverQuotaLargeKey) { |
| std::vector<uint8_t> key(kTestSizeLimit, 'a'); |
| std::vector<uint8_t> value = StdStringToUint8Vector("newvalue"); |
| |
| EXPECT_FALSE(PutSync(key, value)); |
| |
| key.resize(kTestSizeLimit / 2); |
| EXPECT_TRUE(PutSync(key, value)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, PutWhenAlreadyOverQuota) { |
| std::string key = "largedata"; |
| std::vector<uint8_t> value(kTestSizeLimit, 4); |
| |
| set_mock_data(kTestPrefix + key, Uint8VectorToStdString(value)); |
| |
| // Put with same data should succeed. |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value)); |
| |
| // Put with same data size should succeed. |
| value[1] = 13; |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value)); |
| |
| // Adding a new key when already over quota should not succeed. |
| EXPECT_FALSE(PutSync(StdStringToUint8Vector("newkey"), {1, 2, 3})); |
| |
| // Reducing size should also succeed. |
| value.resize(kTestSizeLimit / 2); |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value)); |
| |
| // Increasing size again should succeed, as still under the limit. |
| value.resize(value.size() + 1); |
| EXPECT_TRUE(PutSync(StdStringToUint8Vector(key), value)); |
| |
| // But increasing back to original size should fail. |
| value.resize(kTestSizeLimit); |
| EXPECT_FALSE(PutSync(StdStringToUint8Vector(key), value)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, PutWhenAlreadyOverQuotaBecauseOfLargeKey) { |
| std::vector<uint8_t> key(kTestSizeLimit, 'x'); |
| std::vector<uint8_t> value = StdStringToUint8Vector("value"); |
| |
| set_mock_data(kTestPrefix + Uint8VectorToStdString(key), |
| Uint8VectorToStdString(value)); |
| |
| // Put with same data size should succeed. |
| value[0] = 'X'; |
| EXPECT_TRUE(PutSync(key, value)); |
| |
| // Reducing size should also succeed. |
| value.clear(); |
| EXPECT_TRUE(PutSync(key, value)); |
| |
| // Increasing size should fail. |
| value.resize(1, 'a'); |
| EXPECT_FALSE(PutSync(key, value)); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, GetAfterPurgeMemory) { |
| std::vector<uint8_t> result; |
| EXPECT_TRUE(GetSync(StdStringToUint8Vector("123"), &result)); |
| EXPECT_EQ(StdStringToUint8Vector("123data"), result); |
| EXPECT_EQ(delegate()->map_load_count(), 1); |
| |
| // Reading again doesn't load map again. |
| EXPECT_TRUE(GetSync(StdStringToUint8Vector("123"), &result)); |
| EXPECT_EQ(delegate()->map_load_count(), 1); |
| |
| wrapper_impl()->PurgeMemory(); |
| |
| // Now reading should still work, and load map again. |
| result.clear(); |
| EXPECT_TRUE(GetSync(StdStringToUint8Vector("123"), &result)); |
| EXPECT_EQ(StdStringToUint8Vector("123data"), result); |
| EXPECT_EQ(delegate()->map_load_count(), 2); |
| } |
| |
| TEST_F(LevelDBWrapperImplTest, PurgeMemoryWithPendingChanges) { |
| std::vector<uint8_t> key = StdStringToUint8Vector("123"); |
| std::vector<uint8_t> value = StdStringToUint8Vector("foo"); |
| EXPECT_TRUE(PutSync(key, value)); |
| EXPECT_EQ(delegate()->map_load_count(), 1); |
| |
| // Purge memory, and read. Should not actually have purged, so should not have |
| // triggered a load. |
| wrapper_impl()->PurgeMemory(); |
| |
| std::vector<uint8_t> result; |
| EXPECT_TRUE(GetSync(key, &result)); |
| EXPECT_EQ(value, result); |
| EXPECT_EQ(delegate()->map_load_count(), 1); |
| } |
| |
| } // namespace content |