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