blob: 18dd12317c0c236184531f2704e8ed6a6a038a39 [file] [log] [blame]
// 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 <set>
#include <string>
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "base/time/tick_clock.h"
#include "base/time/time.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/base/invalidation_test_util.h"
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/notifier/ack_tracker.h"
#include "sync/notifier/fake_invalidation_state_tracker.h"
#include "sync/notifier/invalidation_util.h"
#include "sync/notifier/sync_invalidation_listener.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 kMinVersion = FakeInvalidationStateTracker::kMinVersion;
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)
: listener_(listener),
state_(TRANSIENT_INVALIDATION_ERROR) {}
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 "";
} 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();
}
}
InvalidatorState GetInvalidatorState() const {
return state_;
}
void Acknowledge(const ObjectId& id) {
listener_->Acknowledge(id, invalidations_[id].back().ack_handle());
}
// 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::map<ObjectId, int, ObjectIdLessThan> ObjectIdCountMap;
typedef std::vector<Invalidation> List;
typedef std::map<ObjectId, List, ObjectIdLessThan> Map;
Map invalidations_;
SyncInvalidationListener* listener_;
InvalidatorState state_;
};
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;
}
// TODO(dcheng): FakeTickClock and FakeBackoffEntry ought to be factored out
// into a helpers file so it can be shared with the AckTracker unittest.
class FakeTickClock : public base::TickClock {
public:
FakeTickClock() {}
virtual ~FakeTickClock() {}
void LeapForward(int seconds) {
ASSERT_GT(seconds, 0);
fake_now_ticks_ += base::TimeDelta::FromSeconds(seconds);
}
virtual base::TimeTicks NowTicks() OVERRIDE {
return fake_now_ticks_;
}
private:
base::TimeTicks fake_now_ticks_;
DISALLOW_COPY_AND_ASSIGN(FakeTickClock);
};
class FakeBackoffEntry : public net::BackoffEntry {
public:
FakeBackoffEntry(const Policy *const policy, base::TickClock* tick_clock)
: BackoffEntry(policy), tick_clock_(tick_clock) {
}
protected:
virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE {
return tick_clock_->NowTicks();
}
private:
base::TickClock* const tick_clock_;
};
scoped_ptr<net::BackoffEntry> CreateMockEntry(
base::TickClock* tick_clock,
const net::BackoffEntry::Policy *const policy) {
return scoped_ptr<net::BackoffEntry>(
new FakeBackoffEntry(policy, tick_clock));
}
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_(&tick_clock_,
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_.GetAllInvalidationStates(),
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.
message_loop_.RunUntilIdle();
fake_invalidation_client_ = NULL;
listener_.StopForTest();
}
int 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);
}
InvalidatorState GetInvalidatorState() const {
return fake_delegate_.GetInvalidatorState();
}
int64 GetMaxVersion(const ObjectId& id) const {
return fake_tracker_.GetMaxVersion(id);
}
std::string GetInvalidatorClientId() const {
return fake_tracker_.GetInvalidatorClientId();
}
std::string GetBootstrapData() const {
return fake_tracker_.GetBootstrapData();
}
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);
// Pump message loop to trigger InvalidationStateTracker::SetMaxVersion()
// and callback from InvalidationStateTracker::GenerateAckHandles().
message_loop_.RunUntilIdle();
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);
// Pump message loop to trigger callback from
// InvalidationStateTracker::GenerateAckHandles().
message_loop_.RunUntilIdle();
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);
// Pump message loop to trigger callback from
// InvalidationStateTracker::GenerateAckHandles().
message_loop_.RunUntilIdle();
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().
message_loop_.RunUntilIdle();
}
void EnableNotifications() {
fake_push_client_->EnableNotifications();
}
void DisableNotifications(notifier::NotificationsDisabledReason reason) {
fake_push_client_->DisableNotifications(reason);
}
void VerifyUnacknowledged(const ObjectId& object_id) {
InvalidationStateMap state_map = fake_tracker_.GetAllInvalidationStates();
EXPECT_THAT(state_map[object_id].current,
Not(Eq(state_map[object_id].expected)));
EXPECT_EQ(listener_.GetStateMapForTest(), state_map);
}
void VerifyAcknowledged(const ObjectId& object_id) {
InvalidationStateMap state_map = fake_tracker_.GetAllInvalidationStates();
EXPECT_THAT(state_map[object_id].current,
Eq(state_map[object_id].expected));
EXPECT_EQ(listener_.GetStateMapForTest(), state_map);
}
void AcknowledgeAndVerify(const ObjectId& object_id) {
VerifyUnacknowledged(object_id);
fake_delegate_.Acknowledge(object_id);
// Pump message loop to trigger
// InvalidationStateTracker::Acknowledge().
message_loop_.RunUntilIdle();
VerifyAcknowledged(object_id);
}
const ObjectId kBookmarksId_;
const ObjectId kPreferencesId_;
const ObjectId kExtensionsId_;
const ObjectId kAppsId_;
ObjectIdSet registered_ids_;
private:
base::MessageLoop message_loop_;
FakeInvalidationStateTracker fake_tracker_;
notifier::FakePushClient* const fake_push_client_;
protected:
// Tests need to access these directly.
FakeInvalidationClient* fake_invalidation_client_;
FakeTickClock tick_clock_;
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);
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_EQ(kVersion1, GetVersion(id));
EXPECT_EQ("", GetPayload(id));
EXPECT_EQ(kVersion1, GetMaxVersion(id));
AcknowledgeAndVerify(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, "");
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_EQ(kVersion1, GetVersion(id));
EXPECT_EQ("", GetPayload(id));
EXPECT_EQ(kVersion1, GetMaxVersion(id));
AcknowledgeAndVerify(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);
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_EQ(kVersion1, GetVersion(id));
EXPECT_EQ(kPayload1, GetPayload(id));
EXPECT_EQ(kVersion1, GetMaxVersion(id));
AcknowledgeAndVerify(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, InvalidateUnregisteredWithPayload) {
const ObjectId kUnregisteredId(
kChromeSyncSourceId, "unregistered");
const ObjectId& id = kUnregisteredId;
EXPECT_EQ(0, GetInvalidationCount(id));
EXPECT_EQ(kMinVersion, GetMaxVersion(id));
FireInvalidate(id, kVersion1, "unregistered payload");
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_EQ(kVersion1, GetVersion(id));
EXPECT_EQ("unregistered payload", GetPayload(id));
EXPECT_EQ(kVersion1, GetMaxVersion(id));
AcknowledgeAndVerify(id);
}
// Fire an invalidation, then fire another one with a lower version.
// The first one should be processed and should update the payload and
// version, but the second one shouldn't.
TEST_F(SyncInvalidationListenerTest, InvalidateVersion) {
const ObjectId& id = kPreferencesId_;
FireInvalidate(id, kVersion2, kPayload2);
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_EQ(kVersion2, GetVersion(id));
EXPECT_EQ(kPayload2, GetPayload(id));
EXPECT_EQ(kVersion2, GetMaxVersion(id));
AcknowledgeAndVerify(id);
FireInvalidate(id, kVersion1, kPayload1);
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_EQ(kVersion2, GetVersion(id));
EXPECT_EQ(kPayload2, GetPayload(id));
EXPECT_EQ(kVersion2, GetMaxVersion(id));
VerifyAcknowledged(id);
}
// Fire an invalidation with an unknown version twice. It shouldn't update the
// version either time, but it should still be processed.
TEST_F(SyncInvalidationListenerTest, InvalidateUnknownVersion) {
const ObjectId& id = kBookmarksId_;
FireInvalidateUnknownVersion(id);
EXPECT_EQ(1, GetInvalidationCount(id));
EXPECT_TRUE(IsUnknownVersion(id));
AcknowledgeAndVerify(id);
FireInvalidateUnknownVersion(id);
EXPECT_EQ(kMinVersion, GetMaxVersion(id));
AcknowledgeAndVerify(id);
}
// Fire an invalidation for all enabled IDs. It shouldn't update the
// payload or version, but it should still invalidate the IDs.
TEST_F(SyncInvalidationListenerTest, InvalidateAll) {
FireInvalidateAll();
for (ObjectIdSet::const_iterator it = registered_ids_.begin();
it != registered_ids_.end(); ++it) {
EXPECT_EQ(1, GetInvalidationCount(*it));
EXPECT_TRUE(IsUnknownVersion(*it));
EXPECT_EQ(kMinVersion, GetMaxVersion(*it));
AcknowledgeAndVerify(*it);
}
}
// Comprehensive test of various scenarios for multiple IDs.
TEST_F(SyncInvalidationListenerTest, InvalidateMultipleIds) {
FireInvalidate(kBookmarksId_, 3, NULL);
EXPECT_EQ(1, GetInvalidationCount(kBookmarksId_));
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
AcknowledgeAndVerify(kBookmarksId_);
FireInvalidate(kExtensionsId_, 2, NULL);
EXPECT_EQ(1, GetInvalidationCount(kExtensionsId_));
EXPECT_EQ(2, GetVersion(kExtensionsId_));
EXPECT_EQ("", GetPayload(kExtensionsId_));
EXPECT_EQ(2, GetMaxVersion(kExtensionsId_));
AcknowledgeAndVerify(kExtensionsId_);
// Invalidations with lower version numbers should be ignored.
FireInvalidate(kBookmarksId_, 1, NULL);
EXPECT_EQ(1, GetInvalidationCount(kBookmarksId_));
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
FireInvalidate(kExtensionsId_, 1, NULL);
EXPECT_EQ(1, GetInvalidationCount(kExtensionsId_));
EXPECT_EQ(2, GetVersion(kExtensionsId_));
EXPECT_EQ("", GetPayload(kExtensionsId_));
EXPECT_EQ(2, GetMaxVersion(kExtensionsId_));
// InvalidateAll shouldn't change any version state.
FireInvalidateAll();
EXPECT_EQ(2, GetInvalidationCount(kBookmarksId_));
EXPECT_TRUE(IsUnknownVersion(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
AcknowledgeAndVerify(kBookmarksId_);
EXPECT_EQ(1, GetInvalidationCount(kPreferencesId_));
EXPECT_TRUE(IsUnknownVersion(kBookmarksId_));
EXPECT_EQ(kMinVersion, GetMaxVersion(kPreferencesId_));
AcknowledgeAndVerify(kPreferencesId_);
// Note that kExtensionsId_ is not registered, so InvalidateAll() shouldn't
// affect it.
EXPECT_EQ(1, GetInvalidationCount(kExtensionsId_));
EXPECT_EQ(2, GetVersion(kExtensionsId_));
EXPECT_EQ("", GetPayload(kExtensionsId_));
EXPECT_EQ(2, GetMaxVersion(kExtensionsId_));
VerifyAcknowledged(kExtensionsId_);
// Invalidations with higher version numbers should be processed.
FireInvalidate(kPreferencesId_, 5, NULL);
EXPECT_EQ(2, GetInvalidationCount(kPreferencesId_));
EXPECT_EQ(5, GetVersion(kPreferencesId_));
EXPECT_EQ("", GetPayload(kPreferencesId_));
EXPECT_EQ(5, GetMaxVersion(kPreferencesId_));
AcknowledgeAndVerify(kPreferencesId_);
FireInvalidate(kExtensionsId_, 3, NULL);
EXPECT_EQ(2, GetInvalidationCount(kExtensionsId_));
EXPECT_EQ(3, GetVersion(kExtensionsId_));
EXPECT_EQ("", GetPayload(kExtensionsId_));
EXPECT_EQ(3, GetMaxVersion(kExtensionsId_));
AcknowledgeAndVerify(kExtensionsId_);
FireInvalidate(kBookmarksId_, 4, NULL);
EXPECT_EQ(3, GetInvalidationCount(kBookmarksId_));
EXPECT_EQ(4, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(4, GetMaxVersion(kBookmarksId_));
AcknowledgeAndVerify(kBookmarksId_);
}
// Various tests for the local invalidation feature.
// Tests a "normal" scenario. We allow one timeout period to expire by sending
// ack handles that are not the "latest" ack handle. Once the timeout expires,
// we verify that we get a second callback and then acknowledge it. Once
// acknowledged, no further timeouts should occur.
TEST_F(SyncInvalidationListenerTest, InvalidateOneTimeout) {
listener_.GetAckTrackerForTest()->SetCreateBackoffEntryCallbackForTest(
base::Bind(&CreateMockEntry, &tick_clock_));
// Trigger the initial invalidation.
FireInvalidate(kBookmarksId_, 3, NULL);
EXPECT_EQ(1, GetInvalidationCount(kBookmarksId_));
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
VerifyUnacknowledged(kBookmarksId_);
// Trigger one timeout.
tick_clock_.LeapForward(60);
EXPECT_TRUE(listener_.GetAckTrackerForTest()->TriggerTimeoutAtForTest(
tick_clock_.NowTicks()));
EXPECT_EQ(2, GetInvalidationCount(kBookmarksId_));
// Other properties should remain the same.
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
AcknowledgeAndVerify(kBookmarksId_);
// No more invalidations should remain in the queue.
EXPECT_TRUE(listener_.GetAckTrackerForTest()->IsQueueEmptyForTest());
}
// Test that an unacknowledged invalidation triggers reminders if the listener
// is restarted.
TEST_F(SyncInvalidationListenerTest, InvalidationTimeoutRestart) {
listener_.GetAckTrackerForTest()->SetCreateBackoffEntryCallbackForTest(
base::Bind(&CreateMockEntry, &tick_clock_));
FireInvalidate(kBookmarksId_, 3, NULL);
EXPECT_EQ(1, GetInvalidationCount(kBookmarksId_));
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
// Trigger one timeout.
tick_clock_.LeapForward(60);
EXPECT_TRUE(listener_.GetAckTrackerForTest()->TriggerTimeoutAtForTest(
tick_clock_.NowTicks()));
EXPECT_EQ(2, GetInvalidationCount(kBookmarksId_));
// Other properties should remain the same.
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
// Restarting the client should reset the retry count and the timeout period
// (e.g. it shouldn't increase to 120 seconds). Skip ahead 1200 seconds to be
// on the safe side.
StopClient();
tick_clock_.LeapForward(1200);
StartClient();
// The bookmark invalidation state should not have changed.
EXPECT_EQ(2, GetInvalidationCount(kBookmarksId_));
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
// Now trigger the invalidation reminder after the client restarts.
tick_clock_.LeapForward(60);
EXPECT_TRUE(listener_.GetAckTrackerForTest()->TriggerTimeoutAtForTest(
tick_clock_.NowTicks()));
EXPECT_EQ(3, GetInvalidationCount(kBookmarksId_));
// Other properties should remain the same.
EXPECT_EQ(3, GetVersion(kBookmarksId_));
EXPECT_EQ("", GetPayload(kBookmarksId_));
EXPECT_EQ(3, GetMaxVersion(kBookmarksId_));
AcknowledgeAndVerify(kBookmarksId_);
// No more invalidations should remain in the queue.
EXPECT_TRUE(listener_.GetAckTrackerForTest()->IsQueueEmptyForTest());
// The queue should remain empty when we restart now.
RestartClient();
EXPECT_TRUE(listener_.GetAckTrackerForTest()->IsQueueEmptyForTest());
}
// 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) {
listener_.Ready(fake_invalidation_client_);
EXPECT_TRUE(listener_.GetStateMapForTest().empty());
FireInvalidate(kBookmarksId_, 1, "hello");
EXPECT_EQ(1U, listener_.GetStateMapForTest().size());
EXPECT_TRUE(ContainsKey(listener_.GetStateMapForTest(), kBookmarksId_));
FireInvalidate(kPreferencesId_, 2, "world");
EXPECT_EQ(2U, listener_.GetStateMapForTest().size());
EXPECT_TRUE(ContainsKey(listener_.GetStateMapForTest(), kBookmarksId_));
EXPECT_TRUE(ContainsKey(listener_.GetStateMapForTest(), kPreferencesId_));
ObjectIdSet ids;
ids.insert(kBookmarksId_);
listener_.UpdateRegisteredIds(ids);
EXPECT_EQ(1U, listener_.GetStateMapForTest().size());
EXPECT_TRUE(ContainsKey(listener_.GetStateMapForTest(), kBookmarksId_));
}
// 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());
}
} // namespace
} // namespace syncer