| Index: sync/sessions/nudge_tracker_unittest.cc | 
| diff --git a/sync/sessions/nudge_tracker_unittest.cc b/sync/sessions/nudge_tracker_unittest.cc | 
| index 192a737c9369c91004db68e44f361bb92628900f..5f8b9b3ca9690283e3fbc28c67ec40f542c043a2 100644 | 
| --- a/sync/sessions/nudge_tracker_unittest.cc | 
| +++ b/sync/sessions/nudge_tracker_unittest.cc | 
| @@ -5,107 +5,309 @@ | 
| #include "sync/sessions/nudge_tracker.h" | 
|  | 
| #include "sync/internal_api/public/base/model_type_invalidation_map.h" | 
| +#include "sync/internal_api/public/sessions/sync_source_info.h" | 
| #include "testing/gtest/include/gtest/gtest.h" | 
|  | 
| namespace syncer { | 
|  | 
| namespace { | 
|  | 
| -ModelTypeSet ParamsMeaningAllEnabledTypes() { | 
| -  ModelTypeSet request_params(BOOKMARKS, AUTOFILL); | 
| -  return request_params; | 
| -} | 
| - | 
| -ModelTypeSet ParamsMeaningJustOneEnabledType() { | 
| -  return ModelTypeSet(AUTOFILL); | 
| +testing::AssertionResult ModelTypeSetEquals(ModelTypeSet a, ModelTypeSet b) { | 
| +  if (a.Equals(b)) { | 
| +    return testing::AssertionSuccess(); | 
| +  } else { | 
| +    return testing::AssertionFailure() | 
| +        << "Left side " << ModelTypeSetToString(a) | 
| +        << ", does not match rigth side: " << ModelTypeSetToString(b); | 
| +  } | 
| } | 
|  | 
| }  // namespace | 
|  | 
| namespace sessions { | 
|  | 
| -TEST(NudgeTrackerTest, CoalesceSources) { | 
| -  ModelTypeInvalidationMap one_type = | 
| -      ModelTypeSetToInvalidationMap( | 
| -          ParamsMeaningJustOneEnabledType(), | 
| -          std::string()); | 
| -  ModelTypeInvalidationMap all_types = | 
| -      ModelTypeSetToInvalidationMap( | 
| -          ParamsMeaningAllEnabledTypes(), | 
| -          std::string()); | 
| -  sessions::SyncSourceInfo source_one( | 
| -      sync_pb::GetUpdatesCallerInfo::NOTIFICATION, one_type); | 
| -  sessions::SyncSourceInfo source_two( | 
| -      sync_pb::GetUpdatesCallerInfo::LOCAL, all_types); | 
| - | 
| -  NudgeTracker tracker; | 
| -  EXPECT_TRUE(tracker.IsEmpty()); | 
| - | 
| -  tracker.CoalesceSources(source_one); | 
| -  EXPECT_EQ(source_one.updates_source, tracker.source_info().updates_source); | 
| - | 
| -  tracker.CoalesceSources(source_two); | 
| -  EXPECT_EQ(source_two.updates_source, tracker.source_info().updates_source); | 
| +class NudgeTrackerTest : public ::testing::Test { | 
| + public: | 
| +  static size_t GetHintBufferSize() { | 
| +    return NudgeTracker::kMaxPayloadsPerType; | 
| +  } | 
| + | 
| +  bool InvalidationsOutOfSync(const NudgeTracker& nudge_tracker) { | 
| +    // We don't currently track invalidations out of sync on a per-type basis. | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| +    return gu_trigger.invalidations_out_of_sync(); | 
| +  } | 
| + | 
| +  int ProtoLocallyModifiedCount(const NudgeTracker& nudge_tracker, | 
| +                                ModelType type) { | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(type, &gu_trigger); | 
| +    return gu_trigger.local_modification_nudges(); | 
| +  } | 
| + | 
| +  int ProtoRefreshRequestedCount(const NudgeTracker& nudge_tracker, | 
| +                                 ModelType type) { | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(type, &gu_trigger); | 
| +    return gu_trigger.datatype_refresh_nudges(); | 
| +  } | 
| +}; | 
| + | 
| +// Exercise an empty NudgeTracker. | 
| +// Use with valgrind to detect uninitialized members. | 
| +TEST_F(NudgeTrackerTest, EmptyNudgeTracker) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN, | 
| +            nudge_tracker.updates_source()); | 
| +  EXPECT_TRUE(nudge_tracker.GetLocallyModifiedTypes().Empty()); | 
| + | 
| +  sync_pb::GetUpdateTriggers gu_trigger; | 
| +  nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| + | 
| +  SyncSourceInfo source_info = nudge_tracker.GetSourceInfo(); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN, | 
| +            source_info.updates_source); | 
| +} | 
| + | 
| +// Verify that nudges override each other based on a priority order. | 
| +// LOCAL < DATATYPE_REFRESH < NOTIFICATION | 
| +TEST_F(NudgeTrackerTest, SourcePriorities) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  // Track a local nudge. | 
| +  nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::LOCAL, | 
| +            nudge_tracker.updates_source()); | 
| + | 
| +  // A refresh request will override it. | 
| +  nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(TYPED_URLS)); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH, | 
| +            nudge_tracker.updates_source()); | 
| + | 
| +  // Another local nudge will not be enough to change it. | 
| +  nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH, | 
| +            nudge_tracker.updates_source()); | 
| + | 
| +  // An invalidation will override the refresh request source. | 
| +  ModelTypeInvalidationMap invalidation_map = | 
| +      ModelTypeSetToInvalidationMap(ModelTypeSet(PREFERENCES), | 
| +                                    std::string("hint")); | 
| +  nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, | 
| +            nudge_tracker.updates_source()); | 
| + | 
| +  // Neither local nudges nor refresh requests will override it. | 
| +  nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, | 
| +            nudge_tracker.updates_source()); | 
| +  nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(TYPED_URLS)); | 
| +  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, | 
| +            nudge_tracker.updates_source()); | 
| } | 
|  | 
| -TEST(NudgeTrackerTest, LocallyModifiedTypes_WithInvalidationFirst) { | 
| -  ModelTypeInvalidationMap one_type = | 
| -      ModelTypeSetToInvalidationMap( | 
| -          ParamsMeaningJustOneEnabledType(), | 
| -          std::string()); | 
| -  ModelTypeInvalidationMap all_types = | 
| -      ModelTypeSetToInvalidationMap( | 
| -          ParamsMeaningAllEnabledTypes(), | 
| -          std::string()); | 
| -  sessions::SyncSourceInfo source_one( | 
| -      sync_pb::GetUpdatesCallerInfo::NOTIFICATION, all_types); | 
| -  sessions::SyncSourceInfo source_two( | 
| -      sync_pb::GetUpdatesCallerInfo::LOCAL, one_type); | 
| - | 
| -  NudgeTracker tracker; | 
| -  EXPECT_TRUE(tracker.IsEmpty()); | 
| -  EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 
| - | 
| -  tracker.CoalesceSources(source_one); | 
| -  EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 
| - | 
| -  tracker.CoalesceSources(source_two); | 
| -  // TODO: This result is wrong, but that's how the code has always been.  A | 
| -  // local invalidation for a single type should mean that we have only one | 
| -  // locally modified source.  It should not "inherit" the list of data types | 
| -  // from the previous source. | 
| -  EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Equals( | 
| -          ParamsMeaningAllEnabledTypes())); | 
| +// Verify locally modified type coalescing and independence from other nudges. | 
| +TEST_F(NudgeTrackerTest, LocallyModifiedTypes) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  // Start with a notification.  Verify it has no effect. | 
| +  ModelTypeInvalidationMap invalidation_map1 = | 
| +      ModelTypeSetToInvalidationMap(ModelTypeSet(PREFERENCES), | 
| +                                    std::string("hint")); | 
| +  nudge_tracker.RecordRemoteInvalidation(invalidation_map1); | 
| +  EXPECT_TRUE(nudge_tracker.GetLocallyModifiedTypes().Empty()); | 
| + | 
| +  // Record a local bookmark change.  Verify it was registered correctly. | 
| +  nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| +  EXPECT_TRUE(ModelTypeSetEquals( | 
| +          ModelTypeSet(BOOKMARKS), | 
| +          nudge_tracker.GetLocallyModifiedTypes())); | 
| + | 
| +  // Record a notification and a refresh request.  Verify they have no effect. | 
| +  ModelTypeInvalidationMap invalidation_map2 = | 
| +      ModelTypeSetToInvalidationMap(ModelTypeSet(PASSWORDS), | 
| +                                    std::string("hint")); | 
| +  nudge_tracker.RecordRemoteInvalidation(invalidation_map2); | 
| +  EXPECT_TRUE(ModelTypeSetEquals( | 
| +          ModelTypeSet(BOOKMARKS), | 
| +          nudge_tracker.GetLocallyModifiedTypes())); | 
| + | 
| +  nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(AUTOFILL)); | 
| +  EXPECT_TRUE(ModelTypeSetEquals( | 
| +          ModelTypeSet(BOOKMARKS), | 
| +          nudge_tracker.GetLocallyModifiedTypes())); | 
| + | 
| +  // Record another local nudge.  Verify it was coalesced correctly. | 
| +  nudge_tracker.RecordLocalChange(ModelTypeSet(THEMES)); | 
| +  EXPECT_TRUE(ModelTypeSetEquals( | 
| +          ModelTypeSet(THEMES, BOOKMARKS), | 
| +          nudge_tracker.GetLocallyModifiedTypes())); | 
| } | 
|  | 
| -TEST(NudgeTrackerTest, LocallyModifiedTypes_WithInvalidationSecond) { | 
| -  ModelTypeInvalidationMap one_type = | 
| -      ModelTypeSetToInvalidationMap( | 
| -          ParamsMeaningJustOneEnabledType(), | 
| -          std::string()); | 
| -  ModelTypeInvalidationMap all_types = | 
| -      ModelTypeSetToInvalidationMap( | 
| -          ParamsMeaningAllEnabledTypes(), | 
| -          std::string()); | 
| -  sessions::SyncSourceInfo source_one( | 
| -      sync_pb::GetUpdatesCallerInfo::LOCAL, one_type); | 
| -  sessions::SyncSourceInfo source_two( | 
| -      sync_pb::GetUpdatesCallerInfo::NOTIFICATION, all_types); | 
| - | 
| -  NudgeTracker tracker; | 
| -  EXPECT_TRUE(tracker.IsEmpty()); | 
| -  EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 
| - | 
| -  tracker.CoalesceSources(source_one); | 
| -  EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Equals( | 
| -          ParamsMeaningJustOneEnabledType())); | 
| - | 
| -  tracker.CoalesceSources(source_two); | 
| - | 
| -  // TODO: This result is wrong, but that's how the code has always been. | 
| -  // The receipt of an invalidation should have no effect on the set of | 
| -  // locally modified types. | 
| -  EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 
| +TEST_F(NudgeTrackerTest, HintCoalescing) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  // Easy case: record one hint. | 
| +  { | 
| +    ModelTypeInvalidationMap invalidation_map = | 
| +        ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
| +                                      std::string("bm_hint_1")); | 
| +    nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
| + | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| +    ASSERT_EQ(1, gu_trigger.notification_hint_size()); | 
| +    EXPECT_EQ("bm_hint_1", gu_trigger.notification_hint(0)); | 
| +    EXPECT_FALSE(gu_trigger.client_dropped_hints()); | 
| +  } | 
| + | 
| +  // Record a second hint for the same type. | 
| +  { | 
| +    ModelTypeInvalidationMap invalidation_map = | 
| +        ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
| +                                      std::string("bm_hint_2")); | 
| +    nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
| + | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| +    ASSERT_EQ(2, gu_trigger.notification_hint_size()); | 
| + | 
| +    // Expect the most hint recent is last in the list. | 
| +    EXPECT_EQ("bm_hint_1", gu_trigger.notification_hint(0)); | 
| +    EXPECT_EQ("bm_hint_2", gu_trigger.notification_hint(1)); | 
| +    EXPECT_FALSE(gu_trigger.client_dropped_hints()); | 
| +  } | 
| + | 
| +  // Record a hint for a different type. | 
| +  { | 
| +    ModelTypeInvalidationMap invalidation_map = | 
| +        ModelTypeSetToInvalidationMap(ModelTypeSet(PASSWORDS), | 
| +                                      std::string("pw_hint_1")); | 
| +    nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
| + | 
| +    // Re-verify the bookmarks to make sure they're unaffected. | 
| +    sync_pb::GetUpdateTriggers bm_gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(BOOKMARKS, &bm_gu_trigger); | 
| +    ASSERT_EQ(2, bm_gu_trigger.notification_hint_size()); | 
| +    EXPECT_EQ("bm_hint_1", bm_gu_trigger.notification_hint(0)); | 
| +    EXPECT_EQ("bm_hint_2", | 
| +              bm_gu_trigger.notification_hint(1)); // most recent last. | 
| +    EXPECT_FALSE(bm_gu_trigger.client_dropped_hints()); | 
| + | 
| +    // Verify the new type, too. | 
| +    sync_pb::GetUpdateTriggers pw_gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(PASSWORDS, &pw_gu_trigger); | 
| +    ASSERT_EQ(1, pw_gu_trigger.notification_hint_size()); | 
| +    EXPECT_EQ("pw_hint_1", pw_gu_trigger.notification_hint(0)); | 
| +    EXPECT_FALSE(pw_gu_trigger.client_dropped_hints()); | 
| +  } | 
| +} | 
| + | 
| +TEST_F(NudgeTrackerTest, DropHintsLocally) { | 
| +  NudgeTracker nudge_tracker; | 
| +  ModelTypeInvalidationMap invalidation_map = | 
| +      ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
| +                                    std::string("hint")); | 
| + | 
| +  for (size_t i = 0; i < GetHintBufferSize(); ++i) { | 
| +    nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
| +  } | 
| +  { | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| +    EXPECT_EQ(GetHintBufferSize(), | 
| +              static_cast<size_t>(gu_trigger.notification_hint_size())); | 
| +    EXPECT_FALSE(gu_trigger.client_dropped_hints()); | 
| +  } | 
| + | 
| +  // Force an overflow. | 
| +  ModelTypeInvalidationMap invalidation_map2 = | 
| +      ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
| +                                    std::string("new_hint")); | 
| +  nudge_tracker.RecordRemoteInvalidation(invalidation_map2); | 
| + | 
| +  { | 
| +    sync_pb::GetUpdateTriggers gu_trigger; | 
| +    nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| +    EXPECT_EQ(GetHintBufferSize(), | 
| +              static_cast<size_t>(gu_trigger.notification_hint_size())); | 
| +    EXPECT_TRUE(gu_trigger.client_dropped_hints()); | 
| + | 
| +    // Verify the newest hint was not dropped and is the last in the list. | 
| +    EXPECT_EQ("new_hint", gu_trigger.notification_hint(GetHintBufferSize()-1)); | 
| + | 
| +    // Verify the oldest hint, too. | 
| +    EXPECT_EQ("hint", gu_trigger.notification_hint(0)); | 
| +  } | 
| +} | 
| + | 
| +// TODO(rlarocque): Add trickles support.  See crbug.com/223437. | 
| +// TEST_F(NudgeTrackerTest, DropHintsAtServer); | 
| + | 
| +// Checks the behaviour of the invalidations-out-of-sync flag. | 
| +TEST_F(NudgeTrackerTest, EnableDisableInvalidations) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  // By default, assume we're out of sync with the invalidation server. | 
| +  EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
| + | 
| +  // Simply enabling invalidations does not bring us back into sync. | 
| +  nudge_tracker.OnInvalidationsEnabled(); | 
| +  EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
| + | 
| +  // We must successfully complete a sync cycle while invalidations are enabled | 
| +  // to be sure that we're in sync. | 
| +  nudge_tracker.RecordSuccessfulSyncCycle(); | 
| +  EXPECT_FALSE(InvalidationsOutOfSync(nudge_tracker)); | 
| + | 
| +  // If the invalidator malfunctions, we go become unsynced again. | 
| +  nudge_tracker.OnInvalidationsDisabled(); | 
| +  EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
| + | 
| +  // A sync cycle while invalidations are disabled won't reset the flag. | 
| +  nudge_tracker.RecordSuccessfulSyncCycle(); | 
| +  EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
| + | 
| +  // Nor will the re-enabling of invalidations be sufficient, even now that | 
| +  // we've had a successful sync cycle. | 
| +  nudge_tracker.RecordSuccessfulSyncCycle(); | 
| +  EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
| +} | 
| + | 
| +// Tests that locally modified types are correctly written out to the | 
| +// GetUpdateTriggers proto. | 
| +TEST_F(NudgeTrackerTest, WriteLocallyModifiedTypesToProto) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  // Should not be locally modified by default. | 
| +  EXPECT_EQ(0, ProtoLocallyModifiedCount(nudge_tracker, PREFERENCES)); | 
| + | 
| +  // Record a local bookmark change.  Verify it was registered correctly. | 
| +  nudge_tracker.RecordLocalChange(ModelTypeSet(PREFERENCES)); | 
| +  EXPECT_EQ(1, ProtoLocallyModifiedCount(nudge_tracker, PREFERENCES)); | 
| + | 
| +  // Record a successful sync cycle.  Verify the count is cleared. | 
| +  nudge_tracker.RecordSuccessfulSyncCycle(); | 
| +  EXPECT_EQ(0, ProtoLocallyModifiedCount(nudge_tracker, PREFERENCES)); | 
| +} | 
| + | 
| +// Tests that refresh requested types are correctly written out to the | 
| +// GetUpdateTriggers proto. | 
| +TEST_F(NudgeTrackerTest, WriteRefreshRequestedTypesToProto) { | 
| +  NudgeTracker nudge_tracker; | 
| + | 
| +  // There should be no refresh requested by default. | 
| +  EXPECT_EQ(0, ProtoRefreshRequestedCount(nudge_tracker, SESSIONS)); | 
| + | 
| +  // Record a local refresh request.  Verify it was registered correctly. | 
| +  nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(SESSIONS)); | 
| +  EXPECT_EQ(1, ProtoRefreshRequestedCount(nudge_tracker, SESSIONS)); | 
| + | 
| +  // Record a successful sync cycle.  Verify the count is cleared. | 
| +  nudge_tracker.RecordSuccessfulSyncCycle(); | 
| +  EXPECT_EQ(0, ProtoRefreshRequestedCount(nudge_tracker, SESSIONS)); | 
| } | 
|  | 
| }  // namespace sessions | 
|  |