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