| // Copyright 2017 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 <memory> |
| #include <utility> |
| |
| #include "base/bind_helpers.h" |
| #include "base/containers/circular_deque.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/values.h" |
| #include "components/prefs/in_memory_pref_store.h" |
| #include "components/prefs/pref_notifier_impl.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "mojo/public/cpp/bindings/binding_set.h" |
| #include "services/preferences/persistent_pref_store_impl.h" |
| #include "services/preferences/public/cpp/dictionary_value_update.h" |
| #include "services/preferences/public/cpp/persistent_pref_store_client.h" |
| #include "services/preferences/public/cpp/scoped_pref_update.h" |
| #include "services/preferences/public/mojom/preferences.mojom.h" |
| #include "services/preferences/unittest_common.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace prefs { |
| namespace { |
| |
| constexpr char kChildKey[] = "child"; |
| constexpr char kOtherDictionaryKey[] = "other_key"; |
| |
| struct UpdateOrAck { |
| std::vector<mojom::PrefUpdatePtr> updates; |
| bool is_ack; |
| }; |
| |
| struct Request { |
| std::string key; |
| std::vector<std::string> path; |
| }; |
| |
| struct UpdateOrRequest { |
| std::vector<mojom::PrefUpdatePtr> updates; |
| Request request; |
| }; |
| |
| // A client connection to a between a PersistentPrefStoreImpl and a |
| // PersistentPrefStoreClient that provides control over when messages between |
| // the two are delivered. |
| class PrefServiceConnection : public mojom::PrefStoreObserver, |
| public mojom::PersistentPrefStore { |
| public: |
| explicit PrefServiceConnection(PersistentPrefStoreImpl* pref_store) |
| : observer_binding_(this), pref_store_binding_(this) { |
| auto connection = pref_store->CreateConnection({ |
| kKey, kOtherDictionaryKey, kDictionaryKey, |
| }); |
| observer_binding_.Bind( |
| std::move(connection->pref_store_connection->observer)); |
| connection->pref_store_connection->observer = mojo::MakeRequest(&observer_); |
| |
| pref_store_.Bind(std::move(connection->pref_store)); |
| pref_store_binding_.Bind(mojo::MakeRequest(&connection->pref_store)); |
| |
| pref_store_client_ = |
| base::MakeRefCounted<PersistentPrefStoreClient>(std::move(connection)); |
| auto pref_notifier = std::make_unique<PrefNotifierImpl>(); |
| auto pref_registry = base::MakeRefCounted<PrefRegistrySimple>(); |
| pref_registry->RegisterIntegerPref(kKey, kInitialValue); |
| pref_registry->RegisterIntegerPref(kOtherDictionaryKey, kInitialValue); |
| pref_registry->RegisterDictionaryPref(kDictionaryKey); |
| auto pref_value_store = std::make_unique<PrefValueStore>( |
| nullptr, nullptr, nullptr, nullptr, pref_store_client_.get(), nullptr, |
| pref_registry->defaults().get(), pref_notifier.get()); |
| pref_service_ = std::make_unique<PrefService>( |
| std::move(pref_notifier), std::move(pref_value_store), |
| pref_store_client_.get(), pref_registry.get(), base::DoNothing(), true); |
| } |
| |
| ~PrefServiceConnection() override { |
| observer_binding_.FlushForTesting(); |
| pref_store_binding_.FlushForTesting(); |
| EXPECT_TRUE(writes_.empty()); |
| EXPECT_TRUE(updates_.empty()); |
| } |
| |
| PrefService& pref_service() { return *pref_service_; } |
| |
| void WaitForWrites(size_t num_writes) { |
| if (writes_.size() >= num_writes) |
| return; |
| |
| expected_writes_ = num_writes; |
| base::RunLoop run_loop; |
| stop_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| void ForwardWrites(size_t num_writes) { |
| WaitForWrites(num_writes); |
| for (size_t i = 0; i < num_writes; ++i) { |
| auto& write = writes_.front(); |
| if (write.updates.empty()) { |
| pref_store_->RequestValue(write.request.key, write.request.path); |
| } else { |
| pref_store_->SetValues(std::move(write.updates)); |
| } |
| writes_.pop_front(); |
| } |
| pref_store_.FlushForTesting(); |
| } |
| void WaitForUpdates(size_t num_updates) { |
| if (updates_.size() >= num_updates) |
| return; |
| |
| expected_updates_ = num_updates; |
| base::RunLoop run_loop; |
| stop_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| void ForwardUpdates(size_t num_updates) { |
| WaitForUpdates(num_updates); |
| for (size_t i = 0; i < num_updates; ++i) { |
| auto& update = updates_.front(); |
| if (update.is_ack) { |
| observer_->OnPrefChangeAck(); |
| } else { |
| observer_->OnPrefsChanged(std::move(update.updates)); |
| } |
| updates_.pop_front(); |
| } |
| observer_.FlushForTesting(); |
| } |
| |
| // mojom::PersistentPrefStore |
| void SetValues(std::vector<mojom::PrefUpdatePtr> updates) override { |
| writes_.push_back({std::move(updates)}); |
| if (writes_.size() == expected_writes_) { |
| expected_writes_ = 0; |
| std::move(stop_).Run(); |
| } |
| } |
| void RequestValue(const std::string& key, |
| const std::vector<std::string>& path) override { |
| writes_.push_back({std::vector<mojom::PrefUpdatePtr>(), {key, path}}); |
| if (writes_.size() == expected_writes_) { |
| expected_writes_ = 0; |
| std::move(stop_).Run(); |
| } |
| } |
| void CommitPendingWrite(base::OnceClosure) override {} |
| void SchedulePendingLossyWrites() override {} |
| void ClearMutableValues() override {} |
| void OnStoreDeletionFromDisk() override {} |
| |
| // mojom::PrefStoreObserver |
| void OnPrefsChanged(std::vector<mojom::PrefUpdatePtr> updates) override { |
| updates_.push_back({std::move(updates), false}); |
| if (updates_.size() == expected_updates_) { |
| expected_updates_ = 0; |
| std::move(stop_).Run(); |
| } |
| } |
| void OnInitializationCompleted(bool succeeded) override { NOTREACHED(); } |
| void OnPrefChangeAck() override { |
| updates_.push_back({std::vector<mojom::PrefUpdatePtr>(), true}); |
| acks_.push_back(updates_.size()); |
| } |
| |
| private: |
| scoped_refptr<PersistentPrefStoreClient> pref_store_client_; |
| std::unique_ptr<PrefService> pref_service_; |
| mojom::PrefStoreObserverPtr observer_; |
| mojom::PersistentPrefStorePtr pref_store_; |
| mojo::Binding<mojom::PrefStoreObserver> observer_binding_; |
| mojo::Binding<mojom::PersistentPrefStore> pref_store_binding_; |
| |
| base::OnceClosure stop_; |
| size_t expected_writes_ = 0; |
| size_t expected_updates_ = 0; |
| |
| base::circular_deque<UpdateOrRequest> writes_; |
| base::circular_deque<UpdateOrAck> updates_; |
| base::circular_deque<size_t> acks_; |
| }; |
| |
| class PersistentPrefStoreConsistencyTest : public testing::Test { |
| public: |
| void SetUp() override { |
| pref_store_ = base::MakeRefCounted<InMemoryPrefStore>(); |
| pref_store_impl_ = std::make_unique<PersistentPrefStoreImpl>( |
| pref_store_, base::DoNothing()); |
| } |
| |
| PersistentPrefStore* pref_store() { return pref_store_.get(); } |
| |
| std::unique_ptr<PrefServiceConnection> CreateConnection() { |
| return std::make_unique<PrefServiceConnection>(pref_store_impl_.get()); |
| } |
| |
| private: |
| scoped_refptr<PersistentPrefStore> pref_store_; |
| std::unique_ptr<PersistentPrefStoreImpl> pref_store_impl_; |
| base::MessageLoop message_loop_; |
| }; |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, TwoPrefs) { |
| pref_store()->SetValue(kKey, std::make_unique<base::Value>(kInitialValue), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| |
| EXPECT_EQ(kInitialValue, pref_service.GetInteger(kKey)); |
| EXPECT_EQ(kInitialValue, pref_service2.GetInteger(kKey)); |
| |
| pref_service.SetInteger(kKey, 2); |
| pref_service2.SetInteger(kKey, 3); |
| |
| // The writes arrive in the order: 2, 3. |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(2, pref_service.GetInteger(kKey)); |
| |
| // This connection already has a later value. It should not move backwards in |
| // time. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(3, pref_service2.GetInteger(kKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(3, pref_service.GetInteger(kKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(3, pref_service2.GetInteger(kKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, TwoSubPrefs) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 2); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| } |
| |
| // The writes arrive in the order: 2, 3. |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue two_dict; |
| two_dict.SetInteger(kKey, 2); |
| base::DictionaryValue three_dict; |
| three_dict.SetInteger(kKey, 3); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(two_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| // This connection already has a later value. It should not move backwards in |
| // time. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, DifferentSubPrefs) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 2); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kOtherDictionaryKey, 3); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue two_dict; |
| two_dict.SetInteger(kKey, 2); |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 2); |
| expected_dict.SetInteger(kOtherDictionaryKey, 3); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(two_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| // This connection already has a later value. It should not move backwards in |
| // time. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, WriteParentThenChild) { |
| auto initial_value = std::make_unique<base::DictionaryValue>(); |
| initial_value->SetDictionary(kDictionaryKey, |
| std::make_unique<base::DictionaryValue>()); |
| pref_store()->SetValue(kDictionaryKey, std::move(initial_value), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 4); |
| update->Clear(); |
| update->SetInteger(kKey, 2); |
| update->SetInteger(kOtherDictionaryKey, 4); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue two_and_four_dict; |
| two_and_four_dict.SetInteger(kKey, 2); |
| two_and_four_dict.SetInteger(kOtherDictionaryKey, 4); |
| base::DictionaryValue three_dict; |
| three_dict.SetInteger(kKey, 3); |
| three_dict.SetDictionary(kDictionaryKey, |
| std::make_unique<base::DictionaryValue>()); |
| base::Value five_dict = three_dict.Clone(); |
| five_dict.SetKey(kKey, base::Value(5)); |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 3); |
| expected_dict.SetInteger(kOtherDictionaryKey, 4); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(two_and_four_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| // Applying the other client's write would lose this client's write so is |
| // ignored. Instead, this client requests an updated value from the service. |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| connection2->ForwardWrites(1); |
| |
| // Perform another update while waiting for the updated value from the |
| // service. |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 5); |
| } |
| connection2->ForwardWrites(1); |
| |
| expected_dict.SetInteger(kKey, 5); |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| // Ack for the original write. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(five_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| // New value that is ignored due to the second in-flight write. |
| connection2->ForwardUpdates(1); |
| // Sending another request for the value from the service. |
| connection2->ForwardWrites(1); |
| EXPECT_EQ(five_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| // Ack for the second write. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(five_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, WriteChildThenParent) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 4); |
| update->Clear(); |
| update->SetInteger(kKey, 2); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| } |
| |
| connection2->ForwardWrites(1); |
| connection->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 2); |
| base::DictionaryValue three_dict; |
| three_dict.SetInteger(kKey, 3); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, WriteChildThenDeleteParent) { |
| pref_store()->SetValue(kDictionaryKey, |
| std::make_unique<base::DictionaryValue>(), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| pref_service.ClearPref(kDictionaryKey); |
| EXPECT_FALSE(pref_service.HasPrefPath(kDictionaryKey)); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| } |
| |
| connection2->ForwardWrites(1); |
| connection->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 2); |
| base::DictionaryValue three_dict; |
| three_dict.SetInteger(kKey, 3); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service.HasPrefPath(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service.HasPrefPath(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service2.HasPrefPath(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, DeleteParentThenWriteChild) { |
| auto initial_value = std::make_unique<base::DictionaryValue>(); |
| initial_value->SetInteger(kOtherDictionaryKey, 5); |
| pref_store()->SetValue(kDictionaryKey, std::move(initial_value), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| pref_service.ClearPref(kDictionaryKey); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue intermediate_dict; |
| intermediate_dict.SetInteger(kKey, 3); |
| intermediate_dict.SetInteger(kOtherDictionaryKey, 5); |
| |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 3); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service.HasPrefPath(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(intermediate_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardWrites(1); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(intermediate_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, WriteParentThenDeleteChild) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 1); |
| update->Clear(); |
| update->SetInteger(kKey, 2); |
| update->SetInteger(kOtherDictionaryKey, 4); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| update->RemovePath(kKey, nullptr); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue two_and_four_dict; |
| two_and_four_dict.SetInteger(kKey, 2); |
| two_and_four_dict.SetInteger(kOtherDictionaryKey, 4); |
| base::DictionaryValue empty_dict; |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kOtherDictionaryKey, 4); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(two_and_four_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(empty_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| // Applying the other client's write would lose this client's write so is |
| // ignored. Instead, this client requests an updated value from the service. |
| EXPECT_EQ(empty_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| connection2->ForwardWrites(1); |
| |
| // Ack for the original write. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(empty_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, DeleteChildThenWriteParent) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 4); |
| update->Clear(); |
| update->SetInteger(kKey, 2); |
| update->SetInteger(kOtherDictionaryKey, 4); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| update->RemovePath(kKey, nullptr); |
| } |
| |
| connection2->ForwardWrites(1); |
| connection->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue empty_dict; |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 2); |
| expected_dict.SetInteger(kOtherDictionaryKey, 4); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(empty_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, ReplaceParentThenWriteChild) { |
| auto initial_value = std::make_unique<base::DictionaryValue>(); |
| initial_value->SetPath({kKey, kOtherDictionaryKey}, base::Value(5)); |
| pref_store()->SetValue(kDictionaryKey, std::move(initial_value), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 1); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetPath({kKey, kChildKey}, base::Value(2)); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue simple_dict; |
| simple_dict.SetInteger(kKey, 1); |
| base::DictionaryValue nested_dict; |
| nested_dict.SetPath({kKey, kChildKey}, base::Value(2)); |
| nested_dict.SetPath({kKey, kOtherDictionaryKey}, base::Value(5)); |
| base::DictionaryValue expected_dict; |
| expected_dict.SetPath({kKey, kChildKey}, base::Value(2)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(simple_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(nested_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardWrites(1); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(nested_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, WriteChildThenReplaceParent) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->SetInteger(kKey, 1); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetPath({kKey, kChildKey}, base::Value(2)); |
| } |
| |
| connection2->ForwardWrites(1); |
| connection->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue simple_dict; |
| simple_dict.SetInteger(kKey, 1); |
| base::DictionaryValue nested_dict; |
| nested_dict.SetPath({kKey, kChildKey}, base::Value(2)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(simple_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(simple_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(nested_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(simple_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, NestedWriteParentThenChild) { |
| pref_store()->SetValue(kKey, std::make_unique<base::DictionaryValue>(), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| auto nested_dict = |
| update->SetDictionary(kKey, std::make_unique<base::DictionaryValue>()); |
| nested_dict->SetInteger(kChildKey, 2); |
| nested_dict->SetInteger(kOtherDictionaryKey, 4); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetPath({kKey, kChildKey}, base::Value(3)); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue two_and_four_dict; |
| two_and_four_dict.SetPath({kKey, kChildKey}, base::Value(2)); |
| two_and_four_dict.SetPath({kKey, kOtherDictionaryKey}, base::Value(4)); |
| base::DictionaryValue three_dict; |
| three_dict.SetPath({kKey, kChildKey}, base::Value(3)); |
| base::Value expected_dict = two_and_four_dict.Clone(); |
| expected_dict.SetPath({kKey, kChildKey}, base::Value(3)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(two_and_four_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| // Applying the other client's write would lose this client's write so is |
| // ignored. Instead, this client requests an updated value from the service. |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| connection2->ForwardWrites(1); |
| |
| // Ack for the original write. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| // Updated value from the service. |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, NestedWriteChildThenParent) { |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| auto nested_dict = |
| update->SetDictionary(kKey, std::make_unique<base::DictionaryValue>()); |
| nested_dict->SetInteger(kChildKey, 2); |
| nested_dict->SetInteger(kOtherDictionaryKey, 4); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetPath({kKey, kChildKey}, base::Value(3)); |
| } |
| |
| connection2->ForwardWrites(1); |
| connection->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue expected_dict; |
| expected_dict.SetPath({kKey, kChildKey}, base::Value(2)); |
| expected_dict.SetPath({kKey, kOtherDictionaryKey}, base::Value(4)); |
| base::DictionaryValue three_dict; |
| three_dict.SetPath({kKey, kChildKey}, base::Value(3)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(three_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, |
| DeleteParentThenWriteChildThenDeleteParent) { |
| auto initial_value = std::make_unique<base::DictionaryValue>(); |
| initial_value->SetInteger(kOtherDictionaryKey, 5); |
| pref_store()->SetValue(kDictionaryKey, std::move(initial_value), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| pref_service.ClearPref(kDictionaryKey); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetInteger(kKey, 3); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue intermediate_dict; |
| intermediate_dict.SetInteger(kKey, 3); |
| intermediate_dict.SetInteger(kOtherDictionaryKey, 5); |
| |
| base::DictionaryValue expected_dict; |
| expected_dict.SetInteger(kKey, 3); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service.HasPrefPath(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(intermediate_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| pref_service.ClearPref(kDictionaryKey); |
| connection->ForwardWrites(1); |
| connection->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service.HasPrefPath(kDictionaryKey)); |
| |
| connection2->ForwardWrites(1); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(intermediate_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service2.HasPrefPath(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_FALSE(pref_service2.HasPrefPath(kDictionaryKey)); |
| } |
| |
| TEST_F(PersistentPrefStoreConsistencyTest, |
| NestedDeleteParentThenWriteChildThenDeleteChild) { |
| auto initial_value = std::make_unique<base::DictionaryValue>(); |
| initial_value->SetPath({kKey, kOtherDictionaryKey}, base::Value(5)); |
| pref_store()->SetValue(kDictionaryKey, std::move(initial_value), 0); |
| auto connection = CreateConnection(); |
| auto connection2 = CreateConnection(); |
| auto& pref_service = connection->pref_service(); |
| auto& pref_service2 = connection2->pref_service(); |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->RemovePath(kKey, nullptr); |
| } |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service2, kDictionaryKey); |
| update->SetPath({kKey, kChildKey}, base::Value(3)); |
| } |
| |
| connection->ForwardWrites(1); |
| connection2->ForwardWrites(1); |
| |
| connection->WaitForUpdates(2); |
| connection2->WaitForUpdates(2); |
| |
| base::DictionaryValue intermediate_dict; |
| intermediate_dict.SetPath({kKey, kChildKey}, base::Value(3)); |
| intermediate_dict.SetPath({kKey, kOtherDictionaryKey}, base::Value(5)); |
| |
| base::DictionaryValue expected_dict; |
| expected_dict.SetPath({kKey, kChildKey}, base::Value(3)); |
| |
| base::DictionaryValue empty_dict; |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(empty_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(intermediate_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection->ForwardUpdates(1); |
| EXPECT_EQ(expected_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| { |
| ScopedDictionaryPrefUpdate update(&pref_service, kDictionaryKey); |
| update->RemovePath(kKey, nullptr); |
| } |
| connection->ForwardWrites(1); |
| connection->ForwardUpdates(1); |
| EXPECT_TRUE(pref_service.HasPrefPath(kDictionaryKey)); |
| EXPECT_EQ(empty_dict, *pref_service.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardWrites(1); |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(intermediate_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(empty_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| |
| connection2->ForwardUpdates(1); |
| EXPECT_EQ(empty_dict, *pref_service2.GetDictionary(kDictionaryKey)); |
| } |
| |
| } // namespace |
| } // namespace prefs |