| // 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); |