blob: 8f57f1c85b267b5271ec0fd4de06ecf55dd0ef44 [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 "sync/notifier/ack_tracker.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/time/tick_clock.h"
#include "google/cacheinvalidation/include/types.h"
#include "google/cacheinvalidation/types.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
class FakeTickClock : public base::TickClock {
public:
FakeTickClock() {}
virtual ~FakeTickClock() {}
void LeapForward(int seconds) {
ASSERT_GT(seconds, 0);
fake_now_ticks_ += base::TimeDelta::FromSeconds(seconds);
}
// After the next call to Now(), immediately leap forward by |seconds|.
void DelayedLeapForward(int seconds) {
ASSERT_GT(seconds, 0);
delayed_leap_ = base::TimeDelta::FromSeconds(seconds);
}
virtual base::TimeTicks NowTicks() OVERRIDE {
base::TimeTicks fake_now_ticks = fake_now_ticks_;
if (delayed_leap_ > base::TimeDelta()) {
fake_now_ticks_ += delayed_leap_;
delayed_leap_ = base::TimeDelta();
}
return fake_now_ticks;
}
private:
base::TimeTicks fake_now_ticks_;
base::TimeDelta delayed_leap_;
};
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_;
};
class MockDelegate : public AckTracker::Delegate {
public:
MOCK_METHOD1(OnTimeout, void(const ObjectIdSet&));
};
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));
}
} // namespace
class AckTrackerTest : public testing::Test {
public:
AckTrackerTest()
: ack_tracker_(&fake_tick_clock_, &delegate_),
kIdOne(ipc::invalidation::ObjectSource::TEST, "one"),
kIdTwo(ipc::invalidation::ObjectSource::TEST, "two") {
ack_tracker_.SetCreateBackoffEntryCallbackForTest(
base::Bind(&CreateMockEntry, &fake_tick_clock_));
}
protected:
bool TriggerTimeoutNow() {
return ack_tracker_.TriggerTimeoutAtForTest(fake_tick_clock_.NowTicks());
}
base::TimeDelta GetTimerDelay() const {
const base::Timer& timer = ack_tracker_.GetTimerForTest();
if (!timer.IsRunning())
ADD_FAILURE() << "Timer is not running!";
return timer.GetCurrentDelay();
}
FakeTickClock fake_tick_clock_;
::testing::StrictMock<MockDelegate> delegate_;
AckTracker ack_tracker_;
const invalidation::ObjectId kIdOne;
const invalidation::ObjectId kIdTwo;
// AckTracker uses base::Timer internally, which depends on the existence of a
// MessageLoop.
base::MessageLoop message_loop_;
};
// Tests that various combinations of Track()/Ack() behave as
// expected.
TEST_F(AckTrackerTest, TrackAndAck) {
ObjectIdSet ids_one;
ids_one.insert(kIdOne);
ObjectIdSet ids_two;
ids_two.insert(kIdTwo);
ObjectIdSet ids_all;
ids_all.insert(kIdOne);
ids_all.insert(kIdTwo);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_one);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_two);
ack_tracker_.Ack(ids_one);
ack_tracker_.Ack(ids_two);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_all);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Ack(ids_one);
ack_tracker_.Ack(ids_two);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_one);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_two);
ack_tracker_.Ack(ids_all);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_all);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Ack(ids_all);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
TEST_F(AckTrackerTest, DoubleTrack) {
ObjectIdSet ids;
ids.insert(kIdOne);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids);
ack_tracker_.Ack(ids);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
TEST_F(AckTrackerTest, UntrackedAck) {
ObjectIdSet ids;
ids.insert(kIdOne);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Ack(ids);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
TEST_F(AckTrackerTest, Clear) {
ObjectIdSet ids;
ids.insert(kIdOne);
ids.insert(kIdOne);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Clear();
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
// Test that timeout behavior for one object ID. The timeout should increase
// exponentially until it hits the cap.
TEST_F(AckTrackerTest, SimpleTimeout) {
ObjectIdSet ids;
ids.insert(kIdOne);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
EXPECT_EQ(base::TimeDelta::FromSeconds(60), GetTimerDelay());
fake_tick_clock_.LeapForward(60);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(120), GetTimerDelay());
fake_tick_clock_.LeapForward(120);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(240), GetTimerDelay());
fake_tick_clock_.LeapForward(240);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(480), GetTimerDelay());
fake_tick_clock_.LeapForward(480);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(600), GetTimerDelay());
fake_tick_clock_.LeapForward(600);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(600), GetTimerDelay());
fake_tick_clock_.LeapForward(600);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Ack(ids);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
// The backoff time should be reset after an Ack/Track cycle.
ack_tracker_.Track(ids);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
EXPECT_EQ(base::TimeDelta::FromSeconds(60), GetTimerDelay());
fake_tick_clock_.LeapForward(60);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Ack(ids);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
// Tests that a sequence of Track() calls that results in interleaving
// timeouts occurs as expected.
TEST_F(AckTrackerTest, InterleavedTimeout) {
ObjectIdSet ids_one;
ids_one.insert(kIdOne);
ObjectIdSet ids_two;
ids_two.insert(kIdTwo);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_one);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
fake_tick_clock_.LeapForward(30);
ack_tracker_.Track(ids_two);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
EXPECT_EQ(base::TimeDelta::FromSeconds(60), GetTimerDelay());
fake_tick_clock_.LeapForward(30);
EXPECT_CALL(delegate_, OnTimeout(ids_one));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(30), GetTimerDelay());
fake_tick_clock_.LeapForward(30);
EXPECT_CALL(delegate_, OnTimeout(ids_two));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(90), GetTimerDelay());
fake_tick_clock_.LeapForward(90);
EXPECT_CALL(delegate_, OnTimeout(ids_one));
EXPECT_TRUE(TriggerTimeoutNow());
EXPECT_EQ(base::TimeDelta::FromSeconds(30), GetTimerDelay());
fake_tick_clock_.LeapForward(30);
EXPECT_CALL(delegate_, OnTimeout(ids_two));
EXPECT_TRUE(TriggerTimeoutNow());
ack_tracker_.Ack(ids_one);
ack_tracker_.Ack(ids_two);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
// Tests that registering a new object ID properly shortens the timeout when
// needed.
TEST_F(AckTrackerTest, ShortenTimeout) {
ObjectIdSet ids_one;
ids_one.insert(kIdOne);
ObjectIdSet ids_two;
ids_two.insert(kIdTwo);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids_one);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
EXPECT_EQ(base::TimeDelta::FromSeconds(60), GetTimerDelay());
fake_tick_clock_.LeapForward(60);
EXPECT_CALL(delegate_, OnTimeout(ids_one));
EXPECT_TRUE(TriggerTimeoutNow());
// Without this next register, the next timeout should occur in 120 seconds
// from the last timeout event.
EXPECT_EQ(base::TimeDelta::FromSeconds(120), GetTimerDelay());
fake_tick_clock_.LeapForward(30);
ack_tracker_.Track(ids_two);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
// Now that we've registered another entry though, we should receive a timeout
// in 60 seconds.
EXPECT_EQ(base::TimeDelta::FromSeconds(60), GetTimerDelay());
fake_tick_clock_.LeapForward(60);
EXPECT_CALL(delegate_, OnTimeout(ids_two));
EXPECT_TRUE(TriggerTimeoutNow());
// Verify that the original timeout for kIdOne still occurs as expected.
EXPECT_EQ(base::TimeDelta::FromSeconds(30), GetTimerDelay());
fake_tick_clock_.LeapForward(30);
EXPECT_CALL(delegate_, OnTimeout(ids_one));
EXPECT_TRUE(TriggerTimeoutNow());
ack_tracker_.Ack(ids_one);
ack_tracker_.Ack(ids_two);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
// Tests that a delay between inserting a new object ID registration and start
// the timer that is greater than the initial timeout period (60 seconds) does
// not break things. This could happen on a heavily loaded system, for instance.
TEST_F(AckTrackerTest, ImmediateTimeout) {
ObjectIdSet ids;
ids.insert(kIdOne);
fake_tick_clock_.DelayedLeapForward(90);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
ack_tracker_.Track(ids);
EXPECT_FALSE(ack_tracker_.IsQueueEmptyForTest());
EXPECT_EQ(base::TimeDelta::FromSeconds(0), GetTimerDelay());
EXPECT_CALL(delegate_, OnTimeout(ids));
message_loop_.RunUntilIdle();
// The next timeout should still be scheduled normally.
EXPECT_EQ(base::TimeDelta::FromSeconds(120), GetTimerDelay());
fake_tick_clock_.LeapForward(120);
EXPECT_CALL(delegate_, OnTimeout(ids));
EXPECT_TRUE(TriggerTimeoutNow());
ack_tracker_.Ack(ids);
EXPECT_TRUE(ack_tracker_.IsQueueEmptyForTest());
}
} // namespace syncer