Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(736)

Side by Side Diff: sync/syncable/directory_unittest.cc

Issue 2130453004: [Sync] Move //sync to //components/sync. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sync/syncable/directory_unittest.h ('k') | sync/syncable/entry.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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.
4
5 #include "sync/syncable/directory_unittest.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <cstdlib>
11
12 #include "base/macros.h"
13 #include "base/rand_util.h"
14 #include "base/run_loop.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/test/values_test_util.h"
17 #include "sync/internal_api/public/base/attachment_id_proto.h"
18 #include "sync/syncable/syncable_proto_util.h"
19 #include "sync/syncable/syncable_util.h"
20 #include "sync/syncable/syncable_write_transaction.h"
21 #include "sync/test/engine/test_syncable_utils.h"
22 #include "sync/test/test_directory_backing_store.h"
23 #include "sync/util/mock_unrecoverable_error_handler.h"
24
25 using base::ExpectDictBooleanValue;
26 using base::ExpectDictStringValue;
27
28 namespace syncer {
29
30 namespace syncable {
31
32 namespace {
33
34 bool IsLegalNewParent(const Entry& a, const Entry& b) {
35 return IsLegalNewParent(a.trans(), a.GetId(), b.GetId());
36 }
37
38 void PutDataAsBookmarkFavicon(WriteTransaction* wtrans,
39 MutableEntry* e,
40 const char* bytes,
41 size_t bytes_length) {
42 sync_pb::EntitySpecifics specifics;
43 specifics.mutable_bookmark()->set_url("http://demo/");
44 specifics.mutable_bookmark()->set_favicon(bytes, bytes_length);
45 e->PutSpecifics(specifics);
46 }
47
48 void ExpectDataFromBookmarkFaviconEquals(BaseTransaction* trans,
49 Entry* e,
50 const char* bytes,
51 size_t bytes_length) {
52 ASSERT_TRUE(e->good());
53 ASSERT_TRUE(e->GetSpecifics().has_bookmark());
54 ASSERT_EQ("http://demo/", e->GetSpecifics().bookmark().url());
55 ASSERT_EQ(std::string(bytes, bytes_length),
56 e->GetSpecifics().bookmark().favicon());
57 }
58
59 } // namespace
60
61 const char SyncableDirectoryTest::kDirectoryName[] = "Foo";
62
63 SyncableDirectoryTest::SyncableDirectoryTest() {
64 }
65
66 SyncableDirectoryTest::~SyncableDirectoryTest() {
67 }
68
69 void SyncableDirectoryTest::SetUp() {
70 ASSERT_TRUE(connection_.OpenInMemory());
71 ASSERT_EQ(OPENED, ReopenDirectory());
72 }
73
74 void SyncableDirectoryTest::TearDown() {
75 if (dir_)
76 dir_->SaveChanges();
77 dir_.reset();
78 }
79
80 DirOpenResult SyncableDirectoryTest::ReopenDirectory() {
81 // Use a TestDirectoryBackingStore and sql::Connection so we can have test
82 // data persist across Directory object lifetimes while getting the
83 // performance benefits of not writing to disk.
84 dir_.reset(new Directory(
85 new TestDirectoryBackingStore(kDirectoryName, &connection_),
86 MakeWeakHandle(handler_.GetWeakPtr()), base::Closure(), NULL, NULL));
87
88 DirOpenResult open_result =
89 dir_->Open(kDirectoryName, &delegate_, NullTransactionObserver());
90
91 if (open_result != OPENED) {
92 dir_.reset();
93 }
94
95 return open_result;
96 }
97
98 // Creates an empty entry and sets the ID field to a default one.
99 void SyncableDirectoryTest::CreateEntry(const ModelType& model_type,
100 const std::string& entryname) {
101 CreateEntry(model_type, entryname, TestIdFactory::FromNumber(-99));
102 }
103
104 // Creates an empty entry and sets the ID field to id.
105 void SyncableDirectoryTest::CreateEntry(const ModelType& model_type,
106 const std::string& entryname,
107 const int id) {
108 CreateEntry(model_type, entryname, TestIdFactory::FromNumber(id));
109 }
110
111 void SyncableDirectoryTest::CreateEntry(const ModelType& model_type,
112 const std::string& entryname,
113 const Id& id) {
114 CreateEntryWithAttachmentMetadata(
115 model_type, entryname, id, sync_pb::AttachmentMetadata());
116 }
117
118 void SyncableDirectoryTest::CreateEntryWithAttachmentMetadata(
119 const ModelType& model_type,
120 const std::string& entryname,
121 const Id& id,
122 const sync_pb::AttachmentMetadata& attachment_metadata) {
123 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get());
124 MutableEntry me(&wtrans, CREATE, model_type, wtrans.root_id(), entryname);
125 ASSERT_TRUE(me.good());
126 me.PutId(id);
127 me.PutAttachmentMetadata(attachment_metadata);
128 me.PutIsUnsynced(true);
129 }
130
131 void SyncableDirectoryTest::DeleteEntry(const Id& id) {
132 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
133 MutableEntry entry(&trans, GET_BY_ID, id);
134 ASSERT_TRUE(entry.good());
135 entry.PutIsDel(true);
136 }
137
138 DirOpenResult SyncableDirectoryTest::SimulateSaveAndReloadDir() {
139 if (!dir_->SaveChanges())
140 return FAILED_IN_UNITTEST;
141
142 return ReopenDirectory();
143 }
144
145 DirOpenResult SyncableDirectoryTest::SimulateCrashAndReloadDir() {
146 return ReopenDirectory();
147 }
148
149 void SyncableDirectoryTest::GetAllMetaHandles(BaseTransaction* trans,
150 MetahandleSet* result) {
151 dir_->GetAllMetaHandles(trans, result);
152 }
153
154 void SyncableDirectoryTest::CheckPurgeEntriesWithTypeInSucceeded(
155 ModelTypeSet types_to_purge,
156 bool before_reload) {
157 SCOPED_TRACE(testing::Message("Before reload: ") << before_reload);
158 {
159 ReadTransaction trans(FROM_HERE, dir_.get());
160 MetahandleSet all_set;
161 dir_->GetAllMetaHandles(&trans, &all_set);
162 EXPECT_EQ(4U, all_set.size());
163 if (before_reload)
164 EXPECT_EQ(6U, dir_->kernel()->metahandles_to_purge.size());
165 for (MetahandleSet::iterator iter = all_set.begin(); iter != all_set.end();
166 ++iter) {
167 Entry e(&trans, GET_BY_HANDLE, *iter);
168 const ModelType local_type = e.GetModelType();
169 const ModelType server_type = e.GetServerModelType();
170
171 // Note the dance around incrementing |it|, since we sometimes erase().
172 if ((IsRealDataType(local_type) && types_to_purge.Has(local_type)) ||
173 (IsRealDataType(server_type) && types_to_purge.Has(server_type))) {
174 FAIL() << "Illegal type should have been deleted.";
175 }
176 }
177 }
178
179 for (ModelTypeSet::Iterator it = types_to_purge.First(); it.Good();
180 it.Inc()) {
181 EXPECT_FALSE(dir_->InitialSyncEndedForType(it.Get()));
182 sync_pb::DataTypeProgressMarker progress;
183 dir_->GetDownloadProgress(it.Get(), &progress);
184 EXPECT_EQ("", progress.token());
185
186 ReadTransaction trans(FROM_HERE, dir_.get());
187 sync_pb::DataTypeContext context;
188 dir_->GetDataTypeContext(&trans, it.Get(), &context);
189 EXPECT_TRUE(context.SerializeAsString().empty());
190 }
191 EXPECT_FALSE(types_to_purge.Has(BOOKMARKS));
192 EXPECT_TRUE(dir_->InitialSyncEndedForType(BOOKMARKS));
193 }
194
195 bool SyncableDirectoryTest::IsInDirtyMetahandles(int64_t metahandle) {
196 return 1 == dir_->kernel()->dirty_metahandles.count(metahandle);
197 }
198
199 bool SyncableDirectoryTest::IsInMetahandlesToPurge(int64_t metahandle) {
200 return 1 == dir_->kernel()->metahandles_to_purge.count(metahandle);
201 }
202
203 std::unique_ptr<Directory>& SyncableDirectoryTest::dir() {
204 return dir_;
205 }
206
207 DirectoryChangeDelegate* SyncableDirectoryTest::directory_change_delegate() {
208 return &delegate_;
209 }
210
211 Encryptor* SyncableDirectoryTest::encryptor() {
212 return &encryptor_;
213 }
214
215
216 TestUnrecoverableErrorHandler*
217 SyncableDirectoryTest::unrecoverable_error_handler() {
218 return &handler_;
219 }
220
221 void SyncableDirectoryTest::ValidateEntry(BaseTransaction* trans,
222 int64_t id,
223 bool check_name,
224 const std::string& name,
225 int64_t base_version,
226 int64_t server_version,
227 bool is_del) {
228 Entry e(trans, GET_BY_ID, TestIdFactory::FromNumber(id));
229 ASSERT_TRUE(e.good());
230 if (check_name)
231 ASSERT_TRUE(name == e.GetNonUniqueName());
232 ASSERT_TRUE(base_version == e.GetBaseVersion());
233 ASSERT_TRUE(server_version == e.GetServerVersion());
234 ASSERT_TRUE(is_del == e.GetIsDel());
235 }
236
237 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) {
238 const int metas_to_create = 50;
239 MetahandleSet expected_purges;
240 MetahandleSet all_handles;
241 {
242 dir()->SetDownloadProgress(BOOKMARKS, BuildProgress(BOOKMARKS));
243 dir()->SetDownloadProgress(PREFERENCES, BuildProgress(PREFERENCES));
244 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
245 for (int i = 0; i < metas_to_create; i++) {
246 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo");
247 e.PutIsUnsynced(true);
248 sync_pb::EntitySpecifics specs;
249 if (i % 2 == 0) {
250 AddDefaultFieldValue(BOOKMARKS, &specs);
251 expected_purges.insert(e.GetMetahandle());
252 all_handles.insert(e.GetMetahandle());
253 } else {
254 AddDefaultFieldValue(PREFERENCES, &specs);
255 all_handles.insert(e.GetMetahandle());
256 }
257 e.PutSpecifics(specs);
258 e.PutServerSpecifics(specs);
259 }
260 }
261
262 ModelTypeSet to_purge(BOOKMARKS);
263 dir()->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet());
264
265 Directory::SaveChangesSnapshot snapshot1;
266 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex);
267 dir()->TakeSnapshotForSaveChanges(&snapshot1);
268 EXPECT_TRUE(expected_purges == snapshot1.metahandles_to_purge);
269
270 to_purge.Clear();
271 to_purge.Put(PREFERENCES);
272 dir()->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet());
273
274 dir()->HandleSaveChangesFailure(snapshot1);
275
276 Directory::SaveChangesSnapshot snapshot2;
277 dir()->TakeSnapshotForSaveChanges(&snapshot2);
278 EXPECT_TRUE(all_handles == snapshot2.metahandles_to_purge);
279 }
280
281 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) {
282 const int metahandles_to_create = 100;
283 std::vector<int64_t> expected_dirty_metahandles;
284 {
285 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
286 for (int i = 0; i < metahandles_to_create; i++) {
287 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo");
288 expected_dirty_metahandles.push_back(e.GetMetahandle());
289 e.PutIsUnsynced(true);
290 }
291 }
292 // Fake SaveChanges() and make sure we got what we expected.
293 {
294 Directory::SaveChangesSnapshot snapshot;
295 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex);
296 dir()->TakeSnapshotForSaveChanges(&snapshot);
297 // Make sure there's an entry for each new metahandle. Make sure all
298 // entries are marked dirty.
299 ASSERT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size());
300 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
301 i != snapshot.dirty_metas.end();
302 ++i) {
303 ASSERT_TRUE((*i)->is_dirty());
304 }
305 dir()->VacuumAfterSaveChanges(snapshot);
306 }
307 // Put a new value with existing transactions as well as adding new ones.
308 {
309 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
310 std::vector<int64_t> new_dirty_metahandles;
311 for (std::vector<int64_t>::const_iterator i =
312 expected_dirty_metahandles.begin();
313 i != expected_dirty_metahandles.end(); ++i) {
314 // Change existing entries to directories to dirty them.
315 MutableEntry e1(&trans, GET_BY_HANDLE, *i);
316 e1.PutIsDir(true);
317 e1.PutIsUnsynced(true);
318 // Add new entries
319 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar");
320 e2.PutIsUnsynced(true);
321 new_dirty_metahandles.push_back(e2.GetMetahandle());
322 }
323 expected_dirty_metahandles.insert(expected_dirty_metahandles.end(),
324 new_dirty_metahandles.begin(),
325 new_dirty_metahandles.end());
326 }
327 // Fake SaveChanges() and make sure we got what we expected.
328 {
329 Directory::SaveChangesSnapshot snapshot;
330 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex);
331 dir()->TakeSnapshotForSaveChanges(&snapshot);
332 // Make sure there's an entry for each new metahandle. Make sure all
333 // entries are marked dirty.
334 EXPECT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size());
335 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
336 i != snapshot.dirty_metas.end();
337 ++i) {
338 EXPECT_TRUE((*i)->is_dirty());
339 }
340 dir()->VacuumAfterSaveChanges(snapshot);
341 }
342 }
343
344 TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) {
345 const int metahandles_to_create = 100;
346
347 // half of 2 * metahandles_to_create
348 const unsigned int number_changed = 100u;
349 std::vector<int64_t> expected_dirty_metahandles;
350 {
351 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
352 for (int i = 0; i < metahandles_to_create; i++) {
353 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo");
354 expected_dirty_metahandles.push_back(e.GetMetahandle());
355 e.PutIsUnsynced(true);
356 }
357 }
358 dir()->SaveChanges();
359 // Put a new value with existing transactions as well as adding new ones.
360 {
361 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
362 std::vector<int64_t> new_dirty_metahandles;
363 for (std::vector<int64_t>::const_iterator i =
364 expected_dirty_metahandles.begin();
365 i != expected_dirty_metahandles.end(); ++i) {
366 // Change existing entries to directories to dirty them.
367 MutableEntry e1(&trans, GET_BY_HANDLE, *i);
368 ASSERT_TRUE(e1.good());
369 e1.PutIsDir(true);
370 e1.PutIsUnsynced(true);
371 // Add new entries
372 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar");
373 e2.PutIsUnsynced(true);
374 new_dirty_metahandles.push_back(e2.GetMetahandle());
375 }
376 expected_dirty_metahandles.insert(expected_dirty_metahandles.end(),
377 new_dirty_metahandles.begin(),
378 new_dirty_metahandles.end());
379 }
380 dir()->SaveChanges();
381 // Don't make any changes whatsoever and ensure nothing comes back.
382 {
383 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
384 for (std::vector<int64_t>::const_iterator i =
385 expected_dirty_metahandles.begin();
386 i != expected_dirty_metahandles.end(); ++i) {
387 MutableEntry e(&trans, GET_BY_HANDLE, *i);
388 ASSERT_TRUE(e.good());
389 // We aren't doing anything to dirty these entries.
390 }
391 }
392 // Fake SaveChanges() and make sure we got what we expected.
393 {
394 Directory::SaveChangesSnapshot snapshot;
395 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex);
396 dir()->TakeSnapshotForSaveChanges(&snapshot);
397 // Make sure there are no dirty_metahandles.
398 EXPECT_EQ(0u, snapshot.dirty_metas.size());
399 dir()->VacuumAfterSaveChanges(snapshot);
400 }
401 {
402 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
403 bool should_change = false;
404 for (std::vector<int64_t>::const_iterator i =
405 expected_dirty_metahandles.begin();
406 i != expected_dirty_metahandles.end(); ++i) {
407 // Maybe change entries by flipping IS_DIR.
408 MutableEntry e(&trans, GET_BY_HANDLE, *i);
409 ASSERT_TRUE(e.good());
410 should_change = !should_change;
411 if (should_change) {
412 bool not_dir = !e.GetIsDir();
413 e.PutIsDir(not_dir);
414 e.PutIsUnsynced(true);
415 }
416 }
417 }
418 // Fake SaveChanges() and make sure we got what we expected.
419 {
420 Directory::SaveChangesSnapshot snapshot;
421 base::AutoLock scoped_lock(dir()->kernel()->save_changes_mutex);
422 dir()->TakeSnapshotForSaveChanges(&snapshot);
423 // Make sure there's an entry for each changed metahandle. Make sure all
424 // entries are marked dirty.
425 EXPECT_EQ(number_changed, snapshot.dirty_metas.size());
426 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
427 i != snapshot.dirty_metas.end();
428 ++i) {
429 EXPECT_TRUE((*i)->is_dirty());
430 }
431 dir()->VacuumAfterSaveChanges(snapshot);
432 }
433 }
434
435 // Test delete journals management.
436 TEST_F(SyncableDirectoryTest, ManageDeleteJournals) {
437 sync_pb::EntitySpecifics bookmark_specifics;
438 AddDefaultFieldValue(BOOKMARKS, &bookmark_specifics);
439 bookmark_specifics.mutable_bookmark()->set_url("url");
440
441 // The first two IDs are server IDs.
442 Id id1 = TestIdFactory::FromNumber(1);
443 Id id2 = TestIdFactory::FromNumber(2);
444 // The third one is a client ID.
445 Id id3 = TestIdFactory::FromNumber(-3);
446 int64_t handle1 = 0;
447 int64_t handle2 = 0;
448 int64_t handle3 = 0;
449 {
450 // Create 3 bookmark entries and save in database.
451 {
452 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
453
454 MutableEntry item1(&trans, CREATE, BOOKMARKS, trans.root_id(), "item1");
455 item1.PutId(id1);
456 item1.PutSpecifics(bookmark_specifics);
457 item1.PutServerSpecifics(bookmark_specifics);
458 item1.PutIsUnappliedUpdate(true);
459 item1.PutBaseVersion(10);
460 handle1 = item1.GetMetahandle();
461
462 MutableEntry item2(&trans, CREATE, BOOKMARKS, trans.root_id(), "item2");
463 item2.PutId(id2);
464 item2.PutSpecifics(bookmark_specifics);
465 item2.PutServerSpecifics(bookmark_specifics);
466 item2.PutIsUnappliedUpdate(true);
467 item2.PutBaseVersion(10);
468 handle2 = item2.GetMetahandle();
469
470 MutableEntry item3(&trans, CREATE, BOOKMARKS, trans.root_id(), "item3");
471 item3.PutId(id3);
472 item3.PutSpecifics(bookmark_specifics);
473 item3.PutServerSpecifics(bookmark_specifics);
474 item3.PutIsUnsynced(true);
475 handle3 = item3.GetMetahandle();
476 }
477 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
478 }
479
480 { // Test adding and saving delete journals.
481 DeleteJournal* delete_journal = dir()->delete_journal();
482 {
483 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
484 EntryKernelSet journal_entries;
485 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries);
486 ASSERT_EQ(0u, journal_entries.size());
487
488 // Set SERVER_IS_DEL of the entries to true and they should be added to
489 // delete journals, but only if the deletion is initiated in update e.g.
490 // IS_UNAPPLIED_UPDATE is also true.
491 MutableEntry item1(&trans, GET_BY_ID, id1);
492 ASSERT_TRUE(item1.good());
493 item1.PutServerIsDel(true);
494 MutableEntry item2(&trans, GET_BY_ID, id2);
495 ASSERT_TRUE(item2.good());
496 item2.PutServerIsDel(true);
497 MutableEntry item3(&trans, GET_BY_ID, id3);
498 ASSERT_TRUE(item3.good());
499 item3.PutServerIsDel(true);
500 // Expect only the first two items to be in the delete journal.
501 EntryKernel tmp;
502 tmp.put(ID, id1);
503 EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp));
504 tmp.put(ID, id2);
505 EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp));
506 tmp.put(ID, id3);
507 EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp));
508 }
509
510 // Save delete journals in database and verify memory clearing.
511 ASSERT_TRUE(dir()->SaveChanges());
512 {
513 ReadTransaction trans(FROM_HERE, dir().get());
514 EXPECT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans));
515 }
516 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
517 }
518
519 {
520 {
521 // Test reading delete journals from database.
522 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
523 DeleteJournal* delete_journal = dir()->delete_journal();
524 EntryKernelSet journal_entries;
525 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries);
526 ASSERT_EQ(2u, journal_entries.size());
527 EntryKernel tmp;
528 tmp.put(META_HANDLE, handle1);
529 EXPECT_TRUE(journal_entries.count(&tmp));
530 tmp.put(META_HANDLE, handle2);
531 EXPECT_TRUE(journal_entries.count(&tmp));
532 tmp.put(META_HANDLE, handle3);
533 EXPECT_FALSE(journal_entries.count(&tmp));
534
535 // Purge item2.
536 MetahandleSet to_purge;
537 to_purge.insert(handle2);
538 delete_journal->PurgeDeleteJournals(&trans, to_purge);
539
540 // Verify that item2 is purged from journals in memory and will be
541 // purged from database.
542 tmp.put(ID, id2);
543 EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp));
544 EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size());
545 EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle2));
546 }
547 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
548 }
549
550 {
551 {
552 // Verify purged entry is gone in database.
553 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
554 DeleteJournal* delete_journal = dir()->delete_journal();
555 EntryKernelSet journal_entries;
556 delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries);
557 ASSERT_EQ(1u, journal_entries.size());
558 EntryKernel tmp;
559 tmp.put(ID, id1);
560 tmp.put(META_HANDLE, handle1);
561 EXPECT_TRUE(journal_entries.count(&tmp));
562
563 // Undelete item1 (IS_UNAPPLIED_UPDATE shouldn't matter in this case).
564 MutableEntry item1(&trans, GET_BY_ID, id1);
565 ASSERT_TRUE(item1.good());
566 item1.PutIsUnappliedUpdate(false);
567 item1.PutServerIsDel(false);
568 EXPECT_TRUE(delete_journal->delete_journals_.empty());
569 EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size());
570 EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle1));
571 }
572 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
573 }
574
575 {
576 // Verify undeleted entry is gone from database.
577 ReadTransaction trans(FROM_HERE, dir().get());
578 DeleteJournal* delete_journal = dir()->delete_journal();
579 ASSERT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans));
580 }
581 }
582
583 TEST_F(SyncableDirectoryTest, TestPurgeDeletedEntriesOnReload) {
584 sync_pb::EntitySpecifics specifics;
585 AddDefaultFieldValue(PREFERENCES, &specifics);
586
587 const int kClientCount = 2;
588 const int kServerCount = 5;
589 const int kTestCount = kClientCount + kServerCount;
590 int64_t handles[kTestCount];
591
592 // The idea is to recreate various combinations of IDs, IS_DEL,
593 // IS_UNSYNCED, and IS_UNAPPLIED_UPDATE flags to test all combinations
594 // for DirectoryBackingStore::SafeToPurgeOnLoading.
595 // 0: client ID, IS_DEL, IS_UNSYNCED
596 // 1: client ID, IS_UNSYNCED
597 // 2: server ID, IS_DEL, IS_UNSYNCED, IS_UNAPPLIED_UPDATE
598 // 3: server ID, IS_DEL, IS_UNSYNCED
599 // 4: server ID, IS_DEL, IS_UNAPPLIED_UPDATE
600 // 5: server ID, IS_DEL
601 // 6: server ID
602 {
603 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
604
605 for (int i = 0; i < kTestCount; i++) {
606 std::string name = base::StringPrintf("item%d", i);
607 MutableEntry item(&trans, CREATE, PREFERENCES, trans.root_id(), name);
608 ASSERT_TRUE(item.good());
609
610 handles[i] = item.GetMetahandle();
611
612 if (i < kClientCount) {
613 item.PutId(TestIdFactory::FromNumber(i - kClientCount));
614 } else {
615 item.PutId(TestIdFactory::FromNumber(i));
616 }
617
618 item.PutUniqueClientTag(name);
619 item.PutIsUnsynced(true);
620 item.PutSpecifics(specifics);
621 item.PutServerSpecifics(specifics);
622
623 if (i >= kClientCount) {
624 item.PutBaseVersion(10);
625 item.PutServerVersion(10);
626 }
627
628 // Set flags
629 if (i != 1 && i != 6)
630 item.PutIsDel(true);
631
632 if (i >= 4)
633 item.PutIsUnsynced(false);
634
635 if (i == 2 || i == 4)
636 item.PutIsUnappliedUpdate(true);
637 }
638 }
639 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
640
641 // Expect items 0 and 5 to be purged according to
642 // DirectoryBackingStore::SafeToPurgeOnLoading:
643 // - Item 0 is an item with IS_DEL flag and client ID.
644 // - Item 5 is an item with IS_DEL flag which has both
645 // IS_UNSYNCED and IS_UNAPPLIED_UPDATE unset.
646 std::vector<int64_t> expected_purged;
647 expected_purged.push_back(0);
648 expected_purged.push_back(5);
649
650 std::vector<int64_t> actually_purged;
651 {
652 ReadTransaction trans(FROM_HERE, dir().get());
653 for (int i = 0; i < kTestCount; i++) {
654 Entry item(&trans, GET_BY_HANDLE, handles[i]);
655 if (!item.good()) {
656 actually_purged.push_back(i);
657 }
658 }
659 }
660
661 EXPECT_EQ(expected_purged, actually_purged);
662 }
663
664 TEST_F(SyncableDirectoryTest, TestBasicLookupNonExistantID) {
665 ReadTransaction rtrans(FROM_HERE, dir().get());
666 Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99));
667 ASSERT_FALSE(e.good());
668 }
669
670 TEST_F(SyncableDirectoryTest, TestBasicLookupValidID) {
671 CreateEntry(BOOKMARKS, "rtc");
672 ReadTransaction rtrans(FROM_HERE, dir().get());
673 Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99));
674 ASSERT_TRUE(e.good());
675 }
676
677 TEST_F(SyncableDirectoryTest, TestDelete) {
678 std::string name = "peanut butter jelly time";
679 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
680 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), name);
681 ASSERT_TRUE(e1.good());
682 e1.PutIsDel(true);
683 MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), name);
684 ASSERT_TRUE(e2.good());
685 e2.PutIsDel(true);
686 MutableEntry e3(&trans, CREATE, BOOKMARKS, trans.root_id(), name);
687 ASSERT_TRUE(e3.good());
688 e3.PutIsDel(true);
689
690 e1.PutIsDel(false);
691 e2.PutIsDel(false);
692 e3.PutIsDel(false);
693
694 e1.PutIsDel(true);
695 e2.PutIsDel(true);
696 e3.PutIsDel(true);
697 }
698
699 TEST_F(SyncableDirectoryTest, TestGetUnsynced) {
700 Directory::Metahandles handles;
701 int64_t handle1, handle2;
702 {
703 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
704
705 dir()->GetUnsyncedMetaHandles(&trans, &handles);
706 ASSERT_EQ(0u, handles.size());
707
708 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba");
709 ASSERT_TRUE(e1.good());
710 handle1 = e1.GetMetahandle();
711 e1.PutBaseVersion(1);
712 e1.PutIsDir(true);
713 e1.PutId(TestIdFactory::FromNumber(101));
714
715 MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread");
716 ASSERT_TRUE(e2.good());
717 handle2 = e2.GetMetahandle();
718 e2.PutBaseVersion(1);
719 e2.PutId(TestIdFactory::FromNumber(102));
720 }
721 dir()->SaveChanges();
722 {
723 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
724
725 dir()->GetUnsyncedMetaHandles(&trans, &handles);
726 ASSERT_EQ(0u, handles.size());
727
728 MutableEntry e3(&trans, GET_BY_HANDLE, handle1);
729 ASSERT_TRUE(e3.good());
730 e3.PutIsUnsynced(true);
731 }
732 dir()->SaveChanges();
733 {
734 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
735 dir()->GetUnsyncedMetaHandles(&trans, &handles);
736 ASSERT_EQ(1u, handles.size());
737 ASSERT_TRUE(handle1 == handles[0]);
738
739 MutableEntry e4(&trans, GET_BY_HANDLE, handle2);
740 ASSERT_TRUE(e4.good());
741 e4.PutIsUnsynced(true);
742 }
743 dir()->SaveChanges();
744 {
745 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
746 dir()->GetUnsyncedMetaHandles(&trans, &handles);
747 ASSERT_EQ(2u, handles.size());
748 if (handle1 == handles[0]) {
749 ASSERT_TRUE(handle2 == handles[1]);
750 } else {
751 ASSERT_TRUE(handle2 == handles[0]);
752 ASSERT_TRUE(handle1 == handles[1]);
753 }
754
755 MutableEntry e5(&trans, GET_BY_HANDLE, handle1);
756 ASSERT_TRUE(e5.good());
757 ASSERT_TRUE(e5.GetIsUnsynced());
758 ASSERT_TRUE(e5.PutIsUnsynced(false));
759 ASSERT_FALSE(e5.GetIsUnsynced());
760 }
761 dir()->SaveChanges();
762 {
763 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
764 dir()->GetUnsyncedMetaHandles(&trans, &handles);
765 ASSERT_EQ(1u, handles.size());
766 ASSERT_TRUE(handle2 == handles[0]);
767 }
768 }
769
770 TEST_F(SyncableDirectoryTest, TestGetUnappliedUpdates) {
771 std::vector<int64_t> handles;
772 int64_t handle1, handle2;
773 const FullModelTypeSet all_types = FullModelTypeSet::All();
774 {
775 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
776
777 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
778 ASSERT_EQ(0u, handles.size());
779
780 MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba");
781 ASSERT_TRUE(e1.good());
782 handle1 = e1.GetMetahandle();
783 e1.PutIsUnappliedUpdate(false);
784 e1.PutBaseVersion(1);
785 e1.PutId(TestIdFactory::FromNumber(101));
786 e1.PutIsDir(true);
787
788 MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread");
789 ASSERT_TRUE(e2.good());
790 handle2 = e2.GetMetahandle();
791 e2.PutIsUnappliedUpdate(false);
792 e2.PutBaseVersion(1);
793 e2.PutId(TestIdFactory::FromNumber(102));
794 }
795 dir()->SaveChanges();
796 {
797 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
798
799 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
800 ASSERT_EQ(0u, handles.size());
801
802 MutableEntry e3(&trans, GET_BY_HANDLE, handle1);
803 ASSERT_TRUE(e3.good());
804 e3.PutIsUnappliedUpdate(true);
805 }
806 dir()->SaveChanges();
807 {
808 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
809 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
810 ASSERT_EQ(1u, handles.size());
811 ASSERT_TRUE(handle1 == handles[0]);
812
813 MutableEntry e4(&trans, GET_BY_HANDLE, handle2);
814 ASSERT_TRUE(e4.good());
815 e4.PutIsUnappliedUpdate(true);
816 }
817 dir()->SaveChanges();
818 {
819 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
820 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
821 ASSERT_EQ(2u, handles.size());
822 if (handle1 == handles[0]) {
823 ASSERT_TRUE(handle2 == handles[1]);
824 } else {
825 ASSERT_TRUE(handle2 == handles[0]);
826 ASSERT_TRUE(handle1 == handles[1]);
827 }
828
829 MutableEntry e5(&trans, GET_BY_HANDLE, handle1);
830 ASSERT_TRUE(e5.good());
831 e5.PutIsUnappliedUpdate(false);
832 }
833 dir()->SaveChanges();
834 {
835 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
836 dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles);
837 ASSERT_EQ(1u, handles.size());
838 ASSERT_TRUE(handle2 == handles[0]);
839 }
840 }
841
842 TEST_F(SyncableDirectoryTest, DeleteBug_531383) {
843 // Try to evoke a check failure...
844 TestIdFactory id_factory;
845 int64_t grandchild_handle;
846 {
847 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
848 MutableEntry parent(&wtrans, CREATE, BOOKMARKS, id_factory.root(), "Bob");
849 ASSERT_TRUE(parent.good());
850 parent.PutIsDir(true);
851 parent.PutId(id_factory.NewServerId());
852 parent.PutBaseVersion(1);
853 MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob");
854 ASSERT_TRUE(child.good());
855 child.PutIsDir(true);
856 child.PutId(id_factory.NewServerId());
857 child.PutBaseVersion(1);
858 MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob");
859 ASSERT_TRUE(grandchild.good());
860 grandchild.PutId(id_factory.NewServerId());
861 grandchild.PutBaseVersion(1);
862 grandchild.PutIsDel(true);
863 MutableEntry twin(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob");
864 ASSERT_TRUE(twin.good());
865 twin.PutIsDel(true);
866 grandchild.PutIsDel(false);
867
868 grandchild_handle = grandchild.GetMetahandle();
869 }
870 dir()->SaveChanges();
871 {
872 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
873 MutableEntry grandchild(&wtrans, GET_BY_HANDLE, grandchild_handle);
874 grandchild.PutIsDel(true); // Used to CHECK fail here.
875 }
876 }
877
878 TEST_F(SyncableDirectoryTest, TestIsLegalNewParent) {
879 TestIdFactory id_factory;
880 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
881 Entry root(&wtrans, GET_BY_ID, id_factory.root());
882 ASSERT_TRUE(root.good());
883 MutableEntry parent(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Bob");
884 ASSERT_TRUE(parent.good());
885 parent.PutIsDir(true);
886 parent.PutId(id_factory.NewServerId());
887 parent.PutBaseVersion(1);
888 MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob");
889 ASSERT_TRUE(child.good());
890 child.PutIsDir(true);
891 child.PutId(id_factory.NewServerId());
892 child.PutBaseVersion(1);
893 MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob");
894 ASSERT_TRUE(grandchild.good());
895 grandchild.PutId(id_factory.NewServerId());
896 grandchild.PutBaseVersion(1);
897
898 MutableEntry parent2(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Pete");
899 ASSERT_TRUE(parent2.good());
900 parent2.PutIsDir(true);
901 parent2.PutId(id_factory.NewServerId());
902 parent2.PutBaseVersion(1);
903 MutableEntry child2(&wtrans, CREATE, BOOKMARKS, parent2.GetId(), "Pete");
904 ASSERT_TRUE(child2.good());
905 child2.PutIsDir(true);
906 child2.PutId(id_factory.NewServerId());
907 child2.PutBaseVersion(1);
908 MutableEntry grandchild2(&wtrans, CREATE, BOOKMARKS, child2.GetId(), "Pete");
909 ASSERT_TRUE(grandchild2.good());
910 grandchild2.PutId(id_factory.NewServerId());
911 grandchild2.PutBaseVersion(1);
912 // resulting tree
913 // root
914 // / |
915 // parent parent2
916 // | |
917 // child child2
918 // | |
919 // grandchild grandchild2
920 ASSERT_TRUE(IsLegalNewParent(child, root));
921 ASSERT_TRUE(IsLegalNewParent(child, parent));
922 ASSERT_FALSE(IsLegalNewParent(child, child));
923 ASSERT_FALSE(IsLegalNewParent(child, grandchild));
924 ASSERT_TRUE(IsLegalNewParent(child, parent2));
925 ASSERT_TRUE(IsLegalNewParent(child, grandchild2));
926 ASSERT_FALSE(IsLegalNewParent(parent, grandchild));
927 ASSERT_FALSE(IsLegalNewParent(root, grandchild));
928 ASSERT_FALSE(IsLegalNewParent(parent, grandchild));
929 }
930
931 TEST_F(SyncableDirectoryTest, TestEntryIsInFolder) {
932 // Create a subdir and an entry.
933 int64_t entry_handle;
934 syncable::Id folder_id;
935 syncable::Id entry_id;
936 std::string entry_name = "entry";
937
938 {
939 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
940 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "folder");
941 ASSERT_TRUE(folder.good());
942 folder.PutIsDir(true);
943 EXPECT_TRUE(folder.PutIsUnsynced(true));
944 folder_id = folder.GetId();
945
946 MutableEntry entry(&trans, CREATE, BOOKMARKS, folder.GetId(), entry_name);
947 ASSERT_TRUE(entry.good());
948 entry_handle = entry.GetMetahandle();
949 entry.PutIsUnsynced(true);
950 entry_id = entry.GetId();
951 }
952
953 // Make sure we can find the entry in the folder.
954 {
955 ReadTransaction trans(FROM_HERE, dir().get());
956 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), entry_name));
957 EXPECT_EQ(1, CountEntriesWithName(&trans, folder_id, entry_name));
958
959 Entry entry(&trans, GET_BY_ID, entry_id);
960 ASSERT_TRUE(entry.good());
961 EXPECT_EQ(entry_handle, entry.GetMetahandle());
962 EXPECT_TRUE(entry.GetNonUniqueName() == entry_name);
963 EXPECT_TRUE(entry.GetParentId() == folder_id);
964 }
965 }
966
967 TEST_F(SyncableDirectoryTest, TestParentIdIndexUpdate) {
968 std::string child_name = "child";
969
970 WriteTransaction wt(FROM_HERE, UNITTEST, dir().get());
971 MutableEntry parent_folder(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder1");
972 parent_folder.PutIsUnsynced(true);
973 parent_folder.PutIsDir(true);
974
975 MutableEntry parent_folder2(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder2");
976 parent_folder2.PutIsUnsynced(true);
977 parent_folder2.PutIsDir(true);
978
979 MutableEntry child(&wt, CREATE, BOOKMARKS, parent_folder.GetId(), child_name);
980 child.PutIsDir(true);
981 child.PutIsUnsynced(true);
982
983 ASSERT_TRUE(child.good());
984
985 EXPECT_EQ(0, CountEntriesWithName(&wt, wt.root_id(), child_name));
986 EXPECT_EQ(parent_folder.GetId(), child.GetParentId());
987 EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder.GetId(), child_name));
988 EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name));
989 child.PutParentId(parent_folder2.GetId());
990 EXPECT_EQ(parent_folder2.GetId(), child.GetParentId());
991 EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder.GetId(), child_name));
992 EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name));
993 }
994
995 TEST_F(SyncableDirectoryTest, TestNoReindexDeletedItems) {
996 std::string folder_name = "folder";
997 std::string new_name = "new_name";
998
999 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1000 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), folder_name);
1001 ASSERT_TRUE(folder.good());
1002 folder.PutIsDir(true);
1003 folder.PutIsDel(true);
1004
1005 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name));
1006
1007 MutableEntry deleted(&trans, GET_BY_ID, folder.GetId());
1008 ASSERT_TRUE(deleted.good());
1009 deleted.PutParentId(trans.root_id());
1010 deleted.PutNonUniqueName(new_name);
1011
1012 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name));
1013 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), new_name));
1014 }
1015
1016 TEST_F(SyncableDirectoryTest, TestCaseChangeRename) {
1017 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1018 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "CaseChange");
1019 ASSERT_TRUE(folder.good());
1020 folder.PutParentId(trans.root_id());
1021 folder.PutNonUniqueName("CASECHANGE");
1022 folder.PutIsDel(true);
1023 }
1024
1025 // Create items of each model type, and check that GetModelType and
1026 // GetServerModelType return the right value.
1027 TEST_F(SyncableDirectoryTest, GetModelType) {
1028 TestIdFactory id_factory;
1029 ModelTypeSet protocol_types = ProtocolTypes();
1030 for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
1031 iter.Inc()) {
1032 ModelType datatype = iter.Get();
1033 SCOPED_TRACE(testing::Message("Testing model type ") << datatype);
1034 switch (datatype) {
1035 case UNSPECIFIED:
1036 case TOP_LEVEL_FOLDER:
1037 continue; // Datatype isn't a function of Specifics.
1038 default:
1039 break;
1040 }
1041 sync_pb::EntitySpecifics specifics;
1042 AddDefaultFieldValue(datatype, &specifics);
1043
1044 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1045
1046 MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "Folder");
1047 ASSERT_TRUE(folder.good());
1048 folder.PutId(id_factory.NewServerId());
1049 folder.PutSpecifics(specifics);
1050 folder.PutBaseVersion(1);
1051 folder.PutIsDir(true);
1052 folder.PutIsDel(false);
1053 ASSERT_EQ(datatype, folder.GetModelType());
1054
1055 MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item");
1056 ASSERT_TRUE(item.good());
1057 item.PutId(id_factory.NewServerId());
1058 item.PutSpecifics(specifics);
1059 item.PutBaseVersion(1);
1060 item.PutIsDir(false);
1061 item.PutIsDel(false);
1062 ASSERT_EQ(datatype, item.GetModelType());
1063
1064 // It's critical that deletion records retain their datatype, so that
1065 // they can be dispatched to the appropriate change processor.
1066 MutableEntry deleted_item(
1067 &trans, CREATE, BOOKMARKS, trans.root_id(), "Deleted Item");
1068 ASSERT_TRUE(item.good());
1069 deleted_item.PutId(id_factory.NewServerId());
1070 deleted_item.PutSpecifics(specifics);
1071 deleted_item.PutBaseVersion(1);
1072 deleted_item.PutIsDir(false);
1073 deleted_item.PutIsDel(true);
1074 ASSERT_EQ(datatype, deleted_item.GetModelType());
1075
1076 MutableEntry server_folder(
1077 &trans, CREATE_NEW_UPDATE_ITEM, id_factory.NewServerId());
1078 ASSERT_TRUE(server_folder.good());
1079 server_folder.PutServerSpecifics(specifics);
1080 server_folder.PutBaseVersion(1);
1081 server_folder.PutServerIsDir(true);
1082 server_folder.PutServerIsDel(false);
1083 ASSERT_EQ(datatype, server_folder.GetServerModelType());
1084
1085 MutableEntry server_item(
1086 &trans, CREATE_NEW_UPDATE_ITEM, id_factory.NewServerId());
1087 ASSERT_TRUE(server_item.good());
1088 server_item.PutServerSpecifics(specifics);
1089 server_item.PutBaseVersion(1);
1090 server_item.PutServerIsDir(false);
1091 server_item.PutServerIsDel(false);
1092 ASSERT_EQ(datatype, server_item.GetServerModelType());
1093
1094 sync_pb::SyncEntity folder_entity;
1095 folder_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId()));
1096 folder_entity.set_deleted(false);
1097 folder_entity.set_folder(true);
1098 folder_entity.mutable_specifics()->CopyFrom(specifics);
1099 ASSERT_EQ(datatype, GetModelType(folder_entity));
1100
1101 sync_pb::SyncEntity item_entity;
1102 item_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId()));
1103 item_entity.set_deleted(false);
1104 item_entity.set_folder(false);
1105 item_entity.mutable_specifics()->CopyFrom(specifics);
1106 ASSERT_EQ(datatype, GetModelType(item_entity));
1107 }
1108 }
1109
1110 // A test that roughly mimics the directory interaction that occurs when a
1111 // bookmark folder and entry are created then synced for the first time. It is
1112 // a more common variant of the 'DeletedAndUnsyncedChild' scenario tested below.
1113 TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ParentAndChild) {
1114 TestIdFactory id_factory;
1115 Id orig_parent_id;
1116 Id orig_child_id;
1117
1118 {
1119 // Create two client-side items, a parent and child.
1120 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1121
1122 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1123 parent.PutIsDir(true);
1124 parent.PutIsUnsynced(true);
1125
1126 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child");
1127 child.PutIsUnsynced(true);
1128
1129 orig_parent_id = parent.GetId();
1130 orig_child_id = child.GetId();
1131 }
1132
1133 {
1134 // Simulate what happens after committing two items. Their IDs will be
1135 // replaced with server IDs. The child is renamed first, then the parent.
1136 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1137
1138 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id);
1139 MutableEntry child(&trans, GET_BY_ID, orig_child_id);
1140
1141 ChangeEntryIDAndUpdateChildren(&trans, &child, id_factory.NewServerId());
1142 child.PutIsUnsynced(false);
1143 child.PutBaseVersion(1);
1144 child.PutServerVersion(1);
1145
1146 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId());
1147 parent.PutIsUnsynced(false);
1148 parent.PutBaseVersion(1);
1149 parent.PutServerVersion(1);
1150 }
1151
1152 // Final check for validity.
1153 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1154 }
1155
1156 // A test that roughly mimics the directory interaction that occurs when a
1157 // type root folder is created locally and then re-created (updated) from the
1158 // server.
1159 TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ImplicitParent) {
1160 TestIdFactory id_factory;
1161 Id orig_parent_id;
1162 Id child_id;
1163
1164 {
1165 // Create two client-side items, a parent and child.
1166 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1167
1168 MutableEntry parent(&trans, CREATE, PREFERENCES, id_factory.root(),
1169 "parent");
1170 parent.PutIsDir(true);
1171 parent.PutIsUnsynced(true);
1172
1173 // The child has unset parent ID. The parent is inferred from the type.
1174 MutableEntry child(&trans, CREATE, PREFERENCES, "child");
1175 child.PutIsUnsynced(true);
1176
1177 orig_parent_id = parent.GetId();
1178 child_id = child.GetId();
1179 }
1180
1181 {
1182 // Simulate what happens after committing two items. Their IDs will be
1183 // replaced with server IDs. The child is renamed first, then the parent.
1184 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1185
1186 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id);
1187
1188 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId());
1189 parent.PutIsUnsynced(false);
1190 parent.PutBaseVersion(1);
1191 parent.PutServerVersion(1);
1192 }
1193
1194 // Final check for validity.
1195 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1196
1197 // Verify that child's PARENT_ID hasn't been updated.
1198 {
1199 ReadTransaction trans(FROM_HERE, dir().get());
1200 Entry child(&trans, GET_BY_ID, child_id);
1201 EXPECT_TRUE(child.good());
1202 EXPECT_TRUE(child.GetParentId().IsNull());
1203 }
1204 }
1205
1206 // A test based on the scenario where we create a bookmark folder and entry
1207 // locally, but with a twist. In this case, the bookmark is deleted before we
1208 // are able to sync either it or its parent folder. This scenario used to cause
1209 // directory corruption, see crbug.com/125381.
1210 TEST_F(SyncableDirectoryTest,
1211 ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild) {
1212 TestIdFactory id_factory;
1213 Id orig_parent_id;
1214 Id orig_child_id;
1215
1216 {
1217 // Create two client-side items, a parent and child.
1218 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1219
1220 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1221 parent.PutIsDir(true);
1222 parent.PutIsUnsynced(true);
1223
1224 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child");
1225 child.PutIsUnsynced(true);
1226
1227 orig_parent_id = parent.GetId();
1228 orig_child_id = child.GetId();
1229 }
1230
1231 {
1232 // Delete the child.
1233 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1234
1235 MutableEntry child(&trans, GET_BY_ID, orig_child_id);
1236 child.PutIsDel(true);
1237 }
1238
1239 {
1240 // Simulate what happens after committing the parent. Its ID will be
1241 // replaced with server a ID.
1242 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1243
1244 MutableEntry parent(&trans, GET_BY_ID, orig_parent_id);
1245
1246 ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId());
1247 parent.PutIsUnsynced(false);
1248 parent.PutBaseVersion(1);
1249 parent.PutServerVersion(1);
1250 }
1251
1252 // Final check for validity.
1253 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1254 }
1255
1256 // Ask the directory to generate a unique ID. Close and re-open the database
1257 // without saving, then ask for another unique ID. Verify IDs are not reused.
1258 // This scenario simulates a crash within the first few seconds of operation.
1259 TEST_F(SyncableDirectoryTest, LocalIdReuseTest) {
1260 Id pre_crash_id = dir()->NextId();
1261 SimulateCrashAndReloadDir();
1262 Id post_crash_id = dir()->NextId();
1263 EXPECT_NE(pre_crash_id, post_crash_id);
1264 }
1265
1266 // Ask the directory to generate a unique ID. Save the directory. Close and
1267 // re-open the database without saving, then ask for another unique ID. Verify
1268 // IDs are not reused. This scenario simulates a steady-state crash.
1269 TEST_F(SyncableDirectoryTest, LocalIdReuseTestWithSave) {
1270 Id pre_crash_id = dir()->NextId();
1271 dir()->SaveChanges();
1272 SimulateCrashAndReloadDir();
1273 Id post_crash_id = dir()->NextId();
1274 EXPECT_NE(pre_crash_id, post_crash_id);
1275 }
1276
1277 // Ensure that the unsynced, is_del and server unkown entries that may have been
1278 // left in the database by old clients will be deleted when we open the old
1279 // database.
1280 TEST_F(SyncableDirectoryTest, OldClientLeftUnsyncedDeletedLocalItem) {
1281 // We must create an entry with the offending properties. This is done with
1282 // some abuse of the MutableEntry's API; it doesn't expect us to modify an
1283 // item after it is deleted. If this hack becomes impractical we will need to
1284 // find a new way to simulate this scenario.
1285
1286 TestIdFactory id_factory;
1287
1288 // Happy-path: These valid entries should not get deleted.
1289 Id server_knows_id = id_factory.NewServerId();
1290 Id not_is_del_id = id_factory.NewLocalId();
1291
1292 // The ID of the entry which will be unsynced, is_del and !ServerKnows().
1293 Id zombie_id = id_factory.NewLocalId();
1294
1295 // We're about to do some bad things. Tell the directory verification
1296 // routines to look the other way.
1297 dir()->SetInvariantCheckLevel(OFF);
1298
1299 {
1300 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1301
1302 // Create an uncommitted tombstone entry.
1303 MutableEntry server_knows(
1304 &trans, CREATE, BOOKMARKS, id_factory.root(), "server_knows");
1305 server_knows.PutId(server_knows_id);
1306 server_knows.PutIsUnsynced(true);
1307 server_knows.PutIsDel(true);
1308 server_knows.PutBaseVersion(5);
1309 server_knows.PutServerVersion(4);
1310
1311 // Create a valid update entry.
1312 MutableEntry not_is_del(
1313 &trans, CREATE, BOOKMARKS, id_factory.root(), "not_is_del");
1314 not_is_del.PutId(not_is_del_id);
1315 not_is_del.PutIsDel(false);
1316 not_is_del.PutIsUnsynced(true);
1317
1318 // Create a tombstone which should never be sent to the server because the
1319 // server never knew about the item's existence.
1320 //
1321 // New clients should never put entries into this state. We work around
1322 // this by setting IS_DEL before setting IS_UNSYNCED, something which the
1323 // client should never do in practice.
1324 MutableEntry zombie(&trans, CREATE, BOOKMARKS, id_factory.root(), "zombie");
1325 zombie.PutId(zombie_id);
1326 zombie.PutIsDel(true);
1327 zombie.PutIsUnsynced(true);
1328 }
1329
1330 ASSERT_EQ(OPENED, SimulateSaveAndReloadDir());
1331
1332 {
1333 ReadTransaction trans(FROM_HERE, dir().get());
1334
1335 // The directory loading routines should have cleaned things up, making it
1336 // safe to check invariants once again.
1337 dir()->FullyCheckTreeInvariants(&trans);
1338
1339 Entry server_knows(&trans, GET_BY_ID, server_knows_id);
1340 EXPECT_TRUE(server_knows.good());
1341
1342 Entry not_is_del(&trans, GET_BY_ID, not_is_del_id);
1343 EXPECT_TRUE(not_is_del.good());
1344
1345 Entry zombie(&trans, GET_BY_ID, zombie_id);
1346 EXPECT_FALSE(zombie.good());
1347 }
1348 }
1349
1350 TEST_F(SyncableDirectoryTest, PositionWithNullSurvivesSaveAndReload) {
1351 TestIdFactory id_factory;
1352 Id null_child_id;
1353 const char null_cstr[] = "\0null\0test";
1354 std::string null_str(null_cstr, arraysize(null_cstr) - 1);
1355 // Pad up to the minimum length with 0x7f characters, then add a string that
1356 // contains a few NULLs to the end. This is slightly wrong, since the suffix
1357 // part of a UniquePosition shouldn't contain NULLs, but it's good enough for
1358 // this test.
1359 std::string suffix =
1360 std::string(UniquePosition::kSuffixLength - null_str.length(), '\x7f') +
1361 null_str;
1362 UniquePosition null_pos = UniquePosition::FromInt64(10, suffix);
1363
1364 {
1365 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1366
1367 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1368 parent.PutIsDir(true);
1369 parent.PutIsUnsynced(true);
1370
1371 MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child");
1372 child.PutIsUnsynced(true);
1373 child.PutUniquePosition(null_pos);
1374 child.PutServerUniquePosition(null_pos);
1375
1376 null_child_id = child.GetId();
1377 }
1378
1379 EXPECT_EQ(OPENED, SimulateSaveAndReloadDir());
1380
1381 {
1382 ReadTransaction trans(FROM_HERE, dir().get());
1383
1384 Entry null_ordinal_child(&trans, GET_BY_ID, null_child_id);
1385 EXPECT_TRUE(null_pos.Equals(null_ordinal_child.GetUniquePosition()));
1386 EXPECT_TRUE(null_pos.Equals(null_ordinal_child.GetServerUniquePosition()));
1387 }
1388 }
1389
1390 // Any item with BOOKMARKS in their local specifics should have a valid local
1391 // unique position. If there is an item in the loaded DB that does not match
1392 // this criteria, we consider the whole DB to be corrupt.
1393 TEST_F(SyncableDirectoryTest, BadPositionCountsAsCorruption) {
1394 TestIdFactory id_factory;
1395
1396 {
1397 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1398
1399 MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent");
1400 parent.PutIsDir(true);
1401 parent.PutIsUnsynced(true);
1402
1403 // The code is littered with DCHECKs that try to stop us from doing what
1404 // we're about to do. Our work-around is to create a bookmark based on
1405 // a server update, then update its local specifics without updating its
1406 // local unique position.
1407
1408 MutableEntry child(
1409 &trans, CREATE_NEW_UPDATE_ITEM, id_factory.MakeServer("child"));
1410 sync_pb::EntitySpecifics specifics;
1411 AddDefaultFieldValue(BOOKMARKS, &specifics);
1412 child.PutIsUnappliedUpdate(true);
1413 child.PutSpecifics(specifics);
1414
1415 EXPECT_TRUE(child.ShouldMaintainPosition());
1416 EXPECT_TRUE(!child.GetUniquePosition().IsValid());
1417 }
1418
1419 EXPECT_EQ(FAILED_DATABASE_CORRUPT, SimulateSaveAndReloadDir());
1420 }
1421
1422 TEST_F(SyncableDirectoryTest, General) {
1423 int64_t written_metahandle;
1424 const Id id = TestIdFactory::FromNumber(99);
1425 std::string name = "Jeff";
1426 // Test simple read operations on an empty DB.
1427 {
1428 ReadTransaction rtrans(FROM_HERE, dir().get());
1429 Entry e(&rtrans, GET_BY_ID, id);
1430 ASSERT_FALSE(e.good()); // Hasn't been written yet.
1431
1432 Directory::Metahandles child_handles;
1433 dir()->GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles);
1434 EXPECT_TRUE(child_handles.empty());
1435 }
1436
1437 // Test creating a new meta entry.
1438 {
1439 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
1440 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name);
1441 ASSERT_TRUE(me.good());
1442 me.PutId(id);
1443 me.PutBaseVersion(1);
1444 written_metahandle = me.GetMetahandle();
1445 }
1446
1447 // Test GetChildHandles* after something is now in the DB.
1448 // Also check that GET_BY_ID works.
1449 {
1450 ReadTransaction rtrans(FROM_HERE, dir().get());
1451 Entry e(&rtrans, GET_BY_ID, id);
1452 ASSERT_TRUE(e.good());
1453
1454 Directory::Metahandles child_handles;
1455 dir()->GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles);
1456 EXPECT_EQ(1u, child_handles.size());
1457
1458 for (Directory::Metahandles::iterator i = child_handles.begin();
1459 i != child_handles.end(); ++i) {
1460 EXPECT_EQ(*i, written_metahandle);
1461 }
1462 }
1463
1464 // Test writing data to an entity. Also check that GET_BY_HANDLE works.
1465 static const char s[] = "Hello World.";
1466 {
1467 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1468 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle);
1469 ASSERT_TRUE(e.good());
1470 PutDataAsBookmarkFavicon(&trans, &e, s, sizeof(s));
1471 }
1472
1473 // Test reading back the contents that we just wrote.
1474 {
1475 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1476 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle);
1477 ASSERT_TRUE(e.good());
1478 ExpectDataFromBookmarkFaviconEquals(&trans, &e, s, sizeof(s));
1479 }
1480
1481 // Verify it exists in the folder.
1482 {
1483 ReadTransaction rtrans(FROM_HERE, dir().get());
1484 EXPECT_EQ(1, CountEntriesWithName(&rtrans, rtrans.root_id(), name));
1485 }
1486
1487 // Now delete it.
1488 {
1489 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1490 MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle);
1491 e.PutIsDel(true);
1492
1493 EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), name));
1494 }
1495
1496 dir()->SaveChanges();
1497 }
1498
1499 TEST_F(SyncableDirectoryTest, ChildrenOps) {
1500 int64_t written_metahandle;
1501 const Id id = TestIdFactory::FromNumber(99);
1502 std::string name = "Jeff";
1503 {
1504 ReadTransaction rtrans(FROM_HERE, dir().get());
1505 Entry e(&rtrans, GET_BY_ID, id);
1506 ASSERT_FALSE(e.good()); // Hasn't been written yet.
1507
1508 Entry root(&rtrans, GET_BY_ID, rtrans.root_id());
1509 ASSERT_TRUE(root.good());
1510 EXPECT_FALSE(dir()->HasChildren(&rtrans, rtrans.root_id()));
1511 EXPECT_TRUE(root.GetFirstChildId().IsNull());
1512 }
1513
1514 {
1515 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
1516 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name);
1517 ASSERT_TRUE(me.good());
1518 me.PutId(id);
1519 me.PutBaseVersion(1);
1520 written_metahandle = me.GetMetahandle();
1521 }
1522
1523 // Test children ops after something is now in the DB.
1524 {
1525 ReadTransaction rtrans(FROM_HERE, dir().get());
1526 Entry e(&rtrans, GET_BY_ID, id);
1527 ASSERT_TRUE(e.good());
1528
1529 Entry child(&rtrans, GET_BY_HANDLE, written_metahandle);
1530 ASSERT_TRUE(child.good());
1531
1532 Entry root(&rtrans, GET_BY_ID, rtrans.root_id());
1533 ASSERT_TRUE(root.good());
1534 EXPECT_TRUE(dir()->HasChildren(&rtrans, rtrans.root_id()));
1535 EXPECT_EQ(e.GetId(), root.GetFirstChildId());
1536 }
1537
1538 {
1539 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
1540 MutableEntry me(&wtrans, GET_BY_HANDLE, written_metahandle);
1541 ASSERT_TRUE(me.good());
1542 me.PutIsDel(true);
1543 }
1544
1545 // Test children ops after the children have been deleted.
1546 {
1547 ReadTransaction rtrans(FROM_HERE, dir().get());
1548 Entry e(&rtrans, GET_BY_ID, id);
1549 ASSERT_TRUE(e.good());
1550
1551 Entry root(&rtrans, GET_BY_ID, rtrans.root_id());
1552 ASSERT_TRUE(root.good());
1553 EXPECT_FALSE(dir()->HasChildren(&rtrans, rtrans.root_id()));
1554 EXPECT_TRUE(root.GetFirstChildId().IsNull());
1555 }
1556
1557 dir()->SaveChanges();
1558 }
1559
1560 TEST_F(SyncableDirectoryTest, ClientIndexRebuildsProperly) {
1561 int64_t written_metahandle;
1562 TestIdFactory factory;
1563 const Id id = factory.NewServerId();
1564 std::string name = "cheesepuffs";
1565 std::string tag = "dietcoke";
1566
1567 // Test creating a new meta entry.
1568 {
1569 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
1570 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name);
1571 ASSERT_TRUE(me.good());
1572 me.PutId(id);
1573 me.PutBaseVersion(1);
1574 me.PutUniqueClientTag(tag);
1575 written_metahandle = me.GetMetahandle();
1576 }
1577 dir()->SaveChanges();
1578
1579 // Close and reopen, causing index regeneration.
1580 ReopenDirectory();
1581 {
1582 ReadTransaction trans(FROM_HERE, dir().get());
1583 Entry me(&trans, GET_BY_CLIENT_TAG, tag);
1584 ASSERT_TRUE(me.good());
1585 EXPECT_EQ(me.GetId(), id);
1586 EXPECT_EQ(me.GetBaseVersion(), 1);
1587 EXPECT_EQ(me.GetUniqueClientTag(), tag);
1588 EXPECT_EQ(me.GetMetahandle(), written_metahandle);
1589 }
1590 }
1591
1592 TEST_F(SyncableDirectoryTest, ClientIndexRebuildsDeletedProperly) {
1593 TestIdFactory factory;
1594 const Id id = factory.NewServerId();
1595 std::string tag = "dietcoke";
1596
1597 // Test creating a deleted, unsynced, server meta entry.
1598 {
1599 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
1600 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "deleted");
1601 ASSERT_TRUE(me.good());
1602 me.PutId(id);
1603 me.PutBaseVersion(1);
1604 me.PutUniqueClientTag(tag);
1605 me.PutIsDel(true);
1606 me.PutIsUnsynced(true); // Or it might be purged.
1607 }
1608 dir()->SaveChanges();
1609
1610 // Close and reopen, causing index regeneration.
1611 ReopenDirectory();
1612 {
1613 ReadTransaction trans(FROM_HERE, dir().get());
1614 Entry me(&trans, GET_BY_CLIENT_TAG, tag);
1615 // Should still be present and valid in the client tag index.
1616 ASSERT_TRUE(me.good());
1617 EXPECT_EQ(me.GetId(), id);
1618 EXPECT_EQ(me.GetUniqueClientTag(), tag);
1619 EXPECT_TRUE(me.GetIsDel());
1620 EXPECT_TRUE(me.GetIsUnsynced());
1621 }
1622 }
1623
1624 TEST_F(SyncableDirectoryTest, ToValue) {
1625 const Id id = TestIdFactory::FromNumber(99);
1626 {
1627 ReadTransaction rtrans(FROM_HERE, dir().get());
1628 Entry e(&rtrans, GET_BY_ID, id);
1629 EXPECT_FALSE(e.good()); // Hasn't been written yet.
1630
1631 std::unique_ptr<base::DictionaryValue> value(e.ToValue(NULL));
1632 ExpectDictBooleanValue(false, *value, "good");
1633 EXPECT_EQ(1u, value->size());
1634 }
1635
1636 // Test creating a new meta entry.
1637 {
1638 WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get());
1639 MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "new");
1640 ASSERT_TRUE(me.good());
1641 me.PutId(id);
1642 me.PutBaseVersion(1);
1643
1644 std::unique_ptr<base::DictionaryValue> value(me.ToValue(NULL));
1645 ExpectDictBooleanValue(true, *value, "good");
1646 EXPECT_TRUE(value->HasKey("kernel"));
1647 ExpectDictStringValue("Bookmarks", *value, "modelType");
1648 ExpectDictBooleanValue(true, *value, "existsOnClientBecauseNameIsNonEmpty");
1649 ExpectDictBooleanValue(false, *value, "isRoot");
1650 }
1651
1652 dir()->SaveChanges();
1653 }
1654
1655 // A thread that creates a bunch of directory entries.
1656 class StressTransactionsDelegate : public base::PlatformThread::Delegate {
1657 public:
1658 StressTransactionsDelegate(Directory* dir, int thread_number)
1659 : dir_(dir), thread_number_(thread_number) {}
1660
1661 private:
1662 Directory* const dir_;
1663 const int thread_number_;
1664
1665 // PlatformThread::Delegate methods:
1666 void ThreadMain() override {
1667 int entry_count = 0;
1668 std::string path_name;
1669
1670 for (int i = 0; i < 20; ++i) {
1671 const int rand_action = base::RandInt(0, 9);
1672 if (rand_action < 4 && !path_name.empty()) {
1673 ReadTransaction trans(FROM_HERE, dir_);
1674 EXPECT_EQ(1, CountEntriesWithName(&trans, trans.root_id(), path_name));
1675 base::PlatformThread::Sleep(
1676 base::TimeDelta::FromMilliseconds(base::RandInt(0, 9)));
1677 } else {
1678 std::string unique_name =
1679 base::StringPrintf("%d.%d", thread_number_, entry_count++);
1680 path_name.assign(unique_name.begin(), unique_name.end());
1681 WriteTransaction trans(FROM_HERE, UNITTEST, dir_);
1682 MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), path_name);
1683 EXPECT_TRUE(e.good());
1684 base::PlatformThread::Sleep(
1685 base::TimeDelta::FromMilliseconds(base::RandInt(0, 19)));
1686 e.PutIsUnsynced(true);
1687 if (e.PutId(TestIdFactory::FromNumber(base::RandInt(0, RAND_MAX))) &&
1688 e.GetId().ServerKnows() && !e.GetId().IsRoot()) {
1689 e.PutBaseVersion(1);
1690 }
1691 }
1692 }
1693 }
1694
1695 DISALLOW_COPY_AND_ASSIGN(StressTransactionsDelegate);
1696 };
1697
1698 // Stress test Directory by accessing it from several threads concurrently.
1699 TEST_F(SyncableDirectoryTest, StressTransactions) {
1700 const int kThreadCount = 7;
1701 base::PlatformThreadHandle threads[kThreadCount];
1702 std::unique_ptr<StressTransactionsDelegate> thread_delegates[kThreadCount];
1703
1704 for (int i = 0; i < kThreadCount; ++i) {
1705 thread_delegates[i].reset(new StressTransactionsDelegate(dir().get(), i));
1706 ASSERT_TRUE(base::PlatformThread::Create(
1707 0, thread_delegates[i].get(), &threads[i]));
1708 }
1709
1710 for (int i = 0; i < kThreadCount; ++i) {
1711 base::PlatformThread::Join(threads[i]);
1712 }
1713 }
1714
1715 // Verify that Directory is notifed when a MutableEntry's AttachmentMetadata
1716 // changes.
1717 TEST_F(SyncableDirectoryTest, MutableEntry_PutAttachmentMetadata) {
1718 sync_pb::AttachmentMetadata attachment_metadata;
1719 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record();
1720 sync_pb::AttachmentIdProto attachment_id_proto =
1721 syncer::CreateAttachmentIdProto(0, 0);
1722 *record->mutable_id() = attachment_id_proto;
1723 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1724 {
1725 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1726
1727 // Create an entry with attachment metadata and see that the attachment id
1728 // is not linked.
1729 MutableEntry entry(
1730 &trans, CREATE, PREFERENCES, trans.root_id(), "some entry");
1731 entry.PutId(TestIdFactory::FromNumber(-1));
1732 entry.PutIsUnsynced(true);
1733
1734 Directory::Metahandles metahandles;
1735 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1736 dir()->GetMetahandlesByAttachmentId(
1737 &trans, attachment_id_proto, &metahandles);
1738 ASSERT_TRUE(metahandles.empty());
1739
1740 // Now add the attachment metadata and see that Directory believes it is
1741 // linked.
1742 entry.PutAttachmentMetadata(attachment_metadata);
1743 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto));
1744 dir()->GetMetahandlesByAttachmentId(
1745 &trans, attachment_id_proto, &metahandles);
1746 ASSERT_FALSE(metahandles.empty());
1747 ASSERT_EQ(metahandles[0], entry.GetMetahandle());
1748
1749 // Clear out the attachment metadata and see that it's no longer linked.
1750 sync_pb::AttachmentMetadata empty_attachment_metadata;
1751 entry.PutAttachmentMetadata(empty_attachment_metadata);
1752 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1753 dir()->GetMetahandlesByAttachmentId(
1754 &trans, attachment_id_proto, &metahandles);
1755 ASSERT_TRUE(metahandles.empty());
1756 }
1757 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1758 }
1759
1760 // Verify that UpdateAttachmentId updates attachment_id and is_on_server flag.
1761 TEST_F(SyncableDirectoryTest, MutableEntry_UpdateAttachmentId) {
1762 sync_pb::AttachmentMetadata attachment_metadata;
1763 sync_pb::AttachmentMetadataRecord* r1 = attachment_metadata.add_record();
1764 sync_pb::AttachmentMetadataRecord* r2 = attachment_metadata.add_record();
1765 *r1->mutable_id() = syncer::CreateAttachmentIdProto(0, 0);
1766 *r2->mutable_id() = syncer::CreateAttachmentIdProto(0, 0);
1767 sync_pb::AttachmentIdProto attachment_id_proto = r1->id();
1768
1769 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1770
1771 MutableEntry entry(
1772 &trans, CREATE, PREFERENCES, trans.root_id(), "some entry");
1773 entry.PutId(TestIdFactory::FromNumber(-1));
1774 entry.PutAttachmentMetadata(attachment_metadata);
1775
1776 {
1777 const sync_pb::AttachmentMetadata& entry_metadata =
1778 entry.GetAttachmentMetadata();
1779 ASSERT_EQ(2, entry_metadata.record_size());
1780 ASSERT_FALSE(entry_metadata.record(0).is_on_server());
1781 ASSERT_FALSE(entry_metadata.record(1).is_on_server());
1782 ASSERT_FALSE(entry.GetIsUnsynced());
1783 }
1784
1785 entry.MarkAttachmentAsOnServer(attachment_id_proto);
1786
1787 {
1788 // Re-get entry_metadata because it is immutable in the directory and
1789 // entry_metadata reference has been made invalid by
1790 // MarkAttachmentAsOnServer call above.
1791 const sync_pb::AttachmentMetadata& entry_metadata =
1792 entry.GetAttachmentMetadata();
1793 ASSERT_TRUE(entry_metadata.record(0).is_on_server());
1794 ASSERT_FALSE(entry_metadata.record(1).is_on_server());
1795 ASSERT_TRUE(entry.GetIsUnsynced());
1796 }
1797 }
1798
1799 // Verify that deleted entries with attachments will retain the attachments.
1800 TEST_F(SyncableDirectoryTest, Directory_DeleteDoesNotUnlinkAttachments) {
1801 sync_pb::AttachmentMetadata attachment_metadata;
1802 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record();
1803 sync_pb::AttachmentIdProto attachment_id_proto =
1804 syncer::CreateAttachmentIdProto(0, 0);
1805 *record->mutable_id() = attachment_id_proto;
1806 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1807 const Id id = TestIdFactory::FromNumber(-1);
1808
1809 // Create an entry with attachment metadata and see that the attachment id
1810 // is linked.
1811 CreateEntryWithAttachmentMetadata(
1812 PREFERENCES, "some entry", id, attachment_metadata);
1813 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto));
1814
1815 // Delete the entry and see that it's still linked because the entry hasn't
1816 // yet been purged.
1817 DeleteEntry(id);
1818 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto));
1819
1820 // Reload the Directory, purging the deleted entry, and see that the
1821 // attachment is no longer linked.
1822 SimulateSaveAndReloadDir();
1823 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1824 }
1825
1826 // Verify that a given attachment can be referenced by multiple entries and that
1827 // any one of the references is sufficient to ensure it remains linked.
1828 TEST_F(SyncableDirectoryTest, Directory_LastReferenceUnlinksAttachments) {
1829 // Create one attachment.
1830 sync_pb::AttachmentMetadata attachment_metadata;
1831 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record();
1832 sync_pb::AttachmentIdProto attachment_id_proto =
1833 syncer::CreateAttachmentIdProto(0, 0);
1834 *record->mutable_id() = attachment_id_proto;
1835
1836 // Create two entries, each referencing the attachment.
1837 const Id id1 = TestIdFactory::FromNumber(-1);
1838 const Id id2 = TestIdFactory::FromNumber(-2);
1839 CreateEntryWithAttachmentMetadata(
1840 PREFERENCES, "some entry", id1, attachment_metadata);
1841 CreateEntryWithAttachmentMetadata(
1842 PREFERENCES, "some other entry", id2, attachment_metadata);
1843
1844 // See that the attachment is considered linked.
1845 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto));
1846
1847 // Delete the first entry, reload the Directory, see that the attachment is
1848 // still linked.
1849 DeleteEntry(id1);
1850 SimulateSaveAndReloadDir();
1851 ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto));
1852
1853 // Delete the second entry, reload the Directory, see that the attachment is
1854 // no loner linked.
1855 DeleteEntry(id2);
1856 SimulateSaveAndReloadDir();
1857 ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto));
1858 }
1859
1860 TEST_F(SyncableDirectoryTest, Directory_GetAttachmentIdsToUpload) {
1861 // Create one attachment, referenced by two entries.
1862 AttachmentId attachment_id = AttachmentId::Create(0, 0);
1863 sync_pb::AttachmentIdProto attachment_id_proto = attachment_id.GetProto();
1864 sync_pb::AttachmentMetadata attachment_metadata;
1865 sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record();
1866 *record->mutable_id() = attachment_id_proto;
1867 const Id id1 = TestIdFactory::FromNumber(-1);
1868 const Id id2 = TestIdFactory::FromNumber(-2);
1869 CreateEntryWithAttachmentMetadata(
1870 PREFERENCES, "some entry", id1, attachment_metadata);
1871 CreateEntryWithAttachmentMetadata(
1872 PREFERENCES, "some other entry", id2, attachment_metadata);
1873
1874 // See that Directory reports that this attachment is not on the server.
1875 AttachmentIdList ids;
1876 {
1877 ReadTransaction trans(FROM_HERE, dir().get());
1878 dir()->GetAttachmentIdsToUpload(&trans, PREFERENCES, &ids);
1879 }
1880 ASSERT_EQ(1U, ids.size());
1881 ASSERT_EQ(attachment_id, *ids.begin());
1882
1883 // Call again, but this time with a ModelType for which there are no entries.
1884 // See that Directory correctly reports that there are none.
1885 {
1886 ReadTransaction trans(FROM_HERE, dir().get());
1887 dir()->GetAttachmentIdsToUpload(&trans, PASSWORDS, &ids);
1888 }
1889 ASSERT_TRUE(ids.empty());
1890
1891 // Now, mark the attachment as "on the server" via entry_1.
1892 {
1893 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1894 MutableEntry entry_1(&trans, GET_BY_ID, id1);
1895 entry_1.MarkAttachmentAsOnServer(attachment_id_proto);
1896 }
1897
1898 // See that Directory no longer reports that this attachment is not on the
1899 // server.
1900 {
1901 ReadTransaction trans(FROM_HERE, dir().get());
1902 dir()->GetAttachmentIdsToUpload(&trans, PREFERENCES, &ids);
1903 }
1904 ASSERT_TRUE(ids.empty());
1905 }
1906
1907 // Verify that the directory accepts entries with unset parent ID.
1908 TEST_F(SyncableDirectoryTest, MutableEntry_ImplicitParentId) {
1909 TestIdFactory id_factory;
1910 const Id root_id = TestIdFactory::root();
1911 const Id p_root_id = id_factory.NewServerId();
1912 const Id a_root_id = id_factory.NewServerId();
1913 const Id item1_id = id_factory.NewServerId();
1914 const Id item2_id = id_factory.NewServerId();
1915 const Id item3_id = id_factory.NewServerId();
1916 // Create two type root folders that are necessary (for now)
1917 // for creating items without explicitly set Parent ID
1918 {
1919 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1920 MutableEntry p_root(&trans, CREATE, PREFERENCES, root_id, "P");
1921 ASSERT_TRUE(p_root.good());
1922 p_root.PutIsDir(true);
1923 p_root.PutId(p_root_id);
1924 p_root.PutBaseVersion(1);
1925
1926 MutableEntry a_root(&trans, CREATE, AUTOFILL, root_id, "A");
1927 ASSERT_TRUE(a_root.good());
1928 a_root.PutIsDir(true);
1929 a_root.PutId(a_root_id);
1930 a_root.PutBaseVersion(1);
1931 }
1932
1933 // Create two entries with implicit parent nodes and one entry with explicit
1934 // parent node.
1935 {
1936 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1937 MutableEntry item1(&trans, CREATE, PREFERENCES, "P1");
1938 item1.PutBaseVersion(1);
1939 item1.PutId(item1_id);
1940 MutableEntry item2(&trans, CREATE, AUTOFILL, "A1");
1941 item2.PutBaseVersion(1);
1942 item2.PutId(item2_id);
1943 // Placing an AUTOFILL item under the root isn't expected,
1944 // but let's test it to verify that explicit root overrides the implicit
1945 // one and this entry doesn't end up under the "A" root.
1946 MutableEntry item3(&trans, CREATE, AUTOFILL, root_id, "A2");
1947 item3.PutBaseVersion(1);
1948 item3.PutId(item3_id);
1949 }
1950
1951 {
1952 ReadTransaction trans(FROM_HERE, dir().get());
1953 // Verify that item1 and item2 are good and have no ParentId.
1954 Entry item1(&trans, GET_BY_ID, item1_id);
1955 ASSERT_TRUE(item1.good());
1956 ASSERT_TRUE(item1.GetParentId().IsNull());
1957 Entry item2(&trans, GET_BY_ID, item2_id);
1958 ASSERT_TRUE(item2.good());
1959 ASSERT_TRUE(item2.GetParentId().IsNull());
1960 // Verify that p_root and a_root have exactly one child each
1961 // (subtract one to exclude roots themselves).
1962 Entry p_root(&trans, GET_BY_ID, p_root_id);
1963 ASSERT_EQ(item1_id, p_root.GetFirstChildId());
1964 ASSERT_EQ(1, p_root.GetTotalNodeCount() - 1);
1965 Entry a_root(&trans, GET_BY_ID, a_root_id);
1966 ASSERT_EQ(item2_id, a_root.GetFirstChildId());
1967 ASSERT_EQ(1, a_root.GetTotalNodeCount() - 1);
1968 }
1969 }
1970
1971 // Verify that the successor / predecessor navigation still works for
1972 // directory entries with unset Parent IDs.
1973 TEST_F(SyncableDirectoryTest, MutableEntry_ImplicitParentId_Siblings) {
1974 TestIdFactory id_factory;
1975 const Id root_id = TestIdFactory::root();
1976 const Id p_root_id = id_factory.NewServerId();
1977 const Id item1_id = id_factory.FromNumber(1);
1978 const Id item2_id = id_factory.FromNumber(2);
1979
1980 // Create type root folder for PREFERENCES.
1981 {
1982 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1983 MutableEntry p_root(&trans, CREATE, PREFERENCES, root_id, "P");
1984 ASSERT_TRUE(p_root.good());
1985 p_root.PutIsDir(true);
1986 p_root.PutId(p_root_id);
1987 p_root.PutBaseVersion(1);
1988 }
1989
1990 // Create two PREFERENCES entries with implicit parent nodes.
1991 {
1992 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
1993 MutableEntry item1(&trans, CREATE, PREFERENCES, "P1");
1994 item1.PutBaseVersion(1);
1995 item1.PutId(item1_id);
1996 MutableEntry item2(&trans, CREATE, PREFERENCES, "P2");
1997 item2.PutBaseVersion(1);
1998 item2.PutId(item2_id);
1999 }
2000
2001 // Verify GetSuccessorId and GetPredecessorId calls for these items.
2002 // Please note that items are sorted according to their ID, e.g.
2003 // item1 first, then item2.
2004 {
2005 ReadTransaction trans(FROM_HERE, dir().get());
2006 Entry item1(&trans, GET_BY_ID, item1_id);
2007 EXPECT_EQ(Id(), item1.GetPredecessorId());
2008 EXPECT_EQ(item2_id, item1.GetSuccessorId());
2009
2010 Entry item2(&trans, GET_BY_ID, item2_id);
2011 EXPECT_EQ(item1_id, item2.GetPredecessorId());
2012 EXPECT_EQ(Id(), item2.GetSuccessorId());
2013 }
2014 }
2015
2016 TEST_F(SyncableDirectoryTest, SaveChangesSnapshot_HasUnsavedMetahandleChanges) {
2017 EntryKernel kernel;
2018 Directory::SaveChangesSnapshot snapshot;
2019 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges());
2020 snapshot.dirty_metas.insert(&kernel);
2021 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges());
2022 snapshot.dirty_metas.clear();
2023
2024 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges());
2025 snapshot.metahandles_to_purge.insert(1);
2026 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges());
2027 snapshot.metahandles_to_purge.clear();
2028
2029 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges());
2030 snapshot.delete_journals.insert(&kernel);
2031 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges());
2032 snapshot.delete_journals.clear();
2033
2034 EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges());
2035 snapshot.delete_journals_to_purge.insert(1);
2036 EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges());
2037 snapshot.delete_journals_to_purge.clear();
2038 }
2039
2040 // Verify that Directory triggers an unrecoverable error when a catastrophic
2041 // DirectoryBackingStore error is detected.
2042 TEST_F(SyncableDirectoryTest, CatastrophicError) {
2043 MockUnrecoverableErrorHandler unrecoverable_error_handler;
2044 Directory dir(new InMemoryDirectoryBackingStore("catastrophic_error"),
2045 MakeWeakHandle(unrecoverable_error_handler.GetWeakPtr()),
2046 base::Closure(), nullptr, nullptr);
2047 ASSERT_EQ(OPENED, dir.Open(kDirectoryName, directory_change_delegate(),
2048 NullTransactionObserver()));
2049 ASSERT_EQ(0, unrecoverable_error_handler.invocation_count());
2050
2051 // Fire off two catastrophic errors. Call it twice to ensure Directory is
2052 // tolerant of multiple invocations since that may happen in the real world.
2053 dir.OnCatastrophicError();
2054 dir.OnCatastrophicError();
2055
2056 base::RunLoop().RunUntilIdle();
2057
2058 // See that the unrecoverable error handler has been invoked twice.
2059 ASSERT_EQ(2, unrecoverable_error_handler.invocation_count());
2060 }
2061
2062 bool EntitySpecificsValuesAreSame(const sync_pb::EntitySpecifics& v1,
2063 const sync_pb::EntitySpecifics& v2) {
2064 return &v1 == &v2;
2065 }
2066
2067 // Verifies that server and client specifics are shared when their values
2068 // are equal.
2069 TEST_F(SyncableDirectoryTest, SharingOfClientAndServerSpecifics) {
2070 sync_pb::EntitySpecifics specifics1;
2071 sync_pb::EntitySpecifics specifics2;
2072 sync_pb::EntitySpecifics specifics3;
2073 AddDefaultFieldValue(BOOKMARKS, &specifics1);
2074 AddDefaultFieldValue(BOOKMARKS, &specifics2);
2075 AddDefaultFieldValue(BOOKMARKS, &specifics3);
2076 specifics1.mutable_bookmark()->set_url("foo");
2077 specifics2.mutable_bookmark()->set_url("bar");
2078 // specifics3 has the same URL as specifics1
2079 specifics3.mutable_bookmark()->set_url("foo");
2080
2081 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
2082 MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "item");
2083 item.PutId(TestIdFactory::FromNumber(1));
2084 item.PutBaseVersion(10);
2085
2086 // Verify sharing.
2087 item.PutSpecifics(specifics1);
2088 item.PutServerSpecifics(specifics1);
2089 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(),
2090 item.GetServerSpecifics()));
2091
2092 // Verify that specifics are no longer shared.
2093 item.PutServerSpecifics(specifics2);
2094 EXPECT_FALSE(EntitySpecificsValuesAreSame(item.GetSpecifics(),
2095 item.GetServerSpecifics()));
2096
2097 // Verify that specifics are shared again because specifics3 matches
2098 // specifics1.
2099 item.PutServerSpecifics(specifics3);
2100 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(),
2101 item.GetServerSpecifics()));
2102
2103 // Verify that copying the same value back to SPECIFICS is still OK.
2104 item.PutSpecifics(specifics3);
2105 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(),
2106 item.GetServerSpecifics()));
2107
2108 // Verify sharing with BASE_SERVER_SPECIFICS.
2109 EXPECT_FALSE(EntitySpecificsValuesAreSame(item.GetServerSpecifics(),
2110 item.GetBaseServerSpecifics()));
2111 item.PutBaseServerSpecifics(specifics3);
2112 EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetServerSpecifics(),
2113 item.GetBaseServerSpecifics()));
2114 }
2115
2116 // Tests checking and marking a type as having its initial sync completed.
2117 TEST_F(SyncableDirectoryTest, InitialSyncEndedForType) {
2118 // Not completed if there is no root node.
2119 EXPECT_FALSE(dir()->InitialSyncEndedForType(PREFERENCES));
2120
2121 WriteTransaction trans(FROM_HERE, UNITTEST, dir().get());
2122 // Create the root node.
2123 ModelNeutralMutableEntry entry(&trans, syncable::CREATE_NEW_TYPE_ROOT,
2124 PREFERENCES);
2125 ASSERT_TRUE(entry.good());
2126
2127 entry.PutServerIsDir(true);
2128 entry.PutUniqueServerTag(ModelTypeToRootTag(PREFERENCES));
2129
2130 // Should still be marked as incomplete.
2131 EXPECT_FALSE(dir()->InitialSyncEndedForType(&trans, PREFERENCES));
2132
2133 // Mark as complete and verify.
2134 dir()->MarkInitialSyncEndedForType(&trans, PREFERENCES);
2135 EXPECT_TRUE(dir()->InitialSyncEndedForType(&trans, PREFERENCES));
2136 }
2137
2138 } // namespace syncable
2139
2140 } // namespace syncer
OLDNEW
« no previous file with comments | « sync/syncable/directory_unittest.h ('k') | sync/syncable/entry.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698