blob: 887bcc1ec3a6d190f015c2a5eea60f1335458839 [file] [log] [blame]
// Copyright (c) 2009 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 <list>
#include <map>
#include "base/lock.h"
#include "base/scoped_ptr.h"
#include "base/time.h"
#include "base/waitable_event.h"
#include "chrome/browser/sync/engine/model_safe_worker.h"
#include "chrome/browser/sync/engine/syncer_thread.h"
#include "chrome/browser/sync/engine/syncer_types.h"
#include "chrome/browser/sync/sessions/sync_session_context.h"
#include "chrome/browser/sync/util/channel.h"
#include "chrome/test/sync/engine/mock_connection_manager.h"
#include "chrome/test/sync/engine/test_directory_setter_upper.h"
#include "chrome/test/sync/sessions/test_scoped_session_event_listener.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::TimeTicks;
using base::TimeDelta;
using base::WaitableEvent;
using testing::_;
using testing::AnyNumber;
using testing::Field;
namespace browser_sync {
using sessions::ErrorCounters;
using sessions::TestScopedSessionEventListener;
using sessions::SyncSessionContext;
using sessions::SyncSessionSnapshot;
using sessions::SyncerStatus;
typedef testing::Test SyncerThreadTest;
typedef SyncerThread::WaitInterval WaitInterval;
ACTION_P(SignalEvent, event) {
event->Signal();
}
SyncSessionSnapshot SessionSnapshotForTest(
int64 num_server_changes_remaining, int64 max_local_timestamp,
int64 unsynced_count) {
return SyncSessionSnapshot(SyncerStatus(), ErrorCounters(),
num_server_changes_remaining, max_local_timestamp, false,
syncable::ModelTypeBitSet(), false, false, unsynced_count, 0, false);
}
class ListenerMock : public SyncEngineEventListener {
public:
MOCK_METHOD1(OnSyncEngineEvent, void(const SyncEngineEvent&));
};
class SyncerThreadWithSyncerTest : public testing::Test,
public ModelSafeWorkerRegistrar,
public SyncEngineEventListener {
public:
SyncerThreadWithSyncerTest()
: max_wait_time_(TimeDelta::FromSeconds(10)),
sync_cycle_ended_event_(false, false) {}
virtual void SetUp() {
metadb_.SetUp();
connection_.reset(new MockConnectionManager(metadb_.manager(),
metadb_.name()));
worker_ = new ModelSafeWorker();
std::vector<SyncEngineEventListener*> listeners;
listeners.push_back(this);
context_ = new SyncSessionContext(connection_.get(), metadb_.manager(),
this, listeners);
syncer_thread_ = new SyncerThread(context_);
syncer_thread_->SetConnected(true);
syncable::ModelTypeBitSet expected_types;
expected_types[syncable::BOOKMARKS] = true;
connection_->ExpectGetUpdatesRequestTypes(expected_types);
}
virtual void TearDown() {
context_ = NULL;
syncer_thread_ = NULL;
connection_.reset();
metadb_.TearDown();
}
// ModelSafeWorkerRegistrar implementation.
virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) {
out->push_back(worker_.get());
}
virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) {
// We're just testing the sync engine here, so we shunt everything to
// the SyncerThread.
(*out)[syncable::BOOKMARKS] = GROUP_PASSIVE;
}
ManuallyOpenedTestDirectorySetterUpper* metadb() { return &metadb_; }
MockConnectionManager* connection() { return connection_.get(); }
SyncerThread* syncer_thread() { return syncer_thread_; }
// Waits an indefinite amount of sync cycles for the syncer thread to become
// throttled. Only call this if a throttle is supposed to occur!
bool WaitForThrottle() {
int max_cycles = 5;
while (max_cycles && !syncer_thread()->IsSyncingCurrentlySilenced()) {
sync_cycle_ended_event_.TimedWait(max_wait_time_);
max_cycles--;
}
return syncer_thread()->IsSyncingCurrentlySilenced();
}
void WaitForDisconnect() {
// Wait for the SyncerThread to detect loss of connection, up to a max of
// 10 seconds to timeout the test.
AutoLock lock(syncer_thread()->lock_);
TimeTicks start = TimeTicks::Now();
TimeDelta ten_seconds = TimeDelta::FromSeconds(10);
while (syncer_thread()->vault_.connected_) {
syncer_thread()->vault_field_changed_.TimedWait(ten_seconds);
if (TimeTicks::Now() - start > ten_seconds)
break;
}
EXPECT_FALSE(syncer_thread()->vault_.connected_);
}
bool Pause(ListenerMock* listener) {
WaitableEvent event(false, false);
{
AutoLock lock(syncer_thread()->lock_);
EXPECT_CALL(*listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_PAUSED))).
WillOnce(SignalEvent(&event));
}
if (!syncer_thread()->RequestPause())
return false;
return event.TimedWait(max_wait_time_);
}
bool Resume(ListenerMock* listener) {
WaitableEvent event(false, false);
{
AutoLock lock(syncer_thread()->lock_);
EXPECT_CALL(*listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_RESUMED))).
WillOnce(SignalEvent(&event));
}
if (!syncer_thread()->RequestResume())
return false;
return event.TimedWait(max_wait_time_);
}
void PreventThreadFromPolling() {
const TimeDelta poll_interval = TimeDelta::FromMinutes(5);
syncer_thread()->SetSyncerShortPollInterval(poll_interval);
}
private:
virtual void OnSyncEngineEvent(const SyncEngineEvent& event) {
if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED)
sync_cycle_ended_event_.Signal();
}
protected:
TimeDelta max_wait_time_;
SyncSessionContext* context_;
private:
ManuallyOpenedTestDirectorySetterUpper metadb_;
scoped_ptr<MockConnectionManager> connection_;
scoped_refptr<SyncerThread> syncer_thread_;
scoped_refptr<ModelSafeWorker> worker_;
base::WaitableEvent sync_cycle_ended_event_;
DISALLOW_COPY_AND_ASSIGN(SyncerThreadWithSyncerTest);
};
class SyncShareIntercept
: public MockConnectionManager::ResponseCodeOverrideRequestor,
public MockConnectionManager::MidCommitObserver {
public:
SyncShareIntercept() : sync_occured_(false, false),
allow_multiple_interceptions_(true) {}
virtual ~SyncShareIntercept() {}
virtual void Observe() {
if (!allow_multiple_interceptions_ && !times_sync_occured_.empty())
FAIL() << "Multiple sync shares occured.";
times_sync_occured_.push_back(TimeTicks::Now());
sync_occured_.Signal();
}
// ResponseCodeOverrideRequestor implementation. This assumes any override
// requested is intended to silence the SyncerThread.
virtual void OnOverrideComplete() {
// We should not see any syncing.
allow_multiple_interceptions_ = false;
times_sync_occured_.clear();
}
void WaitForSyncShare(int at_least_this_many, TimeDelta max_wait) {
while (at_least_this_many-- > 0)
sync_occured_.TimedWait(max_wait);
}
std::vector<TimeTicks> times_sync_occured() const {
return times_sync_occured_;
}
void Reset() {
allow_multiple_interceptions_ = true;
times_sync_occured_.clear();
sync_occured_.Reset();
}
private:
std::vector<TimeTicks> times_sync_occured_;
base::WaitableEvent sync_occured_;
bool allow_multiple_interceptions_;
DISALLOW_COPY_AND_ASSIGN(SyncShareIntercept);
};
TEST_F(SyncerThreadTest, Construction) {
SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL,
std::vector<SyncEngineEventListener*>());
scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context));
}
TEST_F(SyncerThreadTest, StartStop) {
SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL,
std::vector<SyncEngineEventListener*>());
scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context));
EXPECT_TRUE(syncer_thread->Start());
EXPECT_TRUE(syncer_thread->Stop(2000));
// Do it again for good measure. I caught some bugs by adding this so
// I would recommend keeping it.
EXPECT_TRUE(syncer_thread->Start());
EXPECT_TRUE(syncer_thread->Stop(2000));
}
TEST(SyncerThread, GetRecommendedDelay) {
EXPECT_LE(0, SyncerThread::GetRecommendedDelaySeconds(0));
EXPECT_LE(1, SyncerThread::GetRecommendedDelaySeconds(1));
EXPECT_LE(50, SyncerThread::GetRecommendedDelaySeconds(50));
EXPECT_LE(10, SyncerThread::GetRecommendedDelaySeconds(10));
EXPECT_EQ(SyncerThread::kMaxBackoffSeconds,
SyncerThread::GetRecommendedDelaySeconds(
SyncerThread::kMaxBackoffSeconds));
EXPECT_EQ(SyncerThread::kMaxBackoffSeconds,
SyncerThread::GetRecommendedDelaySeconds(
SyncerThread::kMaxBackoffSeconds+1));
}
TEST_F(SyncerThreadTest, CalculateSyncWaitTime) {
SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL,
std::vector<SyncEngineEventListener*>());
scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context));
syncer_thread->DisableIdleDetection();
// Syncer_polling_interval_ is less than max poll interval.
TimeDelta syncer_polling_interval = TimeDelta::FromSeconds(1);
syncer_thread->SetSyncerPollingInterval(syncer_polling_interval);
// user_idle_ms is less than 10 * (syncer_polling_interval*1000).
ASSERT_EQ(syncer_polling_interval.InMilliseconds(),
syncer_thread->CalculateSyncWaitTime(1000, 0));
ASSERT_EQ(syncer_polling_interval.InMilliseconds(),
syncer_thread->CalculateSyncWaitTime(1000, 1));
// user_idle_ms is ge than 10 * (syncer_polling_interval*1000).
int last_poll_time = 2000;
ASSERT_TRUE(last_poll_time <=
syncer_thread->CalculateSyncWaitTime(last_poll_time, 10000));
ASSERT_TRUE(last_poll_time * 3 >=
syncer_thread->CalculateSyncWaitTime(last_poll_time, 10000));
ASSERT_TRUE(last_poll_time <=
syncer_thread->CalculateSyncWaitTime(last_poll_time, 100000));
ASSERT_TRUE(last_poll_time * 3 >=
syncer_thread->CalculateSyncWaitTime(last_poll_time, 100000));
// Maximum backoff time should be syncer_max_interval.
int near_threshold = SyncerThread::kDefaultMaxPollIntervalMs / 2 - 1;
int threshold = SyncerThread::kDefaultMaxPollIntervalMs;
int over_threshold = SyncerThread::kDefaultMaxPollIntervalMs + 1;
ASSERT_TRUE(near_threshold <=
syncer_thread->CalculateSyncWaitTime(near_threshold, 10000));
ASSERT_TRUE(SyncerThread::kDefaultMaxPollIntervalMs >=
syncer_thread->CalculateSyncWaitTime(near_threshold, 10000));
ASSERT_TRUE(SyncerThread::kDefaultMaxPollIntervalMs ==
syncer_thread->CalculateSyncWaitTime(threshold, 10000));
ASSERT_TRUE(SyncerThread::kDefaultMaxPollIntervalMs ==
syncer_thread->CalculateSyncWaitTime(over_threshold, 10000));
// Possible idle time must be capped by syncer_max_interval.
int over_sync_max_interval =
SyncerThread::kDefaultMaxPollIntervalMs + 1;
syncer_polling_interval = TimeDelta::FromSeconds(
over_sync_max_interval / 100); // so 1000* is right
syncer_thread->SetSyncerPollingInterval(syncer_polling_interval);
ASSERT_EQ(syncer_polling_interval.InSeconds() * 1000,
syncer_thread->CalculateSyncWaitTime(1000, over_sync_max_interval));
syncer_polling_interval = TimeDelta::FromSeconds(1);
syncer_thread->SetSyncerPollingInterval(syncer_polling_interval);
ASSERT_TRUE(last_poll_time <=
syncer_thread->CalculateSyncWaitTime(last_poll_time,
over_sync_max_interval));
ASSERT_TRUE(last_poll_time * 3 >=
syncer_thread->CalculateSyncWaitTime(last_poll_time,
over_sync_max_interval));
}
TEST_F(SyncerThreadTest, CalculatePollingWaitTime) {
// Set up the environment.
int user_idle_milliseconds_param = 0;
SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL,
std::vector<SyncEngineEventListener*>());
scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context));
syncer_thread->DisableIdleDetection();
// Hold the lock to appease asserts in code.
AutoLock lock(syncer_thread->lock_);
// Notifications disabled should result in a polling interval of
// kDefaultShortPollInterval.
{
context->set_notifications_enabled(false);
bool continue_sync_cycle_param = false;
// No work and no backoff.
WaitInterval interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_EQ(SyncerThread::kDefaultShortPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
// In this case the continue_sync_cycle is turned off.
continue_sync_cycle_param = true;
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_EQ(SyncerThread::kDefaultShortPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
}
// Notifications enabled should result in a polling interval of
// SyncerThread::kDefaultLongPollIntervalSeconds.
{
context->set_notifications_enabled(true);
bool continue_sync_cycle_param = false;
// No work and no backoff.
WaitInterval interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
// In this case the continue_sync_cycle is turned off.
continue_sync_cycle_param = true;
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
}
// There are two states which can cause a continuation, either the updates
// available do not match the updates received, or the unsynced count is
// non-zero.
{
// More server changes remaining to download.
context->set_last_snapshot(SessionSnapshotForTest(1, 0, 0));
bool continue_sync_cycle_param = false;
WaitInterval interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_LE(0, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
continue_sync_cycle_param = false;
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_GE(3, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_LE(0, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_GE(2, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
// Now simulate no more server changes remaining.
context->set_last_snapshot(SessionSnapshotForTest(1, 1, 0));
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
}
{
// Now try with unsynced local items.
context->set_last_snapshot(SessionSnapshotForTest(0, 0, 1));
bool continue_sync_cycle_param = false;
WaitInterval interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_LE(0, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
continue_sync_cycle_param = false;
interval = syncer_thread->CalculatePollingWaitTime(
0,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_GE(2, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
context->set_last_snapshot(SessionSnapshotForTest(0, 0, 0));
interval = syncer_thread->CalculatePollingWaitTime(
4,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
}
// Regression for exponential backoff reset when the syncer is nudged.
{
context->set_last_snapshot(SessionSnapshotForTest(0, 0, 1));
bool continue_sync_cycle_param = false;
// Expect move from default polling interval to exponential backoff due to
// unsynced_count != 0.
WaitInterval interval = syncer_thread->CalculatePollingWaitTime(
3600,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_LE(0, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
continue_sync_cycle_param = false;
interval = syncer_thread->CalculatePollingWaitTime(
3600,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_GE(2, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
// Expect exponential backoff.
interval = syncer_thread->CalculatePollingWaitTime(
2,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_LE(2, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
interval = syncer_thread->CalculatePollingWaitTime(
2,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_GE(6, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
syncer_thread->vault_.current_wait_interval_ = interval;
interval = syncer_thread->CalculatePollingWaitTime(
static_cast<int>(interval.poll_delta.InSeconds()),
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
true);
// Don't change poll on a failed nudge during backoff.
ASSERT_TRUE(syncer_thread->vault_.current_wait_interval_.poll_delta ==
interval.poll_delta);
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_TRUE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
// If we got a nudge and we weren't in backoff mode, we see exponential
// backoff.
syncer_thread->vault_.current_wait_interval_.mode = WaitInterval::NORMAL;
interval = syncer_thread->CalculatePollingWaitTime(
2,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
true);
// 5 and 3 are bounds on the backoff randomization formula given input of 2.
ASSERT_GE(5, interval.poll_delta.InSeconds());
ASSERT_LE(3, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
// And if another interval expires, we get a bigger backoff.
WaitInterval new_interval = syncer_thread->CalculatePollingWaitTime(
static_cast<int>(interval.poll_delta.InSeconds()),
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
false);
ASSERT_GE(12, new_interval.poll_delta.InSeconds());
ASSERT_LE(5, new_interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode);
ASSERT_FALSE(new_interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
// A nudge resets the continue_sync_cycle_param value, so our backoff
// should return to the minimum.
continue_sync_cycle_param = false;
interval = syncer_thread->CalculatePollingWaitTime(
3600,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
true);
ASSERT_LE(0, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
continue_sync_cycle_param = false;
interval = syncer_thread->CalculatePollingWaitTime(
3600,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
true);
ASSERT_GE(2, interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_TRUE(continue_sync_cycle_param);
// Setting unsynced_count = 0 returns us to the default polling interval.
context->set_last_snapshot(SessionSnapshotForTest(0, 0, 0));
interval = syncer_thread->CalculatePollingWaitTime(
4,
&user_idle_milliseconds_param,
&continue_sync_cycle_param,
true);
ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds,
interval.poll_delta.InSeconds());
ASSERT_EQ(WaitInterval::NORMAL, interval.mode);
ASSERT_FALSE(interval.had_nudge_during_backoff);
ASSERT_FALSE(continue_sync_cycle_param);
}
}
TEST_F(SyncerThreadWithSyncerTest, Polling) {
SyncShareIntercept interceptor;
connection()->SetMidCommitObserver(&interceptor);
const TimeDelta poll_interval = TimeDelta::FromSeconds(1);
syncer_thread()->SetSyncerShortPollInterval(poll_interval);
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
TimeDelta two_polls = poll_interval + poll_interval;
// We could theoretically return immediately from the wait if the interceptor
// was already signaled for a SyncShare (the first one comes quick).
interceptor.WaitForSyncShare(1, two_polls);
EXPECT_FALSE(interceptor.times_sync_occured().empty());
// Wait for at least 2 more SyncShare operations.
interceptor.WaitForSyncShare(2, two_polls);
EXPECT_TRUE(syncer_thread()->Stop(2000));
// Now analyze the run.
std::vector<TimeTicks> data = interceptor.times_sync_occured();
EXPECT_GE(data.size(), static_cast<unsigned int>(3));
for (unsigned int i = 0; i < data.size() - 1; i++) {
TimeTicks optimal_next_sync = data[i] + poll_interval;
EXPECT_TRUE(data[i + 1] >= optimal_next_sync);
// This should be reliable, as there are no blocking or I/O operations
// except the explicit 2 second wait, so if it takes longer than this
// there is a problem.
EXPECT_TRUE(data[i + 1] < optimal_next_sync + poll_interval);
}
}
TEST_F(SyncerThreadWithSyncerTest, Nudge) {
SyncShareIntercept interceptor;
connection()->SetMidCommitObserver(&interceptor);
// We don't want a poll to happen during this test (except the first one).
PreventThreadFromPolling();
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
const TimeDelta poll_interval = TimeDelta::FromMinutes(5);
interceptor.WaitForSyncShare(1, poll_interval + poll_interval);
EXPECT_EQ(static_cast<unsigned int>(1),
interceptor.times_sync_occured().size());
// The SyncerThread should be waiting for the poll now. Nudge it to sync
// immediately (5ms).
syncer_thread()->NudgeSyncer(5, SyncerThread::kUnknown);
interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1));
EXPECT_EQ(static_cast<unsigned int>(2),
interceptor.times_sync_occured().size());
// SyncerThread should be waiting again. Signal it to stop.
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
TEST_F(SyncerThreadWithSyncerTest, NudgeWithDataTypes) {
SyncShareIntercept interceptor;
connection()->SetMidCommitObserver(&interceptor);
// We don't want a poll to happen during this test (except the first one).
PreventThreadFromPolling();
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
const TimeDelta poll_interval = TimeDelta::FromMinutes(5);
interceptor.WaitForSyncShare(1, poll_interval + poll_interval);
EXPECT_EQ(static_cast<unsigned int>(1),
interceptor.times_sync_occured().size());
// The SyncerThread should be waiting for the poll now. Nudge it to sync
// immediately (5ms).
syncable::ModelTypeBitSet model_types;
model_types[syncable::BOOKMARKS] = true;
// Paused so we can verify the nudge types safely.
syncer_thread()->RequestPause();
syncer_thread()->NudgeSyncerWithDataTypes(5,
SyncerThread::kUnknown,
model_types);
EXPECT_EQ(model_types, syncer_thread()->vault_.pending_nudge_types_);
syncer_thread()->RequestResume();
interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1));
EXPECT_EQ(static_cast<unsigned int>(2),
interceptor.times_sync_occured().size());
// SyncerThread should be waiting again. Signal it to stop.
EXPECT_TRUE(syncer_thread()->Stop(2000));
EXPECT_TRUE(syncer_thread()->vault_.pending_nudge_types_.none());
}
TEST_F(SyncerThreadWithSyncerTest, NudgeWithDataTypesCoalesced) {
SyncShareIntercept interceptor;
connection()->SetMidCommitObserver(&interceptor);
// We don't want a poll to happen during this test (except the first one).
PreventThreadFromPolling();
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
const TimeDelta poll_interval = TimeDelta::FromMinutes(5);
interceptor.WaitForSyncShare(1, poll_interval + poll_interval);
EXPECT_EQ(static_cast<unsigned int>(1),
interceptor.times_sync_occured().size());
// The SyncerThread should be waiting for the poll now. Nudge it to sync
// immediately (5ms).
syncable::ModelTypeBitSet model_types;
model_types[syncable::BOOKMARKS] = true;
// Paused so we can verify the nudge types safely.
syncer_thread()->RequestPause();
syncer_thread()->NudgeSyncerWithDataTypes(100,
SyncerThread::kUnknown,
model_types);
EXPECT_EQ(model_types, syncer_thread()->vault_.pending_nudge_types_);
model_types[syncable::BOOKMARKS] = false;
model_types[syncable::AUTOFILL] = true;
syncer_thread()->NudgeSyncerWithDataTypes(0,
SyncerThread::kUnknown,
model_types);
// Reset BOOKMARKS for expectations.
model_types[syncable::BOOKMARKS] = true;
EXPECT_EQ(model_types, syncer_thread()->vault_.pending_nudge_types_);
syncer_thread()->RequestResume();
interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1));
EXPECT_EQ(static_cast<unsigned int>(2),
interceptor.times_sync_occured().size());
// SyncerThread should be waiting again. Signal it to stop.
EXPECT_TRUE(syncer_thread()->Stop(2000));
EXPECT_TRUE(syncer_thread()->vault_.pending_nudge_types_.none());
}
TEST_F(SyncerThreadWithSyncerTest, Throttling) {
SyncShareIntercept interceptor;
connection()->SetMidCommitObserver(&interceptor);
const TimeDelta poll_interval = TimeDelta::FromMilliseconds(10);
syncer_thread()->SetSyncerShortPollInterval(poll_interval);
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
// Wait for some healthy syncing.
interceptor.WaitForSyncShare(4, poll_interval + poll_interval);
// Tell the server to throttle a single request, which should be all it takes
// to silence our syncer (for 2 hours, so we shouldn't hit that in this test).
// This will atomically visit the interceptor so it can switch to throttled
// mode and fail on multiple requests.
connection()->ThrottleNextRequest(&interceptor);
// Try to trigger a sync (we have a really short poll interval already).
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
// Wait until the syncer thread reports that it is throttled. Any further
// sync share interceptions will result in failure. If things are broken,
// we may never halt.
ASSERT_TRUE(WaitForThrottle());
EXPECT_TRUE(syncer_thread()->IsSyncingCurrentlySilenced());
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
TEST_F(SyncerThreadWithSyncerTest, StopSyncPermanently) {
// The SyncerThread should request an exit from the Syncer and set
// conditions for termination.
const TimeDelta poll_interval = TimeDelta::FromMilliseconds(10);
syncer_thread()->SetSyncerShortPollInterval(poll_interval);
ListenerMock listener;
WaitableEvent sync_cycle_ended_event(false, false);
WaitableEvent syncer_thread_exiting_event(false, false);
TestScopedSessionEventListener reg(context_, &listener);
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::STATUS_CHANGED))).
Times(AnyNumber());
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
Times(AnyNumber()).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::STOP_SYNCING_PERMANENTLY)));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_EXITING))).
WillOnce(SignalEvent(&syncer_thread_exiting_event));
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
connection()->set_store_birthday("NotYourLuckyDay");
ASSERT_TRUE(syncer_thread_exiting_event.TimedWait(max_wait_time_));
EXPECT_TRUE(syncer_thread()->Stop(0));
}
TEST_F(SyncerThreadWithSyncerTest, AuthInvalid) {
SyncShareIntercept interceptor;
connection()->SetMidCommitObserver(&interceptor);
const TimeDelta poll_interval = TimeDelta::FromMilliseconds(1);
syncer_thread()->SetSyncerShortPollInterval(poll_interval);
EXPECT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
// Wait for some healthy syncing.
interceptor.WaitForSyncShare(2, TimeDelta::FromSeconds(10));
EXPECT_GE(interceptor.times_sync_occured().size(), 2U);
// Atomically start returning auth invalid and set the interceptor to fail
// on any sync.
connection()->FailWithAuthInvalid(&interceptor);
WaitForDisconnect();
// Try to trigger a sync (the interceptor will assert if one occurs).
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
// Wait several poll intervals but don't expect any syncing besides the cycle
// that lost the connection.
interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1));
EXPECT_EQ(1U, interceptor.times_sync_occured().size());
// Simulate a valid re-authentication and expect resumption of syncing.
interceptor.Reset();
ASSERT_TRUE(interceptor.times_sync_occured().empty());
connection()->StopFailingWithAuthInvalid(NULL);
ServerConnectionEvent e = {ServerConnectionEvent::STATUS_CHANGED,
HttpResponse::SERVER_CONNECTION_OK,
true};
connection()->channel()->NotifyListeners(e);
interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(10));
EXPECT_FALSE(interceptor.times_sync_occured().empty());
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
// TODO(zea): Disabled, along with PauseWhenNotConnected, due to stalling on
// windows, preventing further sync unit tests from running. See crbug/39070.
TEST_F(SyncerThreadWithSyncerTest, DISABLED_Pause) {
WaitableEvent sync_cycle_ended_event(false, false);
WaitableEvent paused_event(false, false);
WaitableEvent resumed_event(false, false);
PreventThreadFromPolling();
ListenerMock listener;
TestScopedSessionEventListener reg(context_, &listener);
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::STATUS_CHANGED))).
Times(AnyNumber());
// Wait for the initial sync to complete.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_EXITING)));
ASSERT_TRUE(syncer_thread()->Start());
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
// Request a pause.
ASSERT_TRUE(Pause(&listener));
// Resuming the pause.
ASSERT_TRUE(Resume(&listener));
// Not paused, should fail.
EXPECT_FALSE(syncer_thread()->RequestResume());
// Request a pause.
ASSERT_TRUE(Pause(&listener));
// Nudge the syncer, this should do nothing while we are paused.
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
// Resuming will cause the nudge to be processed and a sync cycle to run.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
ASSERT_TRUE(Resume(&listener));
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
TEST_F(SyncerThreadWithSyncerTest, StartWhenNotConnected) {
WaitableEvent sync_cycle_ended_event(false, false);
WaitableEvent event(false, false);
ListenerMock listener;
TestScopedSessionEventListener reg(context_, &listener);
PreventThreadFromPolling();
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::STATUS_CHANGED))).
Times(AnyNumber());
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_EXITING)));
connection()->SetServerNotReachable();
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
// Syncer thread will always go through once cycle at the start,
// then it will wait for a connection.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION))).
WillOnce(SignalEvent(&event));
ASSERT_TRUE(syncer_thread()->Start());
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
ASSERT_TRUE(event.TimedWait(max_wait_time_));
// Connect, will put the syncer thread into its usually poll wait.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_CONNECTED))).
WillOnce(SignalEvent(&event));
connection()->SetServerReachable();
ASSERT_TRUE(event.TimedWait(max_wait_time_));
// Nudge the syncer to complete a cycle.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
// See TODO comment on the "Pause" test above.
TEST_F(SyncerThreadWithSyncerTest, DISABLED_PauseWhenNotConnected) {
WaitableEvent sync_cycle_ended_event(false, false);
WaitableEvent event(false, false);
ListenerMock listener;
TestScopedSessionEventListener reg(context_, &listener);
PreventThreadFromPolling();
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::STATUS_CHANGED))).
Times(AnyNumber());
// Put the thread into a "waiting for connection" state.
connection()->SetServerNotReachable();
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION))).
WillOnce(SignalEvent(&event));
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
ASSERT_TRUE(syncer_thread()->Start());
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
ASSERT_TRUE(event.TimedWait(max_wait_time_));
// Pause and resume the thread while waiting for a connection.
ASSERT_TRUE(Pause(&listener));
ASSERT_TRUE(Resume(&listener));
// Make a connection and let the syncer cycle.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_CONNECTED))).
WillOnce(SignalEvent(&event));
connection()->SetServerReachable();
ASSERT_TRUE(event.TimedWait(max_wait_time_));
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
// Disconnect and get into the waiting for a connection state.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION))).
WillOnce(SignalEvent(&event));
connection()->SetServerNotReachable();
ASSERT_TRUE(event.TimedWait(max_wait_time_));
// Pause so we can test getting a connection while paused.
ASSERT_TRUE(Pause(&listener));
// Get a connection then resume.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_CONNECTED))).
WillOnce(SignalEvent(&event));
connection()->SetServerReachable();
ASSERT_TRUE(event.TimedWait(max_wait_time_));
ASSERT_TRUE(Resume(&listener));
// Cycle the syncer to show we are not longer paused.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_EXITING)));
syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown);
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
TEST_F(SyncerThreadWithSyncerTest, PauseResumeWhenNotRunning) {
WaitableEvent sync_cycle_ended_event(false, false);
WaitableEvent event(false, false);
ListenerMock listener;
TestScopedSessionEventListener reg(context_, &listener);
PreventThreadFromPolling();
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::STATUS_CHANGED))).
Times(AnyNumber());
// Pause and resume the syncer while not running
ASSERT_TRUE(Pause(&listener));
ASSERT_TRUE(Resume(&listener));
// Pause the thread then start the syncer.
ASSERT_TRUE(Pause(&listener));
metadb()->Open();
syncer_thread()->CreateSyncer(metadb()->name());
ASSERT_TRUE(syncer_thread()->Start());
// Resume and let the syncer cycle.
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNC_CYCLE_ENDED))).
WillOnce(SignalEvent(&sync_cycle_ended_event));
EXPECT_CALL(listener, OnSyncEngineEvent(
Field(&SyncEngineEvent::what_happened,
SyncEngineEvent::SYNCER_THREAD_EXITING)));
ASSERT_TRUE(Resume(&listener));
ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_));
EXPECT_TRUE(syncer_thread()->Stop(2000));
}
} // namespace browser_sync