Home | History | Annotate | Download | only in sessions
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "sync/internal_api/public/base/model_type_test_util.h"
      6 #include "sync/notifier/invalidation_util.h"
      7 #include "sync/notifier/object_id_invalidation_map.h"
      8 #include "sync/sessions/nudge_tracker.h"
      9 #include "testing/gtest/include/gtest/gtest.h"
     10 
     11 namespace syncer {
     12 
     13 namespace {
     14 
     15 testing::AssertionResult ModelTypeSetEquals(ModelTypeSet a, ModelTypeSet b) {
     16   if (a.Equals(b)) {
     17     return testing::AssertionSuccess();
     18   } else {
     19     return testing::AssertionFailure()
     20         << "Left side " << ModelTypeSetToString(a)
     21         << ", does not match rigth side: " << ModelTypeSetToString(b);
     22   }
     23 }
     24 
     25 }  // namespace
     26 
     27 namespace sessions {
     28 
     29 class NudgeTrackerTest : public ::testing::Test {
     30  public:
     31   NudgeTrackerTest() {
     32     SetInvalidationsInSync();
     33   }
     34 
     35   static size_t GetHintBufferSize() {
     36     // Assumes that no test has adjusted this size.
     37     return NudgeTracker::kDefaultMaxPayloadsPerType;
     38   }
     39 
     40   bool InvalidationsOutOfSync() const {
     41     // We don't currently track invalidations out of sync on a per-type basis.
     42     sync_pb::GetUpdateTriggers gu_trigger;
     43     nudge_tracker_.FillProtoMessage(BOOKMARKS, &gu_trigger);
     44     return gu_trigger.invalidations_out_of_sync();
     45   }
     46 
     47   int ProtoLocallyModifiedCount(ModelType type) const {
     48     sync_pb::GetUpdateTriggers gu_trigger;
     49     nudge_tracker_.FillProtoMessage(type, &gu_trigger);
     50     return gu_trigger.local_modification_nudges();
     51   }
     52 
     53   int ProtoRefreshRequestedCount(ModelType type) const {
     54     sync_pb::GetUpdateTriggers gu_trigger;
     55     nudge_tracker_.FillProtoMessage(type, &gu_trigger);
     56     return gu_trigger.datatype_refresh_nudges();
     57   }
     58 
     59   void SetInvalidationsInSync() {
     60     nudge_tracker_.OnInvalidationsEnabled();
     61     nudge_tracker_.RecordSuccessfulSyncCycle();
     62   }
     63 
     64  protected:
     65   NudgeTracker nudge_tracker_;
     66 };
     67 
     68 // Exercise an empty NudgeTracker.
     69 // Use with valgrind to detect uninitialized members.
     70 TEST_F(NudgeTrackerTest, EmptyNudgeTracker) {
     71   // Now we're at the normal, "idle" state.
     72   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
     73   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
     74   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
     75             nudge_tracker_.updates_source());
     76 
     77   sync_pb::GetUpdateTriggers gu_trigger;
     78   nudge_tracker_.FillProtoMessage(BOOKMARKS, &gu_trigger);
     79 
     80   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN,
     81             nudge_tracker_.updates_source());
     82 }
     83 
     84 // Verify that nudges override each other based on a priority order.
     85 // LOCAL < DATATYPE_REFRESH < NOTIFICATION
     86 TEST_F(NudgeTrackerTest, SourcePriorities) {
     87   // Track a local nudge.
     88   nudge_tracker_.RecordLocalChange(ModelTypeSet(BOOKMARKS));
     89   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::LOCAL,
     90             nudge_tracker_.updates_source());
     91 
     92   // A refresh request will override it.
     93   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(TYPED_URLS));
     94   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH,
     95             nudge_tracker_.updates_source());
     96 
     97   // Another local nudge will not be enough to change it.
     98   nudge_tracker_.RecordLocalChange(ModelTypeSet(BOOKMARKS));
     99   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH,
    100             nudge_tracker_.updates_source());
    101 
    102   // An invalidation will override the refresh request source.
    103   ObjectIdInvalidationMap invalidation_map =
    104       BuildInvalidationMap(PREFERENCES, 1, "hint");
    105   nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    106   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION,
    107             nudge_tracker_.updates_source());
    108 
    109   // Neither local nudges nor refresh requests will override it.
    110   nudge_tracker_.RecordLocalChange(ModelTypeSet(BOOKMARKS));
    111   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION,
    112             nudge_tracker_.updates_source());
    113   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(TYPED_URLS));
    114   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION,
    115             nudge_tracker_.updates_source());
    116 }
    117 
    118 TEST_F(NudgeTrackerTest, HintCoalescing) {
    119   // Easy case: record one hint.
    120   {
    121     ObjectIdInvalidationMap invalidation_map =
    122         BuildInvalidationMap(BOOKMARKS, 1, "bm_hint_1");
    123     nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    124 
    125     sync_pb::GetUpdateTriggers gu_trigger;
    126     nudge_tracker_.FillProtoMessage(BOOKMARKS, &gu_trigger);
    127     ASSERT_EQ(1, gu_trigger.notification_hint_size());
    128     EXPECT_EQ("bm_hint_1", gu_trigger.notification_hint(0));
    129     EXPECT_FALSE(gu_trigger.client_dropped_hints());
    130   }
    131 
    132   // Record a second hint for the same type.
    133   {
    134     ObjectIdInvalidationMap invalidation_map =
    135         BuildInvalidationMap(BOOKMARKS, 2, "bm_hint_2");
    136     nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    137 
    138     sync_pb::GetUpdateTriggers gu_trigger;
    139     nudge_tracker_.FillProtoMessage(BOOKMARKS, &gu_trigger);
    140     ASSERT_EQ(2, gu_trigger.notification_hint_size());
    141 
    142     // Expect the most hint recent is last in the list.
    143     EXPECT_EQ("bm_hint_1", gu_trigger.notification_hint(0));
    144     EXPECT_EQ("bm_hint_2", gu_trigger.notification_hint(1));
    145     EXPECT_FALSE(gu_trigger.client_dropped_hints());
    146   }
    147 
    148   // Record a hint for a different type.
    149   {
    150     ObjectIdInvalidationMap invalidation_map =
    151         BuildInvalidationMap(PASSWORDS, 1, "pw_hint_1");
    152     nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    153 
    154     // Re-verify the bookmarks to make sure they're unaffected.
    155     sync_pb::GetUpdateTriggers bm_gu_trigger;
    156     nudge_tracker_.FillProtoMessage(BOOKMARKS, &bm_gu_trigger);
    157     ASSERT_EQ(2, bm_gu_trigger.notification_hint_size());
    158     EXPECT_EQ("bm_hint_1", bm_gu_trigger.notification_hint(0));
    159     EXPECT_EQ("bm_hint_2",
    160               bm_gu_trigger.notification_hint(1)); // most recent last.
    161     EXPECT_FALSE(bm_gu_trigger.client_dropped_hints());
    162 
    163     // Verify the new type, too.
    164     sync_pb::GetUpdateTriggers pw_gu_trigger;
    165     nudge_tracker_.FillProtoMessage(PASSWORDS, &pw_gu_trigger);
    166     ASSERT_EQ(1, pw_gu_trigger.notification_hint_size());
    167     EXPECT_EQ("pw_hint_1", pw_gu_trigger.notification_hint(0));
    168     EXPECT_FALSE(pw_gu_trigger.client_dropped_hints());
    169   }
    170 }
    171 
    172 TEST_F(NudgeTrackerTest, DropHintsLocally) {
    173   ObjectIdInvalidationMap invalidation_map =
    174       BuildInvalidationMap(BOOKMARKS, 1, "hint");
    175 
    176   for (size_t i = 0; i < GetHintBufferSize(); ++i) {
    177     nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    178   }
    179   {
    180     sync_pb::GetUpdateTriggers gu_trigger;
    181     nudge_tracker_.FillProtoMessage(BOOKMARKS, &gu_trigger);
    182     EXPECT_EQ(GetHintBufferSize(),
    183               static_cast<size_t>(gu_trigger.notification_hint_size()));
    184     EXPECT_FALSE(gu_trigger.client_dropped_hints());
    185   }
    186 
    187   // Force an overflow.
    188   ObjectIdInvalidationMap invalidation_map2 =
    189       BuildInvalidationMap(BOOKMARKS, 1000, "new_hint");
    190   nudge_tracker_.RecordRemoteInvalidation(invalidation_map2);
    191 
    192   {
    193     sync_pb::GetUpdateTriggers gu_trigger;
    194     nudge_tracker_.FillProtoMessage(BOOKMARKS, &gu_trigger);
    195     EXPECT_EQ(GetHintBufferSize(),
    196               static_cast<size_t>(gu_trigger.notification_hint_size()));
    197     EXPECT_TRUE(gu_trigger.client_dropped_hints());
    198 
    199     // Verify the newest hint was not dropped and is the last in the list.
    200     EXPECT_EQ("new_hint", gu_trigger.notification_hint(GetHintBufferSize()-1));
    201 
    202     // Verify the oldest hint, too.
    203     EXPECT_EQ("hint", gu_trigger.notification_hint(0));
    204   }
    205 }
    206 
    207 // TODO(rlarocque): Add trickles support.  See crbug.com/223437.
    208 // TEST_F(NudgeTrackerTest, DropHintsAtServer);
    209 
    210 // Checks the behaviour of the invalidations-out-of-sync flag.
    211 TEST_F(NudgeTrackerTest, EnableDisableInvalidations) {
    212   // Start with invalidations offline.
    213   nudge_tracker_.OnInvalidationsDisabled();
    214   EXPECT_TRUE(InvalidationsOutOfSync());
    215   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    216 
    217   // Simply enabling invalidations does not bring us back into sync.
    218   nudge_tracker_.OnInvalidationsEnabled();
    219   EXPECT_TRUE(InvalidationsOutOfSync());
    220   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    221 
    222   // We must successfully complete a sync cycle while invalidations are enabled
    223   // to be sure that we're in sync.
    224   nudge_tracker_.RecordSuccessfulSyncCycle();
    225   EXPECT_FALSE(InvalidationsOutOfSync());
    226   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    227 
    228   // If the invalidator malfunctions, we go become unsynced again.
    229   nudge_tracker_.OnInvalidationsDisabled();
    230   EXPECT_TRUE(InvalidationsOutOfSync());
    231   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    232 
    233   // A sync cycle while invalidations are disabled won't reset the flag.
    234   nudge_tracker_.RecordSuccessfulSyncCycle();
    235   EXPECT_TRUE(InvalidationsOutOfSync());
    236   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    237 
    238   // Nor will the re-enabling of invalidations be sufficient, even now that
    239   // we've had a successful sync cycle.
    240   nudge_tracker_.RecordSuccessfulSyncCycle();
    241   EXPECT_TRUE(InvalidationsOutOfSync());
    242   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    243 }
    244 
    245 // Tests that locally modified types are correctly written out to the
    246 // GetUpdateTriggers proto.
    247 TEST_F(NudgeTrackerTest, WriteLocallyModifiedTypesToProto) {
    248   // Should not be locally modified by default.
    249   EXPECT_EQ(0, ProtoLocallyModifiedCount(PREFERENCES));
    250 
    251   // Record a local bookmark change.  Verify it was registered correctly.
    252   nudge_tracker_.RecordLocalChange(ModelTypeSet(PREFERENCES));
    253   EXPECT_EQ(1, ProtoLocallyModifiedCount(PREFERENCES));
    254 
    255   // Record a successful sync cycle.  Verify the count is cleared.
    256   nudge_tracker_.RecordSuccessfulSyncCycle();
    257   EXPECT_EQ(0, ProtoLocallyModifiedCount(PREFERENCES));
    258 }
    259 
    260 // Tests that refresh requested types are correctly written out to the
    261 // GetUpdateTriggers proto.
    262 TEST_F(NudgeTrackerTest, WriteRefreshRequestedTypesToProto) {
    263   // There should be no refresh requested by default.
    264   EXPECT_EQ(0, ProtoRefreshRequestedCount(SESSIONS));
    265 
    266   // Record a local refresh request.  Verify it was registered correctly.
    267   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(SESSIONS));
    268   EXPECT_EQ(1, ProtoRefreshRequestedCount(SESSIONS));
    269 
    270   // Record a successful sync cycle.  Verify the count is cleared.
    271   nudge_tracker_.RecordSuccessfulSyncCycle();
    272   EXPECT_EQ(0, ProtoRefreshRequestedCount(SESSIONS));
    273 }
    274 
    275 // Basic tests for the IsSyncRequired() flag.
    276 TEST_F(NudgeTrackerTest, IsSyncRequired) {
    277   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    278 
    279   // Local changes.
    280   nudge_tracker_.RecordLocalChange(ModelTypeSet(SESSIONS));
    281   EXPECT_TRUE(nudge_tracker_.IsSyncRequired());
    282   nudge_tracker_.RecordSuccessfulSyncCycle();
    283   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    284 
    285   // Refresh requests.
    286   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(SESSIONS));
    287   EXPECT_TRUE(nudge_tracker_.IsSyncRequired());
    288   nudge_tracker_.RecordSuccessfulSyncCycle();
    289   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    290 
    291   // Invalidations.
    292   ObjectIdInvalidationMap invalidation_map =
    293       BuildInvalidationMap(PREFERENCES, 1, "hint");
    294   nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    295   EXPECT_TRUE(nudge_tracker_.IsSyncRequired());
    296   nudge_tracker_.RecordSuccessfulSyncCycle();
    297   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    298 }
    299 
    300 // Basic tests for the IsGetUpdatesRequired() flag.
    301 TEST_F(NudgeTrackerTest, IsGetUpdatesRequired) {
    302   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    303 
    304   // Local changes.
    305   nudge_tracker_.RecordLocalChange(ModelTypeSet(SESSIONS));
    306   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    307   nudge_tracker_.RecordSuccessfulSyncCycle();
    308   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    309 
    310   // Refresh requests.
    311   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(SESSIONS));
    312   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    313   nudge_tracker_.RecordSuccessfulSyncCycle();
    314   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    315 
    316   // Invalidations.
    317   ObjectIdInvalidationMap invalidation_map =
    318       BuildInvalidationMap(PREFERENCES, 1, "hint");
    319   nudge_tracker_.RecordRemoteInvalidation(invalidation_map);
    320   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    321   nudge_tracker_.RecordSuccessfulSyncCycle();
    322   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    323 }
    324 
    325 // Test IsSyncRequired() responds correctly to data type throttling.
    326 TEST_F(NudgeTrackerTest, IsSyncRequired_Throttling) {
    327   const base::TimeTicks t0 = base::TimeTicks::FromInternalValue(1234);
    328   const base::TimeDelta throttle_length = base::TimeDelta::FromMinutes(10);
    329   const base::TimeTicks t1 = t0 + throttle_length;
    330 
    331   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    332 
    333   // A local change to sessions enables the flag.
    334   nudge_tracker_.RecordLocalChange(ModelTypeSet(SESSIONS));
    335   EXPECT_TRUE(nudge_tracker_.IsSyncRequired());
    336 
    337   // But the throttling of sessions unsets it.
    338   nudge_tracker_.SetTypesThrottledUntil(ModelTypeSet(SESSIONS),
    339                                        throttle_length,
    340                                        t0);
    341   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    342 
    343   // A refresh request for bookmarks means we have reason to sync again.
    344   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(BOOKMARKS));
    345   EXPECT_TRUE(nudge_tracker_.IsSyncRequired());
    346 
    347   // A successful sync cycle means we took care of bookmarks.
    348   nudge_tracker_.RecordSuccessfulSyncCycle();
    349   EXPECT_FALSE(nudge_tracker_.IsSyncRequired());
    350 
    351   // But we still haven't dealt with sessions.  We'll need to remember
    352   // that sessions are out of sync and re-enable the flag when their
    353   // throttling interval expires.
    354   nudge_tracker_.UpdateTypeThrottlingState(t1);
    355   EXPECT_FALSE(nudge_tracker_.IsTypeThrottled(SESSIONS));
    356   EXPECT_TRUE(nudge_tracker_.IsSyncRequired());
    357 }
    358 
    359 // Test IsGetUpdatesRequired() responds correctly to data type throttling.
    360 TEST_F(NudgeTrackerTest, IsGetUpdatesRequired_Throttling) {
    361   const base::TimeTicks t0 = base::TimeTicks::FromInternalValue(1234);
    362   const base::TimeDelta throttle_length = base::TimeDelta::FromMinutes(10);
    363   const base::TimeTicks t1 = t0 + throttle_length;
    364 
    365   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    366 
    367   // A refresh request to sessions enables the flag.
    368   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(SESSIONS));
    369   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    370 
    371   // But the throttling of sessions unsets it.
    372   nudge_tracker_.SetTypesThrottledUntil(ModelTypeSet(SESSIONS),
    373                                        throttle_length,
    374                                        t0);
    375   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    376 
    377   // A refresh request for bookmarks means we have reason to sync again.
    378   nudge_tracker_.RecordLocalRefreshRequest(ModelTypeSet(BOOKMARKS));
    379   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    380 
    381   // A successful sync cycle means we took care of bookmarks.
    382   nudge_tracker_.RecordSuccessfulSyncCycle();
    383   EXPECT_FALSE(nudge_tracker_.IsGetUpdatesRequired());
    384 
    385   // But we still haven't dealt with sessions.  We'll need to remember
    386   // that sessions are out of sync and re-enable the flag when their
    387   // throttling interval expires.
    388   nudge_tracker_.UpdateTypeThrottlingState(t1);
    389   EXPECT_FALSE(nudge_tracker_.IsTypeThrottled(SESSIONS));
    390   EXPECT_TRUE(nudge_tracker_.IsGetUpdatesRequired());
    391 }
    392 
    393 // Tests throttling-related getter functions when no types are throttled.
    394 TEST_F(NudgeTrackerTest, NoTypesThrottled) {
    395   EXPECT_FALSE(nudge_tracker_.IsAnyTypeThrottled());
    396   EXPECT_FALSE(nudge_tracker_.IsTypeThrottled(SESSIONS));
    397   EXPECT_TRUE(nudge_tracker_.GetThrottledTypes().Empty());
    398 }
    399 
    400 // Tests throttling-related getter functions when some types are throttled.
    401 TEST_F(NudgeTrackerTest, ThrottleAndUnthrottle) {
    402   const base::TimeTicks t0 = base::TimeTicks::FromInternalValue(1234);
    403   const base::TimeDelta throttle_length = base::TimeDelta::FromMinutes(10);
    404   const base::TimeTicks t1 = t0 + throttle_length;
    405 
    406   nudge_tracker_.SetTypesThrottledUntil(ModelTypeSet(SESSIONS, PREFERENCES),
    407                                        throttle_length,
    408                                        t0);
    409 
    410   EXPECT_TRUE(nudge_tracker_.IsAnyTypeThrottled());
    411   EXPECT_TRUE(nudge_tracker_.IsTypeThrottled(SESSIONS));
    412   EXPECT_TRUE(nudge_tracker_.IsTypeThrottled(PREFERENCES));
    413   EXPECT_FALSE(nudge_tracker_.GetThrottledTypes().Empty());
    414   EXPECT_EQ(throttle_length, nudge_tracker_.GetTimeUntilNextUnthrottle(t0));
    415 
    416   nudge_tracker_.UpdateTypeThrottlingState(t1);
    417 
    418   EXPECT_FALSE(nudge_tracker_.IsAnyTypeThrottled());
    419   EXPECT_FALSE(nudge_tracker_.IsTypeThrottled(SESSIONS));
    420   EXPECT_TRUE(nudge_tracker_.GetThrottledTypes().Empty());
    421 }
    422 
    423 TEST_F(NudgeTrackerTest, OverlappingThrottleIntervals) {
    424   const base::TimeTicks t0 = base::TimeTicks::FromInternalValue(1234);
    425   const base::TimeDelta throttle1_length = base::TimeDelta::FromMinutes(10);
    426   const base::TimeDelta throttle2_length = base::TimeDelta::FromMinutes(20);
    427   const base::TimeTicks t1 = t0 + throttle1_length;
    428   const base::TimeTicks t2 = t0 + throttle2_length;
    429 
    430   // Setup the longer of two intervals.
    431   nudge_tracker_.SetTypesThrottledUntil(ModelTypeSet(SESSIONS, PREFERENCES),
    432                                        throttle2_length,
    433                                        t0);
    434   EXPECT_TRUE(ModelTypeSetEquals(
    435           ModelTypeSet(SESSIONS, PREFERENCES),
    436           nudge_tracker_.GetThrottledTypes()));
    437   EXPECT_EQ(throttle2_length,
    438             nudge_tracker_.GetTimeUntilNextUnthrottle(t0));
    439 
    440   // Setup the shorter interval.
    441   nudge_tracker_.SetTypesThrottledUntil(ModelTypeSet(SESSIONS, BOOKMARKS),
    442                                        throttle1_length,
    443                                        t0);
    444   EXPECT_TRUE(ModelTypeSetEquals(
    445           ModelTypeSet(SESSIONS, PREFERENCES, BOOKMARKS),
    446           nudge_tracker_.GetThrottledTypes()));
    447   EXPECT_EQ(throttle1_length,
    448             nudge_tracker_.GetTimeUntilNextUnthrottle(t0));
    449 
    450   // Expire the first interval.
    451   nudge_tracker_.UpdateTypeThrottlingState(t1);
    452 
    453   // SESSIONS appeared in both intervals.  We expect it will be throttled for
    454   // the longer of the two, so it's still throttled at time t1.
    455   EXPECT_TRUE(ModelTypeSetEquals(
    456           ModelTypeSet(SESSIONS, PREFERENCES),
    457           nudge_tracker_.GetThrottledTypes()));
    458   EXPECT_EQ(throttle2_length - throttle1_length,
    459             nudge_tracker_.GetTimeUntilNextUnthrottle(t1));
    460 
    461   // Expire the second interval.
    462   nudge_tracker_.UpdateTypeThrottlingState(t2);
    463   EXPECT_TRUE(nudge_tracker_.GetThrottledTypes().Empty());
    464 }
    465 
    466 }  // namespace sessions
    467 }  // namespace syncer
    468