text: // Copyright (c) 2011 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 "base/synchronization/waitable_event.h"
  #include "base/test/test_timeouts.h"
  #include "chrome/browser/sync/engine/mock_model_safe_workers.h"
  #include "chrome/browser/sync/engine/syncer.h"
  #include "chrome/browser/sync/engine/syncer_thread.h"
  #include "chrome/browser/sync/sessions/test_util.h"
  #include "chrome/test/sync/engine/mock_connection_manager.h"
  #include "chrome/test/sync/engine/test_directory_setter_upper.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "testing/gmock/include/gmock/gmock.h"
  
  using base::TimeDelta;
  using base::TimeTicks;
  using testing::_;
  using testing::AtLeast;
  using testing::DoAll;
  using testing::Eq;
  using testing::Invoke;
  using testing::Mock;
  using testing::Return;
  using testing::WithArg;
  
  namespace browser_sync {
  using sessions::SyncSession;
  using sessions::SyncSessionContext;
  using sessions::SyncSessionSnapshot;
  using syncable::ModelTypeBitSet;
  using sync_pb::GetUpdatesCallerInfo;
  
  class MockSyncer : public Syncer {
   public:
    MOCK_METHOD3(SyncShare, void(sessions::SyncSession*, SyncerStep,
                                 SyncerStep));
  };
  
  // Used when tests want to record syncing activity to examine later.
  struct SyncShareRecords {
    std::vector<TimeTicks> times;
    std::vector<linked_ptr<SyncSessionSnapshot> > snapshots;
  };
  
  // Convenient to use in tests wishing to analyze SyncShare calls over time.
  static const size_t kMinNumSamples = 5;
  class SyncerThread2Test : public testing::Test {
   public:
    class MockDelayProvider : public SyncerThread::DelayProvider {
     public:
      MOCK_METHOD1(GetDelay, TimeDelta(const TimeDelta&));
    };
  
    virtual void SetUp() {
      syncdb_.SetUp();
      syncer_ = new MockSyncer();
      delay_ = NULL;
      registrar_.reset(MockModelSafeWorkerRegistrar::PassiveBookmarks());
      connection_.reset(new MockConnectionManager(syncdb_.manager(), "Test"));
      connection_->SetServerReachable();
      context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(),
          registrar_.get(), std::vector<SyncEngineEventListener*>());
      context_->set_notifications_enabled(true);
      context_->set_account_name("Test");
      syncer_thread_.reset(new SyncerThread(context_, syncer_));
    }
  
    virtual void SetUpWithTypes(syncable::ModelTypeBitSet types) {
      syncdb_.SetUp();
      syncer_ = new MockSyncer();
      delay_ = NULL;
      registrar_.reset(MockModelSafeWorkerRegistrar::PassiveForTypes(types));
      connection_.reset(new MockConnectionManager(syncdb_.manager(), "Test"));
      connection_->SetServerReachable();
      context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(),
          registrar_.get(), std::vector<SyncEngineEventListener*>());
      context_->set_notifications_enabled(true);
      context_->set_account_name("Test");
      syncer_thread_.reset(new SyncerThread(context_, syncer_));
    }
  
    SyncerThread* syncer_thread() { return syncer_thread_.get(); }
    MockSyncer* syncer() { return syncer_; }
    MockDelayProvider* delay() { return delay_; }
    MockConnectionManager* connection() { return connection_.get(); }
    TimeDelta zero() { return TimeDelta::FromSeconds(0); }
    TimeDelta timeout() {
      return TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms());
    }
  
    virtual void TearDown() {
      syncer_thread()->Stop();
      syncdb_.TearDown();
    }
  
    void AnalyzePollRun(const SyncShareRecords& records, size_t min_num_samples,
        const TimeTicks& optimal_start, const TimeDelta& poll_interval) {
      const std::vector<TimeTicks>& data(records.times);
      EXPECT_GE(data.size(), min_num_samples);
      for (size_t i = 0; i < data.size(); i++) {
        SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")");
        TimeTicks optimal_next_sync = optimal_start + poll_interval * i;
        EXPECT_GE(data[i], optimal_next_sync);
        EXPECT_EQ(GetUpdatesCallerInfo::PERIODIC,
                  records.snapshots[i]->source.updates_source);
      }
    }
  
    bool GetBackoffAndResetTest(base::WaitableEvent* done) {
      syncable::ModelTypeBitSet nudge_types;
      syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
      syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types,
                                     FROM_HERE);
      done->TimedWait(timeout());
      TearDown();
      done->Reset();
      Mock::VerifyAndClearExpectations(syncer());
      bool backing_off = syncer_thread()->IsBackingOff();
      SetUp();
      UseMockDelayProvider();
      EXPECT_CALL(*delay(), GetDelay(_))
          .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1)));
      return backing_off;
    }
  
    void UseMockDelayProvider() {
      delay_ = new MockDelayProvider();
      syncer_thread_->delay_provider_.reset(delay_);
    }
  
    void PostSignalTask(base::WaitableEvent* done) {
      syncer_thread_->thread_.message_loop()->PostTask(FROM_HERE,
          NewRunnableFunction(&SyncerThread2Test::SignalWaitableEvent, done));
    }
  
    void FlushLastTask(base::WaitableEvent* done) {
      PostSignalTask(done);
      done->TimedWait(timeout());
      done->Reset();
    }
  
    static void SignalWaitableEvent(base::WaitableEvent* event) {
      event->Signal();
    }
  
    static void QuitMessageLoop() {
      MessageLoop::current()->Quit();
    }
  
    // Compare a ModelTypeBitSet to a ModelTypePayloadMap, ignoring
    // payload values.
    bool CompareModelTypeBitSetToModelTypePayloadMap(
        const syncable::ModelTypeBitSet& lhs,
        const syncable::ModelTypePayloadMap& rhs) {
      size_t count = 0;
      for (syncable::ModelTypePayloadMap::const_iterator i = rhs.begin();
           i != rhs.end(); ++i, ++count) {
        if (!lhs.test(i->first))
          return false;
      }
      if (lhs.count() != count)
        return false;
      return true;
    }
  
    SyncSessionContext* context() { return context_; }
  
   private:
    scoped_ptr<SyncerThread> syncer_thread_;
    scoped_ptr<MockConnectionManager> connection_;
    SyncSessionContext* context_;
    MockSyncer* syncer_;
    MockDelayProvider* delay_;
    scoped_ptr<MockModelSafeWorkerRegistrar> registrar_;
    MockDirectorySetterUpper syncdb_;
  };
  
  bool RecordSyncShareImpl(SyncSession* s, SyncShareRecords* record,
                           size_t signal_after) {
    record->times.push_back(TimeTicks::Now());
    record->snapshots.push_back(make_linked_ptr(new SyncSessionSnapshot(
        s->TakeSnapshot())));
    return record->times.size() >= signal_after;
  }
  
  ACTION_P4(RecordSyncShareAndPostSignal, record, signal_after, test, event) {
    if (RecordSyncShareImpl(arg0, record, signal_after) && event)
      test->PostSignalTask(event);
  }
  
  ACTION_P3(RecordSyncShare, record, signal_after, event) {
    if (RecordSyncShareImpl(arg0, record, signal_after) && event)
      event->Signal();
  }
  
  ACTION_P(SignalEvent, event) {
    SyncerThread2Test::SignalWaitableEvent(event);
  }
  
  // Test nudge scheduling.
  TEST_F(SyncerThread2Test, Nudge) {
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    base::WaitableEvent done(false, false);
    SyncShareRecords records;
    syncable::ModelTypeBitSet model_types;
    model_types[syncable::BOOKMARKS] = true;
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done))))
        .RetiresOnSaturation();
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types,
                                   FROM_HERE);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, records.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types,
        records.snapshots[0]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              records.snapshots[0]->source.updates_source);
  
    // Make sure a second, later, nudge is unaffected by first (no coalescing).
    SyncShareRecords records2;
    model_types[syncable::BOOKMARKS] = false;
    model_types[syncable::AUTOFILL] = true;
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records2, 1U, &done))));
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types,
                                   FROM_HERE);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, records2.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types,
        records2.snapshots[0]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              records2.snapshots[0]->source.updates_source);
  }
  
  // Make sure a regular config command is scheduled fine in the absence of any
  // errors.
  TEST_F(SyncerThread2Test, Config) {
    base::WaitableEvent done(false, false);
    SyncShareRecords records;
    syncable::ModelTypeBitSet model_types;
    model_types[syncable::BOOKMARKS] = true;
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done))));
  
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
  
    syncer_thread()->ScheduleConfig(model_types);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, records.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types,
        records.snapshots[0]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::FIRST_UPDATE,
              records.snapshots[0]->source.updates_source);
  }
  
  // Simulate a failure and make sure the config request is retried.
  TEST_F(SyncerThread2Test, ConfigWithBackingOff) {
    base::WaitableEvent done(false, false);
    base::WaitableEvent* dummy = NULL;
    UseMockDelayProvider();
    EXPECT_CALL(*delay(), GetDelay(_))
        .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1)));
    SyncShareRecords records;
    syncable::ModelTypeBitSet model_types;
    model_types[syncable::BOOKMARKS] = true;
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            WithArg<0>(RecordSyncShare(&records, 1U, dummy))))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done))));
  
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
  
    syncer_thread()->ScheduleConfig(model_types);
    done.TimedWait(timeout());
  
    EXPECT_EQ(2U, records.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types,
        records.snapshots[1]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION,
              records.snapshots[1]->source.updates_source);
  }
  
  // Issue 2 config commands. Second one right after the first has failed
  // and make sure LATEST is executed.
  TEST_F(SyncerThread2Test, MultipleConfigWithBackingOff) {
    syncable::ModelTypeBitSet model_types1, model_types2;
    model_types1[syncable::BOOKMARKS] = true;
    model_types2[syncable::AUTOFILL] = true;
    SetUpWithTypes(model_types1 | model_types2);
    base::WaitableEvent done(false, false);
    base::WaitableEvent done1(false, false);
    base::WaitableEvent* dummy = NULL;
    UseMockDelayProvider();
    EXPECT_CALL(*delay(), GetDelay(_))
        .WillRepeatedly(Return(TimeDelta::FromMilliseconds(30)));
    SyncShareRecords records;
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            WithArg<0>(RecordSyncShare(&records, 1U, dummy))))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            WithArg<0>(RecordSyncShare(&records, 1U, &done1))))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done))));
  
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
  
    syncer_thread()->ScheduleConfig(model_types1);
  
    // done1 indicates the first config failed.
    done1.TimedWait(timeout());
    syncer_thread()->ScheduleConfig(model_types2);
    done.TimedWait(timeout());
  
    EXPECT_EQ(3U, records.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types2,
        records.snapshots[2]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::FIRST_UPDATE,
              records.snapshots[2]->source.updates_source);
  }
  
  // Issue a nudge when the config has failed. Make sure both the config and
  // nudge are executed.
  TEST_F(SyncerThread2Test, NudgeWithConfigWithBackingOff) {
    syncable::ModelTypeBitSet model_types;
    model_types[syncable::BOOKMARKS] = true;
    base::WaitableEvent done(false, false);
    base::WaitableEvent done1(false, false);
    base::WaitableEvent done2(false, false);
    base::WaitableEvent* dummy = NULL;
    UseMockDelayProvider();
    EXPECT_CALL(*delay(), GetDelay(_))
        .WillRepeatedly(Return(TimeDelta::FromMilliseconds(50)));
    SyncShareRecords records;
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            WithArg<0>(RecordSyncShare(&records, 1U, dummy))))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            WithArg<0>(RecordSyncShare(&records, 1U, &done1))))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done2))))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done))));
  
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
  
    syncer_thread()->ScheduleConfig(model_types);
    done1.TimedWait(timeout());
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types,
                                   FROM_HERE);
  
    // done2 indicates config suceeded. Now change the mode so nudge can execute.
    done2.TimedWait(timeout());
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    EXPECT_EQ(4U, records.snapshots.size());
  
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types,
        records.snapshots[2]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION,
              records.snapshots[2]->source.updates_source);
  
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types,
        records.snapshots[3]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              records.snapshots[3]->source.updates_source);
  
  }
  
  
  // Test that nudges are coalesced.
  TEST_F(SyncerThread2Test, NudgeCoalescing) {
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    base::WaitableEvent done(false, false);
    SyncShareRecords r;
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&r, 1U, &done))));
    syncable::ModelTypeBitSet types1, types2, types3;
    types1[syncable::BOOKMARKS] = true;
    types2[syncable::AUTOFILL] = true;
    types3[syncable::THEMES] = true;
    TimeDelta delay = TimeDelta::FromMilliseconds(
        TestTimeouts::tiny_timeout_ms());
    TimeTicks optimal_time = TimeTicks::Now() + delay;
    syncer_thread()->ScheduleNudge(delay, NUDGE_SOURCE_UNKNOWN, types1,
                                   FROM_HERE);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types2,
                                   FROM_HERE);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, r.snapshots.size());
    EXPECT_GE(r.times[0], optimal_time);
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(
        types1 | types2, r.snapshots[0]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              r.snapshots[0]->source.updates_source);
  
    SyncShareRecords r2;
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&r2, 1U, &done))));
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_NOTIFICATION, types3,
                                   FROM_HERE);
    done.TimedWait(timeout());
    EXPECT_EQ(1U, r2.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(types3,
        r2.snapshots[0]->source.types));
    EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION,
              r2.snapshots[0]->source.updates_source);
  }
  
  // Test nudge scheduling.
  TEST_F(SyncerThread2Test, NudgeWithPayloads) {
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    base::WaitableEvent done(false, false);
    SyncShareRecords records;
    syncable::ModelTypePayloadMap model_types_with_payloads;
    model_types_with_payloads[syncable::BOOKMARKS] = "test";
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records, 1U, &done))))
        .RetiresOnSaturation();
    syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL,
        model_types_with_payloads, FROM_HERE);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, records.snapshots.size());
    EXPECT_EQ(model_types_with_payloads, records.snapshots[0]->source.types);
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              records.snapshots[0]->source.updates_source);
  
    // Make sure a second, later, nudge is unaffected by first (no coalescing).
    SyncShareRecords records2;
    model_types_with_payloads.erase(syncable::BOOKMARKS);
    model_types_with_payloads[syncable::AUTOFILL] = "test2";
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            WithArg<0>(RecordSyncShare(&records2, 1U, &done))));
    syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL,
        model_types_with_payloads, FROM_HERE);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, records2.snapshots.size());
    EXPECT_EQ(model_types_with_payloads, records2.snapshots[0]->source.types);
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              records2.snapshots[0]->source.updates_source);
  }
  
  // Test that nudges are coalesced.
  TEST_F(SyncerThread2Test, NudgeWithPayloadsCoalescing) {
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    base::WaitableEvent done(false, false);
    SyncShareRecords r;
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&r, 1U, &done))));
    syncable::ModelTypePayloadMap types1, types2, types3;
    types1[syncable::BOOKMARKS] = "test1";
    types2[syncable::AUTOFILL] = "test2";
    types3[syncable::THEMES] = "test3";
    TimeDelta delay = TimeDelta::FromMilliseconds(
        TestTimeouts::tiny_timeout_ms());
    TimeTicks optimal_time = TimeTicks::Now() + delay;
    syncer_thread()->ScheduleNudgeWithPayloads(delay, NUDGE_SOURCE_UNKNOWN,
        types1, FROM_HERE);
    syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL,
        types2, FROM_HERE);
    done.TimedWait(timeout());
  
    EXPECT_EQ(1U, r.snapshots.size());
    EXPECT_GE(r.times[0], optimal_time);
    syncable::ModelTypePayloadMap coalesced_types;
    syncable::CoalescePayloads(&coalesced_types, types1);
    syncable::CoalescePayloads(&coalesced_types, types2);
    EXPECT_EQ(coalesced_types, r.snapshots[0]->source.types);
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              r.snapshots[0]->source.updates_source);
  
    SyncShareRecords r2;
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&r2, 1U, &done))));
    syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_NOTIFICATION,
        types3, FROM_HERE);
    done.TimedWait(timeout());
    EXPECT_EQ(1U, r2.snapshots.size());
    EXPECT_EQ(types3, r2.snapshots[0]->source.types);
    EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION,
              r2.snapshots[0]->source.updates_source);
  }
  
  // Test that polling works as expected.
  TEST_F(SyncerThread2Test, Polling) {
    SyncShareRecords records;
    base::WaitableEvent done(false, false);
    TimeDelta poll_interval(TimeDelta::FromMilliseconds(30));
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll_interval);
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(AtLeast(kMinNumSamples))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done))));
  
    TimeTicks optimal_start = TimeTicks::Now() + poll_interval;
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    syncer_thread()->Stop();
  
    AnalyzePollRun(records, kMinNumSamples, optimal_start, poll_interval);
  }
  
  // Test that the short poll interval is used.
  TEST_F(SyncerThread2Test, PollNotificationsDisabled) {
    SyncShareRecords records;
    base::WaitableEvent done(false, false);
    TimeDelta poll_interval(TimeDelta::FromMilliseconds(30));
    syncer_thread()->OnReceivedShortPollIntervalUpdate(poll_interval);
    syncer_thread()->set_notifications_enabled(false);
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(AtLeast(kMinNumSamples))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done))));
  
    TimeTicks optimal_start = TimeTicks::Now() + poll_interval;
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    syncer_thread()->Stop();
  
    AnalyzePollRun(records, kMinNumSamples, optimal_start, poll_interval);
  }
  
  // Test that polling intervals are updated when needed.
  TEST_F(SyncerThread2Test, PollIntervalUpdate) {
    SyncShareRecords records;
    base::WaitableEvent done(false, false);
    TimeDelta poll1(TimeDelta::FromMilliseconds(120));
    TimeDelta poll2(TimeDelta::FromMilliseconds(30));
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll1);
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(AtLeast(kMinNumSamples))
        .WillOnce(WithArg<0>(
            sessions::test_util::SimulatePollIntervalUpdate(poll2)))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done))));
  
    TimeTicks optimal_start = TimeTicks::Now() + poll1 + poll2;
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    syncer_thread()->Stop();
  
    AnalyzePollRun(records, kMinNumSamples, optimal_start, poll2);
  }
  
  // Test that a sync session is run through to completion.
  TEST_F(SyncerThread2Test, HasMoreToSync) {
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    base::WaitableEvent done(false, false);
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateHasMoreToSync))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
                        SignalEvent(&done)));
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(),
                                   FROM_HERE);
    done.TimedWait(timeout());
    // If more nudges are scheduled, they'll be waited on by TearDown, and would
    // cause our expectation to break.
  }
  
  // Test that no syncing occurs when throttled.
  TEST_F(SyncerThread2Test, ThrottlingDoesThrottle) {
    syncable::ModelTypeBitSet types;
    types[syncable::BOOKMARKS] = true;
    base::WaitableEvent done(false, false);
    TimeDelta poll(TimeDelta::FromMilliseconds(5));
    TimeDelta throttle(TimeDelta::FromMinutes(10));
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(WithArg<0>(sessions::test_util::SimulateThrottled(throttle)));
  
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types,
                                   FROM_HERE);
    FlushLastTask(&done);
  
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
    syncer_thread()->ScheduleConfig(types);
    FlushLastTask(&done);
  }
  
  TEST_F(SyncerThread2Test, ThrottlingExpires) {
    SyncShareRecords records;
    base::WaitableEvent done(false, false);
    TimeDelta poll(TimeDelta::FromMilliseconds(15));
    TimeDelta throttle1(TimeDelta::FromMilliseconds(150));
    TimeDelta throttle2(TimeDelta::FromMinutes(10));
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
  
    ::testing::InSequence seq;
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(WithArg<0>(sessions::test_util::SimulateThrottled(throttle1)))
        .RetiresOnSaturation();
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done))));
  
    TimeTicks optimal_start = TimeTicks::Now() + poll + throttle1;
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    syncer_thread()->Stop();
  
    AnalyzePollRun(records, kMinNumSamples, optimal_start, poll);
  }
  
  // Test nudges / polls don't run in config mode and config tasks do.
  TEST_F(SyncerThread2Test, ConfigurationMode) {
    TimeDelta poll(TimeDelta::FromMilliseconds(15));
    SyncShareRecords records;
    base::WaitableEvent done(false, false);
    base::WaitableEvent* dummy = NULL;
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce((Invoke(sessions::test_util::SimulateSuccess),
             WithArg<0>(RecordSyncShare(&records, 1U, dummy))));
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
    syncable::ModelTypeBitSet nudge_types;
    nudge_types[syncable::AUTOFILL] = true;
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types,
                                   FROM_HERE);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types,
                                   FROM_HERE);
  
    syncable::ModelTypeBitSet config_types;
    config_types[syncable::BOOKMARKS] = true;
    // TODO(tim): This will fail once CONFIGURATION tasks are implemented. Update
    // the EXPECT when that happens.
    syncer_thread()->ScheduleConfig(config_types);
    FlushLastTask(&done);
    syncer_thread()->Stop();
  
    EXPECT_EQ(1U, records.snapshots.size());
    EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(config_types,
        records.snapshots[0]->source.types));
  }
  
  // Test that exponential backoff is properly triggered.
  TEST_F(SyncerThread2Test, BackoffTriggers) {
    base::WaitableEvent done(false, false);
    UseMockDelayProvider();
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            SignalEvent(&done)));
    EXPECT_FALSE(GetBackoffAndResetTest(&done));
    // Note GetBackoffAndResetTest clears mocks and re-instantiates the syncer.
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed))
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            SignalEvent(&done)));
    EXPECT_FALSE(GetBackoffAndResetTest(&done));
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed))
        .WillRepeatedly(DoAll(Invoke(
            sessions::test_util::SimulateDownloadUpdatesFailed),
            SignalEvent(&done)));
    EXPECT_TRUE(GetBackoffAndResetTest(&done));
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            SignalEvent(&done)));
    EXPECT_TRUE(GetBackoffAndResetTest(&done));
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed))
        .WillOnce(Invoke(sessions::test_util::SimulateDownloadUpdatesFailed))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            SignalEvent(&done)));
    EXPECT_FALSE(GetBackoffAndResetTest(&done));
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed))
        .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            SignalEvent(&done)));
    EXPECT_FALSE(GetBackoffAndResetTest(&done));
  }
  
  // Test that no polls or extraneous nudges occur when in backoff.
  TEST_F(SyncerThread2Test, BackoffDropsJobs) {
    SyncShareRecords r;
    TimeDelta poll(TimeDelta::FromMilliseconds(5));
    base::WaitableEvent done(false, false);
    syncable::ModelTypeBitSet types;
    types[syncable::BOOKMARKS] = true;
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
    UseMockDelayProvider();
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(2)
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            RecordSyncShareAndPostSignal(&r, 2U, this, &done)));
    EXPECT_CALL(*delay(), GetDelay(_))
        .WillRepeatedly(Return(TimeDelta::FromDays(1)));
  
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    ASSERT_TRUE(done.TimedWait(timeout()));
    done.Reset();
  
    Mock::VerifyAndClearExpectations(syncer());
    EXPECT_EQ(2U, r.snapshots.size());
    EXPECT_EQ(GetUpdatesCallerInfo::PERIODIC,
              r.snapshots[0]->source.updates_source);
    EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION,
              r.snapshots[1]->source.updates_source);
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(1)
        .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            RecordSyncShareAndPostSignal(&r, 1U, this, &done)));
  
    // We schedule a nudge with enough delay (10X poll interval) that at least
    // one or two polls would have taken place.  The nudge should succeed.
    syncer_thread()->ScheduleNudge(poll * 10, NUDGE_SOURCE_LOCAL, types,
                                   FROM_HERE);
    ASSERT_TRUE(done.TimedWait(timeout()));
    done.Reset();
  
    Mock::VerifyAndClearExpectations(syncer());
    Mock::VerifyAndClearExpectations(delay());
    EXPECT_EQ(3U, r.snapshots.size());
    EXPECT_EQ(GetUpdatesCallerInfo::LOCAL,
              r.snapshots[2]->source.updates_source);
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(0);
    EXPECT_CALL(*delay(), GetDelay(_)).Times(0);
  
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
    syncer_thread()->ScheduleConfig(types);
    FlushLastTask(&done);
  
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types,
                                   FROM_HERE);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types,
                                   FROM_HERE);
    FlushLastTask(&done);
  }
  
  // Test that backoff is shaping traffic properly with consecutive errors.
  TEST_F(SyncerThread2Test, BackoffElevation) {
    SyncShareRecords r;
    const TimeDelta poll(TimeDelta::FromMilliseconds(10));
    base::WaitableEvent done(false, false);
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
    UseMockDelayProvider();
  
    const TimeDelta first = TimeDelta::FromSeconds(1);
    const TimeDelta second = TimeDelta::FromMilliseconds(10);
    const TimeDelta third = TimeDelta::FromMilliseconds(20);
    const TimeDelta fourth = TimeDelta::FromMilliseconds(30);
    const TimeDelta fifth = TimeDelta::FromDays(1);
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(kMinNumSamples)
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateCommitFailed),
            RecordSyncShareAndPostSignal(&r, kMinNumSamples, this, &done)));
  
    EXPECT_CALL(*delay(), GetDelay(Eq(first))).WillOnce(Return(second))
        .RetiresOnSaturation();
    EXPECT_CALL(*delay(), GetDelay(Eq(second))).WillOnce(Return(third))
        .RetiresOnSaturation();
    EXPECT_CALL(*delay(), GetDelay(Eq(third))).WillOnce(Return(fourth))
        .RetiresOnSaturation();
    EXPECT_CALL(*delay(), GetDelay(Eq(fourth))).WillOnce(Return(fifth));
  
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    ASSERT_TRUE(done.TimedWait(timeout()));
  
    EXPECT_GE(r.times[2] - r.times[1], second);
    EXPECT_GE(r.times[3] - r.times[2], third);
    EXPECT_GE(r.times[4] - r.times[3], fourth);
  }
  
  // Test that things go back to normal once a canary task makes forward progress
  // following a succession of failures.
  TEST_F(SyncerThread2Test, BackoffRelief) {
    SyncShareRecords r;
    const TimeDelta poll(TimeDelta::FromMilliseconds(10));
    base::WaitableEvent done(false, false);
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
    UseMockDelayProvider();
  
    const TimeDelta backoff = TimeDelta::FromMilliseconds(100);
  
    EXPECT_CALL(*syncer(), SyncShare(_,_,_))
        .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed))
        .WillOnce(Invoke(sessions::test_util::SimulateCommitFailed))
        .WillRepeatedly(DoAll(Invoke(sessions::test_util::SimulateSuccess),
            RecordSyncShareAndPostSignal(&r, kMinNumSamples, this, &done)));
    EXPECT_CALL(*delay(), GetDelay(_)).WillOnce(Return(backoff));
  
    // Optimal start for the post-backoff poll party.
    TimeTicks optimal_start = TimeTicks::Now() + poll + backoff;
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    syncer_thread()->Stop();
  
    // Check for healthy polling after backoff is relieved.
    // Can't use AnalyzePollRun because first sync is a continuation. Bleh.
    for (size_t i = 0; i < r.times.size(); i++) {
      SCOPED_TRACE(testing::Message() << "SyncShare # (" << i << ")");
      TimeTicks optimal_next_sync = optimal_start + poll * i;
      EXPECT_GE(r.times[i], optimal_next_sync);
      EXPECT_EQ(i == 0 ? GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION
                       : GetUpdatesCallerInfo::PERIODIC,
                r.snapshots[i]->source.updates_source);
    }
  }
  
  TEST_F(SyncerThread2Test, GetRecommendedDelay) {
    EXPECT_LE(TimeDelta::FromSeconds(0),
              SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(0)));
    EXPECT_LE(TimeDelta::FromSeconds(1),
              SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(1)));
    EXPECT_LE(TimeDelta::FromSeconds(50),
              SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(50)));
    EXPECT_LE(TimeDelta::FromSeconds(10),
              SyncerThread::GetRecommendedDelay(TimeDelta::FromSeconds(10)));
    EXPECT_EQ(TimeDelta::FromSeconds(kMaxBackoffSeconds),
              SyncerThread::GetRecommendedDelay(
                  TimeDelta::FromSeconds(kMaxBackoffSeconds)));
    EXPECT_EQ(TimeDelta::FromSeconds(kMaxBackoffSeconds),
              SyncerThread::GetRecommendedDelay(
                  TimeDelta::FromSeconds(kMaxBackoffSeconds + 1)));
  }
  
  // Test that appropriate syncer steps are requested for each job type.
  TEST_F(SyncerThread2Test, SyncerSteps) {
    // Nudges.
    base::WaitableEvent done(false, false);
    EXPECT_CALL(*syncer(), SyncShare(_, SYNCER_BEGIN, SYNCER_END))
        .Times(1);
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(),
                                   FROM_HERE);
    FlushLastTask(&done);
    syncer_thread()->Stop();
    Mock::VerifyAndClearExpectations(syncer());
  
    // ClearUserData.
    EXPECT_CALL(*syncer(), SyncShare(_, CLEAR_PRIVATE_DATA, SYNCER_END))
        .Times(1);
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    syncer_thread()->ScheduleClearUserData();
    FlushLastTask(&done);
    syncer_thread()->Stop();
    Mock::VerifyAndClearExpectations(syncer());
    // Configuration.
    EXPECT_CALL(*syncer(), SyncShare(_, DOWNLOAD_UPDATES, APPLY_UPDATES));
    syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL);
    syncer_thread()->ScheduleConfig(ModelTypeBitSet());
    FlushLastTask(&done);
    syncer_thread()->Stop();
    Mock::VerifyAndClearExpectations(syncer());
  
    // Poll.
    EXPECT_CALL(*syncer(), SyncShare(_, SYNCER_BEGIN, SYNCER_END))
        .Times(AtLeast(1))
        .WillRepeatedly(SignalEvent(&done));
    const TimeDelta poll(TimeDelta::FromMilliseconds(10));
    syncer_thread()->OnReceivedLongPollIntervalUpdate(poll);
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    done.TimedWait(timeout());
    syncer_thread()->Stop();
    Mock::VerifyAndClearExpectations(syncer());
    done.Reset();
  }
  
  // Test config tasks don't run during normal mode.
  // TODO(tim): Implement this test and then the functionality!
  TEST_F(SyncerThread2Test, DISABLED_NoConfigDuringNormal) {
  }
  
  // Test that starting the syncer thread without a valid connection doesn't
  // break things when a connection is detected.
  TEST_F(SyncerThread2Test, StartWhenNotConnected) {
    base::WaitableEvent done(false, false);
    MessageLoop cur;
    connection()->SetServerNotReachable();
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).WillOnce(SignalEvent(&done));
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(),
                                   FROM_HERE);
    FlushLastTask(&done);
  
    connection()->SetServerReachable();
    cur.PostTask(FROM_HERE, NewRunnableFunction(
        &SyncerThread2Test::QuitMessageLoop));
    cur.Run();
  
    // By now, the server connection event should have been posted to the
    // SyncerThread.
    FlushLastTask(&done);
    done.TimedWait(timeout());
  }
  
  TEST_F(SyncerThread2Test, SetsPreviousRoutingInfo) {
    base::WaitableEvent done(false, false);
    ModelSafeRoutingInfo info;
    EXPECT_TRUE(info == context()->previous_session_routing_info());
    ModelSafeRoutingInfo expected;
    context()->registrar()->GetModelSafeRoutingInfo(&expected);
    ASSERT_FALSE(expected.empty());
    EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(1);
  
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
    syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(),
                                   FROM_HERE);
    FlushLastTask(&done);
    syncer_thread()->Stop();
  
    EXPECT_TRUE(expected == context()->previous_session_routing_info());
  }
  
  }  // namespace browser_sync
  
  // SyncerThread won't outlive the test!
  DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::SyncerThread2Test);
  