diff options
author | maniscalco@chromium.org <maniscalco@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 21:20:54 +0000 |
---|---|---|
committer | maniscalco@chromium.org <maniscalco@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 21:20:54 +0000 |
commit | f1ff9844d6cd664b818e137a5fca56b748cda715 (patch) | |
tree | 94918fec7bad1b778b0ef094b497297907ac8ae8 /sync | |
parent | 9d5b2d628c48c30ee8a6873bf7aec206b1bcb0fd (diff) | |
download | chromium_src-f1ff9844d6cd664b818e137a5fca56b748cda715.zip chromium_src-f1ff9844d6cd664b818e137a5fca56b748cda715.tar.gz chromium_src-f1ff9844d6cd664b818e137a5fca56b748cda715.tar.bz2 |
Refactor syncable_unittest.cc into smaller files.
No changes to test cases. Refactoring only. Ran through git cl format.
BUG=
Review URL: https://codereview.chromium.org/248293002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@265741 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r-- | sync/sync_tests.gypi | 3 | ||||
-rw-r--r-- | sync/syncable/directory_unittest.cc | 1188 | ||||
-rw-r--r-- | sync/syncable/directory_unittest.h | 100 | ||||
-rw-r--r-- | sync/syncable/entry_kernel_unittest.cc | 31 | ||||
-rw-r--r-- | sync/syncable/syncable_unittest.cc | 1378 |
5 files changed, 1413 insertions, 1287 deletions
diff --git a/sync/sync_tests.gypi b/sync/sync_tests.gypi index f6f88b7..fffefa5 100644 --- a/sync/sync_tests.gypi +++ b/sync/sync_tests.gypi @@ -303,7 +303,10 @@ 'sessions/model_type_registry_unittest.cc', 'sessions/nudge_tracker_unittest.cc', 'sessions/status_controller_unittest.cc', + 'syncable/directory_unittest.cc', + 'syncable/directory_unittest.h', 'syncable/directory_backing_store_unittest.cc', + 'syncable/entry_kernel_unittest.cc', 'syncable/model_type_unittest.cc', 'syncable/nigori_util_unittest.cc', 'syncable/parent_child_index_unittest.cc', diff --git a/sync/syncable/directory_unittest.cc b/sync/syncable/directory_unittest.cc new file mode 100644 index 0000000..503a12f --- /dev/null +++ b/sync/syncable/directory_unittest.cc @@ -0,0 +1,1188 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/syncable/directory_unittest.h" + +#include "sync/syncable/syncable_proto_util.h" +#include "sync/syncable/syncable_util.h" +#include "sync/syncable/syncable_write_transaction.h" +#include "sync/test/engine/test_syncable_utils.h" + +namespace syncer { + +namespace syncable { + +namespace { + +bool IsLegalNewParent(const Entry& a, const Entry& b) { + return IsLegalNewParent(a.trans(), a.GetId(), b.GetId()); +} + +} // namespace + +const char SyncableDirectoryTest::kDirectoryName[] = "Foo"; + +SyncableDirectoryTest::SyncableDirectoryTest() { +} + +SyncableDirectoryTest::~SyncableDirectoryTest() { +} + +void SyncableDirectoryTest::SetUp() { + dir_.reset(new Directory(new InMemoryDirectoryBackingStore(kDirectoryName), + &handler_, + NULL, + NULL, + NULL)); + ASSERT_TRUE(dir_.get()); + ASSERT_EQ(OPENED, + dir_->Open(kDirectoryName, &delegate_, NullTransactionObserver())); + ASSERT_TRUE(dir_->good()); +} + +void SyncableDirectoryTest::TearDown() { + if (dir_) + dir_->SaveChanges(); + dir_.reset(); +} + +// Creates an empty entry and sets the ID field to a default one. +void SyncableDirectoryTest::CreateEntry(const std::string& entryname) { + CreateEntry(entryname, TestIdFactory::FromNumber(-99)); +} + +// Creates an empty entry and sets the ID field to id. +void SyncableDirectoryTest::CreateEntry(const std::string& entryname, + const int id) { + CreateEntry(entryname, TestIdFactory::FromNumber(id)); +} +void SyncableDirectoryTest::CreateEntry(const std::string& entryname, Id id) { + WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); + MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), entryname); + ASSERT_TRUE(me.good()); + me.PutId(id); + me.PutIsUnsynced(true); +} + +DirOpenResult SyncableDirectoryTest::SimulateSaveAndReloadDir() { + if (!dir_->SaveChanges()) + return FAILED_IN_UNITTEST; + + return ReloadDirImpl(); +} + +DirOpenResult SyncableDirectoryTest::SimulateCrashAndReloadDir() { + return ReloadDirImpl(); +} + +void SyncableDirectoryTest::GetAllMetaHandles(BaseTransaction* trans, + MetahandleSet* result) { + dir_->GetAllMetaHandles(trans, result); +} + +void SyncableDirectoryTest::CheckPurgeEntriesWithTypeInSucceeded( + ModelTypeSet types_to_purge, + bool before_reload) { + SCOPED_TRACE(testing::Message("Before reload: ") << before_reload); + { + ReadTransaction trans(FROM_HERE, dir_.get()); + MetahandleSet all_set; + dir_->GetAllMetaHandles(&trans, &all_set); + EXPECT_EQ(4U, all_set.size()); + if (before_reload) + EXPECT_EQ(6U, dir_->kernel_->metahandles_to_purge.size()); + for (MetahandleSet::iterator iter = all_set.begin(); iter != all_set.end(); + ++iter) { + Entry e(&trans, GET_BY_HANDLE, *iter); + const ModelType local_type = e.GetModelType(); + const ModelType server_type = e.GetServerModelType(); + + // Note the dance around incrementing |it|, since we sometimes erase(). + if ((IsRealDataType(local_type) && types_to_purge.Has(local_type)) || + (IsRealDataType(server_type) && types_to_purge.Has(server_type))) { + FAIL() << "Illegal type should have been deleted."; + } + } + } + + for (ModelTypeSet::Iterator it = types_to_purge.First(); it.Good(); + it.Inc()) { + EXPECT_FALSE(dir_->InitialSyncEndedForType(it.Get())); + sync_pb::DataTypeProgressMarker progress; + dir_->GetDownloadProgress(it.Get(), &progress); + EXPECT_EQ("", progress.token()); + + ReadTransaction trans(FROM_HERE, dir_.get()); + sync_pb::DataTypeContext context; + dir_->GetDataTypeContext(&trans, it.Get(), &context); + EXPECT_TRUE(context.SerializeAsString().empty()); + } + EXPECT_FALSE(types_to_purge.Has(BOOKMARKS)); + EXPECT_TRUE(dir_->InitialSyncEndedForType(BOOKMARKS)); +} + +bool SyncableDirectoryTest::IsInDirtyMetahandles(int64 metahandle) { + return 1 == dir_->kernel_->dirty_metahandles.count(metahandle); +} + +bool SyncableDirectoryTest::IsInMetahandlesToPurge(int64 metahandle) { + return 1 == dir_->kernel_->metahandles_to_purge.count(metahandle); +} + +scoped_ptr<Directory>& SyncableDirectoryTest::dir() { + return dir_; +} + +DirectoryChangeDelegate* SyncableDirectoryTest::directory_change_delegate() { + return &delegate_; +} + +Encryptor* SyncableDirectoryTest::encryptor() { + return &encryptor_; +} + +UnrecoverableErrorHandler* +SyncableDirectoryTest::unrecoverable_error_handler() { + return &handler_; +} + +void SyncableDirectoryTest::ValidateEntry(BaseTransaction* trans, + int64 id, + bool check_name, + const std::string& name, + int64 base_version, + int64 server_version, + bool is_del) { + Entry e(trans, GET_BY_ID, TestIdFactory::FromNumber(id)); + ASSERT_TRUE(e.good()); + if (check_name) + ASSERT_TRUE(name == e.GetNonUniqueName()); + ASSERT_TRUE(base_version == e.GetBaseVersion()); + ASSERT_TRUE(server_version == e.GetServerVersion()); + ASSERT_TRUE(is_del == e.GetIsDel()); +} + +DirOpenResult SyncableDirectoryTest::ReloadDirImpl() { // Do some tricky things + // to preserve the + // backing store. + DirectoryBackingStore* saved_store = dir_->store_.release(); + + // Close the current directory. + dir_->Close(); + dir_.reset(); + + dir_.reset(new Directory(saved_store, &handler_, NULL, NULL, NULL)); + DirOpenResult result = + dir_->OpenImpl(kDirectoryName, &delegate_, NullTransactionObserver()); + + // If something went wrong, we need to clear this member. If we don't, + // TearDown() will be guaranteed to crash when it calls SaveChanges(). + if (result != OPENED) + dir_.reset(); + + return result; +} + +TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) { + const int metas_to_create = 50; + MetahandleSet expected_purges; + MetahandleSet all_handles; + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + for (int i = 0; i < metas_to_create; i++) { + MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); + e.PutIsUnsynced(true); + sync_pb::EntitySpecifics specs; + if (i % 2 == 0) { + AddDefaultFieldValue(BOOKMARKS, &specs); + expected_purges.insert(e.GetMetahandle()); + all_handles.insert(e.GetMetahandle()); + } else { + AddDefaultFieldValue(PREFERENCES, &specs); + all_handles.insert(e.GetMetahandle()); + } + e.PutSpecifics(specs); + e.PutServerSpecifics(specs); + } + } + + ModelTypeSet to_purge(BOOKMARKS); + dir()->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet()); + + Directory::SaveChangesSnapshot snapshot1; + base::AutoLock scoped_lock(dir()->kernel_->save_changes_mutex); + dir()->TakeSnapshotForSaveChanges(&snapshot1); + EXPECT_TRUE(expected_purges == snapshot1.metahandles_to_purge); + + to_purge.Clear(); + to_purge.Put(PREFERENCES); + dir()->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet()); + + dir()->HandleSaveChangesFailure(snapshot1); + + Directory::SaveChangesSnapshot snapshot2; + dir()->TakeSnapshotForSaveChanges(&snapshot2); + EXPECT_TRUE(all_handles == snapshot2.metahandles_to_purge); +} + +TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) { + const int metahandles_to_create = 100; + std::vector<int64> expected_dirty_metahandles; + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + for (int i = 0; i < metahandles_to_create; i++) { + MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); + expected_dirty_metahandles.push_back(e.GetMetahandle()); + e.PutIsUnsynced(true); + } + } + // Fake SaveChanges() and make sure we got what we expected. + { + Directory::SaveChangesSnapshot snapshot; + base::AutoLock scoped_lock(dir()->kernel_->save_changes_mutex); + dir()->TakeSnapshotForSaveChanges(&snapshot); + // Make sure there's an entry for each new metahandle. Make sure all + // entries are marked dirty. + ASSERT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size()); + for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); + i != snapshot.dirty_metas.end(); + ++i) { + ASSERT_TRUE((*i)->is_dirty()); + } + dir()->VacuumAfterSaveChanges(snapshot); + } + // Put a new value with existing transactions as well as adding new ones. + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + std::vector<int64> new_dirty_metahandles; + for (std::vector<int64>::const_iterator i = + expected_dirty_metahandles.begin(); + i != expected_dirty_metahandles.end(); + ++i) { + // Change existing entries to directories to dirty them. + MutableEntry e1(&trans, GET_BY_HANDLE, *i); + e1.PutIsDir(true); + e1.PutIsUnsynced(true); + // Add new entries + MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar"); + e2.PutIsUnsynced(true); + new_dirty_metahandles.push_back(e2.GetMetahandle()); + } + expected_dirty_metahandles.insert(expected_dirty_metahandles.end(), + new_dirty_metahandles.begin(), + new_dirty_metahandles.end()); + } + // Fake SaveChanges() and make sure we got what we expected. + { + Directory::SaveChangesSnapshot snapshot; + base::AutoLock scoped_lock(dir()->kernel_->save_changes_mutex); + dir()->TakeSnapshotForSaveChanges(&snapshot); + // Make sure there's an entry for each new metahandle. Make sure all + // entries are marked dirty. + EXPECT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size()); + for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); + i != snapshot.dirty_metas.end(); + ++i) { + EXPECT_TRUE((*i)->is_dirty()); + } + dir()->VacuumAfterSaveChanges(snapshot); + } +} + +TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) { + const int metahandles_to_create = 100; + + // half of 2 * metahandles_to_create + const unsigned int number_changed = 100u; + std::vector<int64> expected_dirty_metahandles; + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + for (int i = 0; i < metahandles_to_create; i++) { + MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); + expected_dirty_metahandles.push_back(e.GetMetahandle()); + e.PutIsUnsynced(true); + } + } + dir()->SaveChanges(); + // Put a new value with existing transactions as well as adding new ones. + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + std::vector<int64> new_dirty_metahandles; + for (std::vector<int64>::const_iterator i = + expected_dirty_metahandles.begin(); + i != expected_dirty_metahandles.end(); + ++i) { + // Change existing entries to directories to dirty them. + MutableEntry e1(&trans, GET_BY_HANDLE, *i); + ASSERT_TRUE(e1.good()); + e1.PutIsDir(true); + e1.PutIsUnsynced(true); + // Add new entries + MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar"); + e2.PutIsUnsynced(true); + new_dirty_metahandles.push_back(e2.GetMetahandle()); + } + expected_dirty_metahandles.insert(expected_dirty_metahandles.end(), + new_dirty_metahandles.begin(), + new_dirty_metahandles.end()); + } + dir()->SaveChanges(); + // Don't make any changes whatsoever and ensure nothing comes back. + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + for (std::vector<int64>::const_iterator i = + expected_dirty_metahandles.begin(); + i != expected_dirty_metahandles.end(); + ++i) { + MutableEntry e(&trans, GET_BY_HANDLE, *i); + ASSERT_TRUE(e.good()); + // We aren't doing anything to dirty these entries. + } + } + // Fake SaveChanges() and make sure we got what we expected. + { + Directory::SaveChangesSnapshot snapshot; + base::AutoLock scoped_lock(dir()->kernel_->save_changes_mutex); + dir()->TakeSnapshotForSaveChanges(&snapshot); + // Make sure there are no dirty_metahandles. + EXPECT_EQ(0u, snapshot.dirty_metas.size()); + dir()->VacuumAfterSaveChanges(snapshot); + } + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + bool should_change = false; + for (std::vector<int64>::const_iterator i = + expected_dirty_metahandles.begin(); + i != expected_dirty_metahandles.end(); + ++i) { + // Maybe change entries by flipping IS_DIR. + MutableEntry e(&trans, GET_BY_HANDLE, *i); + ASSERT_TRUE(e.good()); + should_change = !should_change; + if (should_change) { + bool not_dir = !e.GetIsDir(); + e.PutIsDir(not_dir); + e.PutIsUnsynced(true); + } + } + } + // Fake SaveChanges() and make sure we got what we expected. + { + Directory::SaveChangesSnapshot snapshot; + base::AutoLock scoped_lock(dir()->kernel_->save_changes_mutex); + dir()->TakeSnapshotForSaveChanges(&snapshot); + // Make sure there's an entry for each changed metahandle. Make sure all + // entries are marked dirty. + EXPECT_EQ(number_changed, snapshot.dirty_metas.size()); + for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); + i != snapshot.dirty_metas.end(); + ++i) { + EXPECT_TRUE((*i)->is_dirty()); + } + dir()->VacuumAfterSaveChanges(snapshot); + } +} + +// Test delete journals management. +TEST_F(SyncableDirectoryTest, ManageDeleteJournals) { + sync_pb::EntitySpecifics bookmark_specifics; + AddDefaultFieldValue(BOOKMARKS, &bookmark_specifics); + bookmark_specifics.mutable_bookmark()->set_url("url"); + + Id id1 = TestIdFactory::FromNumber(-1); + Id id2 = TestIdFactory::FromNumber(-2); + int64 handle1 = 0; + int64 handle2 = 0; + { + // Create two bookmark entries and save in database. + CreateEntry("item1", id1); + CreateEntry("item2", id2); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry item1(&trans, GET_BY_ID, id1); + ASSERT_TRUE(item1.good()); + handle1 = item1.GetMetahandle(); + item1.PutSpecifics(bookmark_specifics); + item1.PutServerSpecifics(bookmark_specifics); + MutableEntry item2(&trans, GET_BY_ID, id2); + ASSERT_TRUE(item2.good()); + handle2 = item2.GetMetahandle(); + item2.PutSpecifics(bookmark_specifics); + item2.PutServerSpecifics(bookmark_specifics); + } + ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); + } + + { // Test adding and saving delete journals. + DeleteJournal* delete_journal = dir()->delete_journal(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + EntryKernelSet journal_entries; + delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); + ASSERT_EQ(0u, journal_entries.size()); + + // Set SERVER_IS_DEL of the entries to true and they should be added to + // delete journals. + MutableEntry item1(&trans, GET_BY_ID, id1); + ASSERT_TRUE(item1.good()); + item1.PutServerIsDel(true); + MutableEntry item2(&trans, GET_BY_ID, id2); + ASSERT_TRUE(item2.good()); + item2.PutServerIsDel(true); + EntryKernel tmp; + tmp.put(ID, id1); + EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp)); + tmp.put(ID, id2); + EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp)); + } + + // Save delete journals in database and verify memory clearing. + ASSERT_TRUE(dir()->SaveChanges()); + { + ReadTransaction trans(FROM_HERE, dir().get()); + EXPECT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans)); + } + ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); + } + + { + { + // Test reading delete journals from database. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + DeleteJournal* delete_journal = dir()->delete_journal(); + EntryKernelSet journal_entries; + delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); + ASSERT_EQ(2u, journal_entries.size()); + EntryKernel tmp; + tmp.put(META_HANDLE, handle1); + EXPECT_TRUE(journal_entries.count(&tmp)); + tmp.put(META_HANDLE, handle2); + EXPECT_TRUE(journal_entries.count(&tmp)); + + // Purge item2. + MetahandleSet to_purge; + to_purge.insert(handle2); + delete_journal->PurgeDeleteJournals(&trans, to_purge); + + // Verify that item2 is purged from journals in memory and will be + // purged from database. + tmp.put(ID, id2); + EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp)); + EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size()); + EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle2)); + } + ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); + } + + { + { + // Verify purged entry is gone in database. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + DeleteJournal* delete_journal = dir()->delete_journal(); + EntryKernelSet journal_entries; + delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); + ASSERT_EQ(1u, journal_entries.size()); + EntryKernel tmp; + tmp.put(ID, id1); + tmp.put(META_HANDLE, handle1); + EXPECT_TRUE(journal_entries.count(&tmp)); + + // Undelete item1. + MutableEntry item1(&trans, GET_BY_ID, id1); + ASSERT_TRUE(item1.good()); + item1.PutServerIsDel(false); + EXPECT_TRUE(delete_journal->delete_journals_.empty()); + EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size()); + EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle1)); + } + ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); + } + + { + // Verify undeleted entry is gone from database. + ReadTransaction trans(FROM_HERE, dir().get()); + DeleteJournal* delete_journal = dir()->delete_journal(); + ASSERT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans)); + } +} + +TEST_F(SyncableDirectoryTest, TestBasicLookupNonExistantID) { + ReadTransaction rtrans(FROM_HERE, dir().get()); + Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99)); + ASSERT_FALSE(e.good()); +} + +TEST_F(SyncableDirectoryTest, TestBasicLookupValidID) { + CreateEntry("rtc"); + ReadTransaction rtrans(FROM_HERE, dir().get()); + Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99)); + ASSERT_TRUE(e.good()); +} + +TEST_F(SyncableDirectoryTest, TestDelete) { + std::string name = "peanut butter jelly time"; + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), name); + ASSERT_TRUE(e1.good()); + e1.PutIsDel(true); + MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), name); + ASSERT_TRUE(e2.good()); + e2.PutIsDel(true); + MutableEntry e3(&trans, CREATE, BOOKMARKS, trans.root_id(), name); + ASSERT_TRUE(e3.good()); + e3.PutIsDel(true); + + e1.PutIsDel(false); + e2.PutIsDel(false); + e3.PutIsDel(false); + + e1.PutIsDel(true); + e2.PutIsDel(true); + e3.PutIsDel(true); +} + +TEST_F(SyncableDirectoryTest, TestGetUnsynced) { + Directory::Metahandles handles; + int64 handle1, handle2; + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + dir()->GetUnsyncedMetaHandles(&trans, &handles); + ASSERT_TRUE(0 == handles.size()); + + MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba"); + ASSERT_TRUE(e1.good()); + handle1 = e1.GetMetahandle(); + e1.PutBaseVersion(1); + e1.PutIsDir(true); + e1.PutId(TestIdFactory::FromNumber(101)); + + MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread"); + ASSERT_TRUE(e2.good()); + handle2 = e2.GetMetahandle(); + e2.PutBaseVersion(1); + e2.PutId(TestIdFactory::FromNumber(102)); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + dir()->GetUnsyncedMetaHandles(&trans, &handles); + ASSERT_TRUE(0 == handles.size()); + + MutableEntry e3(&trans, GET_BY_HANDLE, handle1); + ASSERT_TRUE(e3.good()); + e3.PutIsUnsynced(true); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + dir()->GetUnsyncedMetaHandles(&trans, &handles); + ASSERT_TRUE(1 == handles.size()); + ASSERT_TRUE(handle1 == handles[0]); + + MutableEntry e4(&trans, GET_BY_HANDLE, handle2); + ASSERT_TRUE(e4.good()); + e4.PutIsUnsynced(true); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + dir()->GetUnsyncedMetaHandles(&trans, &handles); + ASSERT_TRUE(2 == handles.size()); + if (handle1 == handles[0]) { + ASSERT_TRUE(handle2 == handles[1]); + } else { + ASSERT_TRUE(handle2 == handles[0]); + ASSERT_TRUE(handle1 == handles[1]); + } + + MutableEntry e5(&trans, GET_BY_HANDLE, handle1); + ASSERT_TRUE(e5.good()); + ASSERT_TRUE(e5.GetIsUnsynced()); + ASSERT_TRUE(e5.PutIsUnsynced(false)); + ASSERT_FALSE(e5.GetIsUnsynced()); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + dir()->GetUnsyncedMetaHandles(&trans, &handles); + ASSERT_TRUE(1 == handles.size()); + ASSERT_TRUE(handle2 == handles[0]); + } +} + +TEST_F(SyncableDirectoryTest, TestGetUnappliedUpdates) { + std::vector<int64> handles; + int64 handle1, handle2; + const FullModelTypeSet all_types = FullModelTypeSet::All(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); + ASSERT_TRUE(0 == handles.size()); + + MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba"); + ASSERT_TRUE(e1.good()); + handle1 = e1.GetMetahandle(); + e1.PutIsUnappliedUpdate(false); + e1.PutBaseVersion(1); + e1.PutId(TestIdFactory::FromNumber(101)); + e1.PutIsDir(true); + + MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread"); + ASSERT_TRUE(e2.good()); + handle2 = e2.GetMetahandle(); + e2.PutIsUnappliedUpdate(false); + e2.PutBaseVersion(1); + e2.PutId(TestIdFactory::FromNumber(102)); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); + ASSERT_TRUE(0 == handles.size()); + + MutableEntry e3(&trans, GET_BY_HANDLE, handle1); + ASSERT_TRUE(e3.good()); + e3.PutIsUnappliedUpdate(true); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); + ASSERT_TRUE(1 == handles.size()); + ASSERT_TRUE(handle1 == handles[0]); + + MutableEntry e4(&trans, GET_BY_HANDLE, handle2); + ASSERT_TRUE(e4.good()); + e4.PutIsUnappliedUpdate(true); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); + ASSERT_TRUE(2 == handles.size()); + if (handle1 == handles[0]) { + ASSERT_TRUE(handle2 == handles[1]); + } else { + ASSERT_TRUE(handle2 == handles[0]); + ASSERT_TRUE(handle1 == handles[1]); + } + + MutableEntry e5(&trans, GET_BY_HANDLE, handle1); + ASSERT_TRUE(e5.good()); + e5.PutIsUnappliedUpdate(false); + } + dir()->SaveChanges(); + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); + ASSERT_TRUE(1 == handles.size()); + ASSERT_TRUE(handle2 == handles[0]); + } +} + +TEST_F(SyncableDirectoryTest, DeleteBug_531383) { + // Try to evoke a check failure... + TestIdFactory id_factory; + int64 grandchild_handle; + { + WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry parent(&wtrans, CREATE, BOOKMARKS, id_factory.root(), "Bob"); + ASSERT_TRUE(parent.good()); + parent.PutIsDir(true); + parent.PutId(id_factory.NewServerId()); + parent.PutBaseVersion(1); + MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob"); + ASSERT_TRUE(child.good()); + child.PutIsDir(true); + child.PutId(id_factory.NewServerId()); + child.PutBaseVersion(1); + MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); + ASSERT_TRUE(grandchild.good()); + grandchild.PutId(id_factory.NewServerId()); + grandchild.PutBaseVersion(1); + grandchild.PutIsDel(true); + MutableEntry twin(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); + ASSERT_TRUE(twin.good()); + twin.PutIsDel(true); + grandchild.PutIsDel(false); + + grandchild_handle = grandchild.GetMetahandle(); + } + dir()->SaveChanges(); + { + WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry grandchild(&wtrans, GET_BY_HANDLE, grandchild_handle); + grandchild.PutIsDel(true); // Used to CHECK fail here. + } +} + +TEST_F(SyncableDirectoryTest, TestIsLegalNewParent) { + TestIdFactory id_factory; + WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); + Entry root(&wtrans, GET_BY_ID, id_factory.root()); + ASSERT_TRUE(root.good()); + MutableEntry parent(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Bob"); + ASSERT_TRUE(parent.good()); + parent.PutIsDir(true); + parent.PutId(id_factory.NewServerId()); + parent.PutBaseVersion(1); + MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob"); + ASSERT_TRUE(child.good()); + child.PutIsDir(true); + child.PutId(id_factory.NewServerId()); + child.PutBaseVersion(1); + MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); + ASSERT_TRUE(grandchild.good()); + grandchild.PutId(id_factory.NewServerId()); + grandchild.PutBaseVersion(1); + + MutableEntry parent2(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Pete"); + ASSERT_TRUE(parent2.good()); + parent2.PutIsDir(true); + parent2.PutId(id_factory.NewServerId()); + parent2.PutBaseVersion(1); + MutableEntry child2(&wtrans, CREATE, BOOKMARKS, parent2.GetId(), "Pete"); + ASSERT_TRUE(child2.good()); + child2.PutIsDir(true); + child2.PutId(id_factory.NewServerId()); + child2.PutBaseVersion(1); + MutableEntry grandchild2(&wtrans, CREATE, BOOKMARKS, child2.GetId(), "Pete"); + ASSERT_TRUE(grandchild2.good()); + grandchild2.PutId(id_factory.NewServerId()); + grandchild2.PutBaseVersion(1); + // resulting tree + // root + // / | + // parent parent2 + // | | + // child child2 + // | | + // grandchild grandchild2 + ASSERT_TRUE(IsLegalNewParent(child, root)); + ASSERT_TRUE(IsLegalNewParent(child, parent)); + ASSERT_FALSE(IsLegalNewParent(child, child)); + ASSERT_FALSE(IsLegalNewParent(child, grandchild)); + ASSERT_TRUE(IsLegalNewParent(child, parent2)); + ASSERT_TRUE(IsLegalNewParent(child, grandchild2)); + ASSERT_FALSE(IsLegalNewParent(parent, grandchild)); + ASSERT_FALSE(IsLegalNewParent(root, grandchild)); + ASSERT_FALSE(IsLegalNewParent(parent, grandchild)); +} + +TEST_F(SyncableDirectoryTest, TestEntryIsInFolder) { + // Create a subdir and an entry. + int64 entry_handle; + syncable::Id folder_id; + syncable::Id entry_id; + std::string entry_name = "entry"; + + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "folder"); + ASSERT_TRUE(folder.good()); + folder.PutIsDir(true); + EXPECT_TRUE(folder.PutIsUnsynced(true)); + folder_id = folder.GetId(); + + MutableEntry entry(&trans, CREATE, BOOKMARKS, folder.GetId(), entry_name); + ASSERT_TRUE(entry.good()); + entry_handle = entry.GetMetahandle(); + entry.PutIsUnsynced(true); + entry_id = entry.GetId(); + } + + // Make sure we can find the entry in the folder. + { + ReadTransaction trans(FROM_HERE, dir().get()); + EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), entry_name)); + EXPECT_EQ(1, CountEntriesWithName(&trans, folder_id, entry_name)); + + Entry entry(&trans, GET_BY_ID, entry_id); + ASSERT_TRUE(entry.good()); + EXPECT_EQ(entry_handle, entry.GetMetahandle()); + EXPECT_TRUE(entry.GetNonUniqueName() == entry_name); + EXPECT_TRUE(entry.GetParentId() == folder_id); + } +} + +TEST_F(SyncableDirectoryTest, TestParentIdIndexUpdate) { + std::string child_name = "child"; + + WriteTransaction wt(FROM_HERE, UNITTEST, dir().get()); + MutableEntry parent_folder(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder1"); + parent_folder.PutIsUnsynced(true); + parent_folder.PutIsDir(true); + + MutableEntry parent_folder2(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder2"); + parent_folder2.PutIsUnsynced(true); + parent_folder2.PutIsDir(true); + + MutableEntry child(&wt, CREATE, BOOKMARKS, parent_folder.GetId(), child_name); + child.PutIsDir(true); + child.PutIsUnsynced(true); + + ASSERT_TRUE(child.good()); + + EXPECT_EQ(0, CountEntriesWithName(&wt, wt.root_id(), child_name)); + EXPECT_EQ(parent_folder.GetId(), child.GetParentId()); + EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder.GetId(), child_name)); + EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name)); + child.PutParentId(parent_folder2.GetId()); + EXPECT_EQ(parent_folder2.GetId(), child.GetParentId()); + EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder.GetId(), child_name)); + EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name)); +} + +TEST_F(SyncableDirectoryTest, TestNoReindexDeletedItems) { + std::string folder_name = "folder"; + std::string new_name = "new_name"; + + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), folder_name); + ASSERT_TRUE(folder.good()); + folder.PutIsDir(true); + folder.PutIsDel(true); + + EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name)); + + MutableEntry deleted(&trans, GET_BY_ID, folder.GetId()); + ASSERT_TRUE(deleted.good()); + deleted.PutParentId(trans.root_id()); + deleted.PutNonUniqueName(new_name); + + EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name)); + EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), new_name)); +} + +TEST_F(SyncableDirectoryTest, TestCaseChangeRename) { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "CaseChange"); + ASSERT_TRUE(folder.good()); + folder.PutParentId(trans.root_id()); + folder.PutNonUniqueName("CASECHANGE"); + folder.PutIsDel(true); +} + +// Create items of each model type, and check that GetModelType and +// GetServerModelType return the right value. +TEST_F(SyncableDirectoryTest, GetModelType) { + TestIdFactory id_factory; + ModelTypeSet protocol_types = ProtocolTypes(); + for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good(); + iter.Inc()) { + ModelType datatype = iter.Get(); + SCOPED_TRACE(testing::Message("Testing model type ") << datatype); + switch (datatype) { + case UNSPECIFIED: + case TOP_LEVEL_FOLDER: + continue; // Datatype isn't a function of Specifics. + default: + break; + } + sync_pb::EntitySpecifics specifics; + AddDefaultFieldValue(datatype, &specifics); + + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "Folder"); + ASSERT_TRUE(folder.good()); + folder.PutId(id_factory.NewServerId()); + folder.PutSpecifics(specifics); + folder.PutBaseVersion(1); + folder.PutIsDir(true); + folder.PutIsDel(false); + ASSERT_EQ(datatype, folder.GetModelType()); + + MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item"); + ASSERT_TRUE(item.good()); + item.PutId(id_factory.NewServerId()); + item.PutSpecifics(specifics); + item.PutBaseVersion(1); + item.PutIsDir(false); + item.PutIsDel(false); + ASSERT_EQ(datatype, item.GetModelType()); + + // It's critical that deletion records retain their datatype, so that + // they can be dispatched to the appropriate change processor. + MutableEntry deleted_item( + &trans, CREATE, BOOKMARKS, trans.root_id(), "Deleted Item"); + ASSERT_TRUE(item.good()); + deleted_item.PutId(id_factory.NewServerId()); + deleted_item.PutSpecifics(specifics); + deleted_item.PutBaseVersion(1); + deleted_item.PutIsDir(false); + deleted_item.PutIsDel(true); + ASSERT_EQ(datatype, deleted_item.GetModelType()); + + MutableEntry server_folder( + &trans, CREATE_NEW_UPDATE_ITEM, id_factory.NewServerId()); + ASSERT_TRUE(server_folder.good()); + server_folder.PutServerSpecifics(specifics); + server_folder.PutBaseVersion(1); + server_folder.PutServerIsDir(true); + server_folder.PutServerIsDel(false); + ASSERT_EQ(datatype, server_folder.GetServerModelType()); + + MutableEntry server_item( + &trans, CREATE_NEW_UPDATE_ITEM, id_factory.NewServerId()); + ASSERT_TRUE(server_item.good()); + server_item.PutServerSpecifics(specifics); + server_item.PutBaseVersion(1); + server_item.PutServerIsDir(false); + server_item.PutServerIsDel(false); + ASSERT_EQ(datatype, server_item.GetServerModelType()); + + sync_pb::SyncEntity folder_entity; + folder_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId())); + folder_entity.set_deleted(false); + folder_entity.set_folder(true); + folder_entity.mutable_specifics()->CopyFrom(specifics); + ASSERT_EQ(datatype, GetModelType(folder_entity)); + + sync_pb::SyncEntity item_entity; + item_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId())); + item_entity.set_deleted(false); + item_entity.set_folder(false); + item_entity.mutable_specifics()->CopyFrom(specifics); + ASSERT_EQ(datatype, GetModelType(item_entity)); + } +} + +// A test that roughly mimics the directory interaction that occurs when a +// bookmark folder and entry are created then synced for the first time. It is +// a more common variant of the 'DeletedAndUnsyncedChild' scenario tested below. +TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ParentAndChild) { + TestIdFactory id_factory; + Id orig_parent_id; + Id orig_child_id; + + { + // Create two client-side items, a parent and child. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); + parent.PutIsDir(true); + parent.PutIsUnsynced(true); + + MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); + child.PutIsUnsynced(true); + + orig_parent_id = parent.GetId(); + orig_child_id = child.GetId(); + } + + { + // Simulate what happens after committing two items. Their IDs will be + // replaced with server IDs. The child is renamed first, then the parent. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); + MutableEntry child(&trans, GET_BY_ID, orig_child_id); + + ChangeEntryIDAndUpdateChildren(&trans, &child, id_factory.NewServerId()); + child.PutIsUnsynced(false); + child.PutBaseVersion(1); + child.PutServerVersion(1); + + ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); + parent.PutIsUnsynced(false); + parent.PutBaseVersion(1); + parent.PutServerVersion(1); + } + + // Final check for validity. + EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); +} + +// A test based on the scenario where we create a bookmark folder and entry +// locally, but with a twist. In this case, the bookmark is deleted before we +// are able to sync either it or its parent folder. This scenario used to cause +// directory corruption, see crbug.com/125381. +TEST_F(SyncableDirectoryTest, + ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild) { + TestIdFactory id_factory; + Id orig_parent_id; + Id orig_child_id; + + { + // Create two client-side items, a parent and child. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); + parent.PutIsDir(true); + parent.PutIsUnsynced(true); + + MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); + child.PutIsUnsynced(true); + + orig_parent_id = parent.GetId(); + orig_child_id = child.GetId(); + } + + { + // Delete the child. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry child(&trans, GET_BY_ID, orig_child_id); + child.PutIsDel(true); + } + + { + // Simulate what happens after committing the parent. Its ID will be + // replaced with server a ID. + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); + + ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); + parent.PutIsUnsynced(false); + parent.PutBaseVersion(1); + parent.PutServerVersion(1); + } + + // Final check for validity. + EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); +} + +// Ask the directory to generate a unique ID. Close and re-open the database +// without saving, then ask for another unique ID. Verify IDs are not reused. +// This scenario simulates a crash within the first few seconds of operation. +TEST_F(SyncableDirectoryTest, LocalIdReuseTest) { + Id pre_crash_id = dir()->NextId(); + SimulateCrashAndReloadDir(); + Id post_crash_id = dir()->NextId(); + EXPECT_NE(pre_crash_id, post_crash_id); +} + +// Ask the directory to generate a unique ID. Save the directory. Close and +// re-open the database without saving, then ask for another unique ID. Verify +// IDs are not reused. This scenario simulates a steady-state crash. +TEST_F(SyncableDirectoryTest, LocalIdReuseTestWithSave) { + Id pre_crash_id = dir()->NextId(); + dir()->SaveChanges(); + SimulateCrashAndReloadDir(); + Id post_crash_id = dir()->NextId(); + EXPECT_NE(pre_crash_id, post_crash_id); +} + +// Ensure that the unsynced, is_del and server unkown entries that may have been +// left in the database by old clients will be deleted when we open the old +// database. +TEST_F(SyncableDirectoryTest, OldClientLeftUnsyncedDeletedLocalItem) { + // We must create an entry with the offending properties. This is done with + // some abuse of the MutableEntry's API; it doesn't expect us to modify an + // item after it is deleted. If this hack becomes impractical we will need to + // find a new way to simulate this scenario. + + TestIdFactory id_factory; + + // Happy-path: These valid entries should not get deleted. + Id server_knows_id = id_factory.NewServerId(); + Id not_is_del_id = id_factory.NewLocalId(); + + // The ID of the entry which will be unsynced, is_del and !ServerKnows(). + Id zombie_id = id_factory.NewLocalId(); + + // We're about to do some bad things. Tell the directory verification + // routines to look the other way. + dir()->SetInvariantCheckLevel(OFF); + + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + // Create an uncommitted tombstone entry. + MutableEntry server_knows( + &trans, CREATE, BOOKMARKS, id_factory.root(), "server_knows"); + server_knows.PutId(server_knows_id); + server_knows.PutIsUnsynced(true); + server_knows.PutIsDel(true); + server_knows.PutBaseVersion(5); + server_knows.PutServerVersion(4); + + // Create a valid update entry. + MutableEntry not_is_del( + &trans, CREATE, BOOKMARKS, id_factory.root(), "not_is_del"); + not_is_del.PutId(not_is_del_id); + not_is_del.PutIsDel(false); + not_is_del.PutIsUnsynced(true); + + // Create a tombstone which should never be sent to the server because the + // server never knew about the item's existence. + // + // New clients should never put entries into this state. We work around + // this by setting IS_DEL before setting IS_UNSYNCED, something which the + // client should never do in practice. + MutableEntry zombie(&trans, CREATE, BOOKMARKS, id_factory.root(), "zombie"); + zombie.PutId(zombie_id); + zombie.PutIsDel(true); + zombie.PutIsUnsynced(true); + } + + ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); + + { + ReadTransaction trans(FROM_HERE, dir().get()); + + // The directory loading routines should have cleaned things up, making it + // safe to check invariants once again. + dir()->FullyCheckTreeInvariants(&trans); + + Entry server_knows(&trans, GET_BY_ID, server_knows_id); + EXPECT_TRUE(server_knows.good()); + + Entry not_is_del(&trans, GET_BY_ID, not_is_del_id); + EXPECT_TRUE(not_is_del.good()); + + Entry zombie(&trans, GET_BY_ID, zombie_id); + EXPECT_FALSE(zombie.good()); + } +} + +TEST_F(SyncableDirectoryTest, PositionWithNullSurvivesSaveAndReload) { + TestIdFactory id_factory; + Id null_child_id; + const char null_cstr[] = "\0null\0test"; + std::string null_str(null_cstr, arraysize(null_cstr) - 1); + // Pad up to the minimum length with 0x7f characters, then add a string that + // contains a few NULLs to the end. This is slightly wrong, since the suffix + // part of a UniquePosition shouldn't contain NULLs, but it's good enough for + // this test. + std::string suffix = + std::string(UniquePosition::kSuffixLength - null_str.length(), '\x7f') + + null_str; + UniquePosition null_pos = UniquePosition::FromInt64(10, suffix); + + { + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); + + MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); + parent.PutIsDir(true); + parent.PutIsUnsynced(true); + + MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); + child.PutIsUnsynced(true); + child.PutUniquePosition(null_pos); + child.PutServerUniquePosition(null_pos); + + null_child_id = child.GetId(); + } + + EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); + + { + ReadTransaction trans(FROM_HERE, dir().get()); + + Entry null_ordinal_child(&trans, GET_BY_ID, null_child_id); + EXPECT_TRUE(null_pos.Equals(null_ordinal_child.GetUniquePosition())); + EXPECT_TRUE(null_pos.Equals(null_ordinal_child.GetServerUniquePosition())); + } +} + +} // namespace syncable + +} // namespace syncer diff --git a/sync/syncable/directory_unittest.h b/sync/syncable/directory_unittest.h new file mode 100644 index 0000000..d924e8b --- /dev/null +++ b/sync/syncable/directory_unittest.h @@ -0,0 +1,100 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYNC_SYNCABLE_DIRECTORY_UNITTEST_H_ +#define SYNC_SYNCABLE_DIRECTORY_UNITTEST_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/message_loop/message_loop.h" +#include "sync/syncable/in_memory_directory_backing_store.h" +#include "sync/syncable/mutable_entry.h" +#include "sync/syncable/syncable_read_transaction.h" +#include "sync/syncable/syncable_write_transaction.h" +#include "sync/test/engine/test_id_factory.h" +#include "sync/test/fake_encryptor.h" +#include "sync/test/null_directory_change_delegate.h" +#include "sync/test/null_transaction_observer.h" +#include "sync/util/test_unrecoverable_error_handler.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncer { + +namespace syncable { + +class BaseTransaction; + +// A test fixture for syncable::Directory. Uses an in-memory database to keep +// the unit tests fast. +// +// Serves as base class for several other test fixtures. +class SyncableDirectoryTest : public testing::Test { + private: + protected: + static const char kDirectoryName[]; + + SyncableDirectoryTest(); + virtual ~SyncableDirectoryTest(); + + virtual void SetUp(); + virtual void TearDown(); + + // Creates an empty entry and sets the ID field to a default one. + void CreateEntry(const std::string& entryname); + + // Creates an empty entry and sets the ID field to id. + void CreateEntry(const std::string& entryname, const int id); + + void CreateEntry(const std::string& entryname, Id id); + + // When a directory is saved then loaded from disk, it will pass through + // DropDeletedEntries(). This will remove some entries from the directory. + // This function is intended to simulate that process. + // + // WARNING: The directory will be deleted by this operation. You should + // not have any pointers to the directory (open transactions included) + // when you call this. + DirOpenResult SimulateSaveAndReloadDir(); + + // This function will close and re-open the directory without saving any + // pending changes. This is intended to simulate the recovery from a crash + // scenario. The same warnings for SimulateSaveAndReloadDir apply here. + DirOpenResult SimulateCrashAndReloadDir(); + + void GetAllMetaHandles(BaseTransaction* trans, MetahandleSet* result); + void CheckPurgeEntriesWithTypeInSucceeded(ModelTypeSet types_to_purge, + bool before_reload); + bool IsInDirtyMetahandles(int64 metahandle); + bool IsInMetahandlesToPurge(int64 metahandle); + + scoped_ptr<Directory>& dir(); + DirectoryChangeDelegate* directory_change_delegate(); + Encryptor* encryptor(); + UnrecoverableErrorHandler* unrecoverable_error_handler(); + + private: + void ValidateEntry(BaseTransaction* trans, + int64 id, + bool check_name, + const std::string& name, + int64 base_version, + int64 server_version, + bool is_del); + + // A helper function for Simulate{Save,Crash}AndReloadDir. + DirOpenResult ReloadDirImpl(); + + base::MessageLoop message_loop_; + scoped_ptr<Directory> dir_; + NullDirectoryChangeDelegate delegate_; + FakeEncryptor encryptor_; + TestUnrecoverableErrorHandler handler_; +}; + +} // namespace syncable + +} // namespace syncer + +#endif // SYNC_SYNCABLE_DIRECTORY_UNITTEST_H_ diff --git a/sync/syncable/entry_kernel_unittest.cc b/sync/syncable/entry_kernel_unittest.cc new file mode 100644 index 0000000..55563d5 --- /dev/null +++ b/sync/syncable/entry_kernel_unittest.cc @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/syncable/entry_kernel.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncer { + +namespace syncable { + +class EntryKernelTest : public testing::Test {}; + +TEST_F(EntryKernelTest, ToValue) { + EntryKernel kernel; + scoped_ptr<base::DictionaryValue> value(kernel.ToValue(NULL)); + if (value) { + // Not much to check without repeating the ToValue() code. + EXPECT_TRUE(value->HasKey("isDirty")); + // The extra +2 is for "isDirty" and "serverModelType". + EXPECT_EQ(BIT_TEMPS_END - BEGIN_FIELDS + 2, + static_cast<int>(value->size())); + } else { + ADD_FAILURE(); + } +} + +} // namespace syncable + +} // namespace syncer diff --git a/sync/syncable/syncable_unittest.cc b/sync/syncable/syncable_unittest.cc index 01176c2..3a14560 100644 --- a/sync/syncable/syncable_unittest.cc +++ b/sync/syncable/syncable_unittest.cc @@ -22,6 +22,7 @@ #include "sync/protocol/bookmark_specifics.pb.h" #include "sync/syncable/directory_backing_store.h" #include "sync/syncable/directory_change_delegate.h" +#include "sync/syncable/directory_unittest.h" #include "sync/syncable/in_memory_directory_backing_store.h" #include "sync/syncable/metahandle_set.h" #include "sync/syncable/mutable_entry.h" @@ -44,25 +45,8 @@ namespace syncable { using base::ExpectDictBooleanValue; using base::ExpectDictStringValue; -class SyncableKernelTest : public testing::Test {}; - -// TODO(akalin): Add unit tests for EntryKernel::ContainsString(). - -TEST_F(SyncableKernelTest, ToValue) { - EntryKernel kernel; - scoped_ptr<base::DictionaryValue> value(kernel.ToValue(NULL)); - if (value) { - // Not much to check without repeating the ToValue() code. - EXPECT_TRUE(value->HasKey("isDirty")); - // The extra +2 is for "isDirty" and "serverModelType". - EXPECT_EQ(BIT_TEMPS_END - BEGIN_FIELDS + 2, - static_cast<int>(value->size())); - } else { - ADD_FAILURE(); - } -} - namespace { + void PutDataAsBookmarkFavicon(WriteTransaction* wtrans, MutableEntry* e, const char* bytes, @@ -83,6 +67,7 @@ void ExpectDataFromBookmarkFaviconEquals(BaseTransaction* trans, ASSERT_EQ(std::string(bytes, bytes_length), e->GetSpecifics().bookmark().favicon()); } + } // namespace class SyncableGeneralTest : public testing::Test { @@ -90,12 +75,12 @@ class SyncableGeneralTest : public testing::Test { static const char kIndexTestName[]; virtual void SetUp() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - db_path_ = temp_dir_.path().Append( - FILE_PATH_LITERAL("SyncableTest.sqlite3")); + db_path_ = + temp_dir_.path().Append(FILE_PATH_LITERAL("SyncableTest.sqlite3")); } - virtual void TearDown() { - } + virtual void TearDown() {} + protected: base::MessageLoop message_loop_; base::ScopedTempDir temp_dir_; @@ -423,1136 +408,7 @@ TEST_F(SyncableGeneralTest, BookmarkTagTest) { } } -// A test fixture for syncable::Directory. Uses an in-memory database to keep -// the unit tests fast. -class SyncableDirectoryTest : public testing::Test { - protected: - base::MessageLoop message_loop_; - static const char kName[]; - - virtual void SetUp() { - dir_.reset(new Directory(new InMemoryDirectoryBackingStore(kName), - &handler_, - NULL, - NULL, - NULL)); - ASSERT_TRUE(dir_.get()); - ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_, - NullTransactionObserver())); - ASSERT_TRUE(dir_->good()); - } - - virtual void TearDown() { - if (dir_) - dir_->SaveChanges(); - dir_.reset(); - } - - void GetAllMetaHandles(BaseTransaction* trans, MetahandleSet* result) { - dir_->GetAllMetaHandles(trans, result); - } - - bool IsInDirtyMetahandles(int64 metahandle) { - return 1 == dir_->kernel_->dirty_metahandles.count(metahandle); - } - - bool IsInMetahandlesToPurge(int64 metahandle) { - return 1 == dir_->kernel_->metahandles_to_purge.count(metahandle); - } - - void CheckPurgeEntriesWithTypeInSucceeded(ModelTypeSet types_to_purge, - bool before_reload) { - SCOPED_TRACE(testing::Message("Before reload: ") << before_reload); - { - ReadTransaction trans(FROM_HERE, dir_.get()); - MetahandleSet all_set; - dir_->GetAllMetaHandles(&trans, &all_set); - EXPECT_EQ(4U, all_set.size()); - if (before_reload) - EXPECT_EQ(6U, dir_->kernel_->metahandles_to_purge.size()); - for (MetahandleSet::iterator iter = all_set.begin(); - iter != all_set.end(); ++iter) { - Entry e(&trans, GET_BY_HANDLE, *iter); - const ModelType local_type = e.GetModelType(); - const ModelType server_type = e.GetServerModelType(); - - // Note the dance around incrementing |it|, since we sometimes erase(). - if ((IsRealDataType(local_type) && - types_to_purge.Has(local_type)) || - (IsRealDataType(server_type) && - types_to_purge.Has(server_type))) { - FAIL() << "Illegal type should have been deleted."; - } - } - } - - for (ModelTypeSet::Iterator it = types_to_purge.First(); - it.Good(); it.Inc()) { - EXPECT_FALSE(dir_->InitialSyncEndedForType(it.Get())); - sync_pb::DataTypeProgressMarker progress; - dir_->GetDownloadProgress(it.Get(), &progress); - EXPECT_EQ("", progress.token()); - - ReadTransaction trans(FROM_HERE, dir_.get()); - sync_pb::DataTypeContext context; - dir_->GetDataTypeContext(&trans, it.Get(), &context); - EXPECT_TRUE(context.SerializeAsString().empty()); - } - EXPECT_FALSE(types_to_purge.Has(BOOKMARKS)); - EXPECT_TRUE(dir_->InitialSyncEndedForType(BOOKMARKS)); - } - - FakeEncryptor encryptor_; - TestUnrecoverableErrorHandler handler_; - scoped_ptr<Directory> dir_; - NullDirectoryChangeDelegate delegate_; - - // Creates an empty entry and sets the ID field to a default one. - void CreateEntry(const std::string& entryname) { - CreateEntry(entryname, TestIdFactory::FromNumber(-99)); - } - - // Creates an empty entry and sets the ID field to id. - void CreateEntry(const std::string& entryname, const int id) { - CreateEntry(entryname, TestIdFactory::FromNumber(id)); - } - void CreateEntry(const std::string& entryname, Id id) { - WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), entryname); - ASSERT_TRUE(me.good()); - me.PutId(id); - me.PutIsUnsynced(true); - } - - void ValidateEntry(BaseTransaction* trans, - int64 id, - bool check_name, - const std::string& name, - int64 base_version, - int64 server_version, - bool is_del); - - // When a directory is saved then loaded from disk, it will pass through - // DropDeletedEntries(). This will remove some entries from the directory. - // This function is intended to simulate that process. - // - // WARNING: The directory will be deleted by this operation. You should - // not have any pointers to the directory (open transactions included) - // when you call this. - DirOpenResult SimulateSaveAndReloadDir(); - - // This function will close and re-open the directory without saving any - // pending changes. This is intended to simulate the recovery from a crash - // scenario. The same warnings for SimulateSaveAndReloadDir apply here. - DirOpenResult SimulateCrashAndReloadDir(); - - private: - // A helper function for Simulate{Save,Crash}AndReloadDir. - DirOpenResult ReloadDirImpl(); -}; - -TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) { - const int metas_to_create = 50; - MetahandleSet expected_purges; - MetahandleSet all_handles; - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - for (int i = 0; i < metas_to_create; i++) { - MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); - e.PutIsUnsynced(true); - sync_pb::EntitySpecifics specs; - if (i % 2 == 0) { - AddDefaultFieldValue(BOOKMARKS, &specs); - expected_purges.insert(e.GetMetahandle()); - all_handles.insert(e.GetMetahandle()); - } else { - AddDefaultFieldValue(PREFERENCES, &specs); - all_handles.insert(e.GetMetahandle()); - } - e.PutSpecifics(specs); - e.PutServerSpecifics(specs); - } - } - - ModelTypeSet to_purge(BOOKMARKS); - dir_->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet()); - - Directory::SaveChangesSnapshot snapshot1; - base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); - dir_->TakeSnapshotForSaveChanges(&snapshot1); - EXPECT_TRUE(expected_purges == snapshot1.metahandles_to_purge); - - to_purge.Clear(); - to_purge.Put(PREFERENCES); - dir_->PurgeEntriesWithTypeIn(to_purge, ModelTypeSet(), ModelTypeSet()); - - dir_->HandleSaveChangesFailure(snapshot1); - - Directory::SaveChangesSnapshot snapshot2; - dir_->TakeSnapshotForSaveChanges(&snapshot2); - EXPECT_TRUE(all_handles == snapshot2.metahandles_to_purge); -} - -TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) { - const int metahandles_to_create = 100; - std::vector<int64> expected_dirty_metahandles; - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - for (int i = 0; i < metahandles_to_create; i++) { - MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); - expected_dirty_metahandles.push_back(e.GetMetahandle()); - e.PutIsUnsynced(true); - } - } - // Fake SaveChanges() and make sure we got what we expected. - { - Directory::SaveChangesSnapshot snapshot; - base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); - dir_->TakeSnapshotForSaveChanges(&snapshot); - // Make sure there's an entry for each new metahandle. Make sure all - // entries are marked dirty. - ASSERT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size()); - for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); - i != snapshot.dirty_metas.end(); ++i) { - ASSERT_TRUE((*i)->is_dirty()); - } - dir_->VacuumAfterSaveChanges(snapshot); - } - // Put a new value with existing transactions as well as adding new ones. - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - std::vector<int64> new_dirty_metahandles; - for (std::vector<int64>::const_iterator i = - expected_dirty_metahandles.begin(); - i != expected_dirty_metahandles.end(); ++i) { - // Change existing entries to directories to dirty them. - MutableEntry e1(&trans, GET_BY_HANDLE, *i); - e1.PutIsDir(true); - e1.PutIsUnsynced(true); - // Add new entries - MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar"); - e2.PutIsUnsynced(true); - new_dirty_metahandles.push_back(e2.GetMetahandle()); - } - expected_dirty_metahandles.insert(expected_dirty_metahandles.end(), - new_dirty_metahandles.begin(), new_dirty_metahandles.end()); - } - // Fake SaveChanges() and make sure we got what we expected. - { - Directory::SaveChangesSnapshot snapshot; - base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); - dir_->TakeSnapshotForSaveChanges(&snapshot); - // Make sure there's an entry for each new metahandle. Make sure all - // entries are marked dirty. - EXPECT_EQ(expected_dirty_metahandles.size(), snapshot.dirty_metas.size()); - for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); - i != snapshot.dirty_metas.end(); ++i) { - EXPECT_TRUE((*i)->is_dirty()); - } - dir_->VacuumAfterSaveChanges(snapshot); - } -} - -TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) { - const int metahandles_to_create = 100; - - // half of 2 * metahandles_to_create - const unsigned int number_changed = 100u; - std::vector<int64> expected_dirty_metahandles; - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - for (int i = 0; i < metahandles_to_create; i++) { - MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), "foo"); - expected_dirty_metahandles.push_back(e.GetMetahandle()); - e.PutIsUnsynced(true); - } - } - dir_->SaveChanges(); - // Put a new value with existing transactions as well as adding new ones. - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - std::vector<int64> new_dirty_metahandles; - for (std::vector<int64>::const_iterator i = - expected_dirty_metahandles.begin(); - i != expected_dirty_metahandles.end(); ++i) { - // Change existing entries to directories to dirty them. - MutableEntry e1(&trans, GET_BY_HANDLE, *i); - ASSERT_TRUE(e1.good()); - e1.PutIsDir(true); - e1.PutIsUnsynced(true); - // Add new entries - MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), "bar"); - e2.PutIsUnsynced(true); - new_dirty_metahandles.push_back(e2.GetMetahandle()); - } - expected_dirty_metahandles.insert(expected_dirty_metahandles.end(), - new_dirty_metahandles.begin(), new_dirty_metahandles.end()); - } - dir_->SaveChanges(); - // Don't make any changes whatsoever and ensure nothing comes back. - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - for (std::vector<int64>::const_iterator i = - expected_dirty_metahandles.begin(); - i != expected_dirty_metahandles.end(); ++i) { - MutableEntry e(&trans, GET_BY_HANDLE, *i); - ASSERT_TRUE(e.good()); - // We aren't doing anything to dirty these entries. - } - } - // Fake SaveChanges() and make sure we got what we expected. - { - Directory::SaveChangesSnapshot snapshot; - base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); - dir_->TakeSnapshotForSaveChanges(&snapshot); - // Make sure there are no dirty_metahandles. - EXPECT_EQ(0u, snapshot.dirty_metas.size()); - dir_->VacuumAfterSaveChanges(snapshot); - } - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - bool should_change = false; - for (std::vector<int64>::const_iterator i = - expected_dirty_metahandles.begin(); - i != expected_dirty_metahandles.end(); ++i) { - // Maybe change entries by flipping IS_DIR. - MutableEntry e(&trans, GET_BY_HANDLE, *i); - ASSERT_TRUE(e.good()); - should_change = !should_change; - if (should_change) { - bool not_dir = !e.GetIsDir(); - e.PutIsDir(not_dir); - e.PutIsUnsynced(true); - } - } - } - // Fake SaveChanges() and make sure we got what we expected. - { - Directory::SaveChangesSnapshot snapshot; - base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); - dir_->TakeSnapshotForSaveChanges(&snapshot); - // Make sure there's an entry for each changed metahandle. Make sure all - // entries are marked dirty. - EXPECT_EQ(number_changed, snapshot.dirty_metas.size()); - for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); - i != snapshot.dirty_metas.end(); ++i) { - EXPECT_TRUE((*i)->is_dirty()); - } - dir_->VacuumAfterSaveChanges(snapshot); - } -} - -// Test delete journals management. -TEST_F(SyncableDirectoryTest, ManageDeleteJournals) { - sync_pb::EntitySpecifics bookmark_specifics; - AddDefaultFieldValue(BOOKMARKS, &bookmark_specifics); - bookmark_specifics.mutable_bookmark()->set_url("url"); - - Id id1 = TestIdFactory::FromNumber(-1); - Id id2 = TestIdFactory::FromNumber(-2); - int64 handle1 = 0; - int64 handle2 = 0; - { - // Create two bookmark entries and save in database. - CreateEntry("item1", id1); - CreateEntry("item2", id2); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry item1(&trans, GET_BY_ID, id1); - ASSERT_TRUE(item1.good()); - handle1 = item1.GetMetahandle(); - item1.PutSpecifics(bookmark_specifics); - item1.PutServerSpecifics(bookmark_specifics); - MutableEntry item2(&trans, GET_BY_ID, id2); - ASSERT_TRUE(item2.good()); - handle2 = item2.GetMetahandle(); - item2.PutSpecifics(bookmark_specifics); - item2.PutServerSpecifics(bookmark_specifics); - } - ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); - } - - { // Test adding and saving delete journals. - DeleteJournal* delete_journal = dir_->delete_journal(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - EntryKernelSet journal_entries; - delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); - ASSERT_EQ(0u, journal_entries.size()); - - // Set SERVER_IS_DEL of the entries to true and they should be added to - // delete journals. - MutableEntry item1(&trans, GET_BY_ID, id1); - ASSERT_TRUE(item1.good()); - item1.PutServerIsDel(true); - MutableEntry item2(&trans, GET_BY_ID, id2); - ASSERT_TRUE(item2.good()); - item2.PutServerIsDel(true); - EntryKernel tmp; - tmp.put(ID, id1); - EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp)); - tmp.put(ID, id2); - EXPECT_TRUE(delete_journal->delete_journals_.count(&tmp)); - } - - // Save delete journals in database and verify memory clearing. - ASSERT_TRUE(dir_->SaveChanges()); - { - ReadTransaction trans(FROM_HERE, dir_.get()); - EXPECT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans)); - } - ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); - } - - { - { - // Test reading delete journals from database. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - DeleteJournal* delete_journal = dir_->delete_journal(); - EntryKernelSet journal_entries; - delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); - ASSERT_EQ(2u, journal_entries.size()); - EntryKernel tmp; - tmp.put(META_HANDLE, handle1); - EXPECT_TRUE(journal_entries.count(&tmp)); - tmp.put(META_HANDLE, handle2); - EXPECT_TRUE(journal_entries.count(&tmp)); - - // Purge item2. - MetahandleSet to_purge; - to_purge.insert(handle2); - delete_journal->PurgeDeleteJournals(&trans, to_purge); - - // Verify that item2 is purged from journals in memory and will be - // purged from database. - tmp.put(ID, id2); - EXPECT_FALSE(delete_journal->delete_journals_.count(&tmp)); - EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size()); - EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle2)); - } - ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); - } - - { - { - // Verify purged entry is gone in database. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - DeleteJournal* delete_journal = dir_->delete_journal(); - EntryKernelSet journal_entries; - delete_journal->GetDeleteJournals(&trans, BOOKMARKS, &journal_entries); - ASSERT_EQ(1u, journal_entries.size()); - EntryKernel tmp; - tmp.put(ID, id1); - tmp.put(META_HANDLE, handle1); - EXPECT_TRUE(journal_entries.count(&tmp)); - - // Undelete item1. - MutableEntry item1(&trans, GET_BY_ID, id1); - ASSERT_TRUE(item1.good()); - item1.PutServerIsDel(false); - EXPECT_TRUE(delete_journal->delete_journals_.empty()); - EXPECT_EQ(1u, delete_journal->delete_journals_to_purge_.size()); - EXPECT_TRUE(delete_journal->delete_journals_to_purge_.count(handle1)); - } - ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); - } - - { - // Verify undeleted entry is gone from database. - ReadTransaction trans(FROM_HERE, dir_.get()); - DeleteJournal* delete_journal = dir_->delete_journal(); - ASSERT_EQ(0u, delete_journal->GetDeleteJournalSize(&trans)); - } -} - -const char SyncableDirectoryTest::kName[] = "Foo"; - -namespace { - -TEST_F(SyncableDirectoryTest, TestBasicLookupNonExistantID) { - ReadTransaction rtrans(FROM_HERE, dir_.get()); - Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99)); - ASSERT_FALSE(e.good()); -} - -TEST_F(SyncableDirectoryTest, TestBasicLookupValidID) { - CreateEntry("rtc"); - ReadTransaction rtrans(FROM_HERE, dir_.get()); - Entry e(&rtrans, GET_BY_ID, TestIdFactory::FromNumber(-99)); - ASSERT_TRUE(e.good()); -} - -TEST_F(SyncableDirectoryTest, TestDelete) { - std::string name = "peanut butter jelly time"; - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), name); - ASSERT_TRUE(e1.good()); - e1.PutIsDel(true); - MutableEntry e2(&trans, CREATE, BOOKMARKS, trans.root_id(), name); - ASSERT_TRUE(e2.good()); - e2.PutIsDel(true); - MutableEntry e3(&trans, CREATE, BOOKMARKS, trans.root_id(), name); - ASSERT_TRUE(e3.good()); - e3.PutIsDel(true); - - e1.PutIsDel(false); - e2.PutIsDel(false); - e3.PutIsDel(false); - - e1.PutIsDel(true); - e2.PutIsDel(true); - e3.PutIsDel(true); -} - -TEST_F(SyncableDirectoryTest, TestGetUnsynced) { - Directory::Metahandles handles; - int64 handle1, handle2; - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - dir_->GetUnsyncedMetaHandles(&trans, &handles); - ASSERT_TRUE(0 == handles.size()); - - MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba"); - ASSERT_TRUE(e1.good()); - handle1 = e1.GetMetahandle(); - e1.PutBaseVersion(1); - e1.PutIsDir(true); - e1.PutId(TestIdFactory::FromNumber(101)); - - MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread"); - ASSERT_TRUE(e2.good()); - handle2 = e2.GetMetahandle(); - e2.PutBaseVersion(1); - e2.PutId(TestIdFactory::FromNumber(102)); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - dir_->GetUnsyncedMetaHandles(&trans, &handles); - ASSERT_TRUE(0 == handles.size()); - - MutableEntry e3(&trans, GET_BY_HANDLE, handle1); - ASSERT_TRUE(e3.good()); - e3.PutIsUnsynced(true); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - dir_->GetUnsyncedMetaHandles(&trans, &handles); - ASSERT_TRUE(1 == handles.size()); - ASSERT_TRUE(handle1 == handles[0]); - - MutableEntry e4(&trans, GET_BY_HANDLE, handle2); - ASSERT_TRUE(e4.good()); - e4.PutIsUnsynced(true); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - dir_->GetUnsyncedMetaHandles(&trans, &handles); - ASSERT_TRUE(2 == handles.size()); - if (handle1 == handles[0]) { - ASSERT_TRUE(handle2 == handles[1]); - } else { - ASSERT_TRUE(handle2 == handles[0]); - ASSERT_TRUE(handle1 == handles[1]); - } - - MutableEntry e5(&trans, GET_BY_HANDLE, handle1); - ASSERT_TRUE(e5.good()); - ASSERT_TRUE(e5.GetIsUnsynced()); - ASSERT_TRUE(e5.PutIsUnsynced(false)); - ASSERT_FALSE(e5.GetIsUnsynced()); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - dir_->GetUnsyncedMetaHandles(&trans, &handles); - ASSERT_TRUE(1 == handles.size()); - ASSERT_TRUE(handle2 == handles[0]); - } -} - -TEST_F(SyncableDirectoryTest, TestGetUnappliedUpdates) { - std::vector<int64> handles; - int64 handle1, handle2; - const FullModelTypeSet all_types = FullModelTypeSet::All(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); - ASSERT_TRUE(0 == handles.size()); - - MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "abba"); - ASSERT_TRUE(e1.good()); - handle1 = e1.GetMetahandle(); - e1.PutIsUnappliedUpdate(false); - e1.PutBaseVersion(1); - e1.PutId(TestIdFactory::FromNumber(101)); - e1.PutIsDir(true); - - MutableEntry e2(&trans, CREATE, BOOKMARKS, e1.GetId(), "bread"); - ASSERT_TRUE(e2.good()); - handle2 = e2.GetMetahandle(); - e2.PutIsUnappliedUpdate(false); - e2.PutBaseVersion(1); - e2.PutId(TestIdFactory::FromNumber(102)); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); - ASSERT_TRUE(0 == handles.size()); - - MutableEntry e3(&trans, GET_BY_HANDLE, handle1); - ASSERT_TRUE(e3.good()); - e3.PutIsUnappliedUpdate(true); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); - ASSERT_TRUE(1 == handles.size()); - ASSERT_TRUE(handle1 == handles[0]); - - MutableEntry e4(&trans, GET_BY_HANDLE, handle2); - ASSERT_TRUE(e4.good()); - e4.PutIsUnappliedUpdate(true); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); - ASSERT_TRUE(2 == handles.size()); - if (handle1 == handles[0]) { - ASSERT_TRUE(handle2 == handles[1]); - } else { - ASSERT_TRUE(handle2 == handles[0]); - ASSERT_TRUE(handle1 == handles[1]); - } - - MutableEntry e5(&trans, GET_BY_HANDLE, handle1); - ASSERT_TRUE(e5.good()); - e5.PutIsUnappliedUpdate(false); - } - dir_->SaveChanges(); - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - dir_->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); - ASSERT_TRUE(1 == handles.size()); - ASSERT_TRUE(handle2 == handles[0]); - } -} - - -TEST_F(SyncableDirectoryTest, DeleteBug_531383) { - // Try to evoke a check failure... - TestIdFactory id_factory; - int64 grandchild_handle; - { - WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry parent(&wtrans, CREATE, BOOKMARKS, id_factory.root(), "Bob"); - ASSERT_TRUE(parent.good()); - parent.PutIsDir(true); - parent.PutId(id_factory.NewServerId()); - parent.PutBaseVersion(1); - MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob"); - ASSERT_TRUE(child.good()); - child.PutIsDir(true); - child.PutId(id_factory.NewServerId()); - child.PutBaseVersion(1); - MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); - ASSERT_TRUE(grandchild.good()); - grandchild.PutId(id_factory.NewServerId()); - grandchild.PutBaseVersion(1); - grandchild.PutIsDel(true); - MutableEntry twin(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); - ASSERT_TRUE(twin.good()); - twin.PutIsDel(true); - grandchild.PutIsDel(false); - - grandchild_handle = grandchild.GetMetahandle(); - } - dir_->SaveChanges(); - { - WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry grandchild(&wtrans, GET_BY_HANDLE, grandchild_handle); - grandchild.PutIsDel(true); // Used to CHECK fail here. - } -} - -static inline bool IsLegalNewParent(const Entry& a, const Entry& b) { - return IsLegalNewParent(a.trans(), a.GetId(), b.GetId()); -} - -TEST_F(SyncableDirectoryTest, TestIsLegalNewParent) { - TestIdFactory id_factory; - WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); - Entry root(&wtrans, GET_BY_ID, id_factory.root()); - ASSERT_TRUE(root.good()); - MutableEntry parent(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Bob"); - ASSERT_TRUE(parent.good()); - parent.PutIsDir(true); - parent.PutId(id_factory.NewServerId()); - parent.PutBaseVersion(1); - MutableEntry child(&wtrans, CREATE, BOOKMARKS, parent.GetId(), "Bob"); - ASSERT_TRUE(child.good()); - child.PutIsDir(true); - child.PutId(id_factory.NewServerId()); - child.PutBaseVersion(1); - MutableEntry grandchild(&wtrans, CREATE, BOOKMARKS, child.GetId(), "Bob"); - ASSERT_TRUE(grandchild.good()); - grandchild.PutId(id_factory.NewServerId()); - grandchild.PutBaseVersion(1); - - MutableEntry parent2(&wtrans, CREATE, BOOKMARKS, root.GetId(), "Pete"); - ASSERT_TRUE(parent2.good()); - parent2.PutIsDir(true); - parent2.PutId(id_factory.NewServerId()); - parent2.PutBaseVersion(1); - MutableEntry child2(&wtrans, CREATE, BOOKMARKS, parent2.GetId(), "Pete"); - ASSERT_TRUE(child2.good()); - child2.PutIsDir(true); - child2.PutId(id_factory.NewServerId()); - child2.PutBaseVersion(1); - MutableEntry grandchild2(&wtrans, CREATE, BOOKMARKS, child2.GetId(), "Pete"); - ASSERT_TRUE(grandchild2.good()); - grandchild2.PutId(id_factory.NewServerId()); - grandchild2.PutBaseVersion(1); - // resulting tree - // root - // / | - // parent parent2 - // | | - // child child2 - // | | - // grandchild grandchild2 - ASSERT_TRUE(IsLegalNewParent(child, root)); - ASSERT_TRUE(IsLegalNewParent(child, parent)); - ASSERT_FALSE(IsLegalNewParent(child, child)); - ASSERT_FALSE(IsLegalNewParent(child, grandchild)); - ASSERT_TRUE(IsLegalNewParent(child, parent2)); - ASSERT_TRUE(IsLegalNewParent(child, grandchild2)); - ASSERT_FALSE(IsLegalNewParent(parent, grandchild)); - ASSERT_FALSE(IsLegalNewParent(root, grandchild)); - ASSERT_FALSE(IsLegalNewParent(parent, grandchild)); -} - -TEST_F(SyncableDirectoryTest, TestEntryIsInFolder) { - // Create a subdir and an entry. - int64 entry_handle; - syncable::Id folder_id; - syncable::Id entry_id; - std::string entry_name = "entry"; - - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "folder"); - ASSERT_TRUE(folder.good()); - folder.PutIsDir(true); - EXPECT_TRUE(folder.PutIsUnsynced(true)); - folder_id = folder.GetId(); - - MutableEntry entry(&trans, CREATE, BOOKMARKS, folder.GetId(), entry_name); - ASSERT_TRUE(entry.good()); - entry_handle = entry.GetMetahandle(); - entry.PutIsUnsynced(true); - entry_id = entry.GetId(); - } - - // Make sure we can find the entry in the folder. - { - ReadTransaction trans(FROM_HERE, dir_.get()); - EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), entry_name)); - EXPECT_EQ(1, CountEntriesWithName(&trans, folder_id, entry_name)); - - Entry entry(&trans, GET_BY_ID, entry_id); - ASSERT_TRUE(entry.good()); - EXPECT_EQ(entry_handle, entry.GetMetahandle()); - EXPECT_TRUE(entry.GetNonUniqueName()== entry_name); - EXPECT_TRUE(entry.GetParentId()== folder_id); - } -} - -TEST_F(SyncableDirectoryTest, TestParentIdIndexUpdate) { - std::string child_name = "child"; - - WriteTransaction wt(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry parent_folder(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder1"); - parent_folder.PutIsUnsynced(true); - parent_folder.PutIsDir(true); - - MutableEntry parent_folder2(&wt, CREATE, BOOKMARKS, wt.root_id(), "folder2"); - parent_folder2.PutIsUnsynced(true); - parent_folder2.PutIsDir(true); - - MutableEntry child(&wt, CREATE, BOOKMARKS, parent_folder.GetId(), child_name); - child.PutIsDir(true); - child.PutIsUnsynced(true); - - ASSERT_TRUE(child.good()); - - EXPECT_EQ(0, CountEntriesWithName(&wt, wt.root_id(), child_name)); - EXPECT_EQ(parent_folder.GetId(), child.GetParentId()); - EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder.GetId(), child_name)); - EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name)); - child.PutParentId(parent_folder2.GetId()); - EXPECT_EQ(parent_folder2.GetId(), child.GetParentId()); - EXPECT_EQ(0, CountEntriesWithName(&wt, parent_folder.GetId(), child_name)); - EXPECT_EQ(1, CountEntriesWithName(&wt, parent_folder2.GetId(), child_name)); -} - -TEST_F(SyncableDirectoryTest, TestNoReindexDeletedItems) { - std::string folder_name = "folder"; - std::string new_name = "new_name"; - - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), folder_name); - ASSERT_TRUE(folder.good()); - folder.PutIsDir(true); - folder.PutIsDel(true); - - EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name)); - - MutableEntry deleted(&trans, GET_BY_ID, folder.GetId()); - ASSERT_TRUE(deleted.good()); - deleted.PutParentId(trans.root_id()); - deleted.PutNonUniqueName(new_name); - - EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), folder_name)); - EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), new_name)); -} - -TEST_F(SyncableDirectoryTest, TestCaseChangeRename) { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "CaseChange"); - ASSERT_TRUE(folder.good()); - folder.PutParentId(trans.root_id()); - folder.PutNonUniqueName("CASECHANGE"); - folder.PutIsDel(true); -} - -// Create items of each model type, and check that GetModelType and -// GetServerModelType return the right value. -TEST_F(SyncableDirectoryTest, GetModelType) { - TestIdFactory id_factory; - ModelTypeSet protocol_types = ProtocolTypes(); - for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good(); - iter.Inc()) { - ModelType datatype = iter.Get(); - SCOPED_TRACE(testing::Message("Testing model type ") << datatype); - switch (datatype) { - case UNSPECIFIED: - case TOP_LEVEL_FOLDER: - continue; // Datatype isn't a function of Specifics. - default: - break; - } - sync_pb::EntitySpecifics specifics; - AddDefaultFieldValue(datatype, &specifics); - - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry folder(&trans, CREATE, BOOKMARKS, trans.root_id(), "Folder"); - ASSERT_TRUE(folder.good()); - folder.PutId(id_factory.NewServerId()); - folder.PutSpecifics(specifics); - folder.PutBaseVersion(1); - folder.PutIsDir(true); - folder.PutIsDel(false); - ASSERT_EQ(datatype, folder.GetModelType()); - - MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item"); - ASSERT_TRUE(item.good()); - item.PutId(id_factory.NewServerId()); - item.PutSpecifics(specifics); - item.PutBaseVersion(1); - item.PutIsDir(false); - item.PutIsDel(false); - ASSERT_EQ(datatype, item.GetModelType()); - - // It's critical that deletion records retain their datatype, so that - // they can be dispatched to the appropriate change processor. - MutableEntry deleted_item( - &trans, CREATE, BOOKMARKS, trans.root_id(), "Deleted Item"); - ASSERT_TRUE(item.good()); - deleted_item.PutId(id_factory.NewServerId()); - deleted_item.PutSpecifics(specifics); - deleted_item.PutBaseVersion(1); - deleted_item.PutIsDir(false); - deleted_item.PutIsDel(true); - ASSERT_EQ(datatype, deleted_item.GetModelType()); - - MutableEntry server_folder(&trans, CREATE_NEW_UPDATE_ITEM, - id_factory.NewServerId()); - ASSERT_TRUE(server_folder.good()); - server_folder.PutServerSpecifics(specifics); - server_folder.PutBaseVersion(1); - server_folder.PutServerIsDir(true); - server_folder.PutServerIsDel(false); - ASSERT_EQ(datatype, server_folder.GetServerModelType()); - - MutableEntry server_item(&trans, CREATE_NEW_UPDATE_ITEM, - id_factory.NewServerId()); - ASSERT_TRUE(server_item.good()); - server_item.PutServerSpecifics(specifics); - server_item.PutBaseVersion(1); - server_item.PutServerIsDir(false); - server_item.PutServerIsDel(false); - ASSERT_EQ(datatype, server_item.GetServerModelType()); - - sync_pb::SyncEntity folder_entity; - folder_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId())); - folder_entity.set_deleted(false); - folder_entity.set_folder(true); - folder_entity.mutable_specifics()->CopyFrom(specifics); - ASSERT_EQ(datatype, GetModelType(folder_entity)); - - sync_pb::SyncEntity item_entity; - item_entity.set_id_string(SyncableIdToProto(id_factory.NewServerId())); - item_entity.set_deleted(false); - item_entity.set_folder(false); - item_entity.mutable_specifics()->CopyFrom(specifics); - ASSERT_EQ(datatype, GetModelType(item_entity)); - } -} - -// A test that roughly mimics the directory interaction that occurs when a -// bookmark folder and entry are created then synced for the first time. It is -// a more common variant of the 'DeletedAndUnsyncedChild' scenario tested below. -TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ParentAndChild) { - TestIdFactory id_factory; - Id orig_parent_id; - Id orig_child_id; - - { - // Create two client-side items, a parent and child. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); - parent.PutIsDir(true); - parent.PutIsUnsynced(true); - - MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); - child.PutIsUnsynced(true); - - orig_parent_id = parent.GetId(); - orig_child_id = child.GetId(); - } - - { - // Simulate what happens after committing two items. Their IDs will be - // replaced with server IDs. The child is renamed first, then the parent. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); - MutableEntry child(&trans, GET_BY_ID, orig_child_id); - - ChangeEntryIDAndUpdateChildren(&trans, &child, id_factory.NewServerId()); - child.PutIsUnsynced(false); - child.PutBaseVersion(1); - child.PutServerVersion(1); - - ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); - parent.PutIsUnsynced(false); - parent.PutBaseVersion(1); - parent.PutServerVersion(1); - } - - // Final check for validity. - EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); -} - -// A test based on the scenario where we create a bookmark folder and entry -// locally, but with a twist. In this case, the bookmark is deleted before we -// are able to sync either it or its parent folder. This scenario used to cause -// directory corruption, see crbug.com/125381. -TEST_F(SyncableDirectoryTest, - ChangeEntryIDAndUpdateChildren_DeletedAndUnsyncedChild) { - TestIdFactory id_factory; - Id orig_parent_id; - Id orig_child_id; - - { - // Create two client-side items, a parent and child. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); - parent.PutIsDir(true); - parent.PutIsUnsynced(true); - - MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); - child.PutIsUnsynced(true); - - orig_parent_id = parent.GetId(); - orig_child_id = child.GetId(); - } - - { - // Delete the child. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry child(&trans, GET_BY_ID, orig_child_id); - child.PutIsDel(true); - } - - { - // Simulate what happens after committing the parent. Its ID will be - // replaced with server a ID. - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry parent(&trans, GET_BY_ID, orig_parent_id); - - ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); - parent.PutIsUnsynced(false); - parent.PutBaseVersion(1); - parent.PutServerVersion(1); - } - - // Final check for validity. - EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); -} - -// Ask the directory to generate a unique ID. Close and re-open the database -// without saving, then ask for another unique ID. Verify IDs are not reused. -// This scenario simulates a crash within the first few seconds of operation. -TEST_F(SyncableDirectoryTest, LocalIdReuseTest) { - Id pre_crash_id = dir_->NextId(); - SimulateCrashAndReloadDir(); - Id post_crash_id = dir_->NextId(); - EXPECT_NE(pre_crash_id, post_crash_id); -} - -// Ask the directory to generate a unique ID. Save the directory. Close and -// re-open the database without saving, then ask for another unique ID. Verify -// IDs are not reused. This scenario simulates a steady-state crash. -TEST_F(SyncableDirectoryTest, LocalIdReuseTestWithSave) { - Id pre_crash_id = dir_->NextId(); - dir_->SaveChanges(); - SimulateCrashAndReloadDir(); - Id post_crash_id = dir_->NextId(); - EXPECT_NE(pre_crash_id, post_crash_id); -} - -// Ensure that the unsynced, is_del and server unkown entries that may have been -// left in the database by old clients will be deleted when we open the old -// database. -TEST_F(SyncableDirectoryTest, OldClientLeftUnsyncedDeletedLocalItem) { - // We must create an entry with the offending properties. This is done with - // some abuse of the MutableEntry's API; it doesn't expect us to modify an - // item after it is deleted. If this hack becomes impractical we will need to - // find a new way to simulate this scenario. - - TestIdFactory id_factory; - - // Happy-path: These valid entries should not get deleted. - Id server_knows_id = id_factory.NewServerId(); - Id not_is_del_id = id_factory.NewLocalId(); - - // The ID of the entry which will be unsynced, is_del and !ServerKnows(). - Id zombie_id = id_factory.NewLocalId(); - - // We're about to do some bad things. Tell the directory verification - // routines to look the other way. - dir_->SetInvariantCheckLevel(OFF); - - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - // Create an uncommitted tombstone entry. - MutableEntry server_knows(&trans, CREATE, BOOKMARKS, id_factory.root(), - "server_knows"); - server_knows.PutId(server_knows_id); - server_knows.PutIsUnsynced(true); - server_knows.PutIsDel(true); - server_knows.PutBaseVersion(5); - server_knows.PutServerVersion(4); - - // Create a valid update entry. - MutableEntry not_is_del( - &trans, CREATE, BOOKMARKS, id_factory.root(), "not_is_del"); - not_is_del.PutId(not_is_del_id); - not_is_del.PutIsDel(false); - not_is_del.PutIsUnsynced(true); - - // Create a tombstone which should never be sent to the server because the - // server never knew about the item's existence. - // - // New clients should never put entries into this state. We work around - // this by setting IS_DEL before setting IS_UNSYNCED, something which the - // client should never do in practice. - MutableEntry zombie(&trans, CREATE, BOOKMARKS, id_factory.root(), "zombie"); - zombie.PutId(zombie_id); - zombie.PutIsDel(true); - zombie.PutIsUnsynced(true); - } - - ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); - - { - ReadTransaction trans(FROM_HERE, dir_.get()); - - // The directory loading routines should have cleaned things up, making it - // safe to check invariants once again. - dir_->FullyCheckTreeInvariants(&trans); - - Entry server_knows(&trans, GET_BY_ID, server_knows_id); - EXPECT_TRUE(server_knows.good()); - - Entry not_is_del(&trans, GET_BY_ID, not_is_del_id); - EXPECT_TRUE(not_is_del.good()); - - Entry zombie(&trans, GET_BY_ID, zombie_id); - EXPECT_FALSE(zombie.good()); - } -} - -TEST_F(SyncableDirectoryTest, PositionWithNullSurvivesSaveAndReload) { - TestIdFactory id_factory; - Id null_child_id; - const char null_cstr[] = "\0null\0test"; - std::string null_str(null_cstr, arraysize(null_cstr) - 1); - // Pad up to the minimum length with 0x7f characters, then add a string that - // contains a few NULLs to the end. This is slightly wrong, since the suffix - // part of a UniquePosition shouldn't contain NULLs, but it's good enough for - // this test. - std::string suffix = - std::string(UniquePosition::kSuffixLength - null_str.length(), '\x7f') - + null_str; - UniquePosition null_pos = UniquePosition::FromInt64(10, suffix); - - { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); - - MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); - parent.PutIsDir(true); - parent.PutIsUnsynced(true); - - MutableEntry child(&trans, CREATE, BOOKMARKS, parent.GetId(), "child"); - child.PutIsUnsynced(true); - child.PutUniquePosition(null_pos); - child.PutServerUniquePosition(null_pos); - - null_child_id = child.GetId(); - } - - EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); - - { - ReadTransaction trans(FROM_HERE, dir_.get()); - - Entry null_ordinal_child(&trans, GET_BY_ID, null_child_id); - EXPECT_TRUE( - null_pos.Equals(null_ordinal_child.GetUniquePosition())); - EXPECT_TRUE( - null_pos.Equals(null_ordinal_child.GetServerUniquePosition())); - } -} - -// An OnDirectoryBackingStore that can be set to always fail SaveChanges. +// An OnDiskDirectoryBackingStore that can be set to always fail SaveChanges. class TestBackingStore : public OnDiskDirectoryBackingStore { public: TestBackingStore(const std::string& dir_name, @@ -1655,6 +511,7 @@ class OnDiskSyncableDirectoryTest : public SyncableDirectoryTest { // SetUp() is called before each test case is run. // The sqlite3 DB is deleted before each test is run. virtual void SetUp() { + SyncableDirectoryTest::SetUp(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); file_path_ = temp_dir_.path().Append( FILE_PATH_LITERAL("Test.sqlite3")); @@ -1664,24 +521,27 @@ class OnDiskSyncableDirectoryTest : public SyncableDirectoryTest { virtual void TearDown() { // This also closes file handles. - dir_->SaveChanges(); - dir_.reset(); + dir()->SaveChanges(); + dir().reset(); base::DeleteFile(file_path_, true); + SyncableDirectoryTest::TearDown(); } // Creates a new directory. Deletes the old directory, if it exists. void CreateDirectory() { - test_directory_ = - TestDirectory::Create(&encryptor_, &handler_, kName, file_path_); - dir_.reset(test_directory_); - ASSERT_TRUE(dir_.get()); - ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_, - NullTransactionObserver())); - ASSERT_TRUE(dir_->good()); + test_directory_ = TestDirectory::Create( + encryptor(), unrecoverable_error_handler(), kDirectoryName, file_path_); + dir().reset(test_directory_); + ASSERT_TRUE(dir().get()); + ASSERT_EQ(OPENED, + dir()->Open(kDirectoryName, + directory_change_delegate(), + NullTransactionObserver())); + ASSERT_TRUE(dir()->good()); } void SaveAndReloadDir() { - dir_->SaveChanges(); + dir()->SaveChanges(); CreateDirectory(); } @@ -1718,26 +578,23 @@ TEST_F(OnDiskSyncableDirectoryTest, TestPurgeEntriesWithTypeIn) { ModelTypeSet types_to_purge(PREFERENCES, AUTOFILL); - dir_->SetDownloadProgress(BOOKMARKS, - BuildProgress(BOOKMARKS)); - dir_->SetDownloadProgress(PREFERENCES, - BuildProgress(PREFERENCES)); - dir_->SetDownloadProgress(AUTOFILL, - BuildProgress(AUTOFILL)); + dir()->SetDownloadProgress(BOOKMARKS, BuildProgress(BOOKMARKS)); + dir()->SetDownloadProgress(PREFERENCES, BuildProgress(PREFERENCES)); + dir()->SetDownloadProgress(AUTOFILL, BuildProgress(AUTOFILL)); TestIdFactory id_factory; // Create some items for each type. { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); - dir_->SetDataTypeContext(&trans, BOOKMARKS, BuildContext(BOOKMARKS)); - dir_->SetDataTypeContext(&trans, PREFERENCES, BuildContext(PREFERENCES)); - dir_->SetDataTypeContext(&trans, AUTOFILL, BuildContext(AUTOFILL)); + dir()->SetDataTypeContext(&trans, BOOKMARKS, BuildContext(BOOKMARKS)); + dir()->SetDataTypeContext(&trans, PREFERENCES, BuildContext(PREFERENCES)); + dir()->SetDataTypeContext(&trans, AUTOFILL, BuildContext(AUTOFILL)); // Make it look like these types have completed initial sync. - CreateTypeRoot(&trans, dir_.get(), BOOKMARKS); - CreateTypeRoot(&trans, dir_.get(), PREFERENCES); - CreateTypeRoot(&trans, dir_.get(), AUTOFILL); + CreateTypeRoot(&trans, dir().get(), BOOKMARKS); + CreateTypeRoot(&trans, dir().get(), PREFERENCES); + CreateTypeRoot(&trans, dir().get(), AUTOFILL); // Add more nodes for this type. Technically, they should be placed under // the proper type root nodes but the assertions in this test won't notice @@ -1780,15 +637,15 @@ TEST_F(OnDiskSyncableDirectoryTest, TestPurgeEntriesWithTypeIn) { item6.PutIsUnappliedUpdate(true); } - dir_->SaveChanges(); + dir()->SaveChanges(); { - ReadTransaction trans(FROM_HERE, dir_.get()); + ReadTransaction trans(FROM_HERE, dir().get()); MetahandleSet all_set; GetAllMetaHandles(&trans, &all_set); ASSERT_EQ(10U, all_set.size()); } - dir_->PurgeEntriesWithTypeIn(types_to_purge, ModelTypeSet(), ModelTypeSet()); + dir()->PurgeEntriesWithTypeIn(types_to_purge, ModelTypeSet(), ModelTypeSet()); // We first query the in-memory data, and then reload the directory (without // saving) to verify that disk does not still have the data. @@ -1798,37 +655,37 @@ TEST_F(OnDiskSyncableDirectoryTest, TestPurgeEntriesWithTypeIn) { } TEST_F(OnDiskSyncableDirectoryTest, TestShareInfo) { - dir_->set_store_birthday("Jan 31st"); + dir()->set_store_birthday("Jan 31st"); const char* const bag_of_chips_array = "\0bag of chips"; const std::string bag_of_chips_string = std::string(bag_of_chips_array, sizeof(bag_of_chips_array)); - dir_->set_bag_of_chips(bag_of_chips_string); + dir()->set_bag_of_chips(bag_of_chips_string); { - ReadTransaction trans(FROM_HERE, dir_.get()); - EXPECT_EQ("Jan 31st", dir_->store_birthday()); - EXPECT_EQ(bag_of_chips_string, dir_->bag_of_chips()); + ReadTransaction trans(FROM_HERE, dir().get()); + EXPECT_EQ("Jan 31st", dir()->store_birthday()); + EXPECT_EQ(bag_of_chips_string, dir()->bag_of_chips()); } - dir_->set_store_birthday("April 10th"); + dir()->set_store_birthday("April 10th"); const char* const bag_of_chips2_array = "\0bag of chips2"; const std::string bag_of_chips2_string = std::string(bag_of_chips2_array, sizeof(bag_of_chips2_array)); - dir_->set_bag_of_chips(bag_of_chips2_string); - dir_->SaveChanges(); + dir()->set_bag_of_chips(bag_of_chips2_string); + dir()->SaveChanges(); { - ReadTransaction trans(FROM_HERE, dir_.get()); - EXPECT_EQ("April 10th", dir_->store_birthday()); - EXPECT_EQ(bag_of_chips2_string, dir_->bag_of_chips()); + ReadTransaction trans(FROM_HERE, dir().get()); + EXPECT_EQ("April 10th", dir()->store_birthday()); + EXPECT_EQ(bag_of_chips2_string, dir()->bag_of_chips()); } const char* const bag_of_chips3_array = "\0bag of chips3"; const std::string bag_of_chips3_string = std::string(bag_of_chips3_array, sizeof(bag_of_chips3_array)); - dir_->set_bag_of_chips(bag_of_chips3_string); + dir()->set_bag_of_chips(bag_of_chips3_string); // Restore the directory from disk. Make sure that nothing's changed. SaveAndReloadDir(); { - ReadTransaction trans(FROM_HERE, dir_.get()); - EXPECT_EQ("April 10th", dir_->store_birthday()); - EXPECT_EQ(bag_of_chips3_string, dir_->bag_of_chips()); + ReadTransaction trans(FROM_HERE, dir().get()); + EXPECT_EQ("April 10th", dir()->store_birthday()); + EXPECT_EQ(bag_of_chips3_string, dir()->bag_of_chips()); } } @@ -1841,7 +698,7 @@ TEST_F(OnDiskSyncableDirectoryTest, std::string create_name = "Create"; { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); MutableEntry create( &trans, CREATE, BOOKMARKS, trans.root_id(), create_name); MutableEntry update(&trans, CREATE_NEW_UPDATE_ITEM, update_id); @@ -1857,19 +714,23 @@ TEST_F(OnDiskSyncableDirectoryTest, create_id = create.GetId(); } - dir_->SaveChanges(); - dir_.reset(new Directory(new OnDiskDirectoryBackingStore(kName, file_path_), - &handler_, - NULL, - NULL, - NULL)); + dir()->SaveChanges(); + dir().reset( + new Directory(new OnDiskDirectoryBackingStore(kDirectoryName, file_path_), + unrecoverable_error_handler(), + NULL, + NULL, + NULL)); - ASSERT_TRUE(dir_.get()); - ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_, NullTransactionObserver())); - ASSERT_TRUE(dir_->good()); + ASSERT_TRUE(dir().get()); + ASSERT_EQ(OPENED, + dir()->Open(kDirectoryName, + directory_change_delegate(), + NullTransactionObserver())); + ASSERT_TRUE(dir()->good()); { - ReadTransaction trans(FROM_HERE, dir_.get()); + ReadTransaction trans(FROM_HERE, dir().get()); Entry create(&trans, GET_BY_ID, create_id); EXPECT_EQ(1, CountEntriesWithName(&trans, trans.root_id(), create_name)); Entry update(&trans, GET_BY_ID, update_id); @@ -1941,7 +802,7 @@ TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) { int64 handle1 = 0; // Set up an item using a regular, saveable directory. { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "aguilera"); ASSERT_TRUE(e1.good()); @@ -1953,12 +814,12 @@ TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) { EXPECT_TRUE(e1.GetKernelCopy().is_dirty()); EXPECT_TRUE(IsInDirtyMetahandles(handle1)); } - ASSERT_TRUE(dir_->SaveChanges()); + ASSERT_TRUE(dir()->SaveChanges()); // Make sure the item is no longer dirty after saving, // and make a modification. { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); MutableEntry aguilera(&trans, GET_BY_HANDLE, handle1); ASSERT_TRUE(aguilera.good()); @@ -1968,15 +829,15 @@ TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) { EXPECT_TRUE(aguilera.GetKernelCopy().is_dirty()); EXPECT_TRUE(IsInDirtyMetahandles(handle1)); } - ASSERT_TRUE(dir_->SaveChanges()); + ASSERT_TRUE(dir()->SaveChanges()); // Now do some operations when SaveChanges() will fail. StartFailingSaveChanges(); - ASSERT_TRUE(dir_->good()); + ASSERT_TRUE(dir()->good()); int64 handle2 = 0; { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); MutableEntry aguilera(&trans, GET_BY_HANDLE, handle1); ASSERT_TRUE(aguilera.good()); @@ -2002,11 +863,11 @@ TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) { // We are using an unsaveable directory, so this can't succeed. However, // the HandleSaveChangesFailure code path should have been triggered. - ASSERT_FALSE(dir_->SaveChanges()); + ASSERT_FALSE(dir()->SaveChanges()); // Make sure things were rolled back and the world is as it was before call. { - ReadTransaction trans(FROM_HERE, dir_.get()); + ReadTransaction trans(FROM_HERE, dir().get()); Entry e1(&trans, GET_BY_HANDLE, handle1); ASSERT_TRUE(e1.good()); EntryKernel aguilera = e1.GetKernelCopy(); @@ -2023,7 +884,7 @@ TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailureWithPurge) { int64 handle1 = 0; // Set up an item using a regular, saveable directory. { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "aguilera"); ASSERT_TRUE(e1.good()); @@ -2040,74 +901,19 @@ TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailureWithPurge) { EXPECT_TRUE(e1.GetKernelCopy().is_dirty()); EXPECT_TRUE(IsInDirtyMetahandles(handle1)); } - ASSERT_TRUE(dir_->SaveChanges()); + ASSERT_TRUE(dir()->SaveChanges()); // Now do some operations while SaveChanges() is set to fail. StartFailingSaveChanges(); - ASSERT_TRUE(dir_->good()); + ASSERT_TRUE(dir()->good()); ModelTypeSet set(BOOKMARKS); - dir_->PurgeEntriesWithTypeIn(set, ModelTypeSet(), ModelTypeSet()); + dir()->PurgeEntriesWithTypeIn(set, ModelTypeSet(), ModelTypeSet()); EXPECT_TRUE(IsInMetahandlesToPurge(handle1)); - ASSERT_FALSE(dir_->SaveChanges()); + ASSERT_FALSE(dir()->SaveChanges()); EXPECT_TRUE(IsInMetahandlesToPurge(handle1)); } -} // namespace - -void SyncableDirectoryTest::ValidateEntry(BaseTransaction* trans, - int64 id, - bool check_name, - const std::string& name, - int64 base_version, - int64 server_version, - bool is_del) { - Entry e(trans, GET_BY_ID, TestIdFactory::FromNumber(id)); - ASSERT_TRUE(e.good()); - if (check_name) - ASSERT_TRUE(name == e.GetNonUniqueName()); - ASSERT_TRUE(base_version == e.GetBaseVersion()); - ASSERT_TRUE(server_version == e.GetServerVersion()); - ASSERT_TRUE(is_del == e.GetIsDel()); -} - -DirOpenResult SyncableDirectoryTest::SimulateSaveAndReloadDir() { - if (!dir_->SaveChanges()) - return FAILED_IN_UNITTEST; - - return ReloadDirImpl(); -} - -DirOpenResult SyncableDirectoryTest::SimulateCrashAndReloadDir() { - return ReloadDirImpl(); -} - -DirOpenResult SyncableDirectoryTest::ReloadDirImpl() { - // Do some tricky things to preserve the backing store. - DirectoryBackingStore* saved_store = dir_->store_.release(); - - // Close the current directory. - dir_->Close(); - dir_.reset(); - - dir_.reset(new Directory(saved_store, - &handler_, - NULL, - NULL, - NULL)); - DirOpenResult result = dir_->OpenImpl(kName, &delegate_, - NullTransactionObserver()); - - // If something went wrong, we need to clear this member. If we don't, - // TearDown() will be guaranteed to crash when it calls SaveChanges(). - if (result != OPENED) - dir_.reset(); - - return result; -} - -namespace { - class SyncableDirectoryManagement : public testing::Test { public: virtual void SetUp() { @@ -2125,14 +931,14 @@ class SyncableDirectoryManagement : public testing::Test { }; TEST_F(SyncableDirectoryManagement, TestFileRelease) { - base::FilePath path = temp_dir_.path().Append( - Directory::kSyncDatabaseFilename); - - syncable::Directory dir(new OnDiskDirectoryBackingStore("ScopeTest", path), - &handler_, - NULL, - NULL, - NULL); + base::FilePath path = + temp_dir_.path().Append(Directory::kSyncDatabaseFilename); + + Directory dir(new OnDiskDirectoryBackingStore("ScopeTest", path), + &handler_, + NULL, + NULL, + NULL); DirOpenResult result = dir.Open("ScopeTest", &delegate_, NullTransactionObserver()); ASSERT_EQ(result, OPENED); @@ -2145,8 +951,7 @@ TEST_F(SyncableDirectoryManagement, TestFileRelease) { class StressTransactionsDelegate : public base::PlatformThread::Delegate { public: StressTransactionsDelegate(Directory* dir, int thread_number) - : dir_(dir), - thread_number_(thread_number) {} + : dir_(dir), thread_number_(thread_number) {} private: Directory* const dir_; @@ -2226,7 +1031,7 @@ class SyncableClientTagTest : public SyncableDirectoryTest { SyncableClientTagTest() : test_name_("test_name"), test_tag_("dietcoke") {} bool CreateWithDefaultTag(Id id, bool deleted) { - WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); MutableEntry me(&wtrans, CREATE, PREFERENCES, wtrans.root_id(), test_name_); CHECK(me.good()); @@ -2243,7 +1048,7 @@ class SyncableClientTagTest : public SyncableDirectoryTest { // Verify an entry exists with the default tag. void VerifyTag(Id id, bool deleted) { // Should still be present and valid in the client tag index. - ReadTransaction trans(FROM_HERE, dir_.get()); + ReadTransaction trans(FROM_HERE, dir().get()); Entry me(&trans, GET_BY_CLIENT_TAG, test_tag_); CHECK(me.good()); EXPECT_EQ(me.GetId(), id); @@ -2264,13 +1069,13 @@ TEST_F(SyncableClientTagTest, TestClientTagClear) { Id server_id = factory_.NewServerId(); EXPECT_TRUE(CreateWithDefaultTag(server_id, false)); { - WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); + WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); MutableEntry me(&trans, GET_BY_CLIENT_TAG, test_tag_); EXPECT_TRUE(me.good()); me.PutUniqueClientTag(std::string()); } { - ReadTransaction trans(FROM_HERE, dir_.get()); + ReadTransaction trans(FROM_HERE, dir().get()); Entry by_tag(&trans, GET_BY_CLIENT_TAG, test_tag_); EXPECT_FALSE(by_tag.good()); @@ -2312,6 +1117,5 @@ TEST_F(SyncableClientTagTest, TestClientTagIndexDuplicateServer) { EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), true)); } -} // namespace } // namespace syncable } // namespace syncer |