// 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 #include #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/files/file_util.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/synchronization/condition_variable.h" #include "base/test/values_test_util.h" #include "base/threading/platform_thread.h" #include "base/values.h" #include "build/build_config.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/directory_unittest.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; // An OnDiskDirectoryBackingStore that can be set to always fail SaveChanges. class TestBackingStore : public OnDiskDirectoryBackingStore { public: TestBackingStore(const std::string& dir_name, const base::FilePath& backing_filepath); ~TestBackingStore() override; 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, const WeakHandle& handler, const std::string& dir_name, const base::FilePath& backing_filepath); ~TestDirectory() override; void StartFailingSaveChanges() { backing_store_->StartFailingSaveChanges(); } private: TestDirectory(Encryptor* encryptor, const WeakHandle& handler, TestBackingStore* backing_store); TestBackingStore* backing_store_; }; TestDirectory* TestDirectory::Create( Encryptor* encryptor, const WeakHandle& 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, const WeakHandle& handler, TestBackingStore* backing_store) : Directory(backing_store, handler, base::Closure(), NULL, NULL), backing_store_(backing_store) {} TestDirectory::~TestDirectory() { } // crbug.com/144422 #if defined(OS_ANDROID) #define MAYBE_FailInitialWrite DISABLED_FailInitialWrite #else #define MAYBE_FailInitialWrite FailInitialWrite #endif TEST(OnDiskSyncableDirectory, MAYBE_FailInitialWrite) { base::MessageLoop message_loop; 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 test_dir(TestDirectory::Create( &encryptor, MakeWeakHandle(handler.GetWeakPtr()), 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. void SetUp() override { SyncableDirectoryTest::SetUp(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); file_path_ = temp_dir_.path().Append( FILE_PATH_LITERAL("Test.sqlite3")); base::DeleteFile(file_path_, false); CreateDirectory(); } void TearDown() override { // This also closes file handles. dir()->SaveChanges(); dir().reset(); base::DeleteFile(file_path_, false); SyncableDirectoryTest::TearDown(); } // Creates a new directory. Deletes the old directory, if it exists. void CreateDirectory() { test_directory_ = TestDirectory::Create( encryptor(), MakeWeakHandle(unrecoverable_error_handler()->GetWeakPtr()), kDirectoryName, file_path_); dir().reset(test_directory_); ASSERT_TRUE(dir().get()); ASSERT_EQ(OPENED, dir()->Open(kDirectoryName, directory_change_delegate(), NullTransactionObserver())); ASSERT_TRUE(dir()->good()); } void SaveAndReloadDir() { dir()->SaveChanges(); CreateDirectory(); } void StartFailingSaveChanges() { test_directory_->StartFailingSaveChanges(); } TestDirectory* test_directory_; // mirrors scoped_ptr dir_ base::ScopedTempDir temp_dir_; base::FilePath file_path_; }; sync_pb::DataTypeContext BuildContext(ModelType type) { sync_pb::DataTypeContext context; context.set_context("context"); context.set_data_type_id(GetSpecificsFieldNumberFromModelType(type)); return context; } 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); dir()->SetDownloadProgress(BOOKMARKS, BuildProgress(BOOKMARKS)); dir()->SetDownloadProgress(PREFERENCES, BuildProgress(PREFERENCES)); dir()->SetDownloadProgress(AUTOFILL, BuildProgress(AUTOFILL)); TestIdFactory id_factory; // Create some items for each type. { WriteTransaction trans(FROM_HERE, UNITTEST, dir().get()); dir()->SetDataTypeContext(&trans, BOOKMARKS, BuildContext(BOOKMARKS)); dir()->SetDataTypeContext(&trans, PREFERENCES, BuildContext(PREFERENCES)); dir()->SetDataTypeContext(&trans, AUTOFILL, BuildContext(AUTOFILL)); // Make it look like these types have completed initial sync. CreateTypeRoot(&trans, dir().get(), BOOKMARKS); CreateTypeRoot(&trans, dir().get(), PREFERENCES); CreateTypeRoot(&trans, dir().get(), AUTOFILL); // 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.PutServerSpecifics(specifics); create_pre_save = create.GetKernelCopy(); update_pre_save = update.GetKernelCopy(); create_id = create.GetId(); } dir()->SaveChanges(); dir().reset( new Directory(new OnDiskDirectoryBackingStore(kDirectoryName, file_path_), MakeWeakHandle(unrecoverable_error_handler()->GetWeakPtr()), base::Closure(), NULL, NULL)); ASSERT_TRUE(dir().get()); ASSERT_EQ(OPENED, dir()->Open(kDirectoryName, directory_change_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_t field #" << i << " changed during save/load"; EXPECT_EQ(update_pre_save.ref((Int64Field)i), update_post_save.ref((Int64Field)i)) << "int64_t 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_t 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_t 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_t handle1 = 0; // Set up an item and progress marker using a regular, saveable directory. dir()->SetDownloadProgress(BOOKMARKS, BuildProgress(BOOKMARKS)); { 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)); } class SyncableDirectoryManagement : public testing::Test { public: void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } void TearDown() override {} 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); { Directory dir(new OnDiskDirectoryBackingStore("ScopeTest", path), MakeWeakHandle(handler_.GetWeakPtr()), base::Closure(), NULL, NULL); DirOpenResult result = dir.Open("ScopeTest", &delegate_, NullTransactionObserver()); ASSERT_EQ(result, OPENED); } // Destroying the directory should have released the backing database file. ASSERT_TRUE(base::DeleteFile(path, true)); } 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 syncable } // namespace syncer