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