| // Copyright 2012 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 <string> |
| |
| #include "base/basictypes.h" |
| #include "base/compiler_specific.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/condition_variable.h" |
| #include "base/test/values_test_util.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/values.h" |
| #include "sync/protocol/bookmark_specifics.pb.h" |
| #include "sync/syncable/directory_backing_store.h" |
| #include "sync/syncable/directory_change_delegate.h" |
| #include "sync/syncable/in_memory_directory_backing_store.h" |
| #include "sync/syncable/metahandle_set.h" |
| #include "sync/syncable/mutable_entry.h" |
| #include "sync/syncable/on_disk_directory_backing_store.h" |
| #include "sync/syncable/syncable_proto_util.h" |
| #include "sync/syncable/syncable_read_transaction.h" |
| #include "sync/syncable/syncable_util.h" |
| #include "sync/syncable/syncable_write_transaction.h" |
| #include "sync/test/engine/test_id_factory.h" |
| #include "sync/test/engine/test_syncable_utils.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 { |
| |
| 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, |
| 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 |
| |
| class SyncableGeneralTest : public testing::Test { |
| public: |
| static const char kIndexTestName[]; |
| virtual void SetUp() { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| db_path_ = temp_dir_.path().Append( |
| FILE_PATH_LITERAL("SyncableTest.sqlite3")); |
| } |
| |
| virtual void TearDown() { |
| } |
| protected: |
| base::MessageLoop message_loop_; |
| base::ScopedTempDir temp_dir_; |
| NullDirectoryChangeDelegate delegate_; |
| FakeEncryptor encryptor_; |
| TestUnrecoverableErrorHandler handler_; |
| base::FilePath db_path_; |
| }; |
| |
| const char SyncableGeneralTest::kIndexTestName[] = "IndexTest"; |
| |
| TEST_F(SyncableGeneralTest, General) { |
| Directory dir(new InMemoryDirectoryBackingStore("SimpleTest"), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| |
| ASSERT_EQ(OPENED, dir.Open( |
| "SimpleTest", &delegate_, NullTransactionObserver())); |
| |
| int64 root_metahandle; |
| { |
| ReadTransaction rtrans(FROM_HERE, &dir); |
| Entry e(&rtrans, GET_BY_ID, rtrans.root_id()); |
| ASSERT_TRUE(e.good()); |
| root_metahandle = e.GetMetahandle(); |
| } |
| |
| int64 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); |
| 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()); |
| |
| dir.GetChildHandlesByHandle(&rtrans, root_metahandle, &child_handles); |
| EXPECT_TRUE(child_handles.empty()); |
| } |
| |
| // Test creating a new meta entry. |
| { |
| WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir); |
| 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); |
| 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); |
| } |
| |
| dir.GetChildHandlesByHandle(&rtrans, root_metahandle, &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); |
| 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); |
| 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); |
| EXPECT_EQ(1, CountEntriesWithName(&rtrans, rtrans.root_id(), name)); |
| } |
| |
| // Now delete it. |
| { |
| WriteTransaction trans(FROM_HERE, UNITTEST, &dir); |
| MutableEntry e(&trans, GET_BY_HANDLE, written_metahandle); |
| e.PutIsDel(true); |
| |
| EXPECT_EQ(0, CountEntriesWithName(&trans, trans.root_id(), name)); |
| } |
| |
| dir.SaveChanges(); |
| } |
| |
| TEST_F(SyncableGeneralTest, ChildrenOps) { |
| Directory dir(new InMemoryDirectoryBackingStore("SimpleTest"), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| ASSERT_EQ(OPENED, dir.Open( |
| "SimpleTest", &delegate_, NullTransactionObserver())); |
| |
| int64 written_metahandle; |
| const Id id = TestIdFactory::FromNumber(99); |
| std::string name = "Jeff"; |
| { |
| ReadTransaction rtrans(FROM_HERE, &dir); |
| 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().IsRoot()); |
| } |
| |
| { |
| WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir); |
| 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); |
| 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); |
| 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); |
| 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().IsRoot()); |
| } |
| |
| dir.SaveChanges(); |
| } |
| |
| TEST_F(SyncableGeneralTest, ClientIndexRebuildsProperly) { |
| int64 written_metahandle; |
| TestIdFactory factory; |
| const Id id = factory.NewServerId(); |
| std::string name = "cheesepuffs"; |
| std::string tag = "dietcoke"; |
| |
| // Test creating a new meta entry. |
| { |
| Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| ASSERT_EQ(OPENED, dir.Open(kIndexTestName, &delegate_, |
| NullTransactionObserver())); |
| { |
| WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir); |
| 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(); |
| } |
| |
| // The DB was closed. Now reopen it. This will cause index regeneration. |
| { |
| Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| ASSERT_EQ(OPENED, dir.Open(kIndexTestName, |
| &delegate_, NullTransactionObserver())); |
| |
| ReadTransaction trans(FROM_HERE, &dir); |
| 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(SyncableGeneralTest, ClientIndexRebuildsDeletedProperly) { |
| TestIdFactory factory; |
| const Id id = factory.NewServerId(); |
| std::string tag = "dietcoke"; |
| |
| // Test creating a deleted, unsynced, server meta entry. |
| { |
| Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| ASSERT_EQ(OPENED, dir.Open(kIndexTestName, &delegate_, |
| NullTransactionObserver())); |
| { |
| WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir); |
| 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(); |
| } |
| |
| // The DB was closed. Now reopen it. This will cause index regeneration. |
| // Should still be present and valid in the client tag index. |
| { |
| Directory dir(new OnDiskDirectoryBackingStore(kIndexTestName, db_path_), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| ASSERT_EQ(OPENED, dir.Open(kIndexTestName, &delegate_, |
| NullTransactionObserver())); |
| |
| ReadTransaction trans(FROM_HERE, &dir); |
| Entry me(&trans, GET_BY_CLIENT_TAG, tag); |
| ASSERT_TRUE(me.good()); |
| EXPECT_EQ(me.GetId(), id); |
| EXPECT_EQ(me.GetUniqueClientTag(), tag); |
| EXPECT_TRUE(me.GetIsDel()); |
| EXPECT_TRUE(me.GetIsUnsynced()); |
| } |
| } |
| |
| TEST_F(SyncableGeneralTest, ToValue) { |
| Directory dir(new InMemoryDirectoryBackingStore("SimpleTest"), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| ASSERT_EQ(OPENED, dir.Open( |
| "SimpleTest", &delegate_, NullTransactionObserver())); |
| |
| const Id id = TestIdFactory::FromNumber(99); |
| { |
| ReadTransaction rtrans(FROM_HERE, &dir); |
| Entry e(&rtrans, GET_BY_ID, id); |
| EXPECT_FALSE(e.good()); // Hasn't been written yet. |
| |
| scoped_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); |
| MutableEntry me(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "new"); |
| ASSERT_TRUE(me.good()); |
| me.PutId(id); |
| me.PutBaseVersion(1); |
| |
| scoped_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(); |
| } |
| |
| // Test that the bookmark tag generation algorithm remains unchanged. |
| TEST_F(SyncableGeneralTest, BookmarkTagTest) { |
| InMemoryDirectoryBackingStore* store = new InMemoryDirectoryBackingStore("x"); |
| |
| // The two inputs that form the bookmark tag are the directory's cache_guid |
| // and its next_id value. We don't need to take any action to ensure |
| // consistent next_id values, but we do need to explicitly request that our |
| // InMemoryDirectoryBackingStore always return the same cache_guid. |
| store->request_consistent_cache_guid(); |
| |
| Directory dir(store, &handler_, NULL, NULL, NULL); |
| ASSERT_EQ(OPENED, dir.Open("x", &delegate_, NullTransactionObserver())); |
| |
| { |
| WriteTransaction wtrans(FROM_HERE, UNITTEST, &dir); |
| MutableEntry bm(&wtrans, CREATE, BOOKMARKS, wtrans.root_id(), "bm"); |
| bm.PutIsUnsynced(true); |
| |
| // If this assertion fails, that might indicate that the algorithm used to |
| // generate bookmark tags has been modified. This could have implications |
| // for bookmark ordering. Please make sure you know what you're doing if |
| // you intend to make such a change. |
| ASSERT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", bm.GetUniqueBookmarkTag()); |
| } |
| } |
| |
| // 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())); |
| } |
| 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. |
| class TestBackingStore : public OnDiskDirectoryBackingStore { |
| public: |
| TestBackingStore(const std::string& dir_name, |
| const base::FilePath& backing_filepath); |
| |
| virtual ~TestBackingStore(); |
| |
| virtual bool SaveChanges(const Directory::SaveChangesSnapshot& snapshot) |
| OVERRIDE; |
| |
| void StartFailingSaveChanges() { |
| fail_save_changes_ = true; |
| } |
| |
| private: |
| bool fail_save_changes_; |
| }; |
| |
| TestBackingStore::TestBackingStore(const std::string& dir_name, |
| const base::FilePath& backing_filepath) |
| : OnDiskDirectoryBackingStore(dir_name, backing_filepath), |
| fail_save_changes_(false) { |
| } |
| |
| TestBackingStore::~TestBackingStore() { } |
| |
| bool TestBackingStore::SaveChanges( |
| const Directory::SaveChangesSnapshot& snapshot){ |
| if (fail_save_changes_) { |
| return false; |
| } else { |
| return OnDiskDirectoryBackingStore::SaveChanges(snapshot); |
| } |
| } |
| |
| // A directory whose Save() function can be set to always fail. |
| class TestDirectory : public Directory { |
| public: |
| // A factory function used to work around some initialization order issues. |
| static TestDirectory* Create( |
| Encryptor *encryptor, |
| UnrecoverableErrorHandler *handler, |
| const std::string& dir_name, |
| const base::FilePath& backing_filepath); |
| |
| virtual ~TestDirectory(); |
| |
| void StartFailingSaveChanges() { |
| backing_store_->StartFailingSaveChanges(); |
| } |
| |
| private: |
| TestDirectory(Encryptor* encryptor, |
| UnrecoverableErrorHandler* handler, |
| TestBackingStore* backing_store); |
| |
| TestBackingStore* backing_store_; |
| }; |
| |
| TestDirectory* TestDirectory::Create( |
| Encryptor *encryptor, |
| UnrecoverableErrorHandler *handler, |
| const std::string& dir_name, |
| const base::FilePath& backing_filepath) { |
| TestBackingStore* backing_store = |
| new TestBackingStore(dir_name, backing_filepath); |
| return new TestDirectory(encryptor, handler, backing_store); |
| } |
| |
| TestDirectory::TestDirectory(Encryptor* encryptor, |
| UnrecoverableErrorHandler* handler, |
| TestBackingStore* backing_store) |
| : Directory(backing_store, handler, NULL, NULL, NULL), |
| backing_store_(backing_store) { |
| } |
| |
| TestDirectory::~TestDirectory() { } |
| |
| TEST(OnDiskSyncableDirectory, FailInitialWrite) { |
| FakeEncryptor encryptor; |
| TestUnrecoverableErrorHandler handler; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| base::FilePath file_path = temp_dir.path().Append( |
| FILE_PATH_LITERAL("Test.sqlite3")); |
| std::string name = "user@x.com"; |
| NullDirectoryChangeDelegate delegate; |
| |
| scoped_ptr<TestDirectory> test_dir( |
| TestDirectory::Create(&encryptor, &handler, name, file_path)); |
| |
| test_dir->StartFailingSaveChanges(); |
| ASSERT_EQ(FAILED_INITIAL_WRITE, test_dir->Open(name, &delegate, |
| NullTransactionObserver())); |
| } |
| |
| // A variant of SyncableDirectoryTest that uses a real sqlite database. |
| class OnDiskSyncableDirectoryTest : public SyncableDirectoryTest { |
| protected: |
| // SetUp() is called before each test case is run. |
| // The sqlite3 DB is deleted before each test is run. |
| virtual void SetUp() { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| file_path_ = temp_dir_.path().Append( |
| FILE_PATH_LITERAL("Test.sqlite3")); |
| base::DeleteFile(file_path_, true); |
| CreateDirectory(); |
| } |
| |
| virtual void TearDown() { |
| // This also closes file handles. |
| dir_->SaveChanges(); |
| dir_.reset(); |
| base::DeleteFile(file_path_, true); |
| } |
| |
| // 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()); |
| } |
| |
| void SaveAndReloadDir() { |
| dir_->SaveChanges(); |
| CreateDirectory(); |
| } |
| |
| void StartFailingSaveChanges() { |
| test_directory_->StartFailingSaveChanges(); |
| } |
| |
| TestDirectory *test_directory_; // mirrors scoped_ptr<Directory> dir_ |
| base::ScopedTempDir temp_dir_; |
| base::FilePath file_path_; |
| }; |
| |
| TEST_F(OnDiskSyncableDirectoryTest, TestPurgeEntriesWithTypeIn) { |
| sync_pb::EntitySpecifics bookmark_specs; |
| sync_pb::EntitySpecifics autofill_specs; |
| sync_pb::EntitySpecifics preference_specs; |
| AddDefaultFieldValue(BOOKMARKS, &bookmark_specs); |
| AddDefaultFieldValue(PREFERENCES, &preference_specs); |
| AddDefaultFieldValue(AUTOFILL, &autofill_specs); |
| |
| ModelTypeSet types_to_purge(PREFERENCES, AUTOFILL); |
| |
| TestIdFactory id_factory; |
| // Create some items for each type. |
| { |
| WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); |
| |
| // 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); |
| |
| // 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 |
| // if their parent isn't quite right. |
| MutableEntry item1(&trans, CREATE, BOOKMARKS, trans.root_id(), "Item"); |
| ASSERT_TRUE(item1.good()); |
| item1.PutServerSpecifics(bookmark_specs); |
| item1.PutIsUnsynced(true); |
| |
| MutableEntry item2(&trans, CREATE_NEW_UPDATE_ITEM, |
| id_factory.NewServerId()); |
| ASSERT_TRUE(item2.good()); |
| item2.PutServerSpecifics(bookmark_specs); |
| item2.PutIsUnappliedUpdate(true); |
| |
| MutableEntry item3(&trans, CREATE, PREFERENCES, |
| trans.root_id(), "Item"); |
| ASSERT_TRUE(item3.good()); |
| item3.PutSpecifics(preference_specs); |
| item3.PutServerSpecifics(preference_specs); |
| item3.PutIsUnsynced(true); |
| |
| MutableEntry item4(&trans, CREATE_NEW_UPDATE_ITEM, |
| id_factory.NewServerId()); |
| ASSERT_TRUE(item4.good()); |
| item4.PutServerSpecifics(preference_specs); |
| item4.PutIsUnappliedUpdate(true); |
| |
| MutableEntry item5(&trans, CREATE, AUTOFILL, |
| trans.root_id(), "Item"); |
| ASSERT_TRUE(item5.good()); |
| item5.PutSpecifics(autofill_specs); |
| item5.PutServerSpecifics(autofill_specs); |
| item5.PutIsUnsynced(true); |
| |
| MutableEntry item6(&trans, CREATE_NEW_UPDATE_ITEM, |
| id_factory.NewServerId()); |
| ASSERT_TRUE(item6.good()); |
| item6.PutServerSpecifics(autofill_specs); |
| item6.PutIsUnappliedUpdate(true); |
| } |
| |
| dir_->SaveChanges(); |
| { |
| 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()); |
| |
| // We first query the in-memory data, and then reload the directory (without |
| // saving) to verify that disk does not still have the data. |
| CheckPurgeEntriesWithTypeInSucceeded(types_to_purge, true); |
| SaveAndReloadDir(); |
| CheckPurgeEntriesWithTypeInSucceeded(types_to_purge, false); |
| } |
| |
| TEST_F(OnDiskSyncableDirectoryTest, TestShareInfo) { |
| 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); |
| { |
| 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"); |
| 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(); |
| { |
| 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); |
| // 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()); |
| } |
| } |
| |
| TEST_F(OnDiskSyncableDirectoryTest, |
| TestSimpleFieldsPreservedDuringSaveChanges) { |
| Id update_id = TestIdFactory::FromNumber(1); |
| Id create_id; |
| EntryKernel create_pre_save, update_pre_save; |
| EntryKernel create_post_save, update_post_save; |
| std::string create_name = "Create"; |
| |
| { |
| 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); |
| create.PutIsUnsynced(true); |
| update.PutIsUnappliedUpdate(true); |
| sync_pb::EntitySpecifics specifics; |
| specifics.mutable_bookmark()->set_favicon("PNG"); |
| specifics.mutable_bookmark()->set_url("http://nowhere"); |
| create.PutSpecifics(specifics); |
| update.PutSpecifics(specifics); |
| create_pre_save = create.GetKernelCopy(); |
| update_pre_save = update.GetKernelCopy(); |
| create_id = create.GetId(); |
| } |
| |
| dir_->SaveChanges(); |
| dir_.reset(new Directory(new OnDiskDirectoryBackingStore(kName, file_path_), |
| &handler_, |
| NULL, |
| NULL, |
| NULL)); |
| |
| ASSERT_TRUE(dir_.get()); |
| ASSERT_EQ(OPENED, dir_->Open(kName, &delegate_, NullTransactionObserver())); |
| ASSERT_TRUE(dir_->good()); |
| |
| { |
| 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); |
| create_post_save = create.GetKernelCopy(); |
| update_post_save = update.GetKernelCopy(); |
| } |
| int i = BEGIN_FIELDS; |
| for ( ; i < INT64_FIELDS_END ; ++i) { |
| EXPECT_EQ(create_pre_save.ref((Int64Field)i) + |
| (i == TRANSACTION_VERSION ? 1 : 0), |
| create_post_save.ref((Int64Field)i)) |
| << "int64 field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((Int64Field)i) + |
| (i == TRANSACTION_VERSION ? 1 : 0), |
| update_post_save.ref((Int64Field)i)) |
| << "int64 field #" << i << " changed during save/load"; |
| } |
| for ( ; i < TIME_FIELDS_END ; ++i) { |
| EXPECT_EQ(create_pre_save.ref((TimeField)i), |
| create_post_save.ref((TimeField)i)) |
| << "time field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((TimeField)i), |
| update_post_save.ref((TimeField)i)) |
| << "time field #" << i << " changed during save/load"; |
| } |
| for ( ; i < ID_FIELDS_END ; ++i) { |
| EXPECT_EQ(create_pre_save.ref((IdField)i), |
| create_post_save.ref((IdField)i)) |
| << "id field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((IdField)i), |
| update_pre_save.ref((IdField)i)) |
| << "id field #" << i << " changed during save/load"; |
| } |
| for ( ; i < BIT_FIELDS_END ; ++i) { |
| EXPECT_EQ(create_pre_save.ref((BitField)i), |
| create_post_save.ref((BitField)i)) |
| << "Bit field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((BitField)i), |
| update_post_save.ref((BitField)i)) |
| << "Bit field #" << i << " changed during save/load"; |
| } |
| for ( ; i < STRING_FIELDS_END ; ++i) { |
| EXPECT_EQ(create_pre_save.ref((StringField)i), |
| create_post_save.ref((StringField)i)) |
| << "String field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((StringField)i), |
| update_post_save.ref((StringField)i)) |
| << "String field #" << i << " changed during save/load"; |
| } |
| for ( ; i < PROTO_FIELDS_END; ++i) { |
| EXPECT_EQ(create_pre_save.ref((ProtoField)i).SerializeAsString(), |
| create_post_save.ref((ProtoField)i).SerializeAsString()) |
| << "Blob field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((ProtoField)i).SerializeAsString(), |
| update_post_save.ref((ProtoField)i).SerializeAsString()) |
| << "Blob field #" << i << " changed during save/load"; |
| } |
| for ( ; i < UNIQUE_POSITION_FIELDS_END; ++i) { |
| EXPECT_TRUE(create_pre_save.ref((UniquePositionField)i).Equals( |
| create_post_save.ref((UniquePositionField)i))) |
| << "Position field #" << i << " changed during save/load"; |
| EXPECT_TRUE(update_pre_save.ref((UniquePositionField)i).Equals( |
| update_post_save.ref((UniquePositionField)i))) |
| << "Position field #" << i << " changed during save/load"; |
| } |
| } |
| |
| TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailure) { |
| int64 handle1 = 0; |
| // Set up an item using a regular, saveable directory. |
| { |
| WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); |
| |
| MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "aguilera"); |
| ASSERT_TRUE(e1.good()); |
| EXPECT_TRUE(e1.GetKernelCopy().is_dirty()); |
| handle1 = e1.GetMetahandle(); |
| e1.PutBaseVersion(1); |
| e1.PutIsDir(true); |
| e1.PutId(TestIdFactory::FromNumber(101)); |
| EXPECT_TRUE(e1.GetKernelCopy().is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle1)); |
| } |
| 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()); |
| |
| MutableEntry aguilera(&trans, GET_BY_HANDLE, handle1); |
| ASSERT_TRUE(aguilera.good()); |
| EXPECT_FALSE(aguilera.GetKernelCopy().is_dirty()); |
| EXPECT_EQ(aguilera.GetNonUniqueName(), "aguilera"); |
| aguilera.PutNonUniqueName("overwritten"); |
| EXPECT_TRUE(aguilera.GetKernelCopy().is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle1)); |
| } |
| ASSERT_TRUE(dir_->SaveChanges()); |
| |
| // Now do some operations when SaveChanges() will fail. |
| StartFailingSaveChanges(); |
| ASSERT_TRUE(dir_->good()); |
| |
| int64 handle2 = 0; |
| { |
| WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); |
| |
| MutableEntry aguilera(&trans, GET_BY_HANDLE, handle1); |
| ASSERT_TRUE(aguilera.good()); |
| EXPECT_FALSE(aguilera.GetKernelCopy().is_dirty()); |
| EXPECT_EQ(aguilera.GetNonUniqueName(), "overwritten"); |
| EXPECT_FALSE(aguilera.GetKernelCopy().is_dirty()); |
| EXPECT_FALSE(IsInDirtyMetahandles(handle1)); |
| aguilera.PutNonUniqueName("christina"); |
| EXPECT_TRUE(aguilera.GetKernelCopy().is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle1)); |
| |
| // New item. |
| MutableEntry kids_on_block( |
| &trans, CREATE, BOOKMARKS, trans.root_id(), "kids"); |
| ASSERT_TRUE(kids_on_block.good()); |
| handle2 = kids_on_block.GetMetahandle(); |
| kids_on_block.PutBaseVersion(1); |
| kids_on_block.PutIsDir(true); |
| kids_on_block.PutId(TestIdFactory::FromNumber(102)); |
| EXPECT_TRUE(kids_on_block.GetKernelCopy().is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle2)); |
| } |
| |
| // We are using an unsaveable directory, so this can't succeed. However, |
| // the HandleSaveChangesFailure code path should have been triggered. |
| 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()); |
| Entry e1(&trans, GET_BY_HANDLE, handle1); |
| ASSERT_TRUE(e1.good()); |
| EntryKernel aguilera = e1.GetKernelCopy(); |
| Entry kids(&trans, GET_BY_HANDLE, handle2); |
| ASSERT_TRUE(kids.good()); |
| EXPECT_TRUE(kids.GetKernelCopy().is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle2)); |
| EXPECT_TRUE(aguilera.is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle1)); |
| } |
| } |
| |
| TEST_F(OnDiskSyncableDirectoryTest, TestSaveChangesFailureWithPurge) { |
| int64 handle1 = 0; |
| // Set up an item using a regular, saveable directory. |
| { |
| WriteTransaction trans(FROM_HERE, UNITTEST, dir_.get()); |
| |
| MutableEntry e1(&trans, CREATE, BOOKMARKS, trans.root_id(), "aguilera"); |
| ASSERT_TRUE(e1.good()); |
| EXPECT_TRUE(e1.GetKernelCopy().is_dirty()); |
| handle1 = e1.GetMetahandle(); |
| e1.PutBaseVersion(1); |
| e1.PutIsDir(true); |
| e1.PutId(TestIdFactory::FromNumber(101)); |
| sync_pb::EntitySpecifics bookmark_specs; |
| AddDefaultFieldValue(BOOKMARKS, &bookmark_specs); |
| e1.PutSpecifics(bookmark_specs); |
| e1.PutServerSpecifics(bookmark_specs); |
| e1.PutId(TestIdFactory::FromNumber(101)); |
| EXPECT_TRUE(e1.GetKernelCopy().is_dirty()); |
| EXPECT_TRUE(IsInDirtyMetahandles(handle1)); |
| } |
| ASSERT_TRUE(dir_->SaveChanges()); |
| |
| // Now do some operations while SaveChanges() is set to fail. |
| StartFailingSaveChanges(); |
| ASSERT_TRUE(dir_->good()); |
| |
| ModelTypeSet set(BOOKMARKS); |
| dir_->PurgeEntriesWithTypeIn(set, ModelTypeSet(), ModelTypeSet()); |
| EXPECT_TRUE(IsInMetahandlesToPurge(handle1)); |
| 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() { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| } |
| |
| virtual void TearDown() { |
| } |
| protected: |
| base::MessageLoop message_loop_; |
| base::ScopedTempDir temp_dir_; |
| FakeEncryptor encryptor_; |
| TestUnrecoverableErrorHandler handler_; |
| NullDirectoryChangeDelegate delegate_; |
| }; |
| |
| TEST_F(SyncableDirectoryManagement, TestFileRelease) { |
| base::FilePath path = temp_dir_.path().Append( |
| Directory::kSyncDatabaseFilename); |
| |
| syncable::Directory dir(new OnDiskDirectoryBackingStore("ScopeTest", path), |
| &handler_, |
| NULL, |
| NULL, |
| NULL); |
| DirOpenResult result = |
| dir.Open("ScopeTest", &delegate_, NullTransactionObserver()); |
| ASSERT_EQ(result, OPENED); |
| dir.Close(); |
| |
| // Closing the directory should have released the backing database file. |
| ASSERT_TRUE(base::DeleteFile(path, true)); |
| } |
| |
| 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: |
| virtual void ThreadMain() OVERRIDE { |
| int entry_count = 0; |
| std::string path_name; |
| |
| for (int i = 0; i < 20; ++i) { |
| const int rand_action = rand() % 10; |
| if (rand_action < 4 && !path_name.empty()) { |
| ReadTransaction trans(FROM_HERE, dir_); |
| CHECK(1 == CountEntriesWithName(&trans, trans.root_id(), path_name)); |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds( |
| rand() % 10)); |
| } 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); |
| CHECK(e.good()); |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds( |
| rand() % 20)); |
| e.PutIsUnsynced(true); |
| if (e.PutId(TestIdFactory::FromNumber(rand())) && |
| e.GetId().ServerKnows() && !e.GetId().IsRoot()) { |
| e.PutBaseVersion(1); |
| } |
| } |
| } |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(StressTransactionsDelegate); |
| }; |
| |
| TEST(SyncableDirectory, StressTransactions) { |
| base::MessageLoop message_loop; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| FakeEncryptor encryptor; |
| TestUnrecoverableErrorHandler handler; |
| NullDirectoryChangeDelegate delegate; |
| std::string dirname = "stress"; |
| Directory dir(new InMemoryDirectoryBackingStore(dirname), |
| &handler, |
| NULL, |
| NULL, |
| NULL); |
| dir.Open(dirname, &delegate, NullTransactionObserver()); |
| |
| const int kThreadCount = 7; |
| base::PlatformThreadHandle threads[kThreadCount]; |
| scoped_ptr<StressTransactionsDelegate> thread_delegates[kThreadCount]; |
| |
| for (int i = 0; i < kThreadCount; ++i) { |
| thread_delegates[i].reset(new StressTransactionsDelegate(&dir, 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]); |
| } |
| |
| dir.Close(); |
| } |
| |
| class SyncableClientTagTest : public SyncableDirectoryTest { |
| public: |
| static const int kBaseVersion = 1; |
| const char* test_name_; |
| const char* test_tag_; |
| |
| SyncableClientTagTest() : test_name_("test_name"), test_tag_("dietcoke") {} |
| |
| bool CreateWithDefaultTag(Id id, bool deleted) { |
| WriteTransaction wtrans(FROM_HERE, UNITTEST, dir_.get()); |
| MutableEntry me(&wtrans, CREATE, PREFERENCES, |
| wtrans.root_id(), test_name_); |
| CHECK(me.good()); |
| me.PutId(id); |
| if (id.ServerKnows()) { |
| me.PutBaseVersion(kBaseVersion); |
| } |
| me.PutIsUnsynced(true); |
| me.PutIsDel(deleted); |
| me.PutIsDir(false); |
| return me.PutUniqueClientTag(test_tag_); |
| } |
| |
| // 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()); |
| Entry me(&trans, GET_BY_CLIENT_TAG, test_tag_); |
| CHECK(me.good()); |
| EXPECT_EQ(me.GetId(), id); |
| EXPECT_EQ(me.GetUniqueClientTag(), test_tag_); |
| EXPECT_EQ(me.GetIsDel(), deleted); |
| |
| // We only sync deleted items that the server knew about. |
| if (me.GetId().ServerKnows() || !me.GetIsDel()) { |
| EXPECT_EQ(me.GetIsUnsynced(), true); |
| } |
| } |
| |
| protected: |
| TestIdFactory factory_; |
| }; |
| |
| TEST_F(SyncableClientTagTest, TestClientTagClear) { |
| Id server_id = factory_.NewServerId(); |
| EXPECT_TRUE(CreateWithDefaultTag(server_id, false)); |
| { |
| 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()); |
| Entry by_tag(&trans, GET_BY_CLIENT_TAG, test_tag_); |
| EXPECT_FALSE(by_tag.good()); |
| |
| Entry by_id(&trans, GET_BY_ID, server_id); |
| EXPECT_TRUE(by_id.good()); |
| EXPECT_TRUE(by_id.GetUniqueClientTag().empty()); |
| } |
| } |
| |
| TEST_F(SyncableClientTagTest, TestClientTagIndexServerId) { |
| Id server_id = factory_.NewServerId(); |
| EXPECT_TRUE(CreateWithDefaultTag(server_id, false)); |
| VerifyTag(server_id, false); |
| } |
| |
| TEST_F(SyncableClientTagTest, TestClientTagIndexClientId) { |
| Id client_id = factory_.NewLocalId(); |
| EXPECT_TRUE(CreateWithDefaultTag(client_id, false)); |
| VerifyTag(client_id, false); |
| } |
| |
| TEST_F(SyncableClientTagTest, TestDeletedClientTagIndexClientId) { |
| Id client_id = factory_.NewLocalId(); |
| EXPECT_TRUE(CreateWithDefaultTag(client_id, true)); |
| VerifyTag(client_id, true); |
| } |
| |
| TEST_F(SyncableClientTagTest, TestDeletedClientTagIndexServerId) { |
| Id server_id = factory_.NewServerId(); |
| EXPECT_TRUE(CreateWithDefaultTag(server_id, true)); |
| VerifyTag(server_id, true); |
| } |
| |
| TEST_F(SyncableClientTagTest, TestClientTagIndexDuplicateServer) { |
| EXPECT_TRUE(CreateWithDefaultTag(factory_.NewServerId(), true)); |
| EXPECT_FALSE(CreateWithDefaultTag(factory_.NewServerId(), true)); |
| EXPECT_FALSE(CreateWithDefaultTag(factory_.NewServerId(), false)); |
| EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), false)); |
| EXPECT_FALSE(CreateWithDefaultTag(factory_.NewLocalId(), true)); |
| } |
| |
| } // namespace |
| } // namespace syncable |
| } // namespace syncer |