| // 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/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 "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); |
| |
| 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) { |
| 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<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() { |
| SyncableDirectoryTest::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); |
| SyncableDirectoryTest::TearDown(); |
| } |
| |
| // Creates a new directory. Deletes the old directory, if it exists. |
| void CreateDirectory() { |
| test_directory_ = TestDirectory::Create( |
| encryptor(), unrecoverable_error_handler(), kDirectoryName, file_path_); |
| dir().reset(test_directory_); |
| ASSERT_TRUE(dir().get()); |
| ASSERT_EQ(OPENED, |
| dir()->Open(kDirectoryName, |
| directory_change_delegate(), |
| NullTransactionObserver())); |
| ASSERT_TRUE(dir()->good()); |
| } |
| |
| void SaveAndReloadDir() { |
| dir()->SaveChanges(); |
| CreateDirectory(); |
| } |
| |
| void StartFailingSaveChanges() { |
| test_directory_->StartFailingSaveChanges(); |
| } |
| |
| TestDirectory *test_directory_; // mirrors scoped_ptr<Directory> 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_), |
| unrecoverable_error_handler(), |
| NULL, |
| 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 field #" << i << " changed during save/load"; |
| EXPECT_EQ(update_pre_save.ref((Int64Field)i), |
| 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 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: |
| 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); |
| |
| 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 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 |