| OLD | NEW | 
|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include "sync/sessions/nudge_tracker.h" | 5 #include "sync/sessions/nudge_tracker.h" | 
| 6 | 6 | 
| 7 #include "sync/internal_api/public/base/model_type_invalidation_map.h" | 7 #include "sync/internal_api/public/base/model_type_invalidation_map.h" | 
|  | 8 #include "sync/internal_api/public/sessions/sync_source_info.h" | 
| 8 #include "testing/gtest/include/gtest/gtest.h" | 9 #include "testing/gtest/include/gtest/gtest.h" | 
| 9 | 10 | 
| 10 namespace syncer { | 11 namespace syncer { | 
| 11 | 12 | 
| 12 namespace { | 13 namespace { | 
| 13 | 14 | 
| 14 ModelTypeSet ParamsMeaningAllEnabledTypes() { | 15 testing::AssertionResult ModelTypeSetEquals(ModelTypeSet a, ModelTypeSet b) { | 
| 15   ModelTypeSet request_params(BOOKMARKS, AUTOFILL); | 16   if (a.Equals(b)) { | 
| 16   return request_params; | 17     return testing::AssertionSuccess(); | 
| 17 } | 18   } else { | 
| 18 | 19     return testing::AssertionFailure() | 
| 19 ModelTypeSet ParamsMeaningJustOneEnabledType() { | 20         << "Left side " << ModelTypeSetToString(a) | 
| 20   return ModelTypeSet(AUTOFILL); | 21         << ", does not match rigth side: " << ModelTypeSetToString(b); | 
|  | 22   } | 
| 21 } | 23 } | 
| 22 | 24 | 
| 23 }  // namespace | 25 }  // namespace | 
| 24 | 26 | 
| 25 namespace sessions { | 27 namespace sessions { | 
| 26 | 28 | 
| 27 TEST(NudgeTrackerTest, CoalesceSources) { | 29 class NudgeTrackerTest : public ::testing::Test { | 
| 28   ModelTypeInvalidationMap one_type = | 30  public: | 
| 29       ModelTypeSetToInvalidationMap( | 31   static size_t GetHintBufferSize() { | 
| 30           ParamsMeaningJustOneEnabledType(), | 32     return NudgeTracker::kMaxPayloadsPerType; | 
| 31           std::string()); | 33   } | 
| 32   ModelTypeInvalidationMap all_types = | 34 | 
| 33       ModelTypeSetToInvalidationMap( | 35   bool InvalidationsOutOfSync(const NudgeTracker& nudge_tracker) { | 
| 34           ParamsMeaningAllEnabledTypes(), | 36     // We don't currently track invalidations out of sync on a per-type basis. | 
| 35           std::string()); | 37     sync_pb::GetUpdateTriggers gu_trigger; | 
| 36   sessions::SyncSourceInfo source_one( | 38     nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| 37       sync_pb::GetUpdatesCallerInfo::NOTIFICATION, one_type); | 39     return gu_trigger.invalidations_out_of_sync(); | 
| 38   sessions::SyncSourceInfo source_two( | 40   } | 
| 39       sync_pb::GetUpdatesCallerInfo::LOCAL, all_types); | 41 | 
| 40 | 42   int ProtoLocallyModifiedCount(const NudgeTracker& nudge_tracker, | 
| 41   NudgeTracker tracker; | 43                                 ModelType type) { | 
| 42   EXPECT_TRUE(tracker.IsEmpty()); | 44     sync_pb::GetUpdateTriggers gu_trigger; | 
| 43 | 45     nudge_tracker.FillProtoMessage(type, &gu_trigger); | 
| 44   tracker.CoalesceSources(source_one); | 46     return gu_trigger.local_modification_nudges(); | 
| 45   EXPECT_EQ(source_one.updates_source, tracker.source_info().updates_source); | 47   } | 
| 46 | 48 | 
| 47   tracker.CoalesceSources(source_two); | 49   int ProtoRefreshRequestedCount(const NudgeTracker& nudge_tracker, | 
| 48   EXPECT_EQ(source_two.updates_source, tracker.source_info().updates_source); | 50                                  ModelType type) { | 
| 49 } | 51     sync_pb::GetUpdateTriggers gu_trigger; | 
| 50 | 52     nudge_tracker.FillProtoMessage(type, &gu_trigger); | 
| 51 TEST(NudgeTrackerTest, LocallyModifiedTypes_WithInvalidationFirst) { | 53     return gu_trigger.datatype_refresh_nudges(); | 
| 52   ModelTypeInvalidationMap one_type = | 54   } | 
| 53       ModelTypeSetToInvalidationMap( | 55 }; | 
| 54           ParamsMeaningJustOneEnabledType(), | 56 | 
| 55           std::string()); | 57 // Exercise an empty NudgeTracker. | 
| 56   ModelTypeInvalidationMap all_types = | 58 // Use with valgrind to detect uninitialized members. | 
| 57       ModelTypeSetToInvalidationMap( | 59 TEST_F(NudgeTrackerTest, EmptyNudgeTracker) { | 
| 58           ParamsMeaningAllEnabledTypes(), | 60   NudgeTracker nudge_tracker; | 
| 59           std::string()); | 61 | 
| 60   sessions::SyncSourceInfo source_one( | 62   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN, | 
| 61       sync_pb::GetUpdatesCallerInfo::NOTIFICATION, all_types); | 63             nudge_tracker.updates_source()); | 
| 62   sessions::SyncSourceInfo source_two( | 64   EXPECT_TRUE(nudge_tracker.GetLocallyModifiedTypes().Empty()); | 
| 63       sync_pb::GetUpdatesCallerInfo::LOCAL, one_type); | 65 | 
| 64 | 66   sync_pb::GetUpdateTriggers gu_trigger; | 
| 65   NudgeTracker tracker; | 67   nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
| 66   EXPECT_TRUE(tracker.IsEmpty()); | 68 | 
| 67   EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 69   SyncSourceInfo source_info = nudge_tracker.GetSourceInfo(); | 
| 68 | 70   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::UNKNOWN, | 
| 69   tracker.CoalesceSources(source_one); | 71             source_info.updates_source); | 
| 70   EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 72 } | 
| 71 | 73 | 
| 72   tracker.CoalesceSources(source_two); | 74 // Verify that nudges override each other based on a priority order. | 
| 73   // TODO: This result is wrong, but that's how the code has always been.  A | 75 // LOCAL < DATATYPE_REFRESH < NOTIFICATION | 
| 74   // local invalidation for a single type should mean that we have only one | 76 TEST_F(NudgeTrackerTest, SourcePriorities) { | 
| 75   // locally modified source.  It should not "inherit" the list of data types | 77   NudgeTracker nudge_tracker; | 
| 76   // from the previous source. | 78 | 
| 77   EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Equals( | 79   // Track a local nudge. | 
| 78           ParamsMeaningAllEnabledTypes())); | 80   nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| 79 } | 81   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::LOCAL, | 
| 80 | 82             nudge_tracker.updates_source()); | 
| 81 TEST(NudgeTrackerTest, LocallyModifiedTypes_WithInvalidationSecond) { | 83 | 
| 82   ModelTypeInvalidationMap one_type = | 84   // A refresh request will override it. | 
| 83       ModelTypeSetToInvalidationMap( | 85   nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(TYPED_URLS)); | 
| 84           ParamsMeaningJustOneEnabledType(), | 86   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH, | 
| 85           std::string()); | 87             nudge_tracker.updates_source()); | 
| 86   ModelTypeInvalidationMap all_types = | 88 | 
| 87       ModelTypeSetToInvalidationMap( | 89   // Another local nudge will not be enough to change it. | 
| 88           ParamsMeaningAllEnabledTypes(), | 90   nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| 89           std::string()); | 91   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH, | 
| 90   sessions::SyncSourceInfo source_one( | 92             nudge_tracker.updates_source()); | 
| 91       sync_pb::GetUpdatesCallerInfo::LOCAL, one_type); | 93 | 
| 92   sessions::SyncSourceInfo source_two( | 94   // An invalidation will override the refresh request source. | 
| 93       sync_pb::GetUpdatesCallerInfo::NOTIFICATION, all_types); | 95   ModelTypeInvalidationMap invalidation_map = | 
| 94 | 96       ModelTypeSetToInvalidationMap(ModelTypeSet(PREFERENCES), | 
| 95   NudgeTracker tracker; | 97                                     std::string("hint")); | 
| 96   EXPECT_TRUE(tracker.IsEmpty()); | 98   nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
| 97   EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 99   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, | 
| 98 | 100             nudge_tracker.updates_source()); | 
| 99   tracker.CoalesceSources(source_one); | 101 | 
| 100   EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Equals( | 102   // Neither local nudges nor refresh requests will override it. | 
| 101           ParamsMeaningJustOneEnabledType())); | 103   nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
| 102 | 104   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, | 
| 103   tracker.CoalesceSources(source_two); | 105             nudge_tracker.updates_source()); | 
| 104 | 106   nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(TYPED_URLS)); | 
| 105   // TODO: This result is wrong, but that's how the code has always been. | 107   EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, | 
| 106   // The receipt of an invalidation should have no effect on the set of | 108             nudge_tracker.updates_source()); | 
| 107   // locally modified types. | 109 } | 
| 108   EXPECT_TRUE(tracker.GetLocallyModifiedTypes().Empty()); | 110 | 
|  | 111 // Verify locally modified type coalescing and independence from other nudges. | 
|  | 112 TEST_F(NudgeTrackerTest, LocallyModifiedTypes) { | 
|  | 113   NudgeTracker nudge_tracker; | 
|  | 114 | 
|  | 115   // Start with a notification.  Verify it has no effect. | 
|  | 116   ModelTypeInvalidationMap invalidation_map1 = | 
|  | 117       ModelTypeSetToInvalidationMap(ModelTypeSet(PREFERENCES), | 
|  | 118                                     std::string("hint")); | 
|  | 119   nudge_tracker.RecordRemoteInvalidation(invalidation_map1); | 
|  | 120   EXPECT_TRUE(nudge_tracker.GetLocallyModifiedTypes().Empty()); | 
|  | 121 | 
|  | 122   // Record a local bookmark change.  Verify it was registered correctly. | 
|  | 123   nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); | 
|  | 124   EXPECT_TRUE(ModelTypeSetEquals( | 
|  | 125           ModelTypeSet(BOOKMARKS), | 
|  | 126           nudge_tracker.GetLocallyModifiedTypes())); | 
|  | 127 | 
|  | 128   // Record a notification and a refresh request.  Verify they have no effect. | 
|  | 129   ModelTypeInvalidationMap invalidation_map2 = | 
|  | 130       ModelTypeSetToInvalidationMap(ModelTypeSet(PASSWORDS), | 
|  | 131                                     std::string("hint")); | 
|  | 132   nudge_tracker.RecordRemoteInvalidation(invalidation_map2); | 
|  | 133   EXPECT_TRUE(ModelTypeSetEquals( | 
|  | 134           ModelTypeSet(BOOKMARKS), | 
|  | 135           nudge_tracker.GetLocallyModifiedTypes())); | 
|  | 136 | 
|  | 137   nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(AUTOFILL)); | 
|  | 138   EXPECT_TRUE(ModelTypeSetEquals( | 
|  | 139           ModelTypeSet(BOOKMARKS), | 
|  | 140           nudge_tracker.GetLocallyModifiedTypes())); | 
|  | 141 | 
|  | 142   // Record another local nudge.  Verify it was coalesced correctly. | 
|  | 143   nudge_tracker.RecordLocalChange(ModelTypeSet(THEMES)); | 
|  | 144   EXPECT_TRUE(ModelTypeSetEquals( | 
|  | 145           ModelTypeSet(THEMES, BOOKMARKS), | 
|  | 146           nudge_tracker.GetLocallyModifiedTypes())); | 
|  | 147 } | 
|  | 148 | 
|  | 149 TEST_F(NudgeTrackerTest, HintCoalescing) { | 
|  | 150   NudgeTracker nudge_tracker; | 
|  | 151 | 
|  | 152   // Easy case: record one hint. | 
|  | 153   { | 
|  | 154     ModelTypeInvalidationMap invalidation_map = | 
|  | 155         ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
|  | 156                                       std::string("bm_hint_1")); | 
|  | 157     nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
|  | 158 | 
|  | 159     sync_pb::GetUpdateTriggers gu_trigger; | 
|  | 160     nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
|  | 161     ASSERT_EQ(1, gu_trigger.notification_hint_size()); | 
|  | 162     EXPECT_EQ("bm_hint_1", gu_trigger.notification_hint(0)); | 
|  | 163     EXPECT_FALSE(gu_trigger.client_dropped_hints()); | 
|  | 164   } | 
|  | 165 | 
|  | 166   // Record a second hint for the same type. | 
|  | 167   { | 
|  | 168     ModelTypeInvalidationMap invalidation_map = | 
|  | 169         ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
|  | 170                                       std::string("bm_hint_2")); | 
|  | 171     nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
|  | 172 | 
|  | 173     sync_pb::GetUpdateTriggers gu_trigger; | 
|  | 174     nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
|  | 175     ASSERT_EQ(2, gu_trigger.notification_hint_size()); | 
|  | 176 | 
|  | 177     // Expect the most hint recent is last in the list. | 
|  | 178     EXPECT_EQ("bm_hint_1", gu_trigger.notification_hint(0)); | 
|  | 179     EXPECT_EQ("bm_hint_2", gu_trigger.notification_hint(1)); | 
|  | 180     EXPECT_FALSE(gu_trigger.client_dropped_hints()); | 
|  | 181   } | 
|  | 182 | 
|  | 183   // Record a hint for a different type. | 
|  | 184   { | 
|  | 185     ModelTypeInvalidationMap invalidation_map = | 
|  | 186         ModelTypeSetToInvalidationMap(ModelTypeSet(PASSWORDS), | 
|  | 187                                       std::string("pw_hint_1")); | 
|  | 188     nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
|  | 189 | 
|  | 190     // Re-verify the bookmarks to make sure they're unaffected. | 
|  | 191     sync_pb::GetUpdateTriggers bm_gu_trigger; | 
|  | 192     nudge_tracker.FillProtoMessage(BOOKMARKS, &bm_gu_trigger); | 
|  | 193     ASSERT_EQ(2, bm_gu_trigger.notification_hint_size()); | 
|  | 194     EXPECT_EQ("bm_hint_1", bm_gu_trigger.notification_hint(0)); | 
|  | 195     EXPECT_EQ("bm_hint_2", | 
|  | 196               bm_gu_trigger.notification_hint(1)); // most recent last. | 
|  | 197     EXPECT_FALSE(bm_gu_trigger.client_dropped_hints()); | 
|  | 198 | 
|  | 199     // Verify the new type, too. | 
|  | 200     sync_pb::GetUpdateTriggers pw_gu_trigger; | 
|  | 201     nudge_tracker.FillProtoMessage(PASSWORDS, &pw_gu_trigger); | 
|  | 202     ASSERT_EQ(1, pw_gu_trigger.notification_hint_size()); | 
|  | 203     EXPECT_EQ("pw_hint_1", pw_gu_trigger.notification_hint(0)); | 
|  | 204     EXPECT_FALSE(pw_gu_trigger.client_dropped_hints()); | 
|  | 205   } | 
|  | 206 } | 
|  | 207 | 
|  | 208 TEST_F(NudgeTrackerTest, DropHintsLocally) { | 
|  | 209   NudgeTracker nudge_tracker; | 
|  | 210   ModelTypeInvalidationMap invalidation_map = | 
|  | 211       ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
|  | 212                                     std::string("hint")); | 
|  | 213 | 
|  | 214   for (size_t i = 0; i < GetHintBufferSize(); ++i) { | 
|  | 215     nudge_tracker.RecordRemoteInvalidation(invalidation_map); | 
|  | 216   } | 
|  | 217   { | 
|  | 218     sync_pb::GetUpdateTriggers gu_trigger; | 
|  | 219     nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
|  | 220     EXPECT_EQ(GetHintBufferSize(), | 
|  | 221               static_cast<size_t>(gu_trigger.notification_hint_size())); | 
|  | 222     EXPECT_FALSE(gu_trigger.client_dropped_hints()); | 
|  | 223   } | 
|  | 224 | 
|  | 225   // Force an overflow. | 
|  | 226   ModelTypeInvalidationMap invalidation_map2 = | 
|  | 227       ModelTypeSetToInvalidationMap(ModelTypeSet(BOOKMARKS), | 
|  | 228                                     std::string("new_hint")); | 
|  | 229   nudge_tracker.RecordRemoteInvalidation(invalidation_map2); | 
|  | 230 | 
|  | 231   { | 
|  | 232     sync_pb::GetUpdateTriggers gu_trigger; | 
|  | 233     nudge_tracker.FillProtoMessage(BOOKMARKS, &gu_trigger); | 
|  | 234     EXPECT_EQ(GetHintBufferSize(), | 
|  | 235               static_cast<size_t>(gu_trigger.notification_hint_size())); | 
|  | 236     EXPECT_TRUE(gu_trigger.client_dropped_hints()); | 
|  | 237 | 
|  | 238     // Verify the newest hint was not dropped and is the last in the list. | 
|  | 239     EXPECT_EQ("new_hint", gu_trigger.notification_hint(GetHintBufferSize()-1)); | 
|  | 240 | 
|  | 241     // Verify the oldest hint, too. | 
|  | 242     EXPECT_EQ("hint", gu_trigger.notification_hint(0)); | 
|  | 243   } | 
|  | 244 } | 
|  | 245 | 
|  | 246 // TODO(rlarocque): Add trickles support.  See crbug.com/223437. | 
|  | 247 // TEST_F(NudgeTrackerTest, DropHintsAtServer); | 
|  | 248 | 
|  | 249 // Checks the behaviour of the invalidations-out-of-sync flag. | 
|  | 250 TEST_F(NudgeTrackerTest, EnableDisableInvalidations) { | 
|  | 251   NudgeTracker nudge_tracker; | 
|  | 252 | 
|  | 253   // By default, assume we're out of sync with the invalidation server. | 
|  | 254   EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
|  | 255 | 
|  | 256   // Simply enabling invalidations does not bring us back into sync. | 
|  | 257   nudge_tracker.OnInvalidationsEnabled(); | 
|  | 258   EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
|  | 259 | 
|  | 260   // We must successfully complete a sync cycle while invalidations are enabled | 
|  | 261   // to be sure that we're in sync. | 
|  | 262   nudge_tracker.RecordSuccessfulSyncCycle(); | 
|  | 263   EXPECT_FALSE(InvalidationsOutOfSync(nudge_tracker)); | 
|  | 264 | 
|  | 265   // If the invalidator malfunctions, we go become unsynced again. | 
|  | 266   nudge_tracker.OnInvalidationsDisabled(); | 
|  | 267   EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
|  | 268 | 
|  | 269   // A sync cycle while invalidations are disabled won't reset the flag. | 
|  | 270   nudge_tracker.RecordSuccessfulSyncCycle(); | 
|  | 271   EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
|  | 272 | 
|  | 273   // Nor will the re-enabling of invalidations be sufficient, even now that | 
|  | 274   // we've had a successful sync cycle. | 
|  | 275   nudge_tracker.RecordSuccessfulSyncCycle(); | 
|  | 276   EXPECT_TRUE(InvalidationsOutOfSync(nudge_tracker)); | 
|  | 277 } | 
|  | 278 | 
|  | 279 // Tests that locally modified types are correctly written out to the | 
|  | 280 // GetUpdateTriggers proto. | 
|  | 281 TEST_F(NudgeTrackerTest, WriteLocallyModifiedTypesToProto) { | 
|  | 282   NudgeTracker nudge_tracker; | 
|  | 283 | 
|  | 284   // Should not be locally modified by default. | 
|  | 285   EXPECT_EQ(0, ProtoLocallyModifiedCount(nudge_tracker, PREFERENCES)); | 
|  | 286 | 
|  | 287   // Record a local bookmark change.  Verify it was registered correctly. | 
|  | 288   nudge_tracker.RecordLocalChange(ModelTypeSet(PREFERENCES)); | 
|  | 289   EXPECT_EQ(1, ProtoLocallyModifiedCount(nudge_tracker, PREFERENCES)); | 
|  | 290 | 
|  | 291   // Record a successful sync cycle.  Verify the count is cleared. | 
|  | 292   nudge_tracker.RecordSuccessfulSyncCycle(); | 
|  | 293   EXPECT_EQ(0, ProtoLocallyModifiedCount(nudge_tracker, PREFERENCES)); | 
|  | 294 } | 
|  | 295 | 
|  | 296 // Tests that refresh requested types are correctly written out to the | 
|  | 297 // GetUpdateTriggers proto. | 
|  | 298 TEST_F(NudgeTrackerTest, WriteRefreshRequestedTypesToProto) { | 
|  | 299   NudgeTracker nudge_tracker; | 
|  | 300 | 
|  | 301   // There should be no refresh requested by default. | 
|  | 302   EXPECT_EQ(0, ProtoRefreshRequestedCount(nudge_tracker, SESSIONS)); | 
|  | 303 | 
|  | 304   // Record a local refresh request.  Verify it was registered correctly. | 
|  | 305   nudge_tracker.RecordLocalRefreshRequest(ModelTypeSet(SESSIONS)); | 
|  | 306   EXPECT_EQ(1, ProtoRefreshRequestedCount(nudge_tracker, SESSIONS)); | 
|  | 307 | 
|  | 308   // Record a successful sync cycle.  Verify the count is cleared. | 
|  | 309   nudge_tracker.RecordSuccessfulSyncCycle(); | 
|  | 310   EXPECT_EQ(0, ProtoRefreshRequestedCount(nudge_tracker, SESSIONS)); | 
| 109 } | 311 } | 
| 110 | 312 | 
| 111 }  // namespace sessions | 313 }  // namespace sessions | 
| 112 }  // namespace syncer | 314 }  // namespace syncer | 
| OLD | NEW | 
|---|