1 // Copyright (c) 2013 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.
5 #include "chrome/browser/history/typed_url_syncable_service.h"
7 #include "base/logging.h"
8 #include "base/memory/ref_counted.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/history/history_backend.h"
12 #include "components/history/core/browser/history_types.h"
13 #include "content/public/browser/notification_types.h"
14 #include "sync/api/fake_sync_change_processor.h"
15 #include "sync/api/sync_change_processor_wrapper_for_test.h"
16 #include "sync/api/sync_error.h"
17 #include "sync/api/sync_error_factory_mock.h"
18 #include "sync/protocol/sync.pb.h"
19 #include "sync/protocol/typed_url_specifics.pb.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 using history::HistoryBackend;
24 using history::URLRow;
25 using history::URLRows;
26 using history::VisitRow;
27 using history::VisitVector;
31 // Constants used to limit size of visits processed.
32 const int kMaxTypedUrlVisits = 100;
34 // Visits with this timestamp are treated as expired.
35 const int EXPIRED_VISIT = -1;
37 // TestHistoryBackend ----------------------------------------------------------
39 class TestHistoryBackend : public HistoryBackend {
41 TestHistoryBackend() : HistoryBackend(base::FilePath(), NULL, NULL) {}
43 // HistoryBackend test implementation.
44 virtual bool IsExpiredVisitTime(const base::Time& time) OVERRIDE {
45 return time.ToInternalValue() == EXPIRED_VISIT;
48 virtual bool GetMostRecentVisitsForURL(
51 VisitVector* visits) OVERRIDE {
52 if (local_db_visits_[id].empty())
55 visits->insert(visits->end(),
56 local_db_visits_[id].begin(),
57 local_db_visits_[id].end());
62 void SetVisitsForUrl(URLID id, VisitVector* visits) {
63 if (!local_db_visits_[id].empty()) {
64 local_db_visits_[id].clear();
67 local_db_visits_[id].insert(local_db_visits_[id].end(),
72 void DeleteVisitsForUrl(const URLID& id) {
73 local_db_visits_.erase(id);
77 virtual ~TestHistoryBackend() {}
79 // Mock of visit table in local db.
80 std::map<URLID, VisitVector> local_db_visits_;
87 // TypedUrlSyncableServiceTest -------------------------------------------------
89 class TypedUrlSyncableServiceTest : public testing::Test {
91 // Create a new row object and add a typed visit to the |visits| vector.
92 // Note that the real history db returns visits in reverse chronological
93 // order, so |visits| is treated this way. If the newest (first) visit
94 // in visits does not match |last_visit|, then a typed visit for this
95 // time is prepended to the front (or if |last_visit| is too old, it is
96 // set equal to the time of the newest visit).
97 static URLRow MakeTypedUrlRow(const char* url,
102 VisitVector* visits);
104 static void AddNewestVisit(URLRow* url,
106 ui::PageTransition transition,
109 static void AddOldestVisit(URLRow* url,
111 ui::PageTransition transition,
114 static bool URLsEqual(URLRow& row,
115 sync_pb::TypedUrlSpecifics& specifics) {
116 return ((row.url().spec().compare(specifics.url()) == 0) &&
117 (base::UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) &&
118 (row.hidden() == specifics.hidden()));
121 bool InitiateServerState(unsigned int num_typed_urls,
122 unsigned int num_reload_urls,
124 std::vector<VisitVector>* visit_vectors,
125 const std::vector<const char*>& urls);
128 TypedUrlSyncableServiceTest() {}
130 virtual ~TypedUrlSyncableServiceTest() {}
132 virtual void SetUp() OVERRIDE {
133 fake_history_backend_ = new TestHistoryBackend();
134 typed_url_sync_service_.reset(
135 new TypedUrlSyncableService(fake_history_backend_.get()));
136 fake_change_processor_.reset(new syncer::FakeSyncChangeProcessor);
139 scoped_refptr<HistoryBackend> fake_history_backend_;
140 scoped_ptr<TypedUrlSyncableService> typed_url_sync_service_;
141 scoped_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_;
144 URLRow TypedUrlSyncableServiceTest::MakeTypedUrlRow(
150 VisitVector* visits) {
151 DCHECK(visits->empty());
153 // Give each URL a unique ID, to mimic the behavior of the real database.
154 static int unique_url_id = 0;
156 URLRow history_url(gurl, ++unique_url_id);
157 history_url.set_title(base::UTF8ToUTF16(title));
158 history_url.set_typed_count(typed_count);
159 history_url.set_hidden(hidden);
161 base::Time last_visit_time = base::Time::FromInternalValue(last_visit);
162 history_url.set_last_visit(last_visit_time);
164 VisitVector::iterator first = visits->begin();
165 if (typed_count > 0) {
166 // Add a typed visit for time |last_visit|.
167 visits->insert(first,
168 VisitRow(history_url.id(), last_visit_time, 0,
169 ui::PAGE_TRANSITION_TYPED, 0));
171 // Add a non-typed visit for time |last_visit|.
172 visits->insert(first,
173 VisitRow(history_url.id(), last_visit_time, 0,
174 ui::PAGE_TRANSITION_RELOAD, 0));
177 history_url.set_visit_count(visits->size());
181 void TypedUrlSyncableServiceTest::AddNewestVisit(
184 ui::PageTransition transition,
186 base::Time time = base::Time::FromInternalValue(visit_time);
187 visits->insert(visits->begin(),
188 VisitRow(url->id(), time, 0, transition, 0));
190 if (transition == ui::PAGE_TRANSITION_TYPED) {
191 url->set_typed_count(url->typed_count() + 1);
194 url->set_last_visit(time);
195 url->set_visit_count(visits->size());
198 void TypedUrlSyncableServiceTest::AddOldestVisit(
201 ui::PageTransition transition,
203 base::Time time = base::Time::FromInternalValue(visit_time);
204 visits->push_back(VisitRow(url->id(), time, 0, transition, 0));
206 if (transition == ui::PAGE_TRANSITION_TYPED) {
207 url->set_typed_count(url->typed_count() + 1);
210 url->set_visit_count(visits->size());
213 bool TypedUrlSyncableServiceTest::InitiateServerState(
214 unsigned int num_typed_urls,
215 unsigned int num_reload_urls,
217 std::vector<VisitVector>* visit_vectors,
218 const std::vector<const char*>& urls) {
219 unsigned int total_urls = num_typed_urls + num_reload_urls;
220 DCHECK(urls.size() >= total_urls);
221 if (!typed_url_sync_service_.get())
224 // Set change processor.
225 syncer::SyncMergeResult result =
226 typed_url_sync_service_->MergeDataAndStartSyncing(
228 syncer::SyncDataList(),
229 scoped_ptr<syncer::SyncChangeProcessor>(
230 new syncer::SyncChangeProcessorWrapperForTest(
231 fake_change_processor_.get())),
232 scoped_ptr<syncer::SyncErrorFactory>(
233 new syncer::SyncErrorFactoryMock()));
234 EXPECT_FALSE(result.error().IsSet()) << result.error().message();
237 // Create new URL rows, populate the mock backend with its visits, and
238 // send to the sync service.
239 URLRows changed_urls;
241 for (unsigned int i = 0; i < total_urls; ++i) {
242 int typed = i < num_typed_urls ? 1 : 0;
244 visit_vectors->push_back(visits);
245 rows->push_back(MakeTypedUrlRow(
246 urls[i], "pie", typed, i + 3, false, &visit_vectors->back()));
247 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
248 SetVisitsForUrl(rows->back().id(), &visit_vectors->back());
249 changed_urls.push_back(rows->back());
252 typed_url_sync_service_->OnUrlsModified(&changed_urls);
254 // Check that communication with sync was successful.
255 if (num_typed_urls != fake_change_processor_->changes().size())
260 TEST_F(TypedUrlSyncableServiceTest, AddLocalTypedUrlAndSync) {
261 // Create a local typed URL (simulate a typed visit) that is not already
262 // in sync. Check that sync is sent an ADD change for the existing URL.
264 std::vector<VisitVector> visit_vectors;
265 std::vector<const char*> urls;
266 urls.push_back("http://pie.com/");
268 ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
270 URLRow url_row = url_rows.front();
271 VisitVector visits = visit_vectors.front();
273 // Check change processor.
274 const syncer::SyncChangeList& changes = fake_change_processor_->changes();
275 ASSERT_EQ(1u, changes.size());
276 ASSERT_TRUE(changes[0].IsValid());
277 EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
278 EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
280 // Get typed url specifics.
281 sync_pb::TypedUrlSpecifics url_specifics =
282 changes[0].sync_data().GetSpecifics().typed_url();
284 EXPECT_TRUE(URLsEqual(url_row, url_specifics));
285 ASSERT_EQ(1, url_specifics.visits_size());
286 ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
287 EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(0));
288 EXPECT_EQ(static_cast<const int>(visits[0].transition),
289 url_specifics.visit_transitions(0));
291 // Check that in-memory representation of sync state is accurate.
292 std::set<GURL> sync_state;
293 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
294 EXPECT_FALSE(sync_state.empty());
295 EXPECT_EQ(1u, sync_state.size());
296 EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
299 TEST_F(TypedUrlSyncableServiceTest, UpdateLocalTypedUrlAndSync) {
301 std::vector<VisitVector> visit_vectors;
302 std::vector<const char*> urls;
303 urls.push_back("http://pie.com/");
305 ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
306 syncer::SyncChangeList& changes = fake_change_processor_->changes();
309 // Update the URL row, adding another typed visit to the visit vector.
310 URLRow url_row = url_rows.front();
311 VisitVector visits = visit_vectors.front();
313 URLRows changed_urls;
314 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_TYPED, 7);
315 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
316 SetVisitsForUrl(url_row.id(), &visits);
317 changed_urls.push_back(url_row);
319 // Notify typed url sync service of the update.
320 typed_url_sync_service_->OnUrlsModified(&changed_urls);
322 ASSERT_EQ(1u, changes.size());
323 ASSERT_TRUE(changes[0].IsValid());
324 EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
325 EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());
327 sync_pb::TypedUrlSpecifics url_specifics =
328 changes[0].sync_data().GetSpecifics().typed_url();
330 EXPECT_TRUE(URLsEqual(url_row, url_specifics));
331 ASSERT_EQ(2, url_specifics.visits_size());
332 ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
334 // Check that each visit has been translated/communicated correctly.
335 // Note that the specifics record visits in chronological order, and the
336 // visits from the db are in reverse chronological order.
337 EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(1));
338 EXPECT_EQ(static_cast<const int>(visits[0].transition),
339 url_specifics.visit_transitions(1));
340 EXPECT_EQ(visits[1].visit_time.ToInternalValue(), url_specifics.visits(0));
341 EXPECT_EQ(static_cast<const int>(visits[1].transition),
342 url_specifics.visit_transitions(0));
344 // Check that in-memory representation of sync state is accurate.
345 std::set<GURL> sync_state;
346 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
347 EXPECT_FALSE(sync_state.empty());
348 EXPECT_EQ(1u, sync_state.size());
349 EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
352 TEST_F(TypedUrlSyncableServiceTest, LinkVisitLocalTypedUrlAndSync) {
354 std::vector<VisitVector> visit_vectors;
355 std::vector<const char*> urls;
356 urls.push_back("http://pie.com/");
358 ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
359 syncer::SyncChangeList& changes = fake_change_processor_->changes();
362 URLRow url_row = url_rows.front();
363 VisitVector visits = visit_vectors.front();
365 // Update the URL row, adding a non-typed visit to the visit vector.
366 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_LINK, 6);
367 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
368 SetVisitsForUrl(url_row.id(), &visits);
370 ui::PageTransition transition = ui::PAGE_TRANSITION_LINK;
371 // Notify typed url sync service of non-typed visit, expect no change.
372 typed_url_sync_service_->OnUrlVisited(transition, &url_row);
373 ASSERT_EQ(0u, changes.size());
376 TEST_F(TypedUrlSyncableServiceTest, TypedVisitLocalTypedUrlAndSync) {
378 std::vector<VisitVector> visit_vectors;
379 std::vector<const char*> urls;
380 urls.push_back("http://pie.com/");
382 ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
383 syncer::SyncChangeList& changes = fake_change_processor_->changes();
386 URLRow url_row = url_rows.front();
387 VisitVector visits = visit_vectors.front();
389 // Update the URL row, adding another typed visit to the visit vector.
390 AddOldestVisit(&url_row, &visits, ui::PAGE_TRANSITION_LINK, 1);
391 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_LINK, 6);
392 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_TYPED, 7);
393 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
394 SetVisitsForUrl(url_row.id(), &visits);
396 // Notify typed url sync service of typed visit.
397 ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
398 typed_url_sync_service_->OnUrlVisited(transition, &url_row);
400 ASSERT_EQ(1u, changes.size());
401 ASSERT_TRUE(changes[0].IsValid());
402 EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
403 EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());
405 sync_pb::TypedUrlSpecifics url_specifics =
406 changes[0].sync_data().GetSpecifics().typed_url();
408 EXPECT_TRUE(URLsEqual(url_row, url_specifics));
409 ASSERT_EQ(4u, visits.size());
410 EXPECT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
412 // Check that each visit has been translated/communicated correctly.
413 // Note that the specifics record visits in chronological order, and the
414 // visits from the db are in reverse chronological order.
415 int r = url_specifics.visits_size() - 1;
416 for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
417 EXPECT_EQ(visits[i].visit_time.ToInternalValue(), url_specifics.visits(r));
418 EXPECT_EQ(static_cast<const int>(visits[i].transition),
419 url_specifics.visit_transitions(r));
422 // Check that in-memory representation of sync state is accurate.
423 std::set<GURL> sync_state;
424 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
425 EXPECT_FALSE(sync_state.empty());
426 EXPECT_EQ(1u, sync_state.size());
427 EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
430 TEST_F(TypedUrlSyncableServiceTest, DeleteLocalTypedUrlAndSync) {
432 std::vector<VisitVector> visit_vectors;
433 std::vector<const char*> urls;
434 urls.push_back("http://pie.com/");
435 urls.push_back("http://cake.com/");
436 urls.push_back("http://google.com/");
437 urls.push_back("http://foo.com/");
438 urls.push_back("http://bar.com/");
440 ASSERT_TRUE(InitiateServerState(4, 1, &url_rows, &visit_vectors, urls));
441 syncer::SyncChangeList& changes = fake_change_processor_->changes();
444 // Check that in-memory representation of sync state is accurate.
445 std::set<GURL> sync_state;
446 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
447 EXPECT_FALSE(sync_state.empty());
448 EXPECT_EQ(4u, sync_state.size());
450 // Simulate visit expiry of typed visit, no syncing is done
451 // This is to test that sync relies on the in-memory cache to know
452 // which urls were typed and synced, and should be deleted.
453 url_rows[0].set_typed_count(0);
455 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
456 SetVisitsForUrl(url_rows[0].id(), &visits);
458 // Delete some urls from backend and create deleted row vector.
460 for (size_t i = 0; i < 3u; ++i) {
461 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
462 DeleteVisitsForUrl(url_rows[i].id());
463 rows.push_back(url_rows[i]);
466 // Notify typed url sync service.
467 typed_url_sync_service_->OnUrlsDeleted(false, false, &rows);
469 ASSERT_EQ(3u, changes.size());
470 for (size_t i = 0; i < changes.size(); ++i) {
471 ASSERT_TRUE(changes[i].IsValid());
472 ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
473 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
474 sync_pb::TypedUrlSpecifics url_specifics =
475 changes[i].sync_data().GetSpecifics().typed_url();
476 EXPECT_EQ(url_rows[i].url().spec(), url_specifics.url());
479 // Check that in-memory representation of sync state is accurate.
480 std::set<GURL> sync_state_deleted;
481 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state_deleted);
482 ASSERT_EQ(1u, sync_state_deleted.size());
483 EXPECT_TRUE(sync_state_deleted.end() !=
484 sync_state_deleted.find(url_rows[3].url()));
487 TEST_F(TypedUrlSyncableServiceTest, DeleteAllLocalTypedUrlAndSync) {
489 std::vector<VisitVector> visit_vectors;
490 std::vector<const char*> urls;
491 urls.push_back("http://pie.com/");
492 urls.push_back("http://cake.com/");
493 urls.push_back("http://google.com/");
494 urls.push_back("http://foo.com/");
495 urls.push_back("http://bar.com/");
497 ASSERT_TRUE(InitiateServerState(4, 1, &url_rows, &visit_vectors, urls));
498 syncer::SyncChangeList& changes = fake_change_processor_->changes();
501 // Check that in-memory representation of sync state is accurate.
502 std::set<GURL> sync_state;
503 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
504 EXPECT_EQ(4u, sync_state.size());
506 // Delete urls from backend.
507 for (size_t i = 0; i < 4u; ++ i) {
508 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
509 DeleteVisitsForUrl(url_rows[i].id());
511 // Delete urls with |all_history| flag set.
512 bool all_history = true;
514 // Notify typed url sync service.
515 typed_url_sync_service_->OnUrlsDeleted(all_history, false, NULL);
517 ASSERT_EQ(4u, changes.size());
518 for (size_t i = 0; i < changes.size(); ++i) {
519 ASSERT_TRUE(changes[i].IsValid());
520 ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
521 EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
523 // Check that in-memory representation of sync state is accurate.
524 std::set<GURL> sync_state_deleted;
525 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state_deleted);
526 EXPECT_TRUE(sync_state_deleted.empty());
529 TEST_F(TypedUrlSyncableServiceTest, MaxVisitLocalTypedUrlAndSync) {
531 std::vector<VisitVector> visit_vectors;
532 std::vector<const char*> urls;
533 urls.push_back("http://pie.com/");
535 ASSERT_TRUE(InitiateServerState(0, 1, &url_rows, &visit_vectors, urls));
537 URLRow url_row = url_rows.front();
540 // Add |kMaxTypedUrlVisits| + 10 visits to the url. The 10 oldest
541 // non-typed visits are expected to be skipped.
543 for (; i <= kMaxTypedUrlVisits - 20; ++i)
544 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_TYPED, i);
545 for (; i <= kMaxTypedUrlVisits; ++i)
546 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_LINK, i);
547 for (; i <= kMaxTypedUrlVisits + 10; ++i)
548 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_TYPED, i);
550 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
551 SetVisitsForUrl(url_row.id(), &visits);
553 // Notify typed url sync service of typed visit.
554 ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
555 typed_url_sync_service_->OnUrlVisited(transition, &url_row);
557 const syncer::SyncChangeList& changes = fake_change_processor_->changes();
558 ASSERT_EQ(1u, changes.size());
559 ASSERT_TRUE(changes[0].IsValid());
560 sync_pb::TypedUrlSpecifics url_specifics =
561 changes[0].sync_data().GetSpecifics().typed_url();
562 ASSERT_EQ(kMaxTypedUrlVisits, url_specifics.visits_size());
564 // Check that each visit has been translated/communicated correctly.
565 // Note that the specifics record visits in chronological order, and the
566 // visits from the db are in reverse chronological order.
567 int num_typed_visits_synced = 0;
568 int num_other_visits_synced = 0;
569 int r = url_specifics.visits_size() - 1;
570 for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
571 if (url_specifics.visit_transitions(i) == ui::PAGE_TRANSITION_TYPED) {
572 ++num_typed_visits_synced;
574 ++num_other_visits_synced;
577 EXPECT_EQ(kMaxTypedUrlVisits - 10, num_typed_visits_synced);
578 EXPECT_EQ(10, num_other_visits_synced);
581 TEST_F(TypedUrlSyncableServiceTest, ThrottleVisitLocalTypedUrlSync) {
583 std::vector<VisitVector> visit_vectors;
584 std::vector<const char*> urls;
585 urls.push_back("http://pie.com/");
587 ASSERT_TRUE(InitiateServerState(0, 1, &url_rows, &visit_vectors, urls));
589 URLRow url_row = url_rows.front();
592 // Add enough visits to the url so that typed count is above the throttle
593 // limit, and not right on the interval that gets synced.
594 for (int i = 1; i < 42; ++i)
595 AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_TYPED, i);
597 static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
598 SetVisitsForUrl(url_row.id(), &visits);
600 // Notify typed url sync service of typed visit.
601 ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
602 typed_url_sync_service_->OnUrlVisited(transition, &url_row);
604 // Should throttle, so sync and local cache should not update.
605 const syncer::SyncChangeList& changes = fake_change_processor_->changes();
606 ASSERT_EQ(0u, changes.size());
607 std::set<GURL> sync_state;
608 typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
609 EXPECT_TRUE(sync_state.empty());
612 } // namespace history