1 // Copyright 2014 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 "sync/syncable/directory_unittest.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/test/values_test_util.h"
9 #include "sync/internal_api/public/base/attachment_id_proto.h"
10 #include "sync/syncable/syncable_proto_util.h"
11 #include "sync/syncable/syncable_util.h"
12 #include "sync/syncable/syncable_write_transaction.h"
13 #include "sync/test/engine/test_syncable_utils.h"
14 #include "sync/test/test_directory_backing_store.h"
15 #include "sync/util/mock_unrecoverable_error_handler.h"
17 using base::ExpectDictBooleanValue
;
18 using base::ExpectDictStringValue
;
26 bool IsLegalNewParent(const Entry
& a
, const Entry
& b
) {
27 return IsLegalNewParent(a
.trans(), a
.GetId(), b
.GetId());
30 void PutDataAsBookmarkFavicon(WriteTransaction
* wtrans
,
33 size_t bytes_length
) {
34 sync_pb::EntitySpecifics specifics
;
35 specifics
.mutable_bookmark()->set_url("http://demo/");
36 specifics
.mutable_bookmark()->set_favicon(bytes
, bytes_length
);
37 e
->PutSpecifics(specifics
);
40 void ExpectDataFromBookmarkFaviconEquals(BaseTransaction
* trans
,
43 size_t bytes_length
) {
44 ASSERT_TRUE(e
->good());
45 ASSERT_TRUE(e
->GetSpecifics().has_bookmark());
46 ASSERT_EQ("http://demo/", e
->GetSpecifics().bookmark().url());
47 ASSERT_EQ(std::string(bytes
, bytes_length
),
48 e
->GetSpecifics().bookmark().favicon());
53 const char SyncableDirectoryTest::kDirectoryName
[] = "Foo";
55 SyncableDirectoryTest::SyncableDirectoryTest() {
58 SyncableDirectoryTest::~SyncableDirectoryTest() {
61 void SyncableDirectoryTest::SetUp() {
62 ASSERT_TRUE(connection_
.OpenInMemory());
63 ASSERT_EQ(OPENED
, ReopenDirectory());
66 void SyncableDirectoryTest::TearDown() {
72 DirOpenResult
SyncableDirectoryTest::ReopenDirectory() {
73 // Use a TestDirectoryBackingStore and sql::Connection so we can have test
74 // data persist across Directory object lifetimes while getting the
75 // performance benefits of not writing to disk.
77 new Directory(new TestDirectoryBackingStore(kDirectoryName
, &connection_
),
78 &handler_
, base::Closure(), NULL
, NULL
));
80 DirOpenResult open_result
=
81 dir_
->Open(kDirectoryName
, &delegate_
, NullTransactionObserver());
83 if (open_result
!= OPENED
) {
90 // Creates an empty entry and sets the ID field to a default one.
91 void SyncableDirectoryTest::CreateEntry(const ModelType
& model_type
,
92 const std::string
& entryname
) {
93 CreateEntry(model_type
, entryname
, TestIdFactory::FromNumber(-99));
96 // Creates an empty entry and sets the ID field to id.
97 void SyncableDirectoryTest::CreateEntry(const ModelType
& model_type
,
98 const std::string
& entryname
,
100 CreateEntry(model_type
, entryname
, TestIdFactory::FromNumber(id
));
103 void SyncableDirectoryTest::CreateEntry(const ModelType
& model_type
,
104 const std::string
& entryname
,
106 CreateEntryWithAttachmentMetadata(
107 model_type
, entryname
, id
, sync_pb::AttachmentMetadata());
110 void SyncableDirectoryTest::CreateEntryWithAttachmentMetadata(
111 const ModelType
& model_type
,
112 const std::string
& entryname
,
114 const sync_pb::AttachmentMetadata
& attachment_metadata
) {
115 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir_
.get());
116 MutableEntry
me(&wtrans
, CREATE
, model_type
, wtrans
.root_id(), entryname
);
117 ASSERT_TRUE(me
.good());
119 me
.PutAttachmentMetadata(attachment_metadata
);
120 me
.PutIsUnsynced(true);
123 void SyncableDirectoryTest::DeleteEntry(const Id
& id
) {
124 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
125 MutableEntry
entry(&trans
, GET_BY_ID
, id
);
126 ASSERT_TRUE(entry
.good());
127 entry
.PutIsDel(true);
130 DirOpenResult
SyncableDirectoryTest::SimulateSaveAndReloadDir() {
131 if (!dir_
->SaveChanges())
132 return FAILED_IN_UNITTEST
;
134 return ReopenDirectory();
137 DirOpenResult
SyncableDirectoryTest::SimulateCrashAndReloadDir() {
138 return ReopenDirectory();
141 void SyncableDirectoryTest::GetAllMetaHandles(BaseTransaction
* trans
,
142 MetahandleSet
* result
) {
143 dir_
->GetAllMetaHandles(trans
, result
);
146 void SyncableDirectoryTest::CheckPurgeEntriesWithTypeInSucceeded(
147 ModelTypeSet types_to_purge
,
148 bool before_reload
) {
149 SCOPED_TRACE(testing::Message("Before reload: ") << before_reload
);
151 ReadTransaction
trans(FROM_HERE
, dir_
.get());
152 MetahandleSet all_set
;
153 dir_
->GetAllMetaHandles(&trans
, &all_set
);
154 EXPECT_EQ(4U, all_set
.size());
156 EXPECT_EQ(6U, dir_
->kernel()->metahandles_to_purge
.size());
157 for (MetahandleSet::iterator iter
= all_set
.begin(); iter
!= all_set
.end();
159 Entry
e(&trans
, GET_BY_HANDLE
, *iter
);
160 const ModelType local_type
= e
.GetModelType();
161 const ModelType server_type
= e
.GetServerModelType();
163 // Note the dance around incrementing |it|, since we sometimes erase().
164 if ((IsRealDataType(local_type
) && types_to_purge
.Has(local_type
)) ||
165 (IsRealDataType(server_type
) && types_to_purge
.Has(server_type
))) {
166 FAIL() << "Illegal type should have been deleted.";
171 for (ModelTypeSet::Iterator it
= types_to_purge
.First(); it
.Good();
173 EXPECT_FALSE(dir_
->InitialSyncEndedForType(it
.Get()));
174 sync_pb::DataTypeProgressMarker progress
;
175 dir_
->GetDownloadProgress(it
.Get(), &progress
);
176 EXPECT_EQ("", progress
.token());
178 ReadTransaction
trans(FROM_HERE
, dir_
.get());
179 sync_pb::DataTypeContext context
;
180 dir_
->GetDataTypeContext(&trans
, it
.Get(), &context
);
181 EXPECT_TRUE(context
.SerializeAsString().empty());
183 EXPECT_FALSE(types_to_purge
.Has(BOOKMARKS
));
184 EXPECT_TRUE(dir_
->InitialSyncEndedForType(BOOKMARKS
));
187 bool SyncableDirectoryTest::IsInDirtyMetahandles(int64 metahandle
) {
188 return 1 == dir_
->kernel()->dirty_metahandles
.count(metahandle
);
191 bool SyncableDirectoryTest::IsInMetahandlesToPurge(int64 metahandle
) {
192 return 1 == dir_
->kernel()->metahandles_to_purge
.count(metahandle
);
195 scoped_ptr
<Directory
>& SyncableDirectoryTest::dir() {
199 DirectoryChangeDelegate
* SyncableDirectoryTest::directory_change_delegate() {
203 Encryptor
* SyncableDirectoryTest::encryptor() {
207 UnrecoverableErrorHandler
*
208 SyncableDirectoryTest::unrecoverable_error_handler() {
212 void SyncableDirectoryTest::ValidateEntry(BaseTransaction
* trans
,
215 const std::string
& name
,
217 int64 server_version
,
219 Entry
e(trans
, GET_BY_ID
, TestIdFactory::FromNumber(id
));
220 ASSERT_TRUE(e
.good());
222 ASSERT_TRUE(name
== e
.GetNonUniqueName());
223 ASSERT_TRUE(base_version
== e
.GetBaseVersion());
224 ASSERT_TRUE(server_version
== e
.GetServerVersion());
225 ASSERT_TRUE(is_del
== e
.GetIsDel());
228 TEST_F(SyncableDirectoryTest
, TakeSnapshotGetsMetahandlesToPurge
) {
229 const int metas_to_create
= 50;
230 MetahandleSet expected_purges
;
231 MetahandleSet all_handles
;
233 dir()->SetDownloadProgress(BOOKMARKS
, BuildProgress(BOOKMARKS
));
234 dir()->SetDownloadProgress(PREFERENCES
, BuildProgress(PREFERENCES
));
235 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
236 for (int i
= 0; i
< metas_to_create
; i
++) {
237 MutableEntry
e(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "foo");
238 e
.PutIsUnsynced(true);
239 sync_pb::EntitySpecifics specs
;
241 AddDefaultFieldValue(BOOKMARKS
, &specs
);
242 expected_purges
.insert(e
.GetMetahandle());
243 all_handles
.insert(e
.GetMetahandle());
245 AddDefaultFieldValue(PREFERENCES
, &specs
);
246 all_handles
.insert(e
.GetMetahandle());
248 e
.PutSpecifics(specs
);
249 e
.PutServerSpecifics(specs
);
253 ModelTypeSet
to_purge(BOOKMARKS
);
254 dir()->PurgeEntriesWithTypeIn(to_purge
, ModelTypeSet(), ModelTypeSet());
256 Directory::SaveChangesSnapshot snapshot1
;
257 base::AutoLock
scoped_lock(dir()->kernel()->save_changes_mutex
);
258 dir()->TakeSnapshotForSaveChanges(&snapshot1
);
259 EXPECT_TRUE(expected_purges
== snapshot1
.metahandles_to_purge
);
262 to_purge
.Put(PREFERENCES
);
263 dir()->PurgeEntriesWithTypeIn(to_purge
, ModelTypeSet(), ModelTypeSet());
265 dir()->HandleSaveChangesFailure(snapshot1
);
267 Directory::SaveChangesSnapshot snapshot2
;
268 dir()->TakeSnapshotForSaveChanges(&snapshot2
);
269 EXPECT_TRUE(all_handles
== snapshot2
.metahandles_to_purge
);
272 TEST_F(SyncableDirectoryTest
, TakeSnapshotGetsAllDirtyHandlesTest
) {
273 const int metahandles_to_create
= 100;
274 std::vector
<int64
> expected_dirty_metahandles
;
276 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
277 for (int i
= 0; i
< metahandles_to_create
; i
++) {
278 MutableEntry
e(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "foo");
279 expected_dirty_metahandles
.push_back(e
.GetMetahandle());
280 e
.PutIsUnsynced(true);
283 // Fake SaveChanges() and make sure we got what we expected.
285 Directory::SaveChangesSnapshot snapshot
;
286 base::AutoLock
scoped_lock(dir()->kernel()->save_changes_mutex
);
287 dir()->TakeSnapshotForSaveChanges(&snapshot
);
288 // Make sure there's an entry for each new metahandle. Make sure all
289 // entries are marked dirty.
290 ASSERT_EQ(expected_dirty_metahandles
.size(), snapshot
.dirty_metas
.size());
291 for (EntryKernelSet::const_iterator i
= snapshot
.dirty_metas
.begin();
292 i
!= snapshot
.dirty_metas
.end();
294 ASSERT_TRUE((*i
)->is_dirty());
296 dir()->VacuumAfterSaveChanges(snapshot
);
298 // Put a new value with existing transactions as well as adding new ones.
300 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
301 std::vector
<int64
> new_dirty_metahandles
;
302 for (std::vector
<int64
>::const_iterator i
=
303 expected_dirty_metahandles
.begin();
304 i
!= expected_dirty_metahandles
.end();
306 // Change existing entries to directories to dirty them.
307 MutableEntry
e1(&trans
, GET_BY_HANDLE
, *i
);
309 e1
.PutIsUnsynced(true);
311 MutableEntry
e2(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "bar");
312 e2
.PutIsUnsynced(true);
313 new_dirty_metahandles
.push_back(e2
.GetMetahandle());
315 expected_dirty_metahandles
.insert(expected_dirty_metahandles
.end(),
316 new_dirty_metahandles
.begin(),
317 new_dirty_metahandles
.end());
319 // Fake SaveChanges() and make sure we got what we expected.
321 Directory::SaveChangesSnapshot snapshot
;
322 base::AutoLock
scoped_lock(dir()->kernel()->save_changes_mutex
);
323 dir()->TakeSnapshotForSaveChanges(&snapshot
);
324 // Make sure there's an entry for each new metahandle. Make sure all
325 // entries are marked dirty.
326 EXPECT_EQ(expected_dirty_metahandles
.size(), snapshot
.dirty_metas
.size());
327 for (EntryKernelSet::const_iterator i
= snapshot
.dirty_metas
.begin();
328 i
!= snapshot
.dirty_metas
.end();
330 EXPECT_TRUE((*i
)->is_dirty());
332 dir()->VacuumAfterSaveChanges(snapshot
);
336 TEST_F(SyncableDirectoryTest
, TakeSnapshotGetsOnlyDirtyHandlesTest
) {
337 const int metahandles_to_create
= 100;
339 // half of 2 * metahandles_to_create
340 const unsigned int number_changed
= 100u;
341 std::vector
<int64
> expected_dirty_metahandles
;
343 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
344 for (int i
= 0; i
< metahandles_to_create
; i
++) {
345 MutableEntry
e(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "foo");
346 expected_dirty_metahandles
.push_back(e
.GetMetahandle());
347 e
.PutIsUnsynced(true);
350 dir()->SaveChanges();
351 // Put a new value with existing transactions as well as adding new ones.
353 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
354 std::vector
<int64
> new_dirty_metahandles
;
355 for (std::vector
<int64
>::const_iterator i
=
356 expected_dirty_metahandles
.begin();
357 i
!= expected_dirty_metahandles
.end();
359 // Change existing entries to directories to dirty them.
360 MutableEntry
e1(&trans
, GET_BY_HANDLE
, *i
);
361 ASSERT_TRUE(e1
.good());
363 e1
.PutIsUnsynced(true);
365 MutableEntry
e2(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "bar");
366 e2
.PutIsUnsynced(true);
367 new_dirty_metahandles
.push_back(e2
.GetMetahandle());
369 expected_dirty_metahandles
.insert(expected_dirty_metahandles
.end(),
370 new_dirty_metahandles
.begin(),
371 new_dirty_metahandles
.end());
373 dir()->SaveChanges();
374 // Don't make any changes whatsoever and ensure nothing comes back.
376 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
377 for (std::vector
<int64
>::const_iterator i
=
378 expected_dirty_metahandles
.begin();
379 i
!= expected_dirty_metahandles
.end();
381 MutableEntry
e(&trans
, GET_BY_HANDLE
, *i
);
382 ASSERT_TRUE(e
.good());
383 // We aren't doing anything to dirty these entries.
386 // Fake SaveChanges() and make sure we got what we expected.
388 Directory::SaveChangesSnapshot snapshot
;
389 base::AutoLock
scoped_lock(dir()->kernel()->save_changes_mutex
);
390 dir()->TakeSnapshotForSaveChanges(&snapshot
);
391 // Make sure there are no dirty_metahandles.
392 EXPECT_EQ(0u, snapshot
.dirty_metas
.size());
393 dir()->VacuumAfterSaveChanges(snapshot
);
396 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
397 bool should_change
= false;
398 for (std::vector
<int64
>::const_iterator i
=
399 expected_dirty_metahandles
.begin();
400 i
!= expected_dirty_metahandles
.end();
402 // Maybe change entries by flipping IS_DIR.
403 MutableEntry
e(&trans
, GET_BY_HANDLE
, *i
);
404 ASSERT_TRUE(e
.good());
405 should_change
= !should_change
;
407 bool not_dir
= !e
.GetIsDir();
409 e
.PutIsUnsynced(true);
413 // Fake SaveChanges() and make sure we got what we expected.
415 Directory::SaveChangesSnapshot snapshot
;
416 base::AutoLock
scoped_lock(dir()->kernel()->save_changes_mutex
);
417 dir()->TakeSnapshotForSaveChanges(&snapshot
);
418 // Make sure there's an entry for each changed metahandle. Make sure all
419 // entries are marked dirty.
420 EXPECT_EQ(number_changed
, snapshot
.dirty_metas
.size());
421 for (EntryKernelSet::const_iterator i
= snapshot
.dirty_metas
.begin();
422 i
!= snapshot
.dirty_metas
.end();
424 EXPECT_TRUE((*i
)->is_dirty());
426 dir()->VacuumAfterSaveChanges(snapshot
);
430 // Test delete journals management.
431 TEST_F(SyncableDirectoryTest
, ManageDeleteJournals
) {
432 sync_pb::EntitySpecifics bookmark_specifics
;
433 AddDefaultFieldValue(BOOKMARKS
, &bookmark_specifics
);
434 bookmark_specifics
.mutable_bookmark()->set_url("url");
436 // The first two IDs are server IDs.
437 Id id1
= TestIdFactory::FromNumber(1);
438 Id id2
= TestIdFactory::FromNumber(2);
439 // The third one is a client ID.
440 Id id3
= TestIdFactory::FromNumber(-3);
445 // Create 3 bookmark entries and save in database.
447 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
449 MutableEntry
item1(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "item1");
451 item1
.PutSpecifics(bookmark_specifics
);
452 item1
.PutServerSpecifics(bookmark_specifics
);
453 item1
.PutIsUnappliedUpdate(true);
454 item1
.PutBaseVersion(10);
455 handle1
= item1
.GetMetahandle();
457 MutableEntry
item2(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "item2");
459 item2
.PutSpecifics(bookmark_specifics
);
460 item2
.PutServerSpecifics(bookmark_specifics
);
461 item2
.PutIsUnappliedUpdate(true);
462 item2
.PutBaseVersion(10);
463 handle2
= item2
.GetMetahandle();
465 MutableEntry
item3(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "item3");
467 item3
.PutSpecifics(bookmark_specifics
);
468 item3
.PutServerSpecifics(bookmark_specifics
);
469 item3
.PutIsUnsynced(true);
470 handle3
= item3
.GetMetahandle();
472 ASSERT_EQ(OPENED
, SimulateSaveAndReloadDir());
475 { // Test adding and saving delete journals.
476 DeleteJournal
* delete_journal
= dir()->delete_journal();
478 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
479 EntryKernelSet journal_entries
;
480 delete_journal
->GetDeleteJournals(&trans
, BOOKMARKS
, &journal_entries
);
481 ASSERT_EQ(0u, journal_entries
.size());
483 // Set SERVER_IS_DEL of the entries to true and they should be added to
484 // delete journals, but only if the deletion is initiated in update e.g.
485 // IS_UNAPPLIED_UPDATE is also true.
486 MutableEntry
item1(&trans
, GET_BY_ID
, id1
);
487 ASSERT_TRUE(item1
.good());
488 item1
.PutServerIsDel(true);
489 MutableEntry
item2(&trans
, GET_BY_ID
, id2
);
490 ASSERT_TRUE(item2
.good());
491 item2
.PutServerIsDel(true);
492 MutableEntry
item3(&trans
, GET_BY_ID
, id3
);
493 ASSERT_TRUE(item3
.good());
494 item3
.PutServerIsDel(true);
495 // Expect only the first two items to be in the delete journal.
498 EXPECT_TRUE(delete_journal
->delete_journals_
.count(&tmp
));
500 EXPECT_TRUE(delete_journal
->delete_journals_
.count(&tmp
));
502 EXPECT_FALSE(delete_journal
->delete_journals_
.count(&tmp
));
505 // Save delete journals in database and verify memory clearing.
506 ASSERT_TRUE(dir()->SaveChanges());
508 ReadTransaction
trans(FROM_HERE
, dir().get());
509 EXPECT_EQ(0u, delete_journal
->GetDeleteJournalSize(&trans
));
511 ASSERT_EQ(OPENED
, SimulateSaveAndReloadDir());
516 // Test reading delete journals from database.
517 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
518 DeleteJournal
* delete_journal
= dir()->delete_journal();
519 EntryKernelSet journal_entries
;
520 delete_journal
->GetDeleteJournals(&trans
, BOOKMARKS
, &journal_entries
);
521 ASSERT_EQ(2u, journal_entries
.size());
523 tmp
.put(META_HANDLE
, handle1
);
524 EXPECT_TRUE(journal_entries
.count(&tmp
));
525 tmp
.put(META_HANDLE
, handle2
);
526 EXPECT_TRUE(journal_entries
.count(&tmp
));
527 tmp
.put(META_HANDLE
, handle3
);
528 EXPECT_FALSE(journal_entries
.count(&tmp
));
531 MetahandleSet to_purge
;
532 to_purge
.insert(handle2
);
533 delete_journal
->PurgeDeleteJournals(&trans
, to_purge
);
535 // Verify that item2 is purged from journals in memory and will be
536 // purged from database.
538 EXPECT_FALSE(delete_journal
->delete_journals_
.count(&tmp
));
539 EXPECT_EQ(1u, delete_journal
->delete_journals_to_purge_
.size());
540 EXPECT_TRUE(delete_journal
->delete_journals_to_purge_
.count(handle2
));
542 ASSERT_EQ(OPENED
, SimulateSaveAndReloadDir());
547 // Verify purged entry is gone in database.
548 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
549 DeleteJournal
* delete_journal
= dir()->delete_journal();
550 EntryKernelSet journal_entries
;
551 delete_journal
->GetDeleteJournals(&trans
, BOOKMARKS
, &journal_entries
);
552 ASSERT_EQ(1u, journal_entries
.size());
555 tmp
.put(META_HANDLE
, handle1
);
556 EXPECT_TRUE(journal_entries
.count(&tmp
));
558 // Undelete item1 (IS_UNAPPLIED_UPDATE shouldn't matter in this case).
559 MutableEntry
item1(&trans
, GET_BY_ID
, id1
);
560 ASSERT_TRUE(item1
.good());
561 item1
.PutIsUnappliedUpdate(false);
562 item1
.PutServerIsDel(false);
563 EXPECT_TRUE(delete_journal
->delete_journals_
.empty());
564 EXPECT_EQ(1u, delete_journal
->delete_journals_to_purge_
.size());
565 EXPECT_TRUE(delete_journal
->delete_journals_to_purge_
.count(handle1
));
567 ASSERT_EQ(OPENED
, SimulateSaveAndReloadDir());
571 // Verify undeleted entry is gone from database.
572 ReadTransaction
trans(FROM_HERE
, dir().get());
573 DeleteJournal
* delete_journal
= dir()->delete_journal();
574 ASSERT_EQ(0u, delete_journal
->GetDeleteJournalSize(&trans
));
578 TEST_F(SyncableDirectoryTest
, TestPurgeDeletedEntriesOnReload
) {
579 sync_pb::EntitySpecifics specifics
;
580 AddDefaultFieldValue(PREFERENCES
, &specifics
);
582 const int kClientCount
= 2;
583 const int kServerCount
= 5;
584 const int kTestCount
= kClientCount
+ kServerCount
;
585 int64 handles
[kTestCount
];
587 // The idea is to recreate various combinations of IDs, IS_DEL,
588 // IS_UNSYNCED, and IS_UNAPPLIED_UPDATE flags to test all combinations
589 // for DirectoryBackingStore::SafeToPurgeOnLoading.
590 // 0: client ID, IS_DEL, IS_UNSYNCED
591 // 1: client ID, IS_UNSYNCED
592 // 2: server ID, IS_DEL, IS_UNSYNCED, IS_UNAPPLIED_UPDATE
593 // 3: server ID, IS_DEL, IS_UNSYNCED
594 // 4: server ID, IS_DEL, IS_UNAPPLIED_UPDATE
595 // 5: server ID, IS_DEL
598 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
600 for (int i
= 0; i
< kTestCount
; i
++) {
601 std::string name
= base::StringPrintf("item%d", i
);
602 MutableEntry
item(&trans
, CREATE
, PREFERENCES
, trans
.root_id(), name
);
603 ASSERT_TRUE(item
.good());
605 handles
[i
] = item
.GetMetahandle();
607 if (i
< kClientCount
) {
608 item
.PutId(TestIdFactory::FromNumber(i
- kClientCount
));
610 item
.PutId(TestIdFactory::FromNumber(i
));
613 item
.PutUniqueClientTag(name
);
614 item
.PutIsUnsynced(true);
615 item
.PutSpecifics(specifics
);
616 item
.PutServerSpecifics(specifics
);
618 if (i
>= kClientCount
) {
619 item
.PutBaseVersion(10);
620 item
.PutServerVersion(10);
624 if (i
!= 1 && i
!= 6)
628 item
.PutIsUnsynced(false);
630 if (i
== 2 || i
== 4)
631 item
.PutIsUnappliedUpdate(true);
634 ASSERT_EQ(OPENED
, SimulateSaveAndReloadDir());
636 // Expect items 0 and 5 to be purged according to
637 // DirectoryBackingStore::SafeToPurgeOnLoading:
638 // - Item 0 is an item with IS_DEL flag and client ID.
639 // - Item 5 is an item with IS_DEL flag which has both
640 // IS_UNSYNCED and IS_UNAPPLIED_UPDATE unset.
641 std::vector
<int64
> expected_purged
;
642 expected_purged
.push_back(0);
643 expected_purged
.push_back(5);
645 std::vector
<int64
> actually_purged
;
647 ReadTransaction
trans(FROM_HERE
, dir().get());
648 for (int i
= 0; i
< kTestCount
; i
++) {
649 Entry
item(&trans
, GET_BY_HANDLE
, handles
[i
]);
651 actually_purged
.push_back(i
);
656 EXPECT_EQ(expected_purged
, actually_purged
);
659 TEST_F(SyncableDirectoryTest
, TestBasicLookupNonExistantID
) {
660 ReadTransaction
rtrans(FROM_HERE
, dir().get());
661 Entry
e(&rtrans
, GET_BY_ID
, TestIdFactory::FromNumber(-99));
662 ASSERT_FALSE(e
.good());
665 TEST_F(SyncableDirectoryTest
, TestBasicLookupValidID
) {
666 CreateEntry(BOOKMARKS
, "rtc");
667 ReadTransaction
rtrans(FROM_HERE
, dir().get());
668 Entry
e(&rtrans
, GET_BY_ID
, TestIdFactory::FromNumber(-99));
669 ASSERT_TRUE(e
.good());
672 TEST_F(SyncableDirectoryTest
, TestDelete
) {
673 std::string name
= "peanut butter jelly time";
674 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
675 MutableEntry
e1(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), name
);
676 ASSERT_TRUE(e1
.good());
678 MutableEntry
e2(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), name
);
679 ASSERT_TRUE(e2
.good());
681 MutableEntry
e3(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), name
);
682 ASSERT_TRUE(e3
.good());
694 TEST_F(SyncableDirectoryTest
, TestGetUnsynced
) {
695 Directory::Metahandles handles
;
696 int64 handle1
, handle2
;
698 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
700 dir()->GetUnsyncedMetaHandles(&trans
, &handles
);
701 ASSERT_TRUE(0 == handles
.size());
703 MutableEntry
e1(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "abba");
704 ASSERT_TRUE(e1
.good());
705 handle1
= e1
.GetMetahandle();
706 e1
.PutBaseVersion(1);
708 e1
.PutId(TestIdFactory::FromNumber(101));
710 MutableEntry
e2(&trans
, CREATE
, BOOKMARKS
, e1
.GetId(), "bread");
711 ASSERT_TRUE(e2
.good());
712 handle2
= e2
.GetMetahandle();
713 e2
.PutBaseVersion(1);
714 e2
.PutId(TestIdFactory::FromNumber(102));
716 dir()->SaveChanges();
718 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
720 dir()->GetUnsyncedMetaHandles(&trans
, &handles
);
721 ASSERT_TRUE(0 == handles
.size());
723 MutableEntry
e3(&trans
, GET_BY_HANDLE
, handle1
);
724 ASSERT_TRUE(e3
.good());
725 e3
.PutIsUnsynced(true);
727 dir()->SaveChanges();
729 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
730 dir()->GetUnsyncedMetaHandles(&trans
, &handles
);
731 ASSERT_TRUE(1 == handles
.size());
732 ASSERT_TRUE(handle1
== handles
[0]);
734 MutableEntry
e4(&trans
, GET_BY_HANDLE
, handle2
);
735 ASSERT_TRUE(e4
.good());
736 e4
.PutIsUnsynced(true);
738 dir()->SaveChanges();
740 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
741 dir()->GetUnsyncedMetaHandles(&trans
, &handles
);
742 ASSERT_TRUE(2 == handles
.size());
743 if (handle1
== handles
[0]) {
744 ASSERT_TRUE(handle2
== handles
[1]);
746 ASSERT_TRUE(handle2
== handles
[0]);
747 ASSERT_TRUE(handle1
== handles
[1]);
750 MutableEntry
e5(&trans
, GET_BY_HANDLE
, handle1
);
751 ASSERT_TRUE(e5
.good());
752 ASSERT_TRUE(e5
.GetIsUnsynced());
753 ASSERT_TRUE(e5
.PutIsUnsynced(false));
754 ASSERT_FALSE(e5
.GetIsUnsynced());
756 dir()->SaveChanges();
758 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
759 dir()->GetUnsyncedMetaHandles(&trans
, &handles
);
760 ASSERT_TRUE(1 == handles
.size());
761 ASSERT_TRUE(handle2
== handles
[0]);
765 TEST_F(SyncableDirectoryTest
, TestGetUnappliedUpdates
) {
766 std::vector
<int64
> handles
;
767 int64 handle1
, handle2
;
768 const FullModelTypeSet all_types
= FullModelTypeSet::All();
770 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
772 dir()->GetUnappliedUpdateMetaHandles(&trans
, all_types
, &handles
);
773 ASSERT_TRUE(0 == handles
.size());
775 MutableEntry
e1(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "abba");
776 ASSERT_TRUE(e1
.good());
777 handle1
= e1
.GetMetahandle();
778 e1
.PutIsUnappliedUpdate(false);
779 e1
.PutBaseVersion(1);
780 e1
.PutId(TestIdFactory::FromNumber(101));
783 MutableEntry
e2(&trans
, CREATE
, BOOKMARKS
, e1
.GetId(), "bread");
784 ASSERT_TRUE(e2
.good());
785 handle2
= e2
.GetMetahandle();
786 e2
.PutIsUnappliedUpdate(false);
787 e2
.PutBaseVersion(1);
788 e2
.PutId(TestIdFactory::FromNumber(102));
790 dir()->SaveChanges();
792 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
794 dir()->GetUnappliedUpdateMetaHandles(&trans
, all_types
, &handles
);
795 ASSERT_TRUE(0 == handles
.size());
797 MutableEntry
e3(&trans
, GET_BY_HANDLE
, handle1
);
798 ASSERT_TRUE(e3
.good());
799 e3
.PutIsUnappliedUpdate(true);
801 dir()->SaveChanges();
803 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
804 dir()->GetUnappliedUpdateMetaHandles(&trans
, all_types
, &handles
);
805 ASSERT_TRUE(1 == handles
.size());
806 ASSERT_TRUE(handle1
== handles
[0]);
808 MutableEntry
e4(&trans
, GET_BY_HANDLE
, handle2
);
809 ASSERT_TRUE(e4
.good());
810 e4
.PutIsUnappliedUpdate(true);
812 dir()->SaveChanges();
814 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
815 dir()->GetUnappliedUpdateMetaHandles(&trans
, all_types
, &handles
);
816 ASSERT_TRUE(2 == handles
.size());
817 if (handle1
== handles
[0]) {
818 ASSERT_TRUE(handle2
== handles
[1]);
820 ASSERT_TRUE(handle2
== handles
[0]);
821 ASSERT_TRUE(handle1
== handles
[1]);
824 MutableEntry
e5(&trans
, GET_BY_HANDLE
, handle1
);
825 ASSERT_TRUE(e5
.good());
826 e5
.PutIsUnappliedUpdate(false);
828 dir()->SaveChanges();
830 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
831 dir()->GetUnappliedUpdateMetaHandles(&trans
, all_types
, &handles
);
832 ASSERT_TRUE(1 == handles
.size());
833 ASSERT_TRUE(handle2
== handles
[0]);
837 TEST_F(SyncableDirectoryTest
, DeleteBug_531383
) {
838 // Try to evoke a check failure...
839 TestIdFactory id_factory
;
840 int64 grandchild_handle
;
842 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
843 MutableEntry
parent(&wtrans
, CREATE
, BOOKMARKS
, id_factory
.root(), "Bob");
844 ASSERT_TRUE(parent
.good());
845 parent
.PutIsDir(true);
846 parent
.PutId(id_factory
.NewServerId());
847 parent
.PutBaseVersion(1);
848 MutableEntry
child(&wtrans
, CREATE
, BOOKMARKS
, parent
.GetId(), "Bob");
849 ASSERT_TRUE(child
.good());
850 child
.PutIsDir(true);
851 child
.PutId(id_factory
.NewServerId());
852 child
.PutBaseVersion(1);
853 MutableEntry
grandchild(&wtrans
, CREATE
, BOOKMARKS
, child
.GetId(), "Bob");
854 ASSERT_TRUE(grandchild
.good());
855 grandchild
.PutId(id_factory
.NewServerId());
856 grandchild
.PutBaseVersion(1);
857 grandchild
.PutIsDel(true);
858 MutableEntry
twin(&wtrans
, CREATE
, BOOKMARKS
, child
.GetId(), "Bob");
859 ASSERT_TRUE(twin
.good());
861 grandchild
.PutIsDel(false);
863 grandchild_handle
= grandchild
.GetMetahandle();
865 dir()->SaveChanges();
867 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
868 MutableEntry
grandchild(&wtrans
, GET_BY_HANDLE
, grandchild_handle
);
869 grandchild
.PutIsDel(true); // Used to CHECK fail here.
873 TEST_F(SyncableDirectoryTest
, TestIsLegalNewParent
) {
874 TestIdFactory id_factory
;
875 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
876 Entry
root(&wtrans
, GET_BY_ID
, id_factory
.root());
877 ASSERT_TRUE(root
.good());
878 MutableEntry
parent(&wtrans
, CREATE
, BOOKMARKS
, root
.GetId(), "Bob");
879 ASSERT_TRUE(parent
.good());
880 parent
.PutIsDir(true);
881 parent
.PutId(id_factory
.NewServerId());
882 parent
.PutBaseVersion(1);
883 MutableEntry
child(&wtrans
, CREATE
, BOOKMARKS
, parent
.GetId(), "Bob");
884 ASSERT_TRUE(child
.good());
885 child
.PutIsDir(true);
886 child
.PutId(id_factory
.NewServerId());
887 child
.PutBaseVersion(1);
888 MutableEntry
grandchild(&wtrans
, CREATE
, BOOKMARKS
, child
.GetId(), "Bob");
889 ASSERT_TRUE(grandchild
.good());
890 grandchild
.PutId(id_factory
.NewServerId());
891 grandchild
.PutBaseVersion(1);
893 MutableEntry
parent2(&wtrans
, CREATE
, BOOKMARKS
, root
.GetId(), "Pete");
894 ASSERT_TRUE(parent2
.good());
895 parent2
.PutIsDir(true);
896 parent2
.PutId(id_factory
.NewServerId());
897 parent2
.PutBaseVersion(1);
898 MutableEntry
child2(&wtrans
, CREATE
, BOOKMARKS
, parent2
.GetId(), "Pete");
899 ASSERT_TRUE(child2
.good());
900 child2
.PutIsDir(true);
901 child2
.PutId(id_factory
.NewServerId());
902 child2
.PutBaseVersion(1);
903 MutableEntry
grandchild2(&wtrans
, CREATE
, BOOKMARKS
, child2
.GetId(), "Pete");
904 ASSERT_TRUE(grandchild2
.good());
905 grandchild2
.PutId(id_factory
.NewServerId());
906 grandchild2
.PutBaseVersion(1);
914 // grandchild grandchild2
915 ASSERT_TRUE(IsLegalNewParent(child
, root
));
916 ASSERT_TRUE(IsLegalNewParent(child
, parent
));
917 ASSERT_FALSE(IsLegalNewParent(child
, child
));
918 ASSERT_FALSE(IsLegalNewParent(child
, grandchild
));
919 ASSERT_TRUE(IsLegalNewParent(child
, parent2
));
920 ASSERT_TRUE(IsLegalNewParent(child
, grandchild2
));
921 ASSERT_FALSE(IsLegalNewParent(parent
, grandchild
));
922 ASSERT_FALSE(IsLegalNewParent(root
, grandchild
));
923 ASSERT_FALSE(IsLegalNewParent(parent
, grandchild
));
926 TEST_F(SyncableDirectoryTest
, TestEntryIsInFolder
) {
927 // Create a subdir and an entry.
929 syncable::Id folder_id
;
930 syncable::Id entry_id
;
931 std::string entry_name
= "entry";
934 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
935 MutableEntry
folder(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "folder");
936 ASSERT_TRUE(folder
.good());
937 folder
.PutIsDir(true);
938 EXPECT_TRUE(folder
.PutIsUnsynced(true));
939 folder_id
= folder
.GetId();
941 MutableEntry
entry(&trans
, CREATE
, BOOKMARKS
, folder
.GetId(), entry_name
);
942 ASSERT_TRUE(entry
.good());
943 entry_handle
= entry
.GetMetahandle();
944 entry
.PutIsUnsynced(true);
945 entry_id
= entry
.GetId();
948 // Make sure we can find the entry in the folder.
950 ReadTransaction
trans(FROM_HERE
, dir().get());
951 EXPECT_EQ(0, CountEntriesWithName(&trans
, trans
.root_id(), entry_name
));
952 EXPECT_EQ(1, CountEntriesWithName(&trans
, folder_id
, entry_name
));
954 Entry
entry(&trans
, GET_BY_ID
, entry_id
);
955 ASSERT_TRUE(entry
.good());
956 EXPECT_EQ(entry_handle
, entry
.GetMetahandle());
957 EXPECT_TRUE(entry
.GetNonUniqueName() == entry_name
);
958 EXPECT_TRUE(entry
.GetParentId() == folder_id
);
962 TEST_F(SyncableDirectoryTest
, TestParentIdIndexUpdate
) {
963 std::string child_name
= "child";
965 WriteTransaction
wt(FROM_HERE
, UNITTEST
, dir().get());
966 MutableEntry
parent_folder(&wt
, CREATE
, BOOKMARKS
, wt
.root_id(), "folder1");
967 parent_folder
.PutIsUnsynced(true);
968 parent_folder
.PutIsDir(true);
970 MutableEntry
parent_folder2(&wt
, CREATE
, BOOKMARKS
, wt
.root_id(), "folder2");
971 parent_folder2
.PutIsUnsynced(true);
972 parent_folder2
.PutIsDir(true);
974 MutableEntry
child(&wt
, CREATE
, BOOKMARKS
, parent_folder
.GetId(), child_name
);
975 child
.PutIsDir(true);
976 child
.PutIsUnsynced(true);
978 ASSERT_TRUE(child
.good());
980 EXPECT_EQ(0, CountEntriesWithName(&wt
, wt
.root_id(), child_name
));
981 EXPECT_EQ(parent_folder
.GetId(), child
.GetParentId());
982 EXPECT_EQ(1, CountEntriesWithName(&wt
, parent_folder
.GetId(), child_name
));
983 EXPECT_EQ(0, CountEntriesWithName(&wt
, parent_folder2
.GetId(), child_name
));
984 child
.PutParentId(parent_folder2
.GetId());
985 EXPECT_EQ(parent_folder2
.GetId(), child
.GetParentId());
986 EXPECT_EQ(0, CountEntriesWithName(&wt
, parent_folder
.GetId(), child_name
));
987 EXPECT_EQ(1, CountEntriesWithName(&wt
, parent_folder2
.GetId(), child_name
));
990 TEST_F(SyncableDirectoryTest
, TestNoReindexDeletedItems
) {
991 std::string folder_name
= "folder";
992 std::string new_name
= "new_name";
994 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
995 MutableEntry
folder(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), folder_name
);
996 ASSERT_TRUE(folder
.good());
997 folder
.PutIsDir(true);
998 folder
.PutIsDel(true);
1000 EXPECT_EQ(0, CountEntriesWithName(&trans
, trans
.root_id(), folder_name
));
1002 MutableEntry
deleted(&trans
, GET_BY_ID
, folder
.GetId());
1003 ASSERT_TRUE(deleted
.good());
1004 deleted
.PutParentId(trans
.root_id());
1005 deleted
.PutNonUniqueName(new_name
);
1007 EXPECT_EQ(0, CountEntriesWithName(&trans
, trans
.root_id(), folder_name
));
1008 EXPECT_EQ(0, CountEntriesWithName(&trans
, trans
.root_id(), new_name
));
1011 TEST_F(SyncableDirectoryTest
, TestCaseChangeRename
) {
1012 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1013 MutableEntry
folder(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "CaseChange");
1014 ASSERT_TRUE(folder
.good());
1015 folder
.PutParentId(trans
.root_id());
1016 folder
.PutNonUniqueName("CASECHANGE");
1017 folder
.PutIsDel(true);
1020 // Create items of each model type, and check that GetModelType and
1021 // GetServerModelType return the right value.
1022 TEST_F(SyncableDirectoryTest
, GetModelType
) {
1023 TestIdFactory id_factory
;
1024 ModelTypeSet protocol_types
= ProtocolTypes();
1025 for (ModelTypeSet::Iterator iter
= protocol_types
.First(); iter
.Good();
1027 ModelType datatype
= iter
.Get();
1028 SCOPED_TRACE(testing::Message("Testing model type ") << datatype
);
1031 case TOP_LEVEL_FOLDER
:
1032 continue; // Datatype isn't a function of Specifics.
1036 sync_pb::EntitySpecifics specifics
;
1037 AddDefaultFieldValue(datatype
, &specifics
);
1039 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1041 MutableEntry
folder(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "Folder");
1042 ASSERT_TRUE(folder
.good());
1043 folder
.PutId(id_factory
.NewServerId());
1044 folder
.PutSpecifics(specifics
);
1045 folder
.PutBaseVersion(1);
1046 folder
.PutIsDir(true);
1047 folder
.PutIsDel(false);
1048 ASSERT_EQ(datatype
, folder
.GetModelType());
1050 MutableEntry
item(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "Item");
1051 ASSERT_TRUE(item
.good());
1052 item
.PutId(id_factory
.NewServerId());
1053 item
.PutSpecifics(specifics
);
1054 item
.PutBaseVersion(1);
1055 item
.PutIsDir(false);
1056 item
.PutIsDel(false);
1057 ASSERT_EQ(datatype
, item
.GetModelType());
1059 // It's critical that deletion records retain their datatype, so that
1060 // they can be dispatched to the appropriate change processor.
1061 MutableEntry
deleted_item(
1062 &trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "Deleted Item");
1063 ASSERT_TRUE(item
.good());
1064 deleted_item
.PutId(id_factory
.NewServerId());
1065 deleted_item
.PutSpecifics(specifics
);
1066 deleted_item
.PutBaseVersion(1);
1067 deleted_item
.PutIsDir(false);
1068 deleted_item
.PutIsDel(true);
1069 ASSERT_EQ(datatype
, deleted_item
.GetModelType());
1071 MutableEntry
server_folder(
1072 &trans
, CREATE_NEW_UPDATE_ITEM
, id_factory
.NewServerId());
1073 ASSERT_TRUE(server_folder
.good());
1074 server_folder
.PutServerSpecifics(specifics
);
1075 server_folder
.PutBaseVersion(1);
1076 server_folder
.PutServerIsDir(true);
1077 server_folder
.PutServerIsDel(false);
1078 ASSERT_EQ(datatype
, server_folder
.GetServerModelType());
1080 MutableEntry
server_item(
1081 &trans
, CREATE_NEW_UPDATE_ITEM
, id_factory
.NewServerId());
1082 ASSERT_TRUE(server_item
.good());
1083 server_item
.PutServerSpecifics(specifics
);
1084 server_item
.PutBaseVersion(1);
1085 server_item
.PutServerIsDir(false);
1086 server_item
.PutServerIsDel(false);
1087 ASSERT_EQ(datatype
, server_item
.GetServerModelType());
1089 sync_pb::SyncEntity folder_entity
;
1090 folder_entity
.set_id_string(SyncableIdToProto(id_factory
.NewServerId()));
1091 folder_entity
.set_deleted(false);
1092 folder_entity
.set_folder(true);
1093 folder_entity
.mutable_specifics()->CopyFrom(specifics
);
1094 ASSERT_EQ(datatype
, GetModelType(folder_entity
));
1096 sync_pb::SyncEntity item_entity
;
1097 item_entity
.set_id_string(SyncableIdToProto(id_factory
.NewServerId()));
1098 item_entity
.set_deleted(false);
1099 item_entity
.set_folder(false);
1100 item_entity
.mutable_specifics()->CopyFrom(specifics
);
1101 ASSERT_EQ(datatype
, GetModelType(item_entity
));
1105 // A test that roughly mimics the directory interaction that occurs when a
1106 // bookmark folder and entry are created then synced for the first time. It is
1107 // a more common variant of the 'DeletedAndUnsyncedChild' scenario tested below.
1108 TEST_F(SyncableDirectoryTest
, ChangeEntryIDAndUpdateChildren_ParentAndChild
) {
1109 TestIdFactory id_factory
;
1114 // Create two client-side items, a parent and child.
1115 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1117 MutableEntry
parent(&trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "parent");
1118 parent
.PutIsDir(true);
1119 parent
.PutIsUnsynced(true);
1121 MutableEntry
child(&trans
, CREATE
, BOOKMARKS
, parent
.GetId(), "child");
1122 child
.PutIsUnsynced(true);
1124 orig_parent_id
= parent
.GetId();
1125 orig_child_id
= child
.GetId();
1129 // Simulate what happens after committing two items. Their IDs will be
1130 // replaced with server IDs. The child is renamed first, then the parent.
1131 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1133 MutableEntry
parent(&trans
, GET_BY_ID
, orig_parent_id
);
1134 MutableEntry
child(&trans
, GET_BY_ID
, orig_child_id
);
1136 ChangeEntryIDAndUpdateChildren(&trans
, &child
, id_factory
.NewServerId());
1137 child
.PutIsUnsynced(false);
1138 child
.PutBaseVersion(1);
1139 child
.PutServerVersion(1);
1141 ChangeEntryIDAndUpdateChildren(&trans
, &parent
, id_factory
.NewServerId());
1142 parent
.PutIsUnsynced(false);
1143 parent
.PutBaseVersion(1);
1144 parent
.PutServerVersion(1);
1147 // Final check for validity.
1148 EXPECT_EQ(OPENED
, SimulateSaveAndReloadDir());
1151 // A test that roughly mimics the directory interaction that occurs when a
1152 // type root folder is created locally and then re-created (updated) from the
1154 TEST_F(SyncableDirectoryTest
, ChangeEntryIDAndUpdateChildren_ImplicitParent
) {
1155 TestIdFactory id_factory
;
1160 // Create two client-side items, a parent and child.
1161 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1163 MutableEntry
parent(&trans
, CREATE
, PREFERENCES
, id_factory
.root(),
1165 parent
.PutIsDir(true);
1166 parent
.PutIsUnsynced(true);
1168 // The child has unset parent ID. The parent is inferred from the type.
1169 MutableEntry
child(&trans
, CREATE
, PREFERENCES
, "child");
1170 child
.PutIsUnsynced(true);
1172 orig_parent_id
= parent
.GetId();
1173 child_id
= child
.GetId();
1177 // Simulate what happens after committing two items. Their IDs will be
1178 // replaced with server IDs. The child is renamed first, then the parent.
1179 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1181 MutableEntry
parent(&trans
, GET_BY_ID
, orig_parent_id
);
1183 ChangeEntryIDAndUpdateChildren(&trans
, &parent
, id_factory
.NewServerId());
1184 parent
.PutIsUnsynced(false);
1185 parent
.PutBaseVersion(1);
1186 parent
.PutServerVersion(1);
1189 // Final check for validity.
1190 EXPECT_EQ(OPENED
, SimulateSaveAndReloadDir());
1192 // Verify that child's PARENT_ID hasn't been updated.
1194 ReadTransaction
trans(FROM_HERE
, dir().get());
1195 Entry
child(&trans
, GET_BY_ID
, child_id
);
1196 EXPECT_TRUE(child
.good());
1197 EXPECT_TRUE(child
.GetParentId().IsNull());
1201 // A test based on the scenario where we create a bookmark folder and entry
1202 // locally, but with a twist. In this case, the bookmark is deleted before we
1203 // are able to sync either it or its parent folder. This scenario used to cause
1204 // directory corruption, see crbug.com/125381.
1205 TEST_F(SyncableDirectoryTest
,
1206 ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild
) {
1207 TestIdFactory id_factory
;
1212 // Create two client-side items, a parent and child.
1213 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1215 MutableEntry
parent(&trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "parent");
1216 parent
.PutIsDir(true);
1217 parent
.PutIsUnsynced(true);
1219 MutableEntry
child(&trans
, CREATE
, BOOKMARKS
, parent
.GetId(), "child");
1220 child
.PutIsUnsynced(true);
1222 orig_parent_id
= parent
.GetId();
1223 orig_child_id
= child
.GetId();
1227 // Delete the child.
1228 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1230 MutableEntry
child(&trans
, GET_BY_ID
, orig_child_id
);
1231 child
.PutIsDel(true);
1235 // Simulate what happens after committing the parent. Its ID will be
1236 // replaced with server a ID.
1237 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1239 MutableEntry
parent(&trans
, GET_BY_ID
, orig_parent_id
);
1241 ChangeEntryIDAndUpdateChildren(&trans
, &parent
, id_factory
.NewServerId());
1242 parent
.PutIsUnsynced(false);
1243 parent
.PutBaseVersion(1);
1244 parent
.PutServerVersion(1);
1247 // Final check for validity.
1248 EXPECT_EQ(OPENED
, SimulateSaveAndReloadDir());
1251 // Ask the directory to generate a unique ID. Close and re-open the database
1252 // without saving, then ask for another unique ID. Verify IDs are not reused.
1253 // This scenario simulates a crash within the first few seconds of operation.
1254 TEST_F(SyncableDirectoryTest
, LocalIdReuseTest
) {
1255 Id pre_crash_id
= dir()->NextId();
1256 SimulateCrashAndReloadDir();
1257 Id post_crash_id
= dir()->NextId();
1258 EXPECT_NE(pre_crash_id
, post_crash_id
);
1261 // Ask the directory to generate a unique ID. Save the directory. Close and
1262 // re-open the database without saving, then ask for another unique ID. Verify
1263 // IDs are not reused. This scenario simulates a steady-state crash.
1264 TEST_F(SyncableDirectoryTest
, LocalIdReuseTestWithSave
) {
1265 Id pre_crash_id
= dir()->NextId();
1266 dir()->SaveChanges();
1267 SimulateCrashAndReloadDir();
1268 Id post_crash_id
= dir()->NextId();
1269 EXPECT_NE(pre_crash_id
, post_crash_id
);
1272 // Ensure that the unsynced, is_del and server unkown entries that may have been
1273 // left in the database by old clients will be deleted when we open the old
1275 TEST_F(SyncableDirectoryTest
, OldClientLeftUnsyncedDeletedLocalItem
) {
1276 // We must create an entry with the offending properties. This is done with
1277 // some abuse of the MutableEntry's API; it doesn't expect us to modify an
1278 // item after it is deleted. If this hack becomes impractical we will need to
1279 // find a new way to simulate this scenario.
1281 TestIdFactory id_factory
;
1283 // Happy-path: These valid entries should not get deleted.
1284 Id server_knows_id
= id_factory
.NewServerId();
1285 Id not_is_del_id
= id_factory
.NewLocalId();
1287 // The ID of the entry which will be unsynced, is_del and !ServerKnows().
1288 Id zombie_id
= id_factory
.NewLocalId();
1290 // We're about to do some bad things. Tell the directory verification
1291 // routines to look the other way.
1292 dir()->SetInvariantCheckLevel(OFF
);
1295 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1297 // Create an uncommitted tombstone entry.
1298 MutableEntry
server_knows(
1299 &trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "server_knows");
1300 server_knows
.PutId(server_knows_id
);
1301 server_knows
.PutIsUnsynced(true);
1302 server_knows
.PutIsDel(true);
1303 server_knows
.PutBaseVersion(5);
1304 server_knows
.PutServerVersion(4);
1306 // Create a valid update entry.
1307 MutableEntry
not_is_del(
1308 &trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "not_is_del");
1309 not_is_del
.PutId(not_is_del_id
);
1310 not_is_del
.PutIsDel(false);
1311 not_is_del
.PutIsUnsynced(true);
1313 // Create a tombstone which should never be sent to the server because the
1314 // server never knew about the item's existence.
1316 // New clients should never put entries into this state. We work around
1317 // this by setting IS_DEL before setting IS_UNSYNCED, something which the
1318 // client should never do in practice.
1319 MutableEntry
zombie(&trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "zombie");
1320 zombie
.PutId(zombie_id
);
1321 zombie
.PutIsDel(true);
1322 zombie
.PutIsUnsynced(true);
1325 ASSERT_EQ(OPENED
, SimulateSaveAndReloadDir());
1328 ReadTransaction
trans(FROM_HERE
, dir().get());
1330 // The directory loading routines should have cleaned things up, making it
1331 // safe to check invariants once again.
1332 dir()->FullyCheckTreeInvariants(&trans
);
1334 Entry
server_knows(&trans
, GET_BY_ID
, server_knows_id
);
1335 EXPECT_TRUE(server_knows
.good());
1337 Entry
not_is_del(&trans
, GET_BY_ID
, not_is_del_id
);
1338 EXPECT_TRUE(not_is_del
.good());
1340 Entry
zombie(&trans
, GET_BY_ID
, zombie_id
);
1341 EXPECT_FALSE(zombie
.good());
1345 TEST_F(SyncableDirectoryTest
, PositionWithNullSurvivesSaveAndReload
) {
1346 TestIdFactory id_factory
;
1348 const char null_cstr
[] = "\0null\0test";
1349 std::string
null_str(null_cstr
, arraysize(null_cstr
) - 1);
1350 // Pad up to the minimum length with 0x7f characters, then add a string that
1351 // contains a few NULLs to the end. This is slightly wrong, since the suffix
1352 // part of a UniquePosition shouldn't contain NULLs, but it's good enough for
1354 std::string suffix
=
1355 std::string(UniquePosition::kSuffixLength
- null_str
.length(), '\x7f') +
1357 UniquePosition null_pos
= UniquePosition::FromInt64(10, suffix
);
1360 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1362 MutableEntry
parent(&trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "parent");
1363 parent
.PutIsDir(true);
1364 parent
.PutIsUnsynced(true);
1366 MutableEntry
child(&trans
, CREATE
, BOOKMARKS
, parent
.GetId(), "child");
1367 child
.PutIsUnsynced(true);
1368 child
.PutUniquePosition(null_pos
);
1369 child
.PutServerUniquePosition(null_pos
);
1371 null_child_id
= child
.GetId();
1374 EXPECT_EQ(OPENED
, SimulateSaveAndReloadDir());
1377 ReadTransaction
trans(FROM_HERE
, dir().get());
1379 Entry
null_ordinal_child(&trans
, GET_BY_ID
, null_child_id
);
1380 EXPECT_TRUE(null_pos
.Equals(null_ordinal_child
.GetUniquePosition()));
1381 EXPECT_TRUE(null_pos
.Equals(null_ordinal_child
.GetServerUniquePosition()));
1385 // Any item with BOOKMARKS in their local specifics should have a valid local
1386 // unique position. If there is an item in the loaded DB that does not match
1387 // this criteria, we consider the whole DB to be corrupt.
1388 TEST_F(SyncableDirectoryTest
, BadPositionCountsAsCorruption
) {
1389 TestIdFactory id_factory
;
1392 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1394 MutableEntry
parent(&trans
, CREATE
, BOOKMARKS
, id_factory
.root(), "parent");
1395 parent
.PutIsDir(true);
1396 parent
.PutIsUnsynced(true);
1398 // The code is littered with DCHECKs that try to stop us from doing what
1399 // we're about to do. Our work-around is to create a bookmark based on
1400 // a server update, then update its local specifics without updating its
1401 // local unique position.
1404 &trans
, CREATE_NEW_UPDATE_ITEM
, id_factory
.MakeServer("child"));
1405 sync_pb::EntitySpecifics specifics
;
1406 AddDefaultFieldValue(BOOKMARKS
, &specifics
);
1407 child
.PutIsUnappliedUpdate(true);
1408 child
.PutSpecifics(specifics
);
1410 EXPECT_TRUE(child
.ShouldMaintainPosition());
1411 EXPECT_TRUE(!child
.GetUniquePosition().IsValid());
1414 EXPECT_EQ(FAILED_DATABASE_CORRUPT
, SimulateSaveAndReloadDir());
1417 TEST_F(SyncableDirectoryTest
, General
) {
1418 int64 written_metahandle
;
1419 const Id id
= TestIdFactory::FromNumber(99);
1420 std::string name
= "Jeff";
1421 // Test simple read operations on an empty DB.
1423 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1424 Entry
e(&rtrans
, GET_BY_ID
, id
);
1425 ASSERT_FALSE(e
.good()); // Hasn't been written yet.
1427 Directory::Metahandles child_handles
;
1428 dir()->GetChildHandlesById(&rtrans
, rtrans
.root_id(), &child_handles
);
1429 EXPECT_TRUE(child_handles
.empty());
1432 // Test creating a new meta entry.
1434 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
1435 MutableEntry
me(&wtrans
, CREATE
, BOOKMARKS
, wtrans
.root_id(), name
);
1436 ASSERT_TRUE(me
.good());
1438 me
.PutBaseVersion(1);
1439 written_metahandle
= me
.GetMetahandle();
1442 // Test GetChildHandles* after something is now in the DB.
1443 // Also check that GET_BY_ID works.
1445 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1446 Entry
e(&rtrans
, GET_BY_ID
, id
);
1447 ASSERT_TRUE(e
.good());
1449 Directory::Metahandles child_handles
;
1450 dir()->GetChildHandlesById(&rtrans
, rtrans
.root_id(), &child_handles
);
1451 EXPECT_EQ(1u, child_handles
.size());
1453 for (Directory::Metahandles::iterator i
= child_handles
.begin();
1454 i
!= child_handles
.end(); ++i
) {
1455 EXPECT_EQ(*i
, written_metahandle
);
1459 // Test writing data to an entity. Also check that GET_BY_HANDLE works.
1460 static const char s
[] = "Hello World.";
1462 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1463 MutableEntry
e(&trans
, GET_BY_HANDLE
, written_metahandle
);
1464 ASSERT_TRUE(e
.good());
1465 PutDataAsBookmarkFavicon(&trans
, &e
, s
, sizeof(s
));
1468 // Test reading back the contents that we just wrote.
1470 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1471 MutableEntry
e(&trans
, GET_BY_HANDLE
, written_metahandle
);
1472 ASSERT_TRUE(e
.good());
1473 ExpectDataFromBookmarkFaviconEquals(&trans
, &e
, s
, sizeof(s
));
1476 // Verify it exists in the folder.
1478 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1479 EXPECT_EQ(1, CountEntriesWithName(&rtrans
, rtrans
.root_id(), name
));
1484 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1485 MutableEntry
e(&trans
, GET_BY_HANDLE
, written_metahandle
);
1488 EXPECT_EQ(0, CountEntriesWithName(&trans
, trans
.root_id(), name
));
1491 dir()->SaveChanges();
1494 TEST_F(SyncableDirectoryTest
, ChildrenOps
) {
1495 int64 written_metahandle
;
1496 const Id id
= TestIdFactory::FromNumber(99);
1497 std::string name
= "Jeff";
1499 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1500 Entry
e(&rtrans
, GET_BY_ID
, id
);
1501 ASSERT_FALSE(e
.good()); // Hasn't been written yet.
1503 Entry
root(&rtrans
, GET_BY_ID
, rtrans
.root_id());
1504 ASSERT_TRUE(root
.good());
1505 EXPECT_FALSE(dir()->HasChildren(&rtrans
, rtrans
.root_id()));
1506 EXPECT_TRUE(root
.GetFirstChildId().IsNull());
1510 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
1511 MutableEntry
me(&wtrans
, CREATE
, BOOKMARKS
, wtrans
.root_id(), name
);
1512 ASSERT_TRUE(me
.good());
1514 me
.PutBaseVersion(1);
1515 written_metahandle
= me
.GetMetahandle();
1518 // Test children ops after something is now in the DB.
1520 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1521 Entry
e(&rtrans
, GET_BY_ID
, id
);
1522 ASSERT_TRUE(e
.good());
1524 Entry
child(&rtrans
, GET_BY_HANDLE
, written_metahandle
);
1525 ASSERT_TRUE(child
.good());
1527 Entry
root(&rtrans
, GET_BY_ID
, rtrans
.root_id());
1528 ASSERT_TRUE(root
.good());
1529 EXPECT_TRUE(dir()->HasChildren(&rtrans
, rtrans
.root_id()));
1530 EXPECT_EQ(e
.GetId(), root
.GetFirstChildId());
1534 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
1535 MutableEntry
me(&wtrans
, GET_BY_HANDLE
, written_metahandle
);
1536 ASSERT_TRUE(me
.good());
1540 // Test children ops after the children have been deleted.
1542 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1543 Entry
e(&rtrans
, GET_BY_ID
, id
);
1544 ASSERT_TRUE(e
.good());
1546 Entry
root(&rtrans
, GET_BY_ID
, rtrans
.root_id());
1547 ASSERT_TRUE(root
.good());
1548 EXPECT_FALSE(dir()->HasChildren(&rtrans
, rtrans
.root_id()));
1549 EXPECT_TRUE(root
.GetFirstChildId().IsNull());
1552 dir()->SaveChanges();
1555 TEST_F(SyncableDirectoryTest
, ClientIndexRebuildsProperly
) {
1556 int64 written_metahandle
;
1557 TestIdFactory factory
;
1558 const Id id
= factory
.NewServerId();
1559 std::string name
= "cheesepuffs";
1560 std::string tag
= "dietcoke";
1562 // Test creating a new meta entry.
1564 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
1565 MutableEntry
me(&wtrans
, CREATE
, BOOKMARKS
, wtrans
.root_id(), name
);
1566 ASSERT_TRUE(me
.good());
1568 me
.PutBaseVersion(1);
1569 me
.PutUniqueClientTag(tag
);
1570 written_metahandle
= me
.GetMetahandle();
1572 dir()->SaveChanges();
1574 // Close and reopen, causing index regeneration.
1577 ReadTransaction
trans(FROM_HERE
, dir().get());
1578 Entry
me(&trans
, GET_BY_CLIENT_TAG
, tag
);
1579 ASSERT_TRUE(me
.good());
1580 EXPECT_EQ(me
.GetId(), id
);
1581 EXPECT_EQ(me
.GetBaseVersion(), 1);
1582 EXPECT_EQ(me
.GetUniqueClientTag(), tag
);
1583 EXPECT_EQ(me
.GetMetahandle(), written_metahandle
);
1587 TEST_F(SyncableDirectoryTest
, ClientIndexRebuildsDeletedProperly
) {
1588 TestIdFactory factory
;
1589 const Id id
= factory
.NewServerId();
1590 std::string tag
= "dietcoke";
1592 // Test creating a deleted, unsynced, server meta entry.
1594 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
1595 MutableEntry
me(&wtrans
, CREATE
, BOOKMARKS
, wtrans
.root_id(), "deleted");
1596 ASSERT_TRUE(me
.good());
1598 me
.PutBaseVersion(1);
1599 me
.PutUniqueClientTag(tag
);
1601 me
.PutIsUnsynced(true); // Or it might be purged.
1603 dir()->SaveChanges();
1605 // Close and reopen, causing index regeneration.
1608 ReadTransaction
trans(FROM_HERE
, dir().get());
1609 Entry
me(&trans
, GET_BY_CLIENT_TAG
, tag
);
1610 // Should still be present and valid in the client tag index.
1611 ASSERT_TRUE(me
.good());
1612 EXPECT_EQ(me
.GetId(), id
);
1613 EXPECT_EQ(me
.GetUniqueClientTag(), tag
);
1614 EXPECT_TRUE(me
.GetIsDel());
1615 EXPECT_TRUE(me
.GetIsUnsynced());
1619 TEST_F(SyncableDirectoryTest
, ToValue
) {
1620 const Id id
= TestIdFactory::FromNumber(99);
1622 ReadTransaction
rtrans(FROM_HERE
, dir().get());
1623 Entry
e(&rtrans
, GET_BY_ID
, id
);
1624 EXPECT_FALSE(e
.good()); // Hasn't been written yet.
1626 scoped_ptr
<base::DictionaryValue
> value(e
.ToValue(NULL
));
1627 ExpectDictBooleanValue(false, *value
, "good");
1628 EXPECT_EQ(1u, value
->size());
1631 // Test creating a new meta entry.
1633 WriteTransaction
wtrans(FROM_HERE
, UNITTEST
, dir().get());
1634 MutableEntry
me(&wtrans
, CREATE
, BOOKMARKS
, wtrans
.root_id(), "new");
1635 ASSERT_TRUE(me
.good());
1637 me
.PutBaseVersion(1);
1639 scoped_ptr
<base::DictionaryValue
> value(me
.ToValue(NULL
));
1640 ExpectDictBooleanValue(true, *value
, "good");
1641 EXPECT_TRUE(value
->HasKey("kernel"));
1642 ExpectDictStringValue("Bookmarks", *value
, "modelType");
1643 ExpectDictBooleanValue(true, *value
, "existsOnClientBecauseNameIsNonEmpty");
1644 ExpectDictBooleanValue(false, *value
, "isRoot");
1647 dir()->SaveChanges();
1650 // A thread that creates a bunch of directory entries.
1651 class StressTransactionsDelegate
: public base::PlatformThread::Delegate
{
1653 StressTransactionsDelegate(Directory
* dir
, int thread_number
)
1654 : dir_(dir
), thread_number_(thread_number
) {}
1657 Directory
* const dir_
;
1658 const int thread_number_
;
1660 // PlatformThread::Delegate methods:
1661 void ThreadMain() override
{
1662 int entry_count
= 0;
1663 std::string path_name
;
1665 for (int i
= 0; i
< 20; ++i
) {
1666 const int rand_action
= rand() % 10;
1667 if (rand_action
< 4 && !path_name
.empty()) {
1668 ReadTransaction
trans(FROM_HERE
, dir_
);
1669 CHECK(1 == CountEntriesWithName(&trans
, trans
.root_id(), path_name
));
1670 base::PlatformThread::Sleep(
1671 base::TimeDelta::FromMilliseconds(rand() % 10));
1673 std::string unique_name
=
1674 base::StringPrintf("%d.%d", thread_number_
, entry_count
++);
1675 path_name
.assign(unique_name
.begin(), unique_name
.end());
1676 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir_
);
1677 MutableEntry
e(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), path_name
);
1679 base::PlatformThread::Sleep(
1680 base::TimeDelta::FromMilliseconds(rand() % 20));
1681 e
.PutIsUnsynced(true);
1682 if (e
.PutId(TestIdFactory::FromNumber(rand())) &&
1683 e
.GetId().ServerKnows() && !e
.GetId().IsRoot()) {
1684 e
.PutBaseVersion(1);
1690 DISALLOW_COPY_AND_ASSIGN(StressTransactionsDelegate
);
1693 // Stress test Directory by accessing it from several threads concurrently.
1694 TEST_F(SyncableDirectoryTest
, StressTransactions
) {
1695 const int kThreadCount
= 7;
1696 base::PlatformThreadHandle threads
[kThreadCount
];
1697 scoped_ptr
<StressTransactionsDelegate
> thread_delegates
[kThreadCount
];
1699 for (int i
= 0; i
< kThreadCount
; ++i
) {
1700 thread_delegates
[i
].reset(new StressTransactionsDelegate(dir().get(), i
));
1701 ASSERT_TRUE(base::PlatformThread::Create(
1702 0, thread_delegates
[i
].get(), &threads
[i
]));
1705 for (int i
= 0; i
< kThreadCount
; ++i
) {
1706 base::PlatformThread::Join(threads
[i
]);
1710 // Verify that Directory is notifed when a MutableEntry's AttachmentMetadata
1712 TEST_F(SyncableDirectoryTest
, MutableEntry_PutAttachmentMetadata
) {
1713 sync_pb::AttachmentMetadata attachment_metadata
;
1714 sync_pb::AttachmentMetadataRecord
* record
= attachment_metadata
.add_record();
1715 sync_pb::AttachmentIdProto attachment_id_proto
=
1716 syncer::CreateAttachmentIdProto(0, 0);
1717 *record
->mutable_id() = attachment_id_proto
;
1718 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1720 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1722 // Create an entry with attachment metadata and see that the attachment id
1725 &trans
, CREATE
, PREFERENCES
, trans
.root_id(), "some entry");
1726 entry
.PutId(TestIdFactory::FromNumber(-1));
1727 entry
.PutIsUnsynced(true);
1729 Directory::Metahandles metahandles
;
1730 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1731 dir()->GetMetahandlesByAttachmentId(
1732 &trans
, attachment_id_proto
, &metahandles
);
1733 ASSERT_TRUE(metahandles
.empty());
1735 // Now add the attachment metadata and see that Directory believes it is
1737 entry
.PutAttachmentMetadata(attachment_metadata
);
1738 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto
));
1739 dir()->GetMetahandlesByAttachmentId(
1740 &trans
, attachment_id_proto
, &metahandles
);
1741 ASSERT_FALSE(metahandles
.empty());
1742 ASSERT_EQ(metahandles
[0], entry
.GetMetahandle());
1744 // Clear out the attachment metadata and see that it's no longer linked.
1745 sync_pb::AttachmentMetadata empty_attachment_metadata
;
1746 entry
.PutAttachmentMetadata(empty_attachment_metadata
);
1747 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1748 dir()->GetMetahandlesByAttachmentId(
1749 &trans
, attachment_id_proto
, &metahandles
);
1750 ASSERT_TRUE(metahandles
.empty());
1752 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1755 // Verify that UpdateAttachmentId updates attachment_id and is_on_server flag.
1756 TEST_F(SyncableDirectoryTest
, MutableEntry_UpdateAttachmentId
) {
1757 sync_pb::AttachmentMetadata attachment_metadata
;
1758 sync_pb::AttachmentMetadataRecord
* r1
= attachment_metadata
.add_record();
1759 sync_pb::AttachmentMetadataRecord
* r2
= attachment_metadata
.add_record();
1760 *r1
->mutable_id() = syncer::CreateAttachmentIdProto(0, 0);
1761 *r2
->mutable_id() = syncer::CreateAttachmentIdProto(0, 0);
1762 sync_pb::AttachmentIdProto attachment_id_proto
= r1
->id();
1764 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1767 &trans
, CREATE
, PREFERENCES
, trans
.root_id(), "some entry");
1768 entry
.PutId(TestIdFactory::FromNumber(-1));
1769 entry
.PutAttachmentMetadata(attachment_metadata
);
1772 const sync_pb::AttachmentMetadata
& entry_metadata
=
1773 entry
.GetAttachmentMetadata();
1774 ASSERT_EQ(2, entry_metadata
.record_size());
1775 ASSERT_FALSE(entry_metadata
.record(0).is_on_server());
1776 ASSERT_FALSE(entry_metadata
.record(1).is_on_server());
1777 ASSERT_FALSE(entry
.GetIsUnsynced());
1780 entry
.MarkAttachmentAsOnServer(attachment_id_proto
);
1783 // Re-get entry_metadata because it is immutable in the directory and
1784 // entry_metadata reference has been made invalid by
1785 // MarkAttachmentAsOnServer call above.
1786 const sync_pb::AttachmentMetadata
& entry_metadata
=
1787 entry
.GetAttachmentMetadata();
1788 ASSERT_TRUE(entry_metadata
.record(0).is_on_server());
1789 ASSERT_FALSE(entry_metadata
.record(1).is_on_server());
1790 ASSERT_TRUE(entry
.GetIsUnsynced());
1794 // Verify that deleted entries with attachments will retain the attachments.
1795 TEST_F(SyncableDirectoryTest
, Directory_DeleteDoesNotUnlinkAttachments
) {
1796 sync_pb::AttachmentMetadata attachment_metadata
;
1797 sync_pb::AttachmentMetadataRecord
* record
= attachment_metadata
.add_record();
1798 sync_pb::AttachmentIdProto attachment_id_proto
=
1799 syncer::CreateAttachmentIdProto(0, 0);
1800 *record
->mutable_id() = attachment_id_proto
;
1801 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1802 const Id id
= TestIdFactory::FromNumber(-1);
1804 // Create an entry with attachment metadata and see that the attachment id
1806 CreateEntryWithAttachmentMetadata(
1807 PREFERENCES
, "some entry", id
, attachment_metadata
);
1808 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto
));
1810 // Delete the entry and see that it's still linked because the entry hasn't
1813 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto
));
1815 // Reload the Directory, purging the deleted entry, and see that the
1816 // attachment is no longer linked.
1817 SimulateSaveAndReloadDir();
1818 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1821 // Verify that a given attachment can be referenced by multiple entries and that
1822 // any one of the references is sufficient to ensure it remains linked.
1823 TEST_F(SyncableDirectoryTest
, Directory_LastReferenceUnlinksAttachments
) {
1824 // Create one attachment.
1825 sync_pb::AttachmentMetadata attachment_metadata
;
1826 sync_pb::AttachmentMetadataRecord
* record
= attachment_metadata
.add_record();
1827 sync_pb::AttachmentIdProto attachment_id_proto
=
1828 syncer::CreateAttachmentIdProto(0, 0);
1829 *record
->mutable_id() = attachment_id_proto
;
1831 // Create two entries, each referencing the attachment.
1832 const Id id1
= TestIdFactory::FromNumber(-1);
1833 const Id id2
= TestIdFactory::FromNumber(-2);
1834 CreateEntryWithAttachmentMetadata(
1835 PREFERENCES
, "some entry", id1
, attachment_metadata
);
1836 CreateEntryWithAttachmentMetadata(
1837 PREFERENCES
, "some other entry", id2
, attachment_metadata
);
1839 // See that the attachment is considered linked.
1840 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto
));
1842 // Delete the first entry, reload the Directory, see that the attachment is
1845 SimulateSaveAndReloadDir();
1846 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto
));
1848 // Delete the second entry, reload the Directory, see that the attachment is
1851 SimulateSaveAndReloadDir();
1852 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto
));
1855 TEST_F(SyncableDirectoryTest
, Directory_GetAttachmentIdsToUpload
) {
1856 // Create one attachment, referenced by two entries.
1857 AttachmentId attachment_id
= AttachmentId::Create(0, 0);
1858 sync_pb::AttachmentIdProto attachment_id_proto
= attachment_id
.GetProto();
1859 sync_pb::AttachmentMetadata attachment_metadata
;
1860 sync_pb::AttachmentMetadataRecord
* record
= attachment_metadata
.add_record();
1861 *record
->mutable_id() = attachment_id_proto
;
1862 const Id id1
= TestIdFactory::FromNumber(-1);
1863 const Id id2
= TestIdFactory::FromNumber(-2);
1864 CreateEntryWithAttachmentMetadata(
1865 PREFERENCES
, "some entry", id1
, attachment_metadata
);
1866 CreateEntryWithAttachmentMetadata(
1867 PREFERENCES
, "some other entry", id2
, attachment_metadata
);
1869 // See that Directory reports that this attachment is not on the server.
1870 AttachmentIdList ids
;
1872 ReadTransaction
trans(FROM_HERE
, dir().get());
1873 dir()->GetAttachmentIdsToUpload(&trans
, PREFERENCES
, &ids
);
1875 ASSERT_EQ(1U, ids
.size());
1876 ASSERT_EQ(attachment_id
, *ids
.begin());
1878 // Call again, but this time with a ModelType for which there are no entries.
1879 // See that Directory correctly reports that there are none.
1881 ReadTransaction
trans(FROM_HERE
, dir().get());
1882 dir()->GetAttachmentIdsToUpload(&trans
, PASSWORDS
, &ids
);
1884 ASSERT_TRUE(ids
.empty());
1886 // Now, mark the attachment as "on the server" via entry_1.
1888 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1889 MutableEntry
entry_1(&trans
, GET_BY_ID
, id1
);
1890 entry_1
.MarkAttachmentAsOnServer(attachment_id_proto
);
1893 // See that Directory no longer reports that this attachment is not on the
1896 ReadTransaction
trans(FROM_HERE
, dir().get());
1897 dir()->GetAttachmentIdsToUpload(&trans
, PREFERENCES
, &ids
);
1899 ASSERT_TRUE(ids
.empty());
1902 // Verify that the directory accepts entries with unset parent ID.
1903 TEST_F(SyncableDirectoryTest
, MutableEntry_ImplicitParentId
) {
1904 TestIdFactory id_factory
;
1905 const Id root_id
= TestIdFactory::root();
1906 const Id p_root_id
= id_factory
.NewServerId();
1907 const Id a_root_id
= id_factory
.NewServerId();
1908 const Id item1_id
= id_factory
.NewServerId();
1909 const Id item2_id
= id_factory
.NewServerId();
1910 const Id item3_id
= id_factory
.NewServerId();
1911 // Create two type root folders that are necessary (for now)
1912 // for creating items without explicitly set Parent ID
1914 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1915 MutableEntry
p_root(&trans
, CREATE
, PREFERENCES
, root_id
, "P");
1916 ASSERT_TRUE(p_root
.good());
1917 p_root
.PutIsDir(true);
1918 p_root
.PutId(p_root_id
);
1919 p_root
.PutBaseVersion(1);
1921 MutableEntry
a_root(&trans
, CREATE
, AUTOFILL
, root_id
, "A");
1922 ASSERT_TRUE(a_root
.good());
1923 a_root
.PutIsDir(true);
1924 a_root
.PutId(a_root_id
);
1925 a_root
.PutBaseVersion(1);
1928 // Create two entries with implicit parent nodes and one entry with explicit
1931 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1932 MutableEntry
item1(&trans
, CREATE
, PREFERENCES
, "P1");
1933 item1
.PutBaseVersion(1);
1934 item1
.PutId(item1_id
);
1935 MutableEntry
item2(&trans
, CREATE
, AUTOFILL
, "A1");
1936 item2
.PutBaseVersion(1);
1937 item2
.PutId(item2_id
);
1938 // Placing an AUTOFILL item under the root isn't expected,
1939 // but let's test it to verify that explicit root overrides the implicit
1940 // one and this entry doesn't end up under the "A" root.
1941 MutableEntry
item3(&trans
, CREATE
, AUTOFILL
, root_id
, "A2");
1942 item3
.PutBaseVersion(1);
1943 item3
.PutId(item3_id
);
1947 ReadTransaction
trans(FROM_HERE
, dir().get());
1948 // Verify that item1 and item2 are good and have no ParentId.
1949 Entry
item1(&trans
, GET_BY_ID
, item1_id
);
1950 ASSERT_TRUE(item1
.good());
1951 ASSERT_TRUE(item1
.GetParentId().IsNull());
1952 Entry
item2(&trans
, GET_BY_ID
, item2_id
);
1953 ASSERT_TRUE(item2
.good());
1954 ASSERT_TRUE(item2
.GetParentId().IsNull());
1955 // Verify that p_root and a_root have exactly one child each
1956 // (subtract one to exclude roots themselves).
1957 Entry
p_root(&trans
, GET_BY_ID
, p_root_id
);
1958 ASSERT_EQ(item1_id
, p_root
.GetFirstChildId());
1959 ASSERT_EQ(1, p_root
.GetTotalNodeCount() - 1);
1960 Entry
a_root(&trans
, GET_BY_ID
, a_root_id
);
1961 ASSERT_EQ(item2_id
, a_root
.GetFirstChildId());
1962 ASSERT_EQ(1, a_root
.GetTotalNodeCount() - 1);
1966 // Verify that the successor / predecessor navigation still works for
1967 // directory entries with unset Parent IDs.
1968 TEST_F(SyncableDirectoryTest
, MutableEntry_ImplicitParentId_Siblings
) {
1969 TestIdFactory id_factory
;
1970 const Id root_id
= TestIdFactory::root();
1971 const Id p_root_id
= id_factory
.NewServerId();
1972 const Id item1_id
= id_factory
.FromNumber(1);
1973 const Id item2_id
= id_factory
.FromNumber(2);
1975 // Create type root folder for PREFERENCES.
1977 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1978 MutableEntry
p_root(&trans
, CREATE
, PREFERENCES
, root_id
, "P");
1979 ASSERT_TRUE(p_root
.good());
1980 p_root
.PutIsDir(true);
1981 p_root
.PutId(p_root_id
);
1982 p_root
.PutBaseVersion(1);
1985 // Create two PREFERENCES entries with implicit parent nodes.
1987 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
1988 MutableEntry
item1(&trans
, CREATE
, PREFERENCES
, "P1");
1989 item1
.PutBaseVersion(1);
1990 item1
.PutId(item1_id
);
1991 MutableEntry
item2(&trans
, CREATE
, PREFERENCES
, "P2");
1992 item2
.PutBaseVersion(1);
1993 item2
.PutId(item2_id
);
1996 // Verify GetSuccessorId and GetPredecessorId calls for these items.
1997 // Please note that items are sorted according to their ID, e.g.
1998 // item1 first, then item2.
2000 ReadTransaction
trans(FROM_HERE
, dir().get());
2001 Entry
item1(&trans
, GET_BY_ID
, item1_id
);
2002 EXPECT_EQ(Id(), item1
.GetPredecessorId());
2003 EXPECT_EQ(item2_id
, item1
.GetSuccessorId());
2005 Entry
item2(&trans
, GET_BY_ID
, item2_id
);
2006 EXPECT_EQ(item1_id
, item2
.GetPredecessorId());
2007 EXPECT_EQ(Id(), item2
.GetSuccessorId());
2011 TEST_F(SyncableDirectoryTest
, SaveChangesSnapshot_HasUnsavedMetahandleChanges
) {
2013 Directory::SaveChangesSnapshot snapshot
;
2014 EXPECT_FALSE(snapshot
.HasUnsavedMetahandleChanges());
2015 snapshot
.dirty_metas
.insert(&kernel
);
2016 EXPECT_TRUE(snapshot
.HasUnsavedMetahandleChanges());
2017 snapshot
.dirty_metas
.clear();
2019 EXPECT_FALSE(snapshot
.HasUnsavedMetahandleChanges());
2020 snapshot
.metahandles_to_purge
.insert(1);
2021 EXPECT_TRUE(snapshot
.HasUnsavedMetahandleChanges());
2022 snapshot
.metahandles_to_purge
.clear();
2024 EXPECT_FALSE(snapshot
.HasUnsavedMetahandleChanges());
2025 snapshot
.delete_journals
.insert(&kernel
);
2026 EXPECT_TRUE(snapshot
.HasUnsavedMetahandleChanges());
2027 snapshot
.delete_journals
.clear();
2029 EXPECT_FALSE(snapshot
.HasUnsavedMetahandleChanges());
2030 snapshot
.delete_journals_to_purge
.insert(1);
2031 EXPECT_TRUE(snapshot
.HasUnsavedMetahandleChanges());
2032 snapshot
.delete_journals_to_purge
.clear();
2035 // Verify that Directory triggers an unrecoverable error when a catastrophic
2036 // DirectoryBackingStore error is detected.
2037 TEST_F(SyncableDirectoryTest
, CatastrophicError
) {
2038 MockUnrecoverableErrorHandler unrecoverable_error_handler
;
2039 Directory
dir(new InMemoryDirectoryBackingStore("catastrophic_error"),
2040 &unrecoverable_error_handler
, base::Closure(), nullptr,
2042 ASSERT_EQ(OPENED
, dir
.Open(kDirectoryName
, directory_change_delegate(),
2043 NullTransactionObserver()));
2044 ASSERT_EQ(0, unrecoverable_error_handler
.invocation_count());
2046 // Fire off two catastrophic errors. Call it twice to ensure Directory is
2047 // tolerant of multiple invocations since that may happen in the real world.
2048 dir
.OnCatastrophicError();
2049 dir
.OnCatastrophicError();
2051 // See that the unrecoverable error handler has been invoked twice.
2052 ASSERT_EQ(2, unrecoverable_error_handler
.invocation_count());
2055 bool EntitySpecificsValuesAreSame(const sync_pb::EntitySpecifics
& v1
,
2056 const sync_pb::EntitySpecifics
& v2
) {
2060 // Verifies that server and client specifics are shared when their values
2062 TEST_F(SyncableDirectoryTest
, SharingOfClientAndServerSpecifics
) {
2063 sync_pb::EntitySpecifics specifics1
;
2064 sync_pb::EntitySpecifics specifics2
;
2065 sync_pb::EntitySpecifics specifics3
;
2066 AddDefaultFieldValue(BOOKMARKS
, &specifics1
);
2067 AddDefaultFieldValue(BOOKMARKS
, &specifics2
);
2068 AddDefaultFieldValue(BOOKMARKS
, &specifics3
);
2069 specifics1
.mutable_bookmark()->set_url("foo");
2070 specifics2
.mutable_bookmark()->set_url("bar");
2071 // specifics3 has the same URL as specifics1
2072 specifics3
.mutable_bookmark()->set_url("foo");
2074 WriteTransaction
trans(FROM_HERE
, UNITTEST
, dir().get());
2075 MutableEntry
item(&trans
, CREATE
, BOOKMARKS
, trans
.root_id(), "item");
2076 item
.PutId(TestIdFactory::FromNumber(1));
2077 item
.PutBaseVersion(10);
2080 item
.PutSpecifics(specifics1
);
2081 item
.PutServerSpecifics(specifics1
);
2082 EXPECT_TRUE(EntitySpecificsValuesAreSame(item
.GetSpecifics(),
2083 item
.GetServerSpecifics()));
2085 // Verify that specifics are no longer shared.
2086 item
.PutServerSpecifics(specifics2
);
2087 EXPECT_FALSE(EntitySpecificsValuesAreSame(item
.GetSpecifics(),
2088 item
.GetServerSpecifics()));
2090 // Verify that specifics are shared again because specifics3 matches
2092 item
.PutServerSpecifics(specifics3
);
2093 EXPECT_TRUE(EntitySpecificsValuesAreSame(item
.GetSpecifics(),
2094 item
.GetServerSpecifics()));
2096 // Verify that copying the same value back to SPECIFICS is still OK.
2097 item
.PutSpecifics(specifics3
);
2098 EXPECT_TRUE(EntitySpecificsValuesAreSame(item
.GetSpecifics(),
2099 item
.GetServerSpecifics()));
2101 // Verify sharing with BASE_SERVER_SPECIFICS.
2102 EXPECT_FALSE(EntitySpecificsValuesAreSame(item
.GetServerSpecifics(),
2103 item
.GetBaseServerSpecifics()));
2104 item
.PutBaseServerSpecifics(specifics3
);
2105 EXPECT_TRUE(EntitySpecificsValuesAreSame(item
.GetServerSpecifics(),
2106 item
.GetBaseServerSpecifics()));
2109 } // namespace syncable
2111 } // namespace syncer