Index: sync/notifier/sync_invalidation_listener_unittest.cc |
diff --git a/sync/notifier/sync_invalidation_listener_unittest.cc b/sync/notifier/sync_invalidation_listener_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5fd9442ae174cb49f46f48ef259451d3a50cd823 |
--- /dev/null |
+++ b/sync/notifier/sync_invalidation_listener_unittest.cc |
@@ -0,0 +1,1129 @@ |
+// 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 <cstddef> |
+#include <map> |
+#include <set> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/compiler_specific.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/stl_util.h" |
+#include "google/cacheinvalidation/include/invalidation-client.h" |
+#include "google/cacheinvalidation/include/types.h" |
+#include "jingle/notifier/listener/fake_push_client.h" |
+#include "sync/internal_api/public/util/weak_handle.h" |
+#include "sync/notifier/dropped_invalidation_tracker.h" |
+#include "sync/notifier/fake_invalidation_state_tracker.h" |
+#include "sync/notifier/invalidation_util.h" |
+#include "sync/notifier/object_id_invalidation_map.h" |
+#include "sync/notifier/push_client_channel.h" |
+#include "sync/notifier/sync_invalidation_listener.h" |
+#include "sync/notifier/unacked_invalidation_set_test_util.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace syncer { |
+ |
+namespace { |
+ |
+using invalidation::AckHandle; |
+using invalidation::ObjectId; |
+ |
+const char kClientId[] = "client_id"; |
+const char kClientInfo[] = "client_info"; |
+ |
+const char kState[] = "state"; |
+const char kNewState[] = "new_state"; |
+ |
+const char kPayload1[] = "payload1"; |
+const char kPayload2[] = "payload2"; |
+ |
+const int64 kVersion1 = 1LL; |
+const int64 kVersion2 = 2LL; |
+ |
+const int kChromeSyncSourceId = 1004; |
+ |
+struct AckHandleLessThan { |
+ bool operator()(const AckHandle& lhs, const AckHandle& rhs) const { |
+ return lhs.handle_data() < rhs.handle_data(); |
+ } |
+}; |
+ |
+typedef std::set<AckHandle, AckHandleLessThan> AckHandleSet; |
+ |
+// Fake invalidation::InvalidationClient implementation that keeps |
+// track of registered IDs and acked handles. |
+class FakeInvalidationClient : public invalidation::InvalidationClient { |
+ public: |
+ FakeInvalidationClient() : started_(false) {} |
+ virtual ~FakeInvalidationClient() {} |
+ |
+ const ObjectIdSet& GetRegisteredIds() const { |
+ return registered_ids_; |
+ } |
+ |
+ void ClearAckedHandles() { |
+ acked_handles_.clear(); |
+ } |
+ |
+ bool IsAckedHandle(const AckHandle& ack_handle) const { |
+ return (acked_handles_.find(ack_handle) != acked_handles_.end()); |
+ } |
+ |
+ // invalidation::InvalidationClient implementation. |
+ |
+ virtual void Start() OVERRIDE { |
+ started_ = true; |
+ } |
+ |
+ virtual void Stop() OVERRIDE { |
+ started_ = false; |
+ } |
+ |
+ virtual void Register(const ObjectId& object_id) OVERRIDE { |
+ if (!started_) { |
+ ADD_FAILURE(); |
+ return; |
+ } |
+ registered_ids_.insert(object_id); |
+ } |
+ |
+ virtual void Register( |
+ const invalidation::vector<ObjectId>& object_ids) OVERRIDE { |
+ if (!started_) { |
+ ADD_FAILURE(); |
+ return; |
+ } |
+ registered_ids_.insert(object_ids.begin(), object_ids.end()); |
+ } |
+ |
+ virtual void Unregister(const ObjectId& object_id) OVERRIDE { |
+ if (!started_) { |
+ ADD_FAILURE(); |
+ return; |
+ } |
+ registered_ids_.erase(object_id); |
+ } |
+ |
+ virtual void Unregister( |
+ const invalidation::vector<ObjectId>& object_ids) OVERRIDE { |
+ if (!started_) { |
+ ADD_FAILURE(); |
+ return; |
+ } |
+ for (invalidation::vector<ObjectId>::const_iterator |
+ it = object_ids.begin(); it != object_ids.end(); ++it) { |
+ registered_ids_.erase(*it); |
+ } |
+ } |
+ |
+ virtual void Acknowledge(const AckHandle& ack_handle) OVERRIDE { |
+ if (!started_) { |
+ ADD_FAILURE(); |
+ return; |
+ } |
+ acked_handles_.insert(ack_handle); |
+ } |
+ |
+ private: |
+ bool started_; |
+ ObjectIdSet registered_ids_; |
+ AckHandleSet acked_handles_; |
+}; |
+ |
+// Fake delegate tkat keeps track of invalidation counts, payloads, |
+// and state. |
+class FakeDelegate : public SyncInvalidationListener::Delegate { |
+ public: |
+ explicit FakeDelegate(SyncInvalidationListener* listener) |
+ : state_(TRANSIENT_INVALIDATION_ERROR), |
+ drop_handlers_deleter_(&drop_handlers_) {} |
+ virtual ~FakeDelegate() {} |
+ |
+ size_t GetInvalidationCount(const ObjectId& id) const { |
+ Map::const_iterator it = invalidations_.find(id); |
+ if (it == invalidations_.end()) { |
+ return 0; |
+ } else { |
+ return it->second.size(); |
+ } |
+ } |
+ |
+ int64 GetVersion(const ObjectId& id) const { |
+ Map::const_iterator it = invalidations_.find(id); |
+ if (it == invalidations_.end()) { |
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id); |
+ return 0; |
+ } else { |
+ return it->second.back().version(); |
+ } |
+ } |
+ |
+ std::string GetPayload(const ObjectId& id) const { |
+ Map::const_iterator it = invalidations_.find(id); |
+ if (it == invalidations_.end()) { |
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id); |
+ return 0; |
+ } else { |
+ return it->second.back().payload(); |
+ } |
+ } |
+ |
+ bool IsUnknownVersion(const ObjectId& id) const { |
+ Map::const_iterator it = invalidations_.find(id); |
+ if (it == invalidations_.end()) { |
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id); |
+ return false; |
+ } else { |
+ return it->second.back().is_unknown_version(); |
+ } |
+ } |
+ |
+ bool StartsWithUnknownVersion(const ObjectId& id) const { |
+ Map::const_iterator it = invalidations_.find(id); |
+ if (it == invalidations_.end()) { |
+ ADD_FAILURE() << "No invalidations for ID " << ObjectIdToString(id); |
+ return false; |
+ } else { |
+ return it->second.front().is_unknown_version(); |
+ } |
+ } |
+ |
+ InvalidatorState GetInvalidatorState() const { |
+ return state_; |
+ } |
+ |
+ DroppedInvalidationTracker* GetDropTrackerForObject(const ObjectId& id) { |
+ DropHandlers::iterator it = drop_handlers_.find(id); |
+ if (it == drop_handlers_.end()) { |
+ drop_handlers_.insert( |
+ std::make_pair(id, new DroppedInvalidationTracker(id))); |
+ return drop_handlers_.find(id)->second; |
+ } else { |
+ return it->second; |
+ } |
+ } |
+ |
+ void AcknowledgeNthInvalidation(const ObjectId& id, size_t n) { |
+ List& list = invalidations_[id]; |
+ List::iterator it = list.begin() + n; |
+ it->Acknowledge(); |
+ } |
+ |
+ void AcknowledgeAll(const ObjectId& id) { |
+ List& list = invalidations_[id]; |
+ for (List::iterator it = list.begin(); it != list.end(); ++it) { |
+ it->Acknowledge(); |
+ } |
+ } |
+ |
+ void DropNthInvalidation(const ObjectId& id, size_t n) { |
+ DroppedInvalidationTracker* drop_tracker = GetDropTrackerForObject(id); |
+ List& list = invalidations_[id]; |
+ List::iterator it = list.begin() + n; |
+ it->Drop(drop_tracker); |
+ } |
+ |
+ void RecoverFromDropEvent(const ObjectId& id) { |
+ DroppedInvalidationTracker* drop_tracker = GetDropTrackerForObject(id); |
+ drop_tracker->RecordRecoveryFromDropEvent(); |
+ } |
+ |
+ // SyncInvalidationListener::Delegate implementation. |
+ virtual void OnInvalidate( |
+ const ObjectIdInvalidationMap& invalidation_map) OVERRIDE { |
+ ObjectIdSet ids = invalidation_map.GetObjectIds(); |
+ for (ObjectIdSet::iterator it = ids.begin(); it != ids.end(); ++it) { |
+ const SingleObjectInvalidationSet& incoming = |
+ invalidation_map.ForObject(*it); |
+ List& list = invalidations_[*it]; |
+ list.insert(list.end(), incoming.begin(), incoming.end()); |
+ } |
+ } |
+ |
+ virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE { |
+ state_ = state; |
+ } |
+ |
+ private: |
+ typedef std::vector<Invalidation> List; |
+ typedef std::map<ObjectId, List, ObjectIdLessThan> Map; |
+ typedef std::map<ObjectId, |
+ DroppedInvalidationTracker*, |
+ ObjectIdLessThan> DropHandlers; |
+ |
+ Map invalidations_; |
+ InvalidatorState state_; |
+ DropHandlers drop_handlers_; |
+ STLValueDeleter<DropHandlers> drop_handlers_deleter_; |
+}; |
+ |
+invalidation::InvalidationClient* CreateFakeInvalidationClient( |
+ FakeInvalidationClient** fake_invalidation_client, |
+ invalidation::SystemResources* resources, |
+ int client_type, |
+ const invalidation::string& client_name, |
+ const invalidation::string& application_name, |
+ invalidation::InvalidationListener* listener) { |
+ *fake_invalidation_client = new FakeInvalidationClient(); |
+ return *fake_invalidation_client; |
+} |
+ |
+class SyncInvalidationListenerTest : public testing::Test { |
+ protected: |
+ SyncInvalidationListenerTest() |
+ : kBookmarksId_(kChromeSyncSourceId, "BOOKMARK"), |
+ kPreferencesId_(kChromeSyncSourceId, "PREFERENCE"), |
+ kExtensionsId_(kChromeSyncSourceId, "EXTENSION"), |
+ kAppsId_(kChromeSyncSourceId, "APP"), |
+ fake_push_client_(new notifier::FakePushClient()), |
+ fake_invalidation_client_(NULL), |
+ listener_(scoped_ptr<SyncNetworkChannel>(new PushClientChannel( |
+ scoped_ptr<notifier::PushClient>(fake_push_client_)))), |
+ fake_delegate_(&listener_) {} |
+ |
+ virtual void SetUp() { |
+ StartClient(); |
+ |
+ registered_ids_.insert(kBookmarksId_); |
+ registered_ids_.insert(kPreferencesId_); |
+ listener_.UpdateRegisteredIds(registered_ids_); |
+ } |
+ |
+ virtual void TearDown() { |
+ StopClient(); |
+ } |
+ |
+ // Restart client without re-registering IDs. |
+ void RestartClient() { |
+ StopClient(); |
+ StartClient(); |
+ } |
+ |
+ void StartClient() { |
+ fake_invalidation_client_ = NULL; |
+ listener_.Start(base::Bind(&CreateFakeInvalidationClient, |
+ &fake_invalidation_client_), |
+ kClientId, kClientInfo, kState, |
+ fake_tracker_.GetSavedInvalidations(), |
+ MakeWeakHandle(fake_tracker_.AsWeakPtr()), |
+ &fake_delegate_); |
+ DCHECK(fake_invalidation_client_); |
+ } |
+ |
+ void StopClient() { |
+ // listener_.StopForTest() stops the invalidation scheduler, which |
+ // deletes any pending tasks without running them. Some tasks |
+ // "run and delete" another task, so they must be run in order to |
+ // avoid leaking the inner task. listener_.StopForTest() does not |
+ // schedule any tasks, so it's both necessary and sufficient to |
+ // drain the task queue before calling it. |
+ FlushPendingWrites(); |
+ fake_invalidation_client_ = NULL; |
+ listener_.StopForTest(); |
+ } |
+ |
+ size_t GetInvalidationCount(const ObjectId& id) const { |
+ return fake_delegate_.GetInvalidationCount(id); |
+ } |
+ |
+ int64 GetVersion(const ObjectId& id) const { |
+ return fake_delegate_.GetVersion(id); |
+ } |
+ |
+ std::string GetPayload(const ObjectId& id) const { |
+ return fake_delegate_.GetPayload(id); |
+ } |
+ |
+ bool IsUnknownVersion(const ObjectId& id) const { |
+ return fake_delegate_.IsUnknownVersion(id); |
+ } |
+ |
+ bool StartsWithUnknownVersion(const ObjectId& id) const { |
+ return fake_delegate_.StartsWithUnknownVersion(id); |
+ } |
+ |
+ void AcknowledgeNthInvalidation(const ObjectId& id, size_t n) { |
+ fake_delegate_.AcknowledgeNthInvalidation(id, n); |
+ } |
+ |
+ void DropNthInvalidation(const ObjectId& id, size_t n) { |
+ return fake_delegate_.DropNthInvalidation(id, n); |
+ } |
+ |
+ void RecoverFromDropEvent(const ObjectId& id) { |
+ return fake_delegate_.RecoverFromDropEvent(id); |
+ } |
+ |
+ void AcknowledgeAll(const ObjectId& id) { |
+ fake_delegate_.AcknowledgeAll(id); |
+ } |
+ |
+ InvalidatorState GetInvalidatorState() const { |
+ return fake_delegate_.GetInvalidatorState(); |
+ } |
+ |
+ std::string GetInvalidatorClientId() const { |
+ return fake_tracker_.GetInvalidatorClientId(); |
+ } |
+ |
+ std::string GetBootstrapData() const { |
+ return fake_tracker_.GetBootstrapData(); |
+ } |
+ |
+ UnackedInvalidationsMap GetSavedInvalidations() { |
+ // Allow any queued writes to go through first. |
+ FlushPendingWrites(); |
+ return fake_tracker_.GetSavedInvalidations(); |
+ } |
+ |
+ SingleObjectInvalidationSet GetSavedInvalidationsForType(const ObjectId& id) { |
+ const UnackedInvalidationsMap& saved_state = GetSavedInvalidations(); |
+ UnackedInvalidationsMap::const_iterator it = |
+ saved_state.find(kBookmarksId_); |
+ if (it == saved_state.end()) { |
+ ADD_FAILURE() << "No state saved for ID " << ObjectIdToString(id); |
+ return SingleObjectInvalidationSet(); |
+ } |
+ ObjectIdInvalidationMap map; |
+ it->second.ExportInvalidations(WeakHandle<AckHandler>(), &map); |
+ if (map.Empty()) { |
+ return SingleObjectInvalidationSet(); |
+ } else { |
+ return map.ForObject(id); |
+ } |
+ } |
+ |
+ ObjectIdSet GetRegisteredIds() const { |
+ return fake_invalidation_client_->GetRegisteredIds(); |
+ } |
+ |
+ // |payload| can be NULL. |
+ void FireInvalidate(const ObjectId& object_id, |
+ int64 version, const char* payload) { |
+ invalidation::Invalidation inv; |
+ if (payload) { |
+ inv = invalidation::Invalidation(object_id, version, payload); |
+ } else { |
+ inv = invalidation::Invalidation(object_id, version); |
+ } |
+ const AckHandle ack_handle("fakedata"); |
+ fake_invalidation_client_->ClearAckedHandles(); |
+ listener_.Invalidate(fake_invalidation_client_, inv, ack_handle); |
+ EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle)); |
+ } |
+ |
+ // |payload| can be NULL, but not |type_name|. |
+ void FireInvalidateUnknownVersion(const ObjectId& object_id) { |
+ const AckHandle ack_handle("fakedata_unknown"); |
+ fake_invalidation_client_->ClearAckedHandles(); |
+ listener_.InvalidateUnknownVersion(fake_invalidation_client_, |
+ object_id, |
+ ack_handle); |
+ EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle)); |
+ } |
+ |
+ void FireInvalidateAll() { |
+ const AckHandle ack_handle("fakedata_all"); |
+ fake_invalidation_client_->ClearAckedHandles(); |
+ listener_.InvalidateAll(fake_invalidation_client_, ack_handle); |
+ EXPECT_TRUE(fake_invalidation_client_->IsAckedHandle(ack_handle)); |
+ } |
+ |
+ void WriteState(const std::string& new_state) { |
+ listener_.WriteState(new_state); |
+ |
+ // Pump message loop to trigger |
+ // InvalidationStateTracker::WriteState(). |
+ FlushPendingWrites(); |
+ } |
+ |
+ void FlushPendingWrites() { |
+ message_loop_.RunUntilIdle(); |
+ } |
+ |
+ void EnableNotifications() { |
+ fake_push_client_->EnableNotifications(); |
+ } |
+ |
+ void DisableNotifications(notifier::NotificationsDisabledReason reason) { |
+ fake_push_client_->DisableNotifications(reason); |
+ } |
+ |
+ const ObjectId kBookmarksId_; |
+ const ObjectId kPreferencesId_; |
+ const ObjectId kExtensionsId_; |
+ const ObjectId kAppsId_; |
+ |
+ ObjectIdSet registered_ids_; |
+ |
+ private: |
+ base::MessageLoop message_loop_; |
+ notifier::FakePushClient* const fake_push_client_; |
+ |
+ protected: |
+ // A derrived test needs direct access to this. |
+ FakeInvalidationStateTracker fake_tracker_; |
+ |
+ // Tests need to access these directly. |
+ FakeInvalidationClient* fake_invalidation_client_; |
+ SyncInvalidationListener listener_; |
+ |
+ private: |
+ FakeDelegate fake_delegate_; |
+}; |
+ |
+// Write a new state to the client. It should propagate to the |
+// tracker. |
+TEST_F(SyncInvalidationListenerTest, WriteState) { |
+ WriteState(kNewState); |
+ |
+ EXPECT_EQ(kNewState, GetBootstrapData()); |
+} |
+ |
+// Invalidation tests. |
+ |
+// Fire an invalidation without a payload. It should be processed, |
+// the payload should remain empty, and the version should be updated. |
+TEST_F(SyncInvalidationListenerTest, InvalidateNoPayload) { |
+ const ObjectId& id = kBookmarksId_; |
+ |
+ FireInvalidate(id, kVersion1, NULL); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(kVersion1, GetVersion(id)); |
+ EXPECT_EQ("", GetPayload(id)); |
+} |
+ |
+// Fire an invalidation with an empty payload. It should be |
+// processed, the payload should remain empty, and the version should |
+// be updated. |
+TEST_F(SyncInvalidationListenerTest, InvalidateEmptyPayload) { |
+ const ObjectId& id = kBookmarksId_; |
+ |
+ FireInvalidate(id, kVersion1, ""); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(kVersion1, GetVersion(id)); |
+ EXPECT_EQ("", GetPayload(id)); |
+} |
+ |
+// Fire an invalidation with a payload. It should be processed, and |
+// both the payload and the version should be updated. |
+TEST_F(SyncInvalidationListenerTest, InvalidateWithPayload) { |
+ const ObjectId& id = kPreferencesId_; |
+ |
+ FireInvalidate(id, kVersion1, kPayload1); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(kVersion1, GetVersion(id)); |
+ EXPECT_EQ(kPayload1, GetPayload(id)); |
+} |
+ |
+// Fire ten invalidations in a row. All should be received. |
+TEST_F(SyncInvalidationListenerTest, ManyInvalidations_NoDrop) { |
+ const int kRepeatCount = 10; |
+ const ObjectId& id = kPreferencesId_; |
+ int64 initial_version = kVersion1; |
+ for (int64 i = initial_version; i < initial_version + kRepeatCount; ++i) { |
+ FireInvalidate(id, i, kPayload1); |
+ } |
+ ASSERT_EQ(static_cast<size_t>(kRepeatCount), GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(kPayload1, GetPayload(id)); |
+ EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(id)); |
+} |
+ |
+// Fire an invalidation for an unregistered object ID with a payload. It should |
+// still be processed, and both the payload and the version should be updated. |
+TEST_F(SyncInvalidationListenerTest, InvalidateBeforeRegistration_Simple) { |
+ const ObjectId kUnregisteredId(kChromeSyncSourceId, "unregistered"); |
+ const ObjectId& id = kUnregisteredId; |
+ ObjectIdSet ids; |
+ ids.insert(id); |
+ |
+ EXPECT_EQ(0U, GetInvalidationCount(id)); |
+ |
+ FireInvalidate(id, kVersion1, kPayload1); |
+ |
+ ASSERT_EQ(0U, GetInvalidationCount(id)); |
+ |
+ EnableNotifications(); |
+ listener_.Ready(fake_invalidation_client_); |
+ listener_.UpdateRegisteredIds(ids); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(kVersion1, GetVersion(id)); |
+ EXPECT_EQ(kPayload1, GetPayload(id)); |
+} |
+ |
+// Fire ten invalidations before an object registers. Some invalidations will |
+// be dropped an replaced with an unknown version invalidation. |
+TEST_F(SyncInvalidationListenerTest, InvalidateBeforeRegistration_Drop) { |
+ const int kRepeatCount = |
+ UnackedInvalidationSet::kMaxBufferedInvalidations + 1; |
+ const ObjectId kUnregisteredId(kChromeSyncSourceId, "unregistered"); |
+ const ObjectId& id = kUnregisteredId; |
+ ObjectIdSet ids; |
+ ids.insert(id); |
+ |
+ EXPECT_EQ(0U, GetInvalidationCount(id)); |
+ |
+ int64 initial_version = kVersion1; |
+ for (int64 i = initial_version; i < initial_version + kRepeatCount; ++i) { |
+ FireInvalidate(id, i, kPayload1); |
+ } |
+ |
+ EnableNotifications(); |
+ listener_.Ready(fake_invalidation_client_); |
+ listener_.UpdateRegisteredIds(ids); |
+ |
+ ASSERT_EQ(UnackedInvalidationSet::kMaxBufferedInvalidations, |
+ GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(initial_version + kRepeatCount - 1, GetVersion(id)); |
+ EXPECT_EQ(kPayload1, GetPayload(id)); |
+ EXPECT_TRUE(StartsWithUnknownVersion(id)); |
+} |
+ |
+// Fire an invalidation, then fire another one with a lower version. Both |
+// should be received. |
+TEST_F(SyncInvalidationListenerTest, InvalidateVersion) { |
+ const ObjectId& id = kPreferencesId_; |
+ |
+ FireInvalidate(id, kVersion2, kPayload2); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ EXPECT_EQ(kVersion2, GetVersion(id)); |
+ EXPECT_EQ(kPayload2, GetPayload(id)); |
+ |
+ FireInvalidate(id, kVersion1, kPayload1); |
+ |
+ ASSERT_EQ(2U, GetInvalidationCount(id)); |
+ ASSERT_FALSE(IsUnknownVersion(id)); |
+ |
+ EXPECT_EQ(kVersion1, GetVersion(id)); |
+ EXPECT_EQ(kPayload1, GetPayload(id)); |
+} |
+ |
+// Fire an invalidation with an unknown version. |
+TEST_F(SyncInvalidationListenerTest, InvalidateUnknownVersion) { |
+ const ObjectId& id = kBookmarksId_; |
+ |
+ FireInvalidateUnknownVersion(id); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(id)); |
+ EXPECT_TRUE(IsUnknownVersion(id)); |
+} |
+ |
+// Fire an invalidation for all enabled IDs. |
+TEST_F(SyncInvalidationListenerTest, InvalidateAll) { |
+ FireInvalidateAll(); |
+ |
+ for (ObjectIdSet::const_iterator it = registered_ids_.begin(); |
+ it != registered_ids_.end(); ++it) { |
+ ASSERT_EQ(1U, GetInvalidationCount(*it)); |
+ EXPECT_TRUE(IsUnknownVersion(*it)); |
+ } |
+} |
+ |
+// Test a simple scenario for multiple IDs. |
+TEST_F(SyncInvalidationListenerTest, InvalidateMultipleIds) { |
+ FireInvalidate(kBookmarksId_, 3, NULL); |
+ |
+ ASSERT_EQ(1U, GetInvalidationCount(kBookmarksId_)); |
+ ASSERT_FALSE(IsUnknownVersion(kBookmarksId_)); |
+ EXPECT_EQ(3, GetVersion(kBookmarksId_)); |
+ EXPECT_EQ("", GetPayload(kBookmarksId_)); |
+ |
+ // kExtensionId is not registered, so the invalidation should not get through. |
+ FireInvalidate(kExtensionsId_, 2, NULL); |
+ ASSERT_EQ(0U, GetInvalidationCount(kExtensionsId_)); |
+} |
+ |
+// Registration tests. |
+ |
+// With IDs already registered, enable notifications then ready the |
+// client. The IDs should be registered only after the client is |
+// readied. |
+TEST_F(SyncInvalidationListenerTest, RegisterEnableReady) { |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// With IDs already registered, ready the client then enable |
+// notifications. The IDs should be registered after the client is |
+// readied. |
+TEST_F(SyncInvalidationListenerTest, RegisterReadyEnable) { |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// Unregister the IDs, enable notifications, re-register the IDs, then |
+// ready the client. The IDs should be registered only after the |
+// client is readied. |
+TEST_F(SyncInvalidationListenerTest, EnableRegisterReady) { |
+ listener_.UpdateRegisteredIds(ObjectIdSet()); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.UpdateRegisteredIds(registered_ids_); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// Unregister the IDs, enable notifications, ready the client, then |
+// re-register the IDs. The IDs should be registered only after the |
+// client is readied. |
+TEST_F(SyncInvalidationListenerTest, EnableReadyRegister) { |
+ listener_.UpdateRegisteredIds(ObjectIdSet()); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.UpdateRegisteredIds(registered_ids_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// Unregister the IDs, ready the client, enable notifications, then |
+// re-register the IDs. The IDs should be registered only after the |
+// client is readied. |
+TEST_F(SyncInvalidationListenerTest, ReadyEnableRegister) { |
+ listener_.UpdateRegisteredIds(ObjectIdSet()); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.UpdateRegisteredIds(registered_ids_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// Unregister the IDs, ready the client, re-register the IDs, then |
+// enable notifications. The IDs should be registered only after the |
+// client is readied. |
+// |
+// This test is important: see http://crbug.com/139424. |
+TEST_F(SyncInvalidationListenerTest, ReadyRegisterEnable) { |
+ listener_.UpdateRegisteredIds(ObjectIdSet()); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.UpdateRegisteredIds(registered_ids_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// With IDs already registered, ready the client, restart the client, |
+// then re-ready it. The IDs should still be registered. |
+TEST_F(SyncInvalidationListenerTest, RegisterTypesPreserved) { |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+ |
+ RestartClient(); |
+ |
+ EXPECT_TRUE(GetRegisteredIds().empty()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(registered_ids_, GetRegisteredIds()); |
+} |
+ |
+// Make sure that state is correctly purged from the local invalidation state |
+// map cache when an ID is unregistered. |
+TEST_F(SyncInvalidationListenerTest, UnregisterCleansUpStateMapCache) { |
+ const ObjectId& id = kBookmarksId_; |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_TRUE(GetSavedInvalidations().empty()); |
+ FireInvalidate(id, 1, "hello"); |
+ EXPECT_EQ(1U, GetSavedInvalidations().size()); |
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id)); |
+ FireInvalidate(kPreferencesId_, 2, "world"); |
+ EXPECT_EQ(2U, GetSavedInvalidations().size()); |
+ |
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id)); |
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), kPreferencesId_)); |
+ |
+ ObjectIdSet ids; |
+ ids.insert(id); |
+ listener_.UpdateRegisteredIds(ids); |
+ EXPECT_EQ(1U, GetSavedInvalidations().size()); |
+ EXPECT_TRUE(ContainsKey(GetSavedInvalidations(), id)); |
+} |
+ |
+TEST_F(SyncInvalidationListenerTest, DuplicateInvaldiations_Simple) { |
+ const ObjectId& id = kBookmarksId_; |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ // Send a stream of invalidations, including two copies of the second. |
+ FireInvalidate(id, 1, "one"); |
+ FireInvalidate(id, 2, "two"); |
+ FireInvalidate(id, 3, "three"); |
+ FireInvalidate(id, 2, "two"); |
+ |
+ // Expect that the duplicate was discarded. |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ EXPECT_EQ(3U, list.GetSize()); |
+ SingleObjectInvalidationSet::const_iterator it = list.begin(); |
+ EXPECT_EQ(1, it->version()); |
+ it++; |
+ EXPECT_EQ(2, it->version()); |
+ it++; |
+ EXPECT_EQ(3, it->version()); |
+} |
+ |
+TEST_F(SyncInvalidationListenerTest, DuplicateInvalidations_NearBufferLimit) { |
+ const size_t kPairsToSend = UnackedInvalidationSet::kMaxBufferedInvalidations; |
+ const ObjectId& id = kBookmarksId_; |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ // We will have enough buffer space in the state tracker for all these |
+ // invalidations only if duplicates are ignored. |
+ for (size_t i = 0; i < kPairsToSend; ++i) { |
+ FireInvalidate(id, i, "payload"); |
+ FireInvalidate(id, i, "payload"); |
+ } |
+ |
+ // Expect that the state map ignored duplicates. |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ EXPECT_EQ(kPairsToSend, list.GetSize()); |
+ EXPECT_FALSE(list.begin()->is_unknown_version()); |
+ |
+ // Expect that all invalidations (including duplicates) were emitted. |
+ EXPECT_EQ(kPairsToSend*2, GetInvalidationCount(id)); |
+ |
+ // Acknowledge all invalidations to clear the internal state. |
+ AcknowledgeAll(id); |
+ EXPECT_TRUE(GetSavedInvalidationsForType(id).IsEmpty()); |
+} |
+ |
+TEST_F(SyncInvalidationListenerTest, DuplicateInvalidations_UnknownVersion) { |
+ const ObjectId& id = kBookmarksId_; |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ FireInvalidateUnknownVersion(id); |
+ FireInvalidateUnknownVersion(id); |
+ |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ EXPECT_EQ(1U, list.GetSize()); |
+ } |
+ |
+ // Acknowledge the second. There should be no effect on the stored list. |
+ ASSERT_EQ(2U, GetInvalidationCount(id)); |
+ AcknowledgeNthInvalidation(id, 1); |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ EXPECT_EQ(1U, list.GetSize()); |
+ } |
+ |
+ // Acknowledge the first. This should remove the invalidation from the list. |
+ ASSERT_EQ(2U, GetInvalidationCount(id)); |
+ AcknowledgeNthInvalidation(id, 0); |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ EXPECT_EQ(0U, list.GetSize()); |
+ } |
+} |
+ |
+// Make sure that acknowledgements erase items from the local store. |
+TEST_F(SyncInvalidationListenerTest, AcknowledgementsCleanUpStateMapCache) { |
+ const ObjectId& id = kBookmarksId_; |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_TRUE(GetSavedInvalidations().empty()); |
+ FireInvalidate(id, 10, "hello"); |
+ FireInvalidate(id, 20, "world"); |
+ FireInvalidateUnknownVersion(id); |
+ |
+ // Expect that all three invalidations have been saved to permanent storage. |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ ASSERT_EQ(3U, list.GetSize()); |
+ EXPECT_TRUE(list.begin()->is_unknown_version()); |
+ EXPECT_EQ(20, list.back().version()); |
+ } |
+ |
+ // Acknowledge the second sent invaldiation (version 20) and verify it was |
+ // removed from storage. |
+ AcknowledgeNthInvalidation(id, 1); |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ ASSERT_EQ(2U, list.GetSize()); |
+ EXPECT_TRUE(list.begin()->is_unknown_version()); |
+ EXPECT_EQ(10, list.back().version()); |
+ } |
+ |
+ // Acknowledge the last sent invalidation (unknown version) and verify it was |
+ // removed from storage. |
+ AcknowledgeNthInvalidation(id, 2); |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ ASSERT_EQ(1U, list.GetSize()); |
+ EXPECT_FALSE(list.begin()->is_unknown_version()); |
+ EXPECT_EQ(10, list.back().version()); |
+ } |
+} |
+ |
+// Make sure that drops erase items from the local store. |
+TEST_F(SyncInvalidationListenerTest, DropsCleanUpStateMapCache) { |
+ const ObjectId& id = kBookmarksId_; |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_TRUE(GetSavedInvalidations().empty()); |
+ FireInvalidate(id, 10, "hello"); |
+ FireInvalidate(id, 20, "world"); |
+ FireInvalidateUnknownVersion(id); |
+ |
+ // Expect that all three invalidations have been saved to permanent storage. |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ ASSERT_EQ(3U, list.GetSize()); |
+ EXPECT_TRUE(list.begin()->is_unknown_version()); |
+ EXPECT_EQ(20, list.back().version()); |
+ } |
+ |
+ // Drop the second sent invalidation (version 20) and verify it was removed |
+ // from storage. Also verify we still have an unknown version invalidation. |
+ DropNthInvalidation(id, 1); |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ ASSERT_EQ(2U, list.GetSize()); |
+ EXPECT_TRUE(list.begin()->is_unknown_version()); |
+ EXPECT_EQ(10, list.back().version()); |
+ } |
+ |
+ // Drop the remaining invalidation. Verify an unknown version is all that |
+ // remains. |
+ DropNthInvalidation(id, 0); |
+ { |
+ SingleObjectInvalidationSet list = GetSavedInvalidationsForType(id); |
+ ASSERT_EQ(1U, list.GetSize()); |
+ EXPECT_TRUE(list.begin()->is_unknown_version()); |
+ } |
+ |
+ // Announce that the delegate has recovered from the drop. Verify no |
+ // invalidations remain saved. |
+ RecoverFromDropEvent(id); |
+ EXPECT_TRUE(GetSavedInvalidationsForType(id).IsEmpty()); |
+ |
+ RecoverFromDropEvent(id); |
+} |
+ |
+// Without readying the client, disable notifications, then enable |
+// them. The listener should still think notifications are disabled. |
+TEST_F(SyncInvalidationListenerTest, EnableNotificationsNotReady) { |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, |
+ GetInvalidatorState()); |
+ |
+ DisableNotifications( |
+ notifier::TRANSIENT_NOTIFICATION_ERROR); |
+ |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState()); |
+ |
+ DisableNotifications(notifier::NOTIFICATION_CREDENTIALS_REJECTED); |
+ |
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState()); |
+} |
+ |
+// Enable notifications then Ready the invalidation client. The |
+// delegate should then be ready. |
+TEST_F(SyncInvalidationListenerTest, EnableNotificationsThenReady) { |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState()); |
+} |
+ |
+// Ready the invalidation client then enable notifications. The |
+// delegate should then be ready. |
+TEST_F(SyncInvalidationListenerTest, ReadyThenEnableNotifications) { |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, GetInvalidatorState()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState()); |
+} |
+ |
+// Enable notifications and ready the client. Then disable |
+// notifications with an auth error and re-enable notifications. The |
+// delegate should go into an auth error mode and then back out. |
+TEST_F(SyncInvalidationListenerTest, PushClientAuthError) { |
+ EnableNotifications(); |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState()); |
+ |
+ DisableNotifications( |
+ notifier::NOTIFICATION_CREDENTIALS_REJECTED); |
+ |
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState()); |
+} |
+ |
+// Enable notifications and ready the client. Then simulate an auth |
+// error from the invalidation client. Simulate some notification |
+// events, then re-ready the client. The delegate should go into an |
+// auth error mode and come out of it only after the client is ready. |
+TEST_F(SyncInvalidationListenerTest, InvalidationClientAuthError) { |
+ EnableNotifications(); |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState()); |
+ |
+ listener_.InformError( |
+ fake_invalidation_client_, |
+ invalidation::ErrorInfo( |
+ invalidation::ErrorReason::AUTH_FAILURE, |
+ false /* is_transient */, |
+ "auth error", |
+ invalidation::ErrorContext())); |
+ |
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState()); |
+ |
+ DisableNotifications(notifier::TRANSIENT_NOTIFICATION_ERROR); |
+ |
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState()); |
+ |
+ DisableNotifications(notifier::TRANSIENT_NOTIFICATION_ERROR); |
+ |
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState()); |
+ |
+ EnableNotifications(); |
+ |
+ EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED, GetInvalidatorState()); |
+ |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_EQ(INVALIDATIONS_ENABLED, GetInvalidatorState()); |
+} |
+ |
+// A variant of SyncInvalidationListenerTest that starts with some initial |
+// state. We make not attempt to abstract away the contents of this state. The |
+// tests that make use of this harness depend on its implementation details. |
+class SyncInvalidationListenerTest_WithInitialState |
+ : public SyncInvalidationListenerTest { |
+ public: |
+ virtual void SetUp() { |
+ UnackedInvalidationSet bm_state(kBookmarksId_); |
+ UnackedInvalidationSet ext_state(kExtensionsId_); |
+ |
+ Invalidation bm_unknown = Invalidation::InitUnknownVersion(kBookmarksId_); |
+ Invalidation bm_v100 = Invalidation::Init(kBookmarksId_, 100, "hundred"); |
+ bm_state.Add(bm_unknown); |
+ bm_state.Add(bm_v100); |
+ |
+ Invalidation ext_v10 = Invalidation::Init(kExtensionsId_, 10, "ten"); |
+ Invalidation ext_v20 = Invalidation::Init(kExtensionsId_, 20, "twenty"); |
+ ext_state.Add(ext_v10); |
+ ext_state.Add(ext_v20); |
+ |
+ initial_state.insert(std::make_pair(kBookmarksId_, bm_state)); |
+ initial_state.insert(std::make_pair(kExtensionsId_, ext_state)); |
+ |
+ fake_tracker_.SetSavedInvalidations(initial_state); |
+ |
+ SyncInvalidationListenerTest::SetUp(); |
+ } |
+ |
+ UnackedInvalidationsMap initial_state; |
+}; |
+ |
+// Verify that saved invalidations are forwarded when handlers register. |
+TEST_F(SyncInvalidationListenerTest_WithInitialState, |
+ ReceiveSavedInvalidations) { |
+ EnableNotifications(); |
+ listener_.Ready(fake_invalidation_client_); |
+ |
+ EXPECT_THAT(initial_state, test_util::Eq(GetSavedInvalidations())); |
+ |
+ ASSERT_EQ(2U, GetInvalidationCount(kBookmarksId_)); |
+ EXPECT_EQ(100, GetVersion(kBookmarksId_)); |
+ |
+ ASSERT_EQ(0U, GetInvalidationCount(kExtensionsId_)); |
+ |
+ FireInvalidate(kExtensionsId_, 30, "thirty"); |
+ |
+ ObjectIdSet ids = GetRegisteredIds(); |
+ ids.insert(kExtensionsId_); |
+ listener_.UpdateRegisteredIds(ids); |
+ |
+ ASSERT_EQ(3U, GetInvalidationCount(kExtensionsId_)); |
+ EXPECT_EQ(30, GetVersion(kExtensionsId_)); |
+} |
+ |
+} // namespace |
+ |
+} // namespace syncer |