Index: sync/syncable/directory_unittest.cc |
diff --git a/sync/syncable/directory_unittest.cc b/sync/syncable/directory_unittest.cc |
deleted file mode 100644 |
index b89bda498406e4095b1f10f3a4bbe2ceb4980f05..0000000000000000000000000000000000000000 |
--- a/sync/syncable/directory_unittest.cc |
+++ /dev/null |
@@ -1,2140 +0,0 @@ |
-// 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 <stddef.h> |
-#include <stdint.h> |
- |
-#include <cstdlib> |
- |
-#include "base/macros.h" |
-#include "base/rand_util.h" |
-#include "base/run_loop.h" |
-#include "base/strings/stringprintf.h" |
-#include "base/test/values_test_util.h" |
-#include "sync/internal_api/public/base/attachment_id_proto.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" |
-#include "sync/test/test_directory_backing_store.h" |
-#include "sync/util/mock_unrecoverable_error_handler.h" |
- |
-using base::ExpectDictBooleanValue; |
-using base::ExpectDictStringValue; |
- |
-namespace syncer { |
- |
-namespace syncable { |
- |
-namespace { |
- |
-bool IsLegalNewParent(const Entry& a, const Entry& b) { |
- return IsLegalNewParent(a.trans(), a.GetId(), b.GetId()); |
-} |
- |
-void PutDataAsBookmarkFavicon(WriteTransaction* wtrans, |
- MutableEntry* e, |
- const char* bytes, |
- size_t bytes_length) { |
- sync_pb::EntitySpecifics specifics; |
- specifics.mutable_bookmark()->set_url("http://demo/"); |
- specifics.mutable_bookmark()->set_favicon(bytes, bytes_length); |
- e->PutSpecifics(specifics); |
-} |
- |
-void ExpectDataFromBookmarkFaviconEquals(BaseTransaction* trans, |
- Entry* e, |
- const char* bytes, |
- size_t bytes_length) { |
- ASSERT_TRUE(e->good()); |
- ASSERT_TRUE(e->GetSpecifics().has_bookmark()); |
- ASSERT_EQ("http://demo/", e->GetSpecifics().bookmark().url()); |
- ASSERT_EQ(std::string(bytes, bytes_length), |
- e->GetSpecifics().bookmark().favicon()); |
-} |
- |
-} // namespace |
- |
-const char SyncableDirectoryTest::kDirectoryName[] = "Foo"; |
- |
-SyncableDirectoryTest::SyncableDirectoryTest() { |
-} |
- |
-SyncableDirectoryTest::~SyncableDirectoryTest() { |
-} |
- |
-void SyncableDirectoryTest::SetUp() { |
- ASSERT_TRUE(connection_.OpenInMemory()); |
- ASSERT_EQ(OPENED, ReopenDirectory()); |
-} |
- |
-void SyncableDirectoryTest::TearDown() { |
- if (dir_) |
- dir_->SaveChanges(); |
- dir_.reset(); |
-} |
- |
-DirOpenResult SyncableDirectoryTest::ReopenDirectory() { |
- // Use a TestDirectoryBackingStore and sql::Connection so we can have test |
- // data persist across Directory object lifetimes while getting the |
- // performance benefits of not writing to disk. |
- dir_.reset(new Directory( |
- new TestDirectoryBackingStore(kDirectoryName, &connection_), |
- MakeWeakHandle(handler_.GetWeakPtr()), base::Closure(), NULL, NULL)); |
- |
- DirOpenResult open_result = |
- dir_->Open(kDirectoryName, &delegate_, NullTransactionObserver()); |
- |
- if (open_result != OPENED) { |
- dir_.reset(); |
- } |
- |
- return open_result; |
-} |
- |
-// Creates an empty entry and sets the ID field to a default one. |
-void SyncableDirectoryTest::CreateEntry(const ModelType& model_type, |
- const std::string& entryname) { |
- CreateEntry(model_type, entryname, TestIdFactory::FromNumber(-99)); |
-} |
- |
-// Creates an empty entry and sets the ID field to id. |
-void SyncableDirectoryTest::CreateEntry(const ModelType& model_type, |
- const std::string& entryname, |
- const int id) { |
- CreateEntry(model_type, entryname, TestIdFactory::FromNumber(id)); |
-} |
- |
-void SyncableDirectoryTest::CreateEntry(const ModelType& model_type, |
- const std::string& entryname, |
- const Id& id) { |
- CreateEntryWithAttachmentMetadata( |
- model_type, entryname, id, sync_pb::AttachmentMetadata()); |
-} |
- |
-void SyncableDirectoryTest::CreateEntryWithAttachmentMetadata( |
- const ModelType& model_type, |
- const std::string& entryname, |
- const Id& id, |
- const sync_pb::AttachmentMetadata& attachment_metadata) { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); |
- MutableEntry me(&wtrans, CREATE, model_type, wtrans.root_id(), entryname); |
- ASSERT_TRUE(me.good()); |
- me.PutId(id); |
- me.PutAttachmentMetadata(attachment_metadata); |
- me.PutIsUnsynced(true); |
-} |
- |
-void SyncableDirectoryTest::DeleteEntry(const Id& id) { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry entry(&trans, GET_BY_ID, id); |
- ASSERT_TRUE(entry.good()); |
- entry.PutIsDel(true); |
-} |
- |
-DirOpenResult SyncableDirectoryTest::SimulateSaveAndReloadDir() { |
- if (!dir_->SaveChanges()) |
- return FAILED_IN_UNITTEST; |
- |
- return ReopenDirectory(); |
-} |
- |
-DirOpenResult SyncableDirectoryTest::SimulateCrashAndReloadDir() { |
- return ReopenDirectory(); |
-} |
- |
-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_t metahandle) { |
- return 1 == dir_->kernel()->dirty_metahandles.count(metahandle); |
-} |
- |
-bool SyncableDirectoryTest::IsInMetahandlesToPurge(int64_t metahandle) { |
- return 1 == dir_->kernel()->metahandles_to_purge.count(metahandle); |
-} |
- |
-std::unique_ptr<Directory>& SyncableDirectoryTest::dir() { |
- return dir_; |
-} |
- |
-DirectoryChangeDelegate* SyncableDirectoryTest::directory_change_delegate() { |
- return &delegate_; |
-} |
- |
-Encryptor* SyncableDirectoryTest::encryptor() { |
- return &encryptor_; |
-} |
- |
- |
-TestUnrecoverableErrorHandler* |
-SyncableDirectoryTest::unrecoverable_error_handler() { |
- return &handler_; |
-} |
- |
-void SyncableDirectoryTest::ValidateEntry(BaseTransaction* trans, |
- int64_t id, |
- bool check_name, |
- const std::string& name, |
- int64_t base_version, |
- int64_t 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()); |
-} |
- |
-TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) { |
- const int metas_to_create = 50; |
- MetahandleSet expected_purges; |
- MetahandleSet all_handles; |
- { |
- dir()->SetDownloadProgress(BOOKMARKS, BuildProgress(BOOKMARKS)); |
- dir()->SetDownloadProgress(PREFERENCES, BuildProgress(PREFERENCES)); |
- 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_t> 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_t> new_dirty_metahandles; |
- for (std::vector<int64_t>::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_t> 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_t> new_dirty_metahandles; |
- for (std::vector<int64_t>::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_t>::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_t>::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"); |
- |
- // The first two IDs are server IDs. |
- Id id1 = TestIdFactory::FromNumber(1); |
- Id id2 = TestIdFactory::FromNumber(2); |
- // The third one is a client ID. |
- Id id3 = TestIdFactory::FromNumber(-3); |
- int64_t handle1 = 0; |
- int64_t handle2 = 0; |
- int64_t handle3 = 0; |
- { |
- // Create 3 bookmark entries and save in database. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- MutableEntry item1(&trans, CREATE, BOOKMARKS, trans.root_id(), "item1"); |
- item1.PutId(id1); |
- item1.PutSpecifics(bookmark_specifics); |
- item1.PutServerSpecifics(bookmark_specifics); |
- item1.PutIsUnappliedUpdate(true); |
- item1.PutBaseVersion(10); |
- handle1 = item1.GetMetahandle(); |
- |
- MutableEntry item2(&trans, CREATE, BOOKMARKS, trans.root_id(), "item2"); |
- item2.PutId(id2); |
- item2.PutSpecifics(bookmark_specifics); |
- item2.PutServerSpecifics(bookmark_specifics); |
- item2.PutIsUnappliedUpdate(true); |
- item2.PutBaseVersion(10); |
- handle2 = item2.GetMetahandle(); |
- |
- MutableEntry item3(&trans, CREATE, BOOKMARKS, trans.root_id(), "item3"); |
- item3.PutId(id3); |
- item3.PutSpecifics(bookmark_specifics); |
- item3.PutServerSpecifics(bookmark_specifics); |
- item3.PutIsUnsynced(true); |
- handle3 = item3.GetMetahandle(); |
- } |
- 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, but only if the deletion is initiated in update e.g. |
- // IS_UNAPPLIED_UPDATE is also true. |
- 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); |
- MutableEntry item3(&trans, GET_BY_ID, id3); |
- ASSERT_TRUE(item3.good()); |
- item3.PutServerIsDel(true); |
- // Expect only the first two items to be in the delete journal. |
- 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)); |
- tmp.put(ID, id3); |
- EXPECT_FALSE(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)); |
- tmp.put(META_HANDLE, handle3); |
- EXPECT_FALSE(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 (IS_UNAPPLIED_UPDATE shouldn't matter in this case). |
- MutableEntry item1(&trans, GET_BY_ID, id1); |
- ASSERT_TRUE(item1.good()); |
- item1.PutIsUnappliedUpdate(false); |
- 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, TestPurgeDeletedEntriesOnReload) { |
- sync_pb::EntitySpecifics specifics; |
- AddDefaultFieldValue(PREFERENCES, &specifics); |
- |
- const int kClientCount = 2; |
- const int kServerCount = 5; |
- const int kTestCount = kClientCount + kServerCount; |
- int64_t handles[kTestCount]; |
- |
- // The idea is to recreate various combinations of IDs, IS_DEL, |
- // IS_UNSYNCED, and IS_UNAPPLIED_UPDATE flags to test all combinations |
- // for DirectoryBackingStore::SafeToPurgeOnLoading. |
- // 0: client ID, IS_DEL, IS_UNSYNCED |
- // 1: client ID, IS_UNSYNCED |
- // 2: server ID, IS_DEL, IS_UNSYNCED, IS_UNAPPLIED_UPDATE |
- // 3: server ID, IS_DEL, IS_UNSYNCED |
- // 4: server ID, IS_DEL, IS_UNAPPLIED_UPDATE |
- // 5: server ID, IS_DEL |
- // 6: server ID |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- for (int i = 0; i < kTestCount; i++) { |
- std::string name = base::StringPrintf("item%d", i); |
- MutableEntry item(&trans, CREATE, PREFERENCES, trans.root_id(), name); |
- ASSERT_TRUE(item.good()); |
- |
- handles[i] = item.GetMetahandle(); |
- |
- if (i < kClientCount) { |
- item.PutId(TestIdFactory::FromNumber(i - kClientCount)); |
- } else { |
- item.PutId(TestIdFactory::FromNumber(i)); |
- } |
- |
- item.PutUniqueClientTag(name); |
- item.PutIsUnsynced(true); |
- item.PutSpecifics(specifics); |
- item.PutServerSpecifics(specifics); |
- |
- if (i >= kClientCount) { |
- item.PutBaseVersion(10); |
- item.PutServerVersion(10); |
- } |
- |
- // Set flags |
- if (i != 1 && i != 6) |
- item.PutIsDel(true); |
- |
- if (i >= 4) |
- item.PutIsUnsynced(false); |
- |
- if (i == 2 || i == 4) |
- item.PutIsUnappliedUpdate(true); |
- } |
- } |
- ASSERT_EQ(OPENED, SimulateSaveAndReloadDir()); |
- |
- // Expect items 0 and 5 to be purged according to |
- // DirectoryBackingStore::SafeToPurgeOnLoading: |
- // - Item 0 is an item with IS_DEL flag and client ID. |
- // - Item 5 is an item with IS_DEL flag which has both |
- // IS_UNSYNCED and IS_UNAPPLIED_UPDATE unset. |
- std::vector<int64_t> expected_purged; |
- expected_purged.push_back(0); |
- expected_purged.push_back(5); |
- |
- std::vector<int64_t> actually_purged; |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- for (int i = 0; i < kTestCount; i++) { |
- Entry item(&trans, GET_BY_HANDLE, handles[i]); |
- if (!item.good()) { |
- actually_purged.push_back(i); |
- } |
- } |
- } |
- |
- EXPECT_EQ(expected_purged, actually_purged); |
-} |
- |
-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(BOOKMARKS, "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_t handle1, handle2; |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- dir()->GetUnsyncedMetaHandles(&trans, &handles); |
- ASSERT_EQ(0u, 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_EQ(0u, 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_EQ(1u, 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_EQ(2u, 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_EQ(1u, handles.size()); |
- ASSERT_TRUE(handle2 == handles[0]); |
- } |
-} |
- |
-TEST_F(SyncableDirectoryTest, TestGetUnappliedUpdates) { |
- std::vector<int64_t> handles; |
- int64_t handle1, handle2; |
- const FullModelTypeSet all_types = FullModelTypeSet::All(); |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- dir()->GetUnappliedUpdateMetaHandles(&trans, all_types, &handles); |
- ASSERT_EQ(0u, 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_EQ(0u, 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_EQ(1u, 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_EQ(2u, 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_EQ(1u, handles.size()); |
- ASSERT_TRUE(handle2 == handles[0]); |
- } |
-} |
- |
-TEST_F(SyncableDirectoryTest, DeleteBug_531383) { |
- // Try to evoke a check failure... |
- TestIdFactory id_factory; |
- int64_t 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_t 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 that roughly mimics the directory interaction that occurs when a |
-// type root folder is created locally and then re-created (updated) from the |
-// server. |
-TEST_F(SyncableDirectoryTest, ChangeEntryIDAndUpdateChildren_ImplicitParent) { |
- TestIdFactory id_factory; |
- Id orig_parent_id; |
- Id child_id; |
- |
- { |
- // Create two client-side items, a parent and child. |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- MutableEntry parent(&trans, CREATE, PREFERENCES, id_factory.root(), |
- "parent"); |
- parent.PutIsDir(true); |
- parent.PutIsUnsynced(true); |
- |
- // The child has unset parent ID. The parent is inferred from the type. |
- MutableEntry child(&trans, CREATE, PREFERENCES, "child"); |
- child.PutIsUnsynced(true); |
- |
- orig_parent_id = parent.GetId(); |
- 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); |
- |
- ChangeEntryIDAndUpdateChildren(&trans, &parent, id_factory.NewServerId()); |
- parent.PutIsUnsynced(false); |
- parent.PutBaseVersion(1); |
- parent.PutServerVersion(1); |
- } |
- |
- // Final check for validity. |
- EXPECT_EQ(OPENED, SimulateSaveAndReloadDir()); |
- |
- // Verify that child's PARENT_ID hasn't been updated. |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- Entry child(&trans, GET_BY_ID, child_id); |
- EXPECT_TRUE(child.good()); |
- EXPECT_TRUE(child.GetParentId().IsNull()); |
- } |
-} |
- |
-// 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())); |
- } |
-} |
- |
-// Any item with BOOKMARKS in their local specifics should have a valid local |
-// unique position. If there is an item in the loaded DB that does not match |
-// this criteria, we consider the whole DB to be corrupt. |
-TEST_F(SyncableDirectoryTest, BadPositionCountsAsCorruption) { |
- TestIdFactory id_factory; |
- |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- MutableEntry parent(&trans, CREATE, BOOKMARKS, id_factory.root(), "parent"); |
- parent.PutIsDir(true); |
- parent.PutIsUnsynced(true); |
- |
- // The code is littered with DCHECKs that try to stop us from doing what |
- // we're about to do. Our work-around is to create a bookmark based on |
- // a server update, then update its local specifics without updating its |
- // local unique position. |
- |
- MutableEntry child( |
- &trans, CREATE_NEW_UPDATE_ITEM, id_factory.MakeServer("child")); |
- sync_pb::EntitySpecifics specifics; |
- AddDefaultFieldValue(BOOKMARKS, &specifics); |
- child.PutIsUnappliedUpdate(true); |
- child.PutSpecifics(specifics); |
- |
- EXPECT_TRUE(child.ShouldMaintainPosition()); |
- EXPECT_TRUE(!child.GetUniquePosition().IsValid()); |
- } |
- |
- EXPECT_EQ(FAILED_DATABASE_CORRUPT, SimulateSaveAndReloadDir()); |
-} |
- |
-TEST_F(SyncableDirectoryTest, General) { |
- int64_t written_metahandle; |
- const Id id = TestIdFactory::FromNumber(99); |
- std::string name = "Jeff"; |
- // Test simple read operations on an empty DB. |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- Entry e(&rtrans, GET_BY_ID, id); |
- ASSERT_FALSE(e.good()); // Hasn't been written yet. |
- |
- Directory::Metahandles child_handles; |
- dir()->GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles); |
- EXPECT_TRUE(child_handles.empty()); |
- } |
- |
- // Test creating a new meta entry. |
- { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name); |
- ASSERT_TRUE(me.good()); |
- me.PutId(id); |
- me.PutBaseVersion(1); |
- written_metahandle = me.GetMetahandle(); |
- } |
- |
- // Test GetChildHandles* after something is now in the DB. |
- // Also check that GET_BY_ID works. |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- Entry e(&rtrans, GET_BY_ID, id); |
- ASSERT_TRUE(e.good()); |
- |
- Directory::Metahandles child_handles; |
- dir()->GetChildHandlesById(&rtrans, rtrans.root_id(), &child_handles); |
- EXPECT_EQ(1u, child_handles.size()); |
- |
- for (Directory::Metahandles::iterator i = child_handles.begin(); |
- i != child_handles.end(); ++i) { |
- EXPECT_EQ(*i, written_metahandle); |
- } |
- } |
- |
- // Test writing data to an entity. Also check that GET_BY_HANDLE works. |
- static const char s[] = "Hello World."; |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); |
- ASSERT_TRUE(e.good()); |
- PutDataAsBookmarkFavicon(&trans, &e, s, sizeof(s)); |
- } |
- |
- // Test reading back the contents that we just wrote. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); |
- ASSERT_TRUE(e.good()); |
- ExpectDataFromBookmarkFaviconEquals(&trans, &e, s, sizeof(s)); |
- } |
- |
- // Verify it exists in the folder. |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- EXPECT_EQ(1, CountEntriesWithName(&rtrans, rtrans.root_id(), name)); |
- } |
- |
- // Now delete it. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); |
- e.PutIsDel(true); |
- |
- EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), name)); |
- } |
- |
- dir()->SaveChanges(); |
-} |
- |
-TEST_F(SyncableDirectoryTest, ChildrenOps) { |
- int64_t written_metahandle; |
- const Id id = TestIdFactory::FromNumber(99); |
- std::string name = "Jeff"; |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- Entry e(&rtrans, GET_BY_ID, id); |
- ASSERT_FALSE(e.good()); // Hasn't been written yet. |
- |
- Entry root(&rtrans, GET_BY_ID, rtrans.root_id()); |
- ASSERT_TRUE(root.good()); |
- EXPECT_FALSE(dir()->HasChildren(&rtrans, rtrans.root_id())); |
- EXPECT_TRUE(root.GetFirstChildId().IsNull()); |
- } |
- |
- { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name); |
- ASSERT_TRUE(me.good()); |
- me.PutId(id); |
- me.PutBaseVersion(1); |
- written_metahandle = me.GetMetahandle(); |
- } |
- |
- // Test children ops after something is now in the DB. |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- Entry e(&rtrans, GET_BY_ID, id); |
- ASSERT_TRUE(e.good()); |
- |
- Entry child(&rtrans, GET_BY_HANDLE, written_metahandle); |
- ASSERT_TRUE(child.good()); |
- |
- Entry root(&rtrans, GET_BY_ID, rtrans.root_id()); |
- ASSERT_TRUE(root.good()); |
- EXPECT_TRUE(dir()->HasChildren(&rtrans, rtrans.root_id())); |
- EXPECT_EQ(e.GetId(), root.GetFirstChildId()); |
- } |
- |
- { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry me(&wtrans, GET_BY_HANDLE, written_metahandle); |
- ASSERT_TRUE(me.good()); |
- me.PutIsDel(true); |
- } |
- |
- // Test children ops after the children have been deleted. |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- Entry e(&rtrans, GET_BY_ID, id); |
- ASSERT_TRUE(e.good()); |
- |
- Entry root(&rtrans, GET_BY_ID, rtrans.root_id()); |
- ASSERT_TRUE(root.good()); |
- EXPECT_FALSE(dir()->HasChildren(&rtrans, rtrans.root_id())); |
- EXPECT_TRUE(root.GetFirstChildId().IsNull()); |
- } |
- |
- dir()->SaveChanges(); |
-} |
- |
-TEST_F(SyncableDirectoryTest, ClientIndexRebuildsProperly) { |
- int64_t written_metahandle; |
- TestIdFactory factory; |
- const Id id = factory.NewServerId(); |
- std::string name = "cheesepuffs"; |
- std::string tag = "dietcoke"; |
- |
- // Test creating a new meta entry. |
- { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), name); |
- ASSERT_TRUE(me.good()); |
- me.PutId(id); |
- me.PutBaseVersion(1); |
- me.PutUniqueClientTag(tag); |
- written_metahandle = me.GetMetahandle(); |
- } |
- dir()->SaveChanges(); |
- |
- // Close and reopen, causing index regeneration. |
- ReopenDirectory(); |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- Entry me(&trans, GET_BY_CLIENT_TAG, tag); |
- ASSERT_TRUE(me.good()); |
- EXPECT_EQ(me.GetId(), id); |
- EXPECT_EQ(me.GetBaseVersion(), 1); |
- EXPECT_EQ(me.GetUniqueClientTag(), tag); |
- EXPECT_EQ(me.GetMetahandle(), written_metahandle); |
- } |
-} |
- |
-TEST_F(SyncableDirectoryTest, ClientIndexRebuildsDeletedProperly) { |
- TestIdFactory factory; |
- const Id id = factory.NewServerId(); |
- std::string tag = "dietcoke"; |
- |
- // Test creating a deleted, unsynced, server meta entry. |
- { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "deleted"); |
- ASSERT_TRUE(me.good()); |
- me.PutId(id); |
- me.PutBaseVersion(1); |
- me.PutUniqueClientTag(tag); |
- me.PutIsDel(true); |
- me.PutIsUnsynced(true); // Or it might be purged. |
- } |
- dir()->SaveChanges(); |
- |
- // Close and reopen, causing index regeneration. |
- ReopenDirectory(); |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- Entry me(&trans, GET_BY_CLIENT_TAG, tag); |
- // Should still be present and valid in the client tag index. |
- ASSERT_TRUE(me.good()); |
- EXPECT_EQ(me.GetId(), id); |
- EXPECT_EQ(me.GetUniqueClientTag(), tag); |
- EXPECT_TRUE(me.GetIsDel()); |
- EXPECT_TRUE(me.GetIsUnsynced()); |
- } |
-} |
- |
-TEST_F(SyncableDirectoryTest, ToValue) { |
- const Id id = TestIdFactory::FromNumber(99); |
- { |
- ReadTransaction rtrans(FROM_HERE, dir().get()); |
- Entry e(&rtrans, GET_BY_ID, id); |
- EXPECT_FALSE(e.good()); // Hasn't been written yet. |
- |
- std::unique_ptr<base::DictionaryValue> value(e.ToValue(NULL)); |
- ExpectDictBooleanValue(false, *value, "good"); |
- EXPECT_EQ(1u, value->size()); |
- } |
- |
- // Test creating a new meta entry. |
- { |
- WriteTransaction wtrans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "new"); |
- ASSERT_TRUE(me.good()); |
- me.PutId(id); |
- me.PutBaseVersion(1); |
- |
- std::unique_ptr<base::DictionaryValue> value(me.ToValue(NULL)); |
- ExpectDictBooleanValue(true, *value, "good"); |
- EXPECT_TRUE(value->HasKey("kernel")); |
- ExpectDictStringValue("Bookmarks", *value, "modelType"); |
- ExpectDictBooleanValue(true, *value, "existsOnClientBecauseNameIsNonEmpty"); |
- ExpectDictBooleanValue(false, *value, "isRoot"); |
- } |
- |
- dir()->SaveChanges(); |
-} |
- |
-// A thread that creates a bunch of directory entries. |
-class StressTransactionsDelegate : public base::PlatformThread::Delegate { |
- public: |
- StressTransactionsDelegate(Directory* dir, int thread_number) |
- : dir_(dir), thread_number_(thread_number) {} |
- |
- private: |
- Directory* const dir_; |
- const int thread_number_; |
- |
- // PlatformThread::Delegate methods: |
- void ThreadMain() override { |
- int entry_count = 0; |
- std::string path_name; |
- |
- for (int i = 0; i < 20; ++i) { |
- const int rand_action = base::RandInt(0, 9); |
- if (rand_action < 4 && !path_name.empty()) { |
- ReadTransaction trans(FROM_HERE, dir_); |
- EXPECT_EQ(1, CountEntriesWithName(&trans, trans.root_id(), path_name)); |
- base::PlatformThread::Sleep( |
- base::TimeDelta::FromMilliseconds(base::RandInt(0, 9))); |
- } else { |
- std::string unique_name = |
- base::StringPrintf("%d.%d", thread_number_, entry_count++); |
- path_name.assign(unique_name.begin(), unique_name.end()); |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir_); |
- MutableEntry e(&trans, CREATE, BOOKMARKS, trans.root_id(), path_name); |
- EXPECT_TRUE(e.good()); |
- base::PlatformThread::Sleep( |
- base::TimeDelta::FromMilliseconds(base::RandInt(0, 19))); |
- e.PutIsUnsynced(true); |
- if (e.PutId(TestIdFactory::FromNumber(base::RandInt(0, RAND_MAX))) && |
- e.GetId().ServerKnows() && !e.GetId().IsRoot()) { |
- e.PutBaseVersion(1); |
- } |
- } |
- } |
- } |
- |
- DISALLOW_COPY_AND_ASSIGN(StressTransactionsDelegate); |
-}; |
- |
-// Stress test Directory by accessing it from several threads concurrently. |
-TEST_F(SyncableDirectoryTest, StressTransactions) { |
- const int kThreadCount = 7; |
- base::PlatformThreadHandle threads[kThreadCount]; |
- std::unique_ptr<StressTransactionsDelegate> thread_delegates[kThreadCount]; |
- |
- for (int i = 0; i < kThreadCount; ++i) { |
- thread_delegates[i].reset(new StressTransactionsDelegate(dir().get(), i)); |
- ASSERT_TRUE(base::PlatformThread::Create( |
- 0, thread_delegates[i].get(), &threads[i])); |
- } |
- |
- for (int i = 0; i < kThreadCount; ++i) { |
- base::PlatformThread::Join(threads[i]); |
- } |
-} |
- |
-// Verify that Directory is notifed when a MutableEntry's AttachmentMetadata |
-// changes. |
-TEST_F(SyncableDirectoryTest, MutableEntry_PutAttachmentMetadata) { |
- sync_pb::AttachmentMetadata attachment_metadata; |
- sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); |
- sync_pb::AttachmentIdProto attachment_id_proto = |
- syncer::CreateAttachmentIdProto(0, 0); |
- *record->mutable_id() = attachment_id_proto; |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- // Create an entry with attachment metadata and see that the attachment id |
- // is not linked. |
- MutableEntry entry( |
- &trans, CREATE, PREFERENCES, trans.root_id(), "some entry"); |
- entry.PutId(TestIdFactory::FromNumber(-1)); |
- entry.PutIsUnsynced(true); |
- |
- Directory::Metahandles metahandles; |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- dir()->GetMetahandlesByAttachmentId( |
- &trans, attachment_id_proto, &metahandles); |
- ASSERT_TRUE(metahandles.empty()); |
- |
- // Now add the attachment metadata and see that Directory believes it is |
- // linked. |
- entry.PutAttachmentMetadata(attachment_metadata); |
- ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- dir()->GetMetahandlesByAttachmentId( |
- &trans, attachment_id_proto, &metahandles); |
- ASSERT_FALSE(metahandles.empty()); |
- ASSERT_EQ(metahandles[0], entry.GetMetahandle()); |
- |
- // Clear out the attachment metadata and see that it's no longer linked. |
- sync_pb::AttachmentMetadata empty_attachment_metadata; |
- entry.PutAttachmentMetadata(empty_attachment_metadata); |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- dir()->GetMetahandlesByAttachmentId( |
- &trans, attachment_id_proto, &metahandles); |
- ASSERT_TRUE(metahandles.empty()); |
- } |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
-} |
- |
-// Verify that UpdateAttachmentId updates attachment_id and is_on_server flag. |
-TEST_F(SyncableDirectoryTest, MutableEntry_UpdateAttachmentId) { |
- sync_pb::AttachmentMetadata attachment_metadata; |
- sync_pb::AttachmentMetadataRecord* r1 = attachment_metadata.add_record(); |
- sync_pb::AttachmentMetadataRecord* r2 = attachment_metadata.add_record(); |
- *r1->mutable_id() = syncer::CreateAttachmentIdProto(0, 0); |
- *r2->mutable_id() = syncer::CreateAttachmentIdProto(0, 0); |
- sync_pb::AttachmentIdProto attachment_id_proto = r1->id(); |
- |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- |
- MutableEntry entry( |
- &trans, CREATE, PREFERENCES, trans.root_id(), "some entry"); |
- entry.PutId(TestIdFactory::FromNumber(-1)); |
- entry.PutAttachmentMetadata(attachment_metadata); |
- |
- { |
- const sync_pb::AttachmentMetadata& entry_metadata = |
- entry.GetAttachmentMetadata(); |
- ASSERT_EQ(2, entry_metadata.record_size()); |
- ASSERT_FALSE(entry_metadata.record(0).is_on_server()); |
- ASSERT_FALSE(entry_metadata.record(1).is_on_server()); |
- ASSERT_FALSE(entry.GetIsUnsynced()); |
- } |
- |
- entry.MarkAttachmentAsOnServer(attachment_id_proto); |
- |
- { |
- // Re-get entry_metadata because it is immutable in the directory and |
- // entry_metadata reference has been made invalid by |
- // MarkAttachmentAsOnServer call above. |
- const sync_pb::AttachmentMetadata& entry_metadata = |
- entry.GetAttachmentMetadata(); |
- ASSERT_TRUE(entry_metadata.record(0).is_on_server()); |
- ASSERT_FALSE(entry_metadata.record(1).is_on_server()); |
- ASSERT_TRUE(entry.GetIsUnsynced()); |
- } |
-} |
- |
-// Verify that deleted entries with attachments will retain the attachments. |
-TEST_F(SyncableDirectoryTest, Directory_DeleteDoesNotUnlinkAttachments) { |
- sync_pb::AttachmentMetadata attachment_metadata; |
- sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); |
- sync_pb::AttachmentIdProto attachment_id_proto = |
- syncer::CreateAttachmentIdProto(0, 0); |
- *record->mutable_id() = attachment_id_proto; |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- const Id id = TestIdFactory::FromNumber(-1); |
- |
- // Create an entry with attachment metadata and see that the attachment id |
- // is linked. |
- CreateEntryWithAttachmentMetadata( |
- PREFERENCES, "some entry", id, attachment_metadata); |
- ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- |
- // Delete the entry and see that it's still linked because the entry hasn't |
- // yet been purged. |
- DeleteEntry(id); |
- ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- |
- // Reload the Directory, purging the deleted entry, and see that the |
- // attachment is no longer linked. |
- SimulateSaveAndReloadDir(); |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
-} |
- |
-// Verify that a given attachment can be referenced by multiple entries and that |
-// any one of the references is sufficient to ensure it remains linked. |
-TEST_F(SyncableDirectoryTest, Directory_LastReferenceUnlinksAttachments) { |
- // Create one attachment. |
- sync_pb::AttachmentMetadata attachment_metadata; |
- sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); |
- sync_pb::AttachmentIdProto attachment_id_proto = |
- syncer::CreateAttachmentIdProto(0, 0); |
- *record->mutable_id() = attachment_id_proto; |
- |
- // Create two entries, each referencing the attachment. |
- const Id id1 = TestIdFactory::FromNumber(-1); |
- const Id id2 = TestIdFactory::FromNumber(-2); |
- CreateEntryWithAttachmentMetadata( |
- PREFERENCES, "some entry", id1, attachment_metadata); |
- CreateEntryWithAttachmentMetadata( |
- PREFERENCES, "some other entry", id2, attachment_metadata); |
- |
- // See that the attachment is considered linked. |
- ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- |
- // Delete the first entry, reload the Directory, see that the attachment is |
- // still linked. |
- DeleteEntry(id1); |
- SimulateSaveAndReloadDir(); |
- ASSERT_TRUE(dir()->IsAttachmentLinked(attachment_id_proto)); |
- |
- // Delete the second entry, reload the Directory, see that the attachment is |
- // no loner linked. |
- DeleteEntry(id2); |
- SimulateSaveAndReloadDir(); |
- ASSERT_FALSE(dir()->IsAttachmentLinked(attachment_id_proto)); |
-} |
- |
-TEST_F(SyncableDirectoryTest, Directory_GetAttachmentIdsToUpload) { |
- // Create one attachment, referenced by two entries. |
- AttachmentId attachment_id = AttachmentId::Create(0, 0); |
- sync_pb::AttachmentIdProto attachment_id_proto = attachment_id.GetProto(); |
- sync_pb::AttachmentMetadata attachment_metadata; |
- sync_pb::AttachmentMetadataRecord* record = attachment_metadata.add_record(); |
- *record->mutable_id() = attachment_id_proto; |
- const Id id1 = TestIdFactory::FromNumber(-1); |
- const Id id2 = TestIdFactory::FromNumber(-2); |
- CreateEntryWithAttachmentMetadata( |
- PREFERENCES, "some entry", id1, attachment_metadata); |
- CreateEntryWithAttachmentMetadata( |
- PREFERENCES, "some other entry", id2, attachment_metadata); |
- |
- // See that Directory reports that this attachment is not on the server. |
- AttachmentIdList ids; |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- dir()->GetAttachmentIdsToUpload(&trans, PREFERENCES, &ids); |
- } |
- ASSERT_EQ(1U, ids.size()); |
- ASSERT_EQ(attachment_id, *ids.begin()); |
- |
- // Call again, but this time with a ModelType for which there are no entries. |
- // See that Directory correctly reports that there are none. |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- dir()->GetAttachmentIdsToUpload(&trans, PASSWORDS, &ids); |
- } |
- ASSERT_TRUE(ids.empty()); |
- |
- // Now, mark the attachment as "on the server" via entry_1. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry entry_1(&trans, GET_BY_ID, id1); |
- entry_1.MarkAttachmentAsOnServer(attachment_id_proto); |
- } |
- |
- // See that Directory no longer reports that this attachment is not on the |
- // server. |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- dir()->GetAttachmentIdsToUpload(&trans, PREFERENCES, &ids); |
- } |
- ASSERT_TRUE(ids.empty()); |
-} |
- |
-// Verify that the directory accepts entries with unset parent ID. |
-TEST_F(SyncableDirectoryTest, MutableEntry_ImplicitParentId) { |
- TestIdFactory id_factory; |
- const Id root_id = TestIdFactory::root(); |
- const Id p_root_id = id_factory.NewServerId(); |
- const Id a_root_id = id_factory.NewServerId(); |
- const Id item1_id = id_factory.NewServerId(); |
- const Id item2_id = id_factory.NewServerId(); |
- const Id item3_id = id_factory.NewServerId(); |
- // Create two type root folders that are necessary (for now) |
- // for creating items without explicitly set Parent ID |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry p_root(&trans, CREATE, PREFERENCES, root_id, "P"); |
- ASSERT_TRUE(p_root.good()); |
- p_root.PutIsDir(true); |
- p_root.PutId(p_root_id); |
- p_root.PutBaseVersion(1); |
- |
- MutableEntry a_root(&trans, CREATE, AUTOFILL, root_id, "A"); |
- ASSERT_TRUE(a_root.good()); |
- a_root.PutIsDir(true); |
- a_root.PutId(a_root_id); |
- a_root.PutBaseVersion(1); |
- } |
- |
- // Create two entries with implicit parent nodes and one entry with explicit |
- // parent node. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry item1(&trans, CREATE, PREFERENCES, "P1"); |
- item1.PutBaseVersion(1); |
- item1.PutId(item1_id); |
- MutableEntry item2(&trans, CREATE, AUTOFILL, "A1"); |
- item2.PutBaseVersion(1); |
- item2.PutId(item2_id); |
- // Placing an AUTOFILL item under the root isn't expected, |
- // but let's test it to verify that explicit root overrides the implicit |
- // one and this entry doesn't end up under the "A" root. |
- MutableEntry item3(&trans, CREATE, AUTOFILL, root_id, "A2"); |
- item3.PutBaseVersion(1); |
- item3.PutId(item3_id); |
- } |
- |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- // Verify that item1 and item2 are good and have no ParentId. |
- Entry item1(&trans, GET_BY_ID, item1_id); |
- ASSERT_TRUE(item1.good()); |
- ASSERT_TRUE(item1.GetParentId().IsNull()); |
- Entry item2(&trans, GET_BY_ID, item2_id); |
- ASSERT_TRUE(item2.good()); |
- ASSERT_TRUE(item2.GetParentId().IsNull()); |
- // Verify that p_root and a_root have exactly one child each |
- // (subtract one to exclude roots themselves). |
- Entry p_root(&trans, GET_BY_ID, p_root_id); |
- ASSERT_EQ(item1_id, p_root.GetFirstChildId()); |
- ASSERT_EQ(1, p_root.GetTotalNodeCount() - 1); |
- Entry a_root(&trans, GET_BY_ID, a_root_id); |
- ASSERT_EQ(item2_id, a_root.GetFirstChildId()); |
- ASSERT_EQ(1, a_root.GetTotalNodeCount() - 1); |
- } |
-} |
- |
-// Verify that the successor / predecessor navigation still works for |
-// directory entries with unset Parent IDs. |
-TEST_F(SyncableDirectoryTest, MutableEntry_ImplicitParentId_Siblings) { |
- TestIdFactory id_factory; |
- const Id root_id = TestIdFactory::root(); |
- const Id p_root_id = id_factory.NewServerId(); |
- const Id item1_id = id_factory.FromNumber(1); |
- const Id item2_id = id_factory.FromNumber(2); |
- |
- // Create type root folder for PREFERENCES. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry p_root(&trans, CREATE, PREFERENCES, root_id, "P"); |
- ASSERT_TRUE(p_root.good()); |
- p_root.PutIsDir(true); |
- p_root.PutId(p_root_id); |
- p_root.PutBaseVersion(1); |
- } |
- |
- // Create two PREFERENCES entries with implicit parent nodes. |
- { |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry item1(&trans, CREATE, PREFERENCES, "P1"); |
- item1.PutBaseVersion(1); |
- item1.PutId(item1_id); |
- MutableEntry item2(&trans, CREATE, PREFERENCES, "P2"); |
- item2.PutBaseVersion(1); |
- item2.PutId(item2_id); |
- } |
- |
- // Verify GetSuccessorId and GetPredecessorId calls for these items. |
- // Please note that items are sorted according to their ID, e.g. |
- // item1 first, then item2. |
- { |
- ReadTransaction trans(FROM_HERE, dir().get()); |
- Entry item1(&trans, GET_BY_ID, item1_id); |
- EXPECT_EQ(Id(), item1.GetPredecessorId()); |
- EXPECT_EQ(item2_id, item1.GetSuccessorId()); |
- |
- Entry item2(&trans, GET_BY_ID, item2_id); |
- EXPECT_EQ(item1_id, item2.GetPredecessorId()); |
- EXPECT_EQ(Id(), item2.GetSuccessorId()); |
- } |
-} |
- |
-TEST_F(SyncableDirectoryTest, SaveChangesSnapshot_HasUnsavedMetahandleChanges) { |
- EntryKernel kernel; |
- Directory::SaveChangesSnapshot snapshot; |
- EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.dirty_metas.insert(&kernel); |
- EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.dirty_metas.clear(); |
- |
- EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.metahandles_to_purge.insert(1); |
- EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.metahandles_to_purge.clear(); |
- |
- EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.delete_journals.insert(&kernel); |
- EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.delete_journals.clear(); |
- |
- EXPECT_FALSE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.delete_journals_to_purge.insert(1); |
- EXPECT_TRUE(snapshot.HasUnsavedMetahandleChanges()); |
- snapshot.delete_journals_to_purge.clear(); |
-} |
- |
-// Verify that Directory triggers an unrecoverable error when a catastrophic |
-// DirectoryBackingStore error is detected. |
-TEST_F(SyncableDirectoryTest, CatastrophicError) { |
- MockUnrecoverableErrorHandler unrecoverable_error_handler; |
- Directory dir(new InMemoryDirectoryBackingStore("catastrophic_error"), |
- MakeWeakHandle(unrecoverable_error_handler.GetWeakPtr()), |
- base::Closure(), nullptr, nullptr); |
- ASSERT_EQ(OPENED, dir.Open(kDirectoryName, directory_change_delegate(), |
- NullTransactionObserver())); |
- ASSERT_EQ(0, unrecoverable_error_handler.invocation_count()); |
- |
- // Fire off two catastrophic errors. Call it twice to ensure Directory is |
- // tolerant of multiple invocations since that may happen in the real world. |
- dir.OnCatastrophicError(); |
- dir.OnCatastrophicError(); |
- |
- base::RunLoop().RunUntilIdle(); |
- |
- // See that the unrecoverable error handler has been invoked twice. |
- ASSERT_EQ(2, unrecoverable_error_handler.invocation_count()); |
-} |
- |
-bool EntitySpecificsValuesAreSame(const sync_pb::EntitySpecifics& v1, |
- const sync_pb::EntitySpecifics& v2) { |
- return &v1 == &v2; |
-} |
- |
-// Verifies that server and client specifics are shared when their values |
-// are equal. |
-TEST_F(SyncableDirectoryTest, SharingOfClientAndServerSpecifics) { |
- sync_pb::EntitySpecifics specifics1; |
- sync_pb::EntitySpecifics specifics2; |
- sync_pb::EntitySpecifics specifics3; |
- AddDefaultFieldValue(BOOKMARKS, &specifics1); |
- AddDefaultFieldValue(BOOKMARKS, &specifics2); |
- AddDefaultFieldValue(BOOKMARKS, &specifics3); |
- specifics1.mutable_bookmark()->set_url("foo"); |
- specifics2.mutable_bookmark()->set_url("bar"); |
- // specifics3 has the same URL as specifics1 |
- specifics3.mutable_bookmark()->set_url("foo"); |
- |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- MutableEntry item(&trans, CREATE, BOOKMARKS, trans.root_id(), "item"); |
- item.PutId(TestIdFactory::FromNumber(1)); |
- item.PutBaseVersion(10); |
- |
- // Verify sharing. |
- item.PutSpecifics(specifics1); |
- item.PutServerSpecifics(specifics1); |
- EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(), |
- item.GetServerSpecifics())); |
- |
- // Verify that specifics are no longer shared. |
- item.PutServerSpecifics(specifics2); |
- EXPECT_FALSE(EntitySpecificsValuesAreSame(item.GetSpecifics(), |
- item.GetServerSpecifics())); |
- |
- // Verify that specifics are shared again because specifics3 matches |
- // specifics1. |
- item.PutServerSpecifics(specifics3); |
- EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(), |
- item.GetServerSpecifics())); |
- |
- // Verify that copying the same value back to SPECIFICS is still OK. |
- item.PutSpecifics(specifics3); |
- EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetSpecifics(), |
- item.GetServerSpecifics())); |
- |
- // Verify sharing with BASE_SERVER_SPECIFICS. |
- EXPECT_FALSE(EntitySpecificsValuesAreSame(item.GetServerSpecifics(), |
- item.GetBaseServerSpecifics())); |
- item.PutBaseServerSpecifics(specifics3); |
- EXPECT_TRUE(EntitySpecificsValuesAreSame(item.GetServerSpecifics(), |
- item.GetBaseServerSpecifics())); |
-} |
- |
-// Tests checking and marking a type as having its initial sync completed. |
-TEST_F(SyncableDirectoryTest, InitialSyncEndedForType) { |
- // Not completed if there is no root node. |
- EXPECT_FALSE(dir()->InitialSyncEndedForType(PREFERENCES)); |
- |
- WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); |
- // Create the root node. |
- ModelNeutralMutableEntry entry(&trans, syncable::CREATE_NEW_TYPE_ROOT, |
- PREFERENCES); |
- ASSERT_TRUE(entry.good()); |
- |
- entry.PutServerIsDir(true); |
- entry.PutUniqueServerTag(ModelTypeToRootTag(PREFERENCES)); |
- |
- // Should still be marked as incomplete. |
- EXPECT_FALSE(dir()->InitialSyncEndedForType(&trans, PREFERENCES)); |
- |
- // Mark as complete and verify. |
- dir()->MarkInitialSyncEndedForType(&trans, PREFERENCES); |
- EXPECT_TRUE(dir()->InitialSyncEndedForType(&trans, PREFERENCES)); |
-} |
- |
-} // namespace syncable |
- |
-} // namespace syncer |