Chromium Code Reviews| Index: chrome/browser/sync/engine/syncer_unittest.cc |
| =================================================================== |
| --- chrome/browser/sync/engine/syncer_unittest.cc (revision 0) |
| +++ chrome/browser/sync/engine/syncer_unittest.cc (revision 0) |
| @@ -0,0 +1,4588 @@ |
| +// Copyright (c) 2009 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 entry. |
| +// |
| +// Syncer unit tests. Unfortunately a lot of these tests |
| +// are outdated and need to be reworked and updated. |
| + |
| +#include <list> |
| +#include <map> |
| +#include <set> |
| +#include <strstream> |
| + |
| +#include "base/at_exit.h" |
| + |
| +#include "base/scoped_ptr.h" |
| +#include "chrome/browser/sync/engine/client_command_channel.h" |
| +#include "chrome/browser/sync/engine/conflict_resolution_view.h" |
| +#include "chrome/browser/sync/engine/conflict_resolver.h" |
| +#include "chrome/browser/sync/engine/get_commit_ids_command.h" |
| +#include "chrome/browser/sync/engine/model_safe_worker.h" |
| +#include "chrome/browser/sync/engine/net/server_connection_manager.h" |
| +#include "chrome/browser/sync/engine/process_updates_command.h" |
| +#include "chrome/browser/sync/engine/syncer.h" |
| +#include "chrome/browser/sync/engine/syncer_util.h" |
|
idana
2009/09/10 05:44:37
syncer_util.h should be moved after syncer_session
|
| +#include "chrome/browser/sync/engine/syncer_proto_util.h" |
| +#include "chrome/browser/sync/engine/syncer_session.h" |
| +#include "chrome/browser/sync/protocol/sync.pb.h" |
| +#include "chrome/browser/sync/syncable/directory_manager.h" |
| +#include "chrome/browser/sync/syncable/syncable.h" |
| +#include "chrome/browser/sync/util/character_set_converters.h" |
| +#include "chrome/browser/sync/util/compat-file.h" |
| +#include "chrome/browser/sync/util/event_sys-inl.h" |
| +#include "chrome/test/sync/engine/mock_server_connection.h" |
| +#include "chrome/test/sync/engine/test_directory_setter_upper.h" |
| +#include "chrome/test/sync/engine/test_id_factory.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +using std::map; |
| +using std::multimap; |
| +using std::set; |
| +using std::string; |
| + |
| +namespace browser_sync { |
| + |
| +using syncable::BaseTransaction; |
| +using syncable::Blob; |
| +using syncable::Directory; |
| +using syncable::Entry; |
| +using syncable::ExtendedAttribute; |
| +using syncable::ExtendedAttributeKey; |
| +using syncable::Id; |
| +using syncable::MutableEntry; |
| +using syncable::MutableExtendedAttribute; |
| +using syncable::ReadTransaction; |
| +using syncable::ScopedDirLookup; |
| +using syncable::WriteTransaction; |
| + |
| +using syncable::BASE_VERSION; |
| +using syncable::CREATE; |
| +using syncable::CREATE_NEW_UPDATE_ITEM; |
| +using syncable::GET_BY_HANDLE; |
| +using syncable::GET_BY_ID; |
| +using syncable::GET_BY_PARENTID_AND_NAME; |
| +using syncable::GET_BY_PATH; |
| +using syncable::GET_BY_TAG; |
| +using syncable::ID; |
| +using syncable::IS_BOOKMARK_OBJECT; |
| +using syncable::IS_DEL; |
| +using syncable::IS_DIR; |
| +using syncable::IS_UNAPPLIED_UPDATE; |
| +using syncable::IS_UNSYNCED; |
| +using syncable::META_HANDLE; |
| +using syncable::MTIME; |
| +using syncable::NAME; |
| +using syncable::NEXT_ID; |
| +using syncable::PARENT_ID; |
| +using syncable::PREV_ID; |
| +using syncable::SERVER_IS_DEL; |
| +using syncable::SERVER_NAME; |
| +using syncable::SERVER_PARENT_ID; |
| +using syncable::SERVER_POSITION_IN_PARENT; |
| +using syncable::SERVER_VERSION; |
| +using syncable::SINGLETON_TAG; |
| +using syncable::UNITTEST; |
| +using syncable::UNSANITIZED_NAME; |
| + |
| +namespace { |
| +const char* kTestData = "Hello World!"; |
| +const int kTestDataLen = 12; |
| +const int64 kTestLogRequestTimestamp = 123456; |
| +} // namespace |
| + |
|
idana
2009/09/10 05:44:37
Extra blank line.
|
| + |
| +class SyncerTest : public testing::Test { |
| + protected: |
| + SyncerTest() : client_command_channel_(0) { |
| + } |
| + |
| + void HandleClientCommand(const sync_pb::ClientCommand* event) { |
| + last_client_command_ = *event; |
| + } |
| + |
| + void HandleSyncerEvent(SyncerEvent event) { |
| + LOG(INFO) << "HandleSyncerEvent in unittest " << event.what_happened; |
| + // we only test for entry-specific events, not status changed ones. |
| + switch (event.what_happened) { |
| + case SyncerEvent::STATUS_CHANGED: |
| + // fall through |
| + case SyncerEvent::SYNC_CYCLE_ENDED: |
| + // fall through |
| + case SyncerEvent::COMMITS_SUCCEEDED: |
| + return; |
| + case SyncerEvent::SHUTDOWN_USE_WITH_CARE: |
| + case SyncerEvent::OVER_QUOTA: |
| + case SyncerEvent::REQUEST_SYNC_NUDGE: |
| + LOG(INFO) << "Handling event type " << event.what_happened; |
| + break; |
| + default: |
| + CHECK(false) << "Handling unknown error type in unit tests!!"; |
| + } |
| + syncer_events_.insert(event); |
| + } |
| + |
| + void LoopSyncShare(Syncer* syncer) { |
| + SyncProcessState state(syncdb_.manager(), syncdb_.name(), |
| + mock_server_.get(), |
| + syncer->conflict_resolver(), |
| + syncer->channel(), |
| + syncer->model_safe_worker()); |
| + bool should_loop = false; |
| + int loop_iterations = 0; |
| + do { |
| + ASSERT_LT(++loop_iterations, 100) << "infinite loop detected. please fix"; |
| + should_loop = syncer->SyncShare(&state); |
| + } while (should_loop); |
| + } |
| + |
| + virtual void SetUp() { |
| + syncdb_.SetUp(); |
| + |
| + mock_server_.reset( |
| + new MockConnectionManager(syncdb_.manager(), syncdb_.name())); |
| + model_safe_worker_.reset(new ModelSafeWorker()); |
| + // Safe to pass NULL as Authwatcher for now since the code path that |
| + // uses it is not unittested yet. |
| + syncer_ = new Syncer(syncdb_.manager(), syncdb_.name(), |
| + mock_server_.get(), |
| + model_safe_worker_.get()); |
| + CHECK(syncer_->channel()); |
| + |
| + hookup_.reset(NewEventListenerHookup(syncer_->channel(), this, |
| + &SyncerTest::HandleSyncerEvent)); |
| + |
| + command_channel_hookup_.reset(NewEventListenerHookup( |
| + &client_command_channel_, this, &SyncerTest::HandleClientCommand)); |
| + syncer_->set_command_channel(&client_command_channel_); |
| + |
| + state_.reset(new SyncProcessState(syncdb_.manager(), syncdb_.name(), |
| + mock_server_.get(), |
| + syncer_->conflict_resolver(), |
| + syncer_->channel(), |
| + syncer_->model_safe_worker())); |
| + |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + syncable::Directory::ChildHandles children; |
| + dir->GetChildHandles(&trans, trans.root_id(), &children); |
| + ASSERT_EQ(0, children.size()); |
| + syncer_events_.clear(); |
| + root_id_ = ids_.root(); |
| + parent_id_ = ids_.MakeServer("parent id"); |
| + child_id_ = ids_.MakeServer("child id"); |
| + } |
| + |
| + virtual void TearDown() { |
| + mock_server_.reset(); |
| + hookup_.reset(); |
| + command_channel_hookup_.reset(); |
| + delete syncer_; |
| + syncdb_.TearDown(); |
| + } |
| + void WriteTestDataToEntry(WriteTransaction* trans, MutableEntry* entry) { |
| + EXPECT_FALSE(entry->Get(IS_DIR)); |
| + EXPECT_FALSE(entry->Get(IS_DEL)); |
| + Blob test_value(kTestData, kTestData + kTestDataLen); |
| + ExtendedAttributeKey key(entry->Get(META_HANDLE), PSTR("DATA")); |
| + MutableExtendedAttribute attr(trans, CREATE, key); |
| + attr.mutable_value()->swap(test_value); |
| + entry->Put(syncable::IS_UNSYNCED, true); |
| + } |
| + void VerifyTestDataInEntry(BaseTransaction* trans, Entry* entry) { |
| + EXPECT_FALSE(entry->Get(IS_DIR)); |
| + EXPECT_FALSE(entry->Get(IS_DEL)); |
| + Blob test_value(kTestData, kTestData + kTestDataLen); |
| + ExtendedAttributeKey key(entry->Get(META_HANDLE), PSTR("DATA")); |
| + ExtendedAttribute attr(trans, GET_BY_HANDLE, key); |
| + EXPECT_FALSE(attr.is_deleted()); |
| + EXPECT_EQ(test_value, attr.value()); |
| + } |
| + bool SyncerStuck(SyncProcessState *state) { |
| + SyncerStatus status(NULL, state); |
| + return status.syncer_stuck(); |
| + } |
| + void SyncRepeatedlyToTriggerConflictResolution(SyncProcessState *state) { |
| + // We should trigger after less than 6 syncs, but we want to avoid brittle |
| + // tests. |
| + for (int i = 0 ; i < 6 ; ++i) |
| + syncer_->SyncShare(state); |
| + } |
| + void SyncRepeatedlyToTriggerStuckSignal(SyncProcessState *state) { |
| + // We should trigger after less than 10 syncs, but we want to avoid brittle |
| + // tests. |
| + for (int i = 0 ; i < 12 ; ++i) |
| + syncer_->SyncShare(state); |
| + } |
| + |
| + // Enumeration of alterations to entries for commit ordering tests. |
| + enum EntryFeature { |
| + LIST_END = 0, // Denotes the end of the list of features from below. |
| + SYNCED, // Items are unsynced by default |
| + DELETED, |
| + OLD_MTIME, |
| + MOVED_FROM_ROOT, |
| + }; |
| + |
| + struct CommitOrderingTest { |
| + // expected commit index. |
| + int commit_index; |
| + // Details about the item |
| + syncable::Id id; |
| + syncable::Id parent_id; |
| + EntryFeature features[10]; |
| + |
| + static const CommitOrderingTest LAST_COMMIT_ITEM; |
| + }; |
| + |
| + void RunCommitOrderingTest(CommitOrderingTest* test) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + map<int, syncable::Id> expected_positions; |
| + { // Transaction scope. |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + while (!test->id.IsRoot()) { |
| + if (test->commit_index >= 0) { |
| + map<int, syncable::Id>::value_type entry(test->commit_index, |
| + test->id); |
| + bool double_position = !expected_positions.insert(entry).second; |
| + ASSERT_FALSE(double_position) << "Two id's expected at one position"; |
| + } |
| + string utf8_name = test->id.GetServerId(); |
| + PathString name(utf8_name.begin(), utf8_name.end()); |
| + MutableEntry entry(&trans, CREATE, test->parent_id, name); |
| + entry.Put(syncable::ID, test->id); |
| + if (test->id.ServerKnows()) { |
| + entry.Put(BASE_VERSION, 5); |
| + entry.Put(SERVER_VERSION, 5); |
| + entry.Put(SERVER_PARENT_ID, test->parent_id); |
| + } |
| + entry.Put(syncable::IS_DIR, true); |
| + entry.Put(syncable::IS_UNSYNCED, true); |
| + // Set the time to 30 seconds in the future to reduce the chance of |
| + // flaky tests. |
| + int64 now_server_time = ClientTimeToServerTime(syncable::Now()); |
| + int64 now_plus_30s = ServerTimeToClientTime(now_server_time + 30000); |
| + int64 now_minus_2h = ServerTimeToClientTime(now_server_time - 7200000); |
| + entry.Put(syncable::MTIME, now_plus_30s); |
| + for (int i = 0 ; i < ARRAYSIZE(test->features) ; ++i) { |
| + switch (test->features[i]) { |
| + case LIST_END: |
| + break; |
| + case SYNCED: |
| + entry.Put(syncable::IS_UNSYNCED, false); |
| + break; |
| + case DELETED: |
| + entry.Put(syncable::IS_DEL, true); |
| + break; |
| + case OLD_MTIME: |
| + entry.Put(MTIME, now_minus_2h); |
| + break; |
| + case MOVED_FROM_ROOT: |
| + entry.Put(SERVER_PARENT_ID, trans.root_id()); |
| + break; |
| + default: |
| + FAIL() << "Bad value in CommitOrderingTest list"; |
| + } |
| + } |
| + test++; |
| + } |
| + } |
| + LoopSyncShare(syncer_); |
| + ASSERT_EQ(expected_positions.size(), mock_server_->committed_ids().size()); |
| + // If this test starts failing, be aware other sort orders could be valid. |
| + for (size_t i = 0; i < expected_positions.size(); ++i) { |
| + EXPECT_EQ(1, expected_positions.count(i)); |
| + EXPECT_EQ(expected_positions[i], mock_server_->committed_ids()[i]); |
| + } |
| + } |
| + |
| + void DoTruncationTest(const ScopedDirLookup& dir, |
| + const vector<int64>& unsynced_handle_view, |
| + const vector<syncable::Id>& expected_id_order) { |
| + // The expected order is "x", "b", "c", "e", truncated appropriately. |
| + for (size_t limit = expected_id_order.size() + 2; limit > 0; --limit) { |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + SyncerSession::ScopedSetWriteTransaction set_trans(&session, &wtrans); |
| + session.set_unsynced_handles(unsynced_handle_view); |
| + |
| + GetCommitIdsCommand command(limit); |
| + command.BuildCommitIds(&session); |
| + vector<syncable::Id> output = command.ordered_commit_set_.GetCommitIds(); |
| + int truncated_size = std::min(limit, expected_id_order.size()); |
| + ASSERT_EQ(truncated_size, output.size()); |
| + for (int i = 0; i < truncated_size; ++i) { |
| + ASSERT_EQ(expected_id_order[i], output[i]) |
| + << "At index " << i << " with batch size limited to " << limit; |
| + } |
| + } |
| + } |
| + |
| + int64 CreateUnsyncedDirectory(const PathString& entry_name, |
| + const string& idstring) { |
| + return CreateUnsyncedDirectory(entry_name, |
| + syncable::Id::CreateFromServerId(idstring)); |
| + } |
| + |
| + int64 CreateUnsyncedDirectory(const PathString& entry_name, |
| + const syncable::Id& id) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&wtrans, syncable::CREATE, wtrans.root_id(), |
| + entry_name); |
| + EXPECT_TRUE(entry.good()); |
| + entry.Put(syncable::IS_UNSYNCED, true); |
| + entry.Put(syncable::IS_DIR, true); |
| + entry.Put(syncable::BASE_VERSION, id.ServerKnows() ? 1 : 0); |
| + entry.Put(syncable::ID, id); |
| + return entry.Get(META_HANDLE); |
| + } |
| + |
| + // Some ids to aid tests. Only the root one's value is specific. The rest |
| + // are named for test clarity. |
| + syncable::Id root_id_; |
| + syncable::Id parent_id_; |
| + syncable::Id child_id_; |
| + |
| + TestIdFactory ids_; |
| + |
| + TestDirectorySetterUpper syncdb_; |
| + scoped_ptr<MockConnectionManager> mock_server_; |
| + scoped_ptr<EventListenerHookup> hookup_; |
| + scoped_ptr<EventListenerHookup> command_channel_hookup_; |
| + ClientCommandChannel client_command_channel_; |
| + |
| + Syncer* syncer_; |
| + scoped_ptr<SyncProcessState> state_; |
| + scoped_ptr<ModelSafeWorker> model_safe_worker_; |
| + std::set<SyncerEvent> syncer_events_; |
| + sync_pb::ClientCommand last_client_command_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SyncerTest); |
| +}; |
| + |
| +TEST_F(SyncerTest, TestCallGatherUnsyncedEntries) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + { |
| + Syncer::UnsyncedMetaHandles handles; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles); |
| + } |
| + ASSERT_EQ(0, handles.size()); |
| + } |
| + // TODO(sync): When we can dynamically connect and disconnect the mock |
| + // ServerConnectionManager test disconnected GetUnsyncedEntries here. It's a |
| + // regression for a very old bug. |
| +} |
| + |
| +TEST_F(SyncerTest, GetCommitIdsCommandTruncates) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + int64 handle_c = CreateUnsyncedDirectory(PSTR("C"), ids_.MakeLocal("c")); |
| + int64 handle_x = CreateUnsyncedDirectory(PSTR("X"), ids_.MakeLocal("x")); |
| + int64 handle_b = CreateUnsyncedDirectory(PSTR("B"), ids_.MakeLocal("b")); |
| + int64 handle_d = CreateUnsyncedDirectory(PSTR("D"), ids_.MakeLocal("d")); |
| + int64 handle_e = CreateUnsyncedDirectory(PSTR("E"), ids_.MakeLocal("e")); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry_x(&wtrans, GET_BY_HANDLE, handle_x); |
| + MutableEntry entry_b(&wtrans, GET_BY_HANDLE, handle_b); |
| + MutableEntry entry_c(&wtrans, GET_BY_HANDLE, handle_c); |
| + MutableEntry entry_d(&wtrans, GET_BY_HANDLE, handle_d); |
| + MutableEntry entry_e(&wtrans, GET_BY_HANDLE, handle_e); |
| + entry_x.Put(IS_BOOKMARK_OBJECT, true); |
| + entry_b.Put(IS_BOOKMARK_OBJECT, true); |
| + entry_c.Put(IS_BOOKMARK_OBJECT, true); |
| + entry_d.Put(IS_BOOKMARK_OBJECT, true); |
| + entry_e.Put(IS_BOOKMARK_OBJECT, true); |
| + entry_b.Put(PARENT_ID, entry_x.Get(ID)); |
| + entry_c.Put(PARENT_ID, entry_x.Get(ID)); |
| + entry_c.PutPredecessor(entry_b.Get(ID)); |
| + entry_d.Put(PARENT_ID, entry_b.Get(ID)); |
| + entry_e.Put(PARENT_ID, entry_c.Get(ID)); |
| + } |
| + |
| + // The arrangement is now: x (b (d) c (e)). |
| + vector<int64> unsynced_handle_view; |
| + vector<syncable::Id> expected_order; |
| + // The expected order is "x", "b", "c", "e", truncated appropriately. |
| + unsynced_handle_view.push_back(handle_e); |
| + expected_order.push_back(ids_.MakeLocal("x")); |
| + expected_order.push_back(ids_.MakeLocal("b")); |
| + expected_order.push_back(ids_.MakeLocal("c")); |
| + expected_order.push_back(ids_.MakeLocal("e")); |
| + DoTruncationTest(dir, unsynced_handle_view, expected_order); |
| +} |
| + |
| +// TODO(chron): More corner case unit tests around validation |
| +TEST_F(SyncerTest, TestCommitMetahandleIterator) { |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + SyncerSession::ScopedSetWriteTransaction set_trans(&session, &wtrans); |
| + |
| + GetCommitIdsCommand::OrderedCommitSet commit_set; |
| + GetCommitIdsCommand::CommitMetahandleIterator iterator(&session, |
| + &commit_set); |
| + EXPECT_FALSE(iterator.Valid()); |
| + EXPECT_FALSE(iterator.Increment()); |
| + } |
| + |
| + { |
| + vector<int64> session_metahandles; |
| + session_metahandles.push_back( |
| + CreateUnsyncedDirectory(PSTR("test1"), "testid1")); |
| + session_metahandles.push_back( |
| + CreateUnsyncedDirectory(PSTR("test2"), "testid2")); |
| + session_metahandles.push_back( |
| + CreateUnsyncedDirectory(PSTR("test3"), "testid3")); |
| + session.set_unsynced_handles(session_metahandles); |
| + |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + SyncerSession::ScopedSetWriteTransaction set_trans(&session, &wtrans); |
| + GetCommitIdsCommand::OrderedCommitSet commit_set; |
| + GetCommitIdsCommand::CommitMetahandleIterator iterator(&session, |
| + &commit_set); |
| + |
| + EXPECT_TRUE(iterator.Valid()); |
| + EXPECT_EQ(iterator.Current(), session_metahandles[0]); |
| + EXPECT_TRUE(iterator.Increment()); |
| + |
| + EXPECT_TRUE(iterator.Valid()); |
| + EXPECT_EQ(iterator.Current(), session_metahandles[1]); |
| + EXPECT_TRUE(iterator.Increment()); |
| + |
| + EXPECT_TRUE(iterator.Valid()); |
| + EXPECT_EQ(iterator.Current(), session_metahandles[2]); |
| + EXPECT_FALSE(iterator.Increment()); |
| + |
| + EXPECT_FALSE(iterator.Valid()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestGetUnsyncedAndSimpleCommit) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + PathString xattr_key = PSTR("key"); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), |
| + PSTR("Pete")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + parent.Put(syncable::ID, parent_id_); |
| + MutableEntry child(&wtrans, syncable::CREATE, parent_id_, PSTR("Pete")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::ID, child_id_); |
| + child.Put(syncable::BASE_VERSION, 1); |
| + WriteTestDataToEntry(&wtrans, &child); |
| + } |
| + |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + |
| + syncer_->SyncShare(&session); |
| + EXPECT_EQ(2, session.unsynced_count()); |
| + ASSERT_EQ(2, mock_server_->committed_ids().size()); |
| + // If this test starts failing, be aware other sort orders could be valid. |
| + EXPECT_EQ(parent_id_, mock_server_->committed_ids()[0]); |
| + EXPECT_EQ(child_id_, mock_server_->committed_ids()[1]); |
| + { |
| + ReadTransaction rt(dir, __FILE__, __LINE__); |
| + Entry entry(&rt, syncable::GET_BY_ID, child_id_); |
| + ASSERT_TRUE(entry.good()); |
| + VerifyTestDataInEntry(&rt, &entry); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingTwoItemsTall) { |
| + CommitOrderingTest items[] = { |
| + {1, ids_.FromNumber(-1001), ids_.FromNumber(-1000)}, |
| + {0, ids_.FromNumber(-1000), ids_.FromNumber(0)}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingThreeItemsTall) { |
| + CommitOrderingTest items[] = { |
| + {1, ids_.FromNumber(-2001), ids_.FromNumber(-2000)}, |
| + {0, ids_.FromNumber(-2000), ids_.FromNumber(0)}, |
| + {2, ids_.FromNumber(-2002), ids_.FromNumber(-2001)}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingThreeItemsTallLimitedSize) { |
| + syncer_->set_max_commit_batch_size(2); |
| + CommitOrderingTest items[] = { |
| + {1, ids_.FromNumber(-2001), ids_.FromNumber(-2000)}, |
| + {0, ids_.FromNumber(-2000), ids_.FromNumber(0)}, |
| + {2, ids_.FromNumber(-2002), ids_.FromNumber(-2001)}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingSingleDeletedItem) { |
| + CommitOrderingTest items[] = { |
| + {0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingSingleUncommittedDeletedItem) { |
| + CommitOrderingTest items[] = { |
| + {-1, ids_.FromNumber(-1000), ids_.FromNumber(0), {DELETED}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingSingleDeletedItemWithUnroll) { |
| + CommitOrderingTest items[] = { |
| + {0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, |
| + TestCommitListOrderingSingleLongDeletedItemWithUnroll) { |
| + CommitOrderingTest items[] = { |
| + {0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingTwoLongDeletedItemWithUnroll) { |
| + CommitOrderingTest items[] = { |
| + {0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + {-1, ids_.FromNumber(1001), ids_.FromNumber(1000), {DELETED, OLD_MTIME}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrdering3LongDeletedItemsWithSizeLimit) { |
| + syncer_->set_max_commit_batch_size(2); |
| + CommitOrderingTest items[] = { |
| + {0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + {1, ids_.FromNumber(1001), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + {2, ids_.FromNumber(1002), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingTwoDeletedItemsWithUnroll) { |
| + CommitOrderingTest items[] = { |
| + {0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED}}, |
| + {-1, ids_.FromNumber(1001), ids_.FromNumber(1000), {DELETED}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingComplexDeletionScenario) { |
| + CommitOrderingTest items[] = { |
| + { 0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + {-1, ids_.FromNumber(1001), ids_.FromNumber(0), {SYNCED}}, |
| + {1, ids_.FromNumber(1002), ids_.FromNumber(1001), {DELETED, OLD_MTIME}}, |
| + {-1, ids_.FromNumber(1003), ids_.FromNumber(1001), {SYNCED}}, |
| + {2, ids_.FromNumber(1004), ids_.FromNumber(1003), {DELETED}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, |
| + TestCommitListOrderingComplexDeletionScenarioWith2RecentDeletes) { |
| + CommitOrderingTest items[] = { |
| + { 0, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + {-1, ids_.FromNumber(1001), ids_.FromNumber(0), {SYNCED}}, |
| + {1, ids_.FromNumber(1002), ids_.FromNumber(1001), {DELETED, OLD_MTIME}}, |
| + {-1, ids_.FromNumber(1003), ids_.FromNumber(1001), {SYNCED}}, |
| + {2, ids_.FromNumber(1004), ids_.FromNumber(1003), {DELETED}}, |
| + {3, ids_.FromNumber(1005), ids_.FromNumber(1003), {DELETED}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingDeleteMovedItems) { |
| + CommitOrderingTest items[] = { |
| + {1, ids_.FromNumber(1000), ids_.FromNumber(0), {DELETED, OLD_MTIME}}, |
| + {0, ids_.FromNumber(1001), ids_.FromNumber(1000), {DELETED, OLD_MTIME, |
| + MOVED_FROM_ROOT}}, |
| + CommitOrderingTest::LAST_COMMIT_ITEM, |
| + }; |
| + RunCommitOrderingTest(items); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingWithNesting) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + int64 now_server_time = ClientTimeToServerTime(syncable::Now()); |
| + int64 now_minus_2h = ServerTimeToClientTime(now_server_time - 7200000); |
| + |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + { |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), |
| + PSTR("Bob")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, ids_.FromNumber(100)); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + MutableEntry child(&wtrans, syncable::CREATE, ids_.FromNumber(100), |
| + PSTR("Bob")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::ID, ids_.FromNumber(101)); |
| + child.Put(syncable::BASE_VERSION, 1); |
| + MutableEntry grandchild(&wtrans, syncable::CREATE, ids_.FromNumber(101), |
| + PSTR("Bob")); |
| + ASSERT_TRUE(grandchild.good()); |
| + grandchild.Put(syncable::ID, ids_.FromNumber(102)); |
| + grandchild.Put(syncable::IS_UNSYNCED, true); |
| + grandchild.Put(syncable::BASE_VERSION, 1); |
| + } |
| + { |
| + // Create three deleted items which deletions we expect to |
| + // be sent to the server. |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), |
| + PSTR("Pete")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::IS_DEL, true); |
| + parent.Put(syncable::ID, ids_.FromNumber(103)); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + parent.Put(syncable::MTIME, now_minus_2h); |
| + MutableEntry child(&wtrans, syncable::CREATE, ids_.FromNumber(103), |
| + PSTR("Pete")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::IS_DEL, true); |
| + child.Put(syncable::ID, ids_.FromNumber(104)); |
| + child.Put(syncable::BASE_VERSION, 1); |
| + child.Put(syncable::MTIME, now_minus_2h); |
| + MutableEntry grandchild(&wtrans, syncable::CREATE, ids_.FromNumber(104), |
| + PSTR("Pete")); |
| + ASSERT_TRUE(grandchild.good()); |
| + grandchild.Put(syncable::IS_UNSYNCED, true); |
| + grandchild.Put(syncable::ID, ids_.FromNumber(105)); |
| + grandchild.Put(syncable::IS_DEL, true); |
| + grandchild.Put(syncable::IS_DIR, false); |
| + grandchild.Put(syncable::BASE_VERSION, 1); |
| + grandchild.Put(syncable::MTIME, now_minus_2h); |
| + } |
| + } |
| + |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + syncer_->SyncShare(&session); |
| + EXPECT_EQ(6, session.unsynced_count()); |
| + ASSERT_EQ(6, mock_server_->committed_ids().size()); |
| + // This test will NOT unroll deletes because SERVER_PARENT_ID is not set. |
| + // It will treat these like moves. |
| + vector<syncable::Id> commit_ids(mock_server_->committed_ids()); |
| + EXPECT_EQ(ids_.FromNumber(100), commit_ids[0]); |
| + EXPECT_EQ(ids_.FromNumber(101), commit_ids[1]); |
| + EXPECT_EQ(ids_.FromNumber(102), commit_ids[2]); |
| + // We don't guarantee the delete orders in this test, only that they occur |
| + // at the end. |
| + std::sort(commit_ids.begin() + 3, commit_ids.end()); |
| + EXPECT_EQ(ids_.FromNumber(103), commit_ids[3]); |
| + EXPECT_EQ(ids_.FromNumber(104), commit_ids[4]); |
| + EXPECT_EQ(ids_.FromNumber(105), commit_ids[5]); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingWithNewItems) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), PSTR("1")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, parent_id_); |
| + MutableEntry child(&wtrans, syncable::CREATE, wtrans.root_id(), PSTR("2")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::ID, child_id_); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + child.Put(syncable::BASE_VERSION, 1); |
| + } |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, parent_id_, PSTR("A")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, ids_.FromNumber(102)); |
| + MutableEntry child(&wtrans, syncable::CREATE, parent_id_, PSTR("B")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::ID, ids_.FromNumber(-103)); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + } |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, child_id_, PSTR("A")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, ids_.FromNumber(-104)); |
| + MutableEntry child(&wtrans, syncable::CREATE, child_id_, PSTR("B")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::ID, ids_.FromNumber(105)); |
| + child.Put(syncable::BASE_VERSION, 1); |
| + } |
| + |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + syncer_->SyncShare(&session); |
| + EXPECT_EQ(6, session.unsynced_count()); |
| + ASSERT_EQ(6, mock_server_->committed_ids().size()); |
| + // If this test starts failing, be aware other sort orders could be valid. |
| + EXPECT_EQ(parent_id_, mock_server_->committed_ids()[0]); |
| + EXPECT_EQ(child_id_, mock_server_->committed_ids()[1]); |
| + EXPECT_EQ(ids_.FromNumber(102), mock_server_->committed_ids()[2]); |
| + EXPECT_EQ(ids_.FromNumber(-103), mock_server_->committed_ids()[3]); |
| + EXPECT_EQ(ids_.FromNumber(-104), mock_server_->committed_ids()[4]); |
| + EXPECT_EQ(ids_.FromNumber(105), mock_server_->committed_ids()[5]); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingCounterexample) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + |
| + syncable::Id child2_id = ids_.NewServerId(); |
| + |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), PSTR("P")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, parent_id_); |
| + MutableEntry child1(&wtrans, syncable::CREATE, parent_id_, PSTR("1")); |
| + ASSERT_TRUE(child1.good()); |
| + child1.Put(syncable::IS_UNSYNCED, true); |
| + child1.Put(syncable::ID, child_id_); |
| + MutableEntry child2(&wtrans, syncable::CREATE, parent_id_, PSTR("2")); |
| + ASSERT_TRUE(child2.good()); |
| + child2.Put(syncable::IS_UNSYNCED, true); |
| + child2.Put(syncable::ID, child2_id); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + child1.Put(syncable::BASE_VERSION, 1); |
| + child2.Put(syncable::BASE_VERSION, 1); |
| + } |
| + |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + syncer_->SyncShare(&session); |
| + EXPECT_EQ(3, session.unsynced_count()); |
| + ASSERT_EQ(3, mock_server_->committed_ids().size()); |
| + // If this test starts failing, be aware other sort orders could be valid. |
| + EXPECT_EQ(parent_id_, mock_server_->committed_ids()[0]); |
| + EXPECT_EQ(child_id_, mock_server_->committed_ids()[1]); |
| + EXPECT_EQ(child2_id, mock_server_->committed_ids()[2]); |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingAndNewParent) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), PSTR("1")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, parent_id_); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + } |
| + |
| + syncable::Id parent2_id = ids_.NewLocalId(); |
| + syncable::Id child2_id = ids_.NewServerId(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, parent_id_, PSTR("A")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, parent2_id); |
| + MutableEntry child(&wtrans, syncable::CREATE, parent2_id, PSTR("B")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::ID, child2_id); |
| + child.Put(syncable::BASE_VERSION, 1); |
| + } |
| + |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + |
| + syncer_->SyncShare(&session); |
| + EXPECT_EQ(3, session.unsynced_count()); |
| + ASSERT_EQ(3, mock_server_->committed_ids().size()); |
| + // If this test starts failing, be aware other sort orders could be valid. |
| + EXPECT_EQ(parent_id_, mock_server_->committed_ids()[0]); |
| + EXPECT_EQ(parent2_id, mock_server_->committed_ids()[1]); |
| + EXPECT_EQ(child2_id, mock_server_->committed_ids()[2]); |
| + { |
| + ReadTransaction rtrans(dir, __FILE__, __LINE__); |
| + PathChar path[] = { '1', *kPathSeparator, 'A', 0}; |
| + Entry entry_1A(&rtrans, syncable::GET_BY_PATH, path); |
| + ASSERT_TRUE(entry_1A.good()); |
| + Entry item_parent2(&rtrans, syncable::GET_BY_ID, parent2_id); |
| + ASSERT_FALSE(item_parent2.good()); |
| + Entry item_child2(&rtrans, syncable::GET_BY_ID, child2_id); |
| + EXPECT_EQ(entry_1A.Get(syncable::ID), item_child2.Get(syncable::PARENT_ID)); |
| + EXPECT_TRUE(entry_1A.Get(syncable::ID).ServerKnows()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestCommitListOrderingAndNewParentAndChild) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, wtrans.root_id(), PSTR("1")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, parent_id_); |
| + parent.Put(syncable::BASE_VERSION, 1); |
| + } |
| + int64 meta_handle_a, meta_handle_b; |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, parent_id_, PSTR("A")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, ids_.FromNumber(-101)); |
| + meta_handle_a = parent.Get(syncable::META_HANDLE); |
| + MutableEntry child(&wtrans, syncable::CREATE, ids_.FromNumber(-101), |
| + PSTR("B")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + child.Put(syncable::ID, ids_.FromNumber(-102)); |
| + meta_handle_b = child.Get(syncable::META_HANDLE); |
| + } |
| + |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + |
| + syncer_->SyncShare(&session); |
| + EXPECT_EQ(3, session.unsynced_count()); |
| + ASSERT_EQ(3, mock_server_->committed_ids().size()); |
| + // If this test starts failing, be aware other sort orders could be valid. |
| + EXPECT_EQ(parent_id_, mock_server_->committed_ids()[0]); |
| + EXPECT_EQ(ids_.FromNumber(-101), mock_server_->committed_ids()[1]); |
| + EXPECT_EQ(ids_.FromNumber(-102), mock_server_->committed_ids()[2]); |
| + { |
| + ReadTransaction rtrans(dir, __FILE__, __LINE__); |
| + PathChar path[] = { '1', *kPathSeparator, 'A', 0}; |
| + Entry entry_1A(&rtrans, syncable::GET_BY_PATH, path); |
| + ASSERT_TRUE(entry_1A.good()); |
| + Entry entry_id_minus_101(&rtrans, syncable::GET_BY_ID, |
| + ids_.FromNumber(-101)); |
| + ASSERT_FALSE(entry_id_minus_101.good()); |
| + Entry entry_b(&rtrans, syncable::GET_BY_HANDLE, meta_handle_b); |
| + EXPECT_EQ(entry_1A.Get(syncable::ID), entry_b.Get(syncable::PARENT_ID)); |
| + EXPECT_TRUE(entry_1A.Get(syncable::ID).ServerKnows()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, UpdateWithZeroLengthName) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // One illegal update |
| + mock_server_->AddUpdateDirectory(1, 0, "", 1, 10); |
| + // And one legal one that we're going to delete. |
| + mock_server_->AddUpdateDirectory(2, 0, "FOO", 1, 10); |
| + syncer_->SyncShare(); |
| + // Delete the legal one. The new update has a null name. |
| + mock_server_->AddUpdateDirectory(2, 0, "", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + syncer_->SyncShare(); |
| +} |
| + |
| +#ifdef OS_WINDOWS |
| +TEST_F(SyncerTest, NameSanitizationWithClientRename) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "okay", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction tr(dir, __FILE__, __LINE__); |
| + Entry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("okay")); |
| + ASSERT_TRUE(e.good()); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "prn", 1, 20); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction tr(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("prn~1")); |
| + ASSERT_TRUE(e.good()); |
| + e.PutName(syncable::Name(PSTR("printer"))); |
| + e.Put(syncable::IS_UNSYNCED, true); |
| + } |
| + syncer_->SyncShare(); |
| + { |
| + vector<CommitMessage*>::const_reverse_iterator it = |
| + mock_server_->commit_messages().rbegin(); |
| + ASSERT_TRUE(mock_server_->commit_messages().rend() != it); |
| + const sync_pb::SyncEntity *const *s = (*it)->entries().data(); |
| + int s_len = (*it)->entries_size(); |
| + ASSERT_EQ(1, s_len); |
| + ASSERT_EQ("printer", (*s)[0].name()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, NameSanitizationWithCascade) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "prn~1", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction tr(dir, __FILE__, __LINE__); |
| + Entry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("prn~1")); |
| + ASSERT_TRUE(e.good()); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "prn", 1, 20); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction tr(dir, __FILE__, __LINE__); |
| + Entry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("prn~2")); |
| + ASSERT_TRUE(e.good()); |
| + } |
| + mock_server_->AddUpdateDirectory(3, 0, "prn~2", 1, 30); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction tr(dir, __FILE__, __LINE__); |
| + Entry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("prn~3")); |
| + ASSERT_TRUE(e.good()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, GetStuckWithConflictingSanitizedNames) { |
| + // We should get stuck here because we get two server updates with exactly the |
| + // same name. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "foo:", 1, 10); |
| + syncer_->SyncShare(); |
| + mock_server_->AddUpdateDirectory(2, 0, "foo:", 1, 20); |
| + SyncRepeatedlyToTriggerStuckSignal(state_.get()); |
| + EXPECT_TRUE(SyncerStuck(state_.get())); |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, MergeFolderWithSanitizedNameMatches) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, CREATE, wtrans.root_id(), PSTR("Folder")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(IS_DIR, true); |
| + parent.Put(IS_UNSYNCED, true); |
| + parent.Put(UNSANITIZED_NAME, PSTR("Folder:")); |
| + } |
| + mock_server_->AddUpdateDirectory(100, 0, "Folder:", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Directory::ChildHandles children; |
| + dir->GetChildHandles(&trans, trans.root_id(), &children); |
| + EXPECT_EQ(1, children.size()); |
| + Directory::UnappliedUpdateMetaHandles unapplied; |
| + dir->GetUnappliedUpdateMetaHandles(&trans, &unapplied); |
| + EXPECT_EQ(0, unapplied.size()); |
| + syncable::Directory::UnsyncedMetaHandles unsynced; |
| + dir->GetUnsyncedMetaHandles(&trans, &unsynced); |
| + EXPECT_EQ(0, unsynced.size()); |
| + syncer_events_.clear(); |
| + } |
| +} |
| + |
| +// These two tests are the same as the two above, but they introduce case |
| +// changes. |
| +TEST_F(SyncerTest, GetStuckWithSanitizedNamesThatDifferOnlyByCase) { |
| + // We should get stuck here because we get two server updates with exactly the |
| + // same name. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "FOO:", 1, 10); |
| + syncer_->SyncShare(); |
| + mock_server_->AddUpdateDirectory(2, 0, "foo:", 1, 20); |
| + SyncRepeatedlyToTriggerStuckSignal(state_.get()); |
| + EXPECT_TRUE(SyncerStuck(state_.get())); |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, MergeFolderWithSanitizedNameThatDiffersOnlyByCase) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, CREATE, wtrans.root_id(), PSTR("FOLDER")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(IS_DIR, true); |
| + parent.Put(IS_UNSYNCED, true); |
| + parent.Put(UNSANITIZED_NAME, PSTR("FOLDER:")); |
| + } |
| + mock_server_->AddUpdateDirectory(100, 0, "Folder:", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); // Good gracious, these tests are not so good. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Directory::ChildHandles children; |
| + dir->GetChildHandles(&trans, trans.root_id(), &children); |
| + EXPECT_EQ(1, children.size()); |
| + Directory::UnappliedUpdateMetaHandles unapplied; |
| + dir->GetUnappliedUpdateMetaHandles(&trans, &unapplied); |
| + EXPECT_EQ(0, unapplied.size()); |
| + syncable::Directory::UnsyncedMetaHandles unsynced; |
| + dir->GetUnsyncedMetaHandles(&trans, &unsynced); |
| + EXPECT_EQ(0, unsynced.size()); |
| + syncer_events_.clear(); |
| + } |
| +} |
| +#else // Mac / Linux ... |
| + |
| +TEST_F(SyncerTest, NameSanitizationWithClientRename) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "okay", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction tr(dir, __FILE__, __LINE__); |
| + Entry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("okay")); |
| + ASSERT_TRUE(e.good()); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "a/b", 1, 20); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction tr(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + PSTR("a:b")); |
| + ASSERT_TRUE(e.good()); |
| + e.PutName(syncable::Name(PSTR("ab"))); |
| + e.Put(syncable::IS_UNSYNCED, true); |
| + } |
| + syncer_->SyncShare(); |
| + { |
| + vector<CommitMessage*>::const_reverse_iterator it = |
| + mock_server_->commit_messages().rbegin(); |
| + ASSERT_TRUE(mock_server_->commit_messages().rend() != it); |
| + const sync_pb::SyncEntity *const *s = (*it)->entries().data(); |
| + int s_len = (*it)->entries_size(); |
| + ASSERT_EQ(1, s_len); |
| + ASSERT_EQ("ab", (*s)[0].name()); |
| + } |
| +} |
| +#endif |
| + |
| +namespace { |
| +void VerifyExistsWithNameInRoot(syncable::Directory *dir, |
| + const PathString &name, |
| + const string &entry, |
| + int line) { |
| + ReadTransaction tr(dir, __FILE__, __LINE__); |
| + Entry e(&tr, syncable::GET_BY_PARENTID_AND_NAME, tr.root_id(), |
| + name); |
| + EXPECT_TRUE(e.good()) << "failed on call from " << entry << ":" << line; |
| +} |
| +} // namespace |
| + |
| +TEST_F(SyncerTest, ExtendedAttributeWithNullCharacter) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + int xattr_count = 2; |
| + PathString xattr_keys[] = { PSTR("key"), PSTR("key2") }; |
| + syncable::Blob xattr_values[2]; |
| + char* value[] = { "value", "val\0ue" }; |
| + int value_length[] = { 5, 6 }; |
| + for (int i = 0; i < xattr_count; i++) { |
| + for (int j = 0; j < value_length[i]; j++) |
| + xattr_values[i].push_back(value[i][j]); |
| + } |
| + sync_pb::SyncEntity* ent = |
| + mock_server_->AddUpdateBookmark(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateExtendedAttributes( |
| + ent, xattr_keys, xattr_values, xattr_count); |
| + |
| + // Add some other items. |
| + mock_server_->AddUpdateBookmark(2, 0, "fred", 2, 10); |
| + mock_server_->AddUpdateBookmark(3, 0, "sue", 15, 10); |
| + |
| + syncer_->SyncShare(); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry1(&trans, syncable::GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(entry1.good()); |
| + EXPECT_EQ(1, entry1.Get(syncable::BASE_VERSION)); |
| + EXPECT_EQ(1, entry1.Get(syncable::SERVER_VERSION)); |
| + set<ExtendedAttribute> client_extended_attributes; |
| + entry1.GetAllExtendedAttributes(&trans, &client_extended_attributes); |
| + EXPECT_EQ(xattr_count, client_extended_attributes.size()); |
| + for (int i = 0; i < xattr_count; i++) { |
| + ExtendedAttributeKey key(entry1.Get(syncable::META_HANDLE), xattr_keys[i]); |
| + ExtendedAttribute expected_xattr(&trans, syncable::GET_BY_HANDLE, key); |
| + EXPECT_TRUE(expected_xattr.good()); |
| + for (int j = 0; j < value_length[i]; ++j) { |
| + EXPECT_EQ(xattr_values[i][j], |
| + static_cast<char>(expected_xattr.value().at(j))); |
| + } |
| + } |
| + Entry entry2(&trans, syncable::GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(entry2.good()); |
| + Entry entry3(&trans, syncable::GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(entry3.good()); |
| +} |
| + |
| +TEST_F(SyncerTest, TestBasicUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + string id = "some_id"; |
| + string parent_id = "0"; |
| + string name = "in_root"; |
| + int64 version = 10; |
| + int64 timestamp = 10; |
| + mock_server_->AddUpdateDirectory(id, parent_id, name, version, timestamp); |
| + |
| + syncer_->SyncShare(state_.get()); |
| + SyncerStatus status(NULL, state_.get()); |
| + EXPECT_EQ(0, status.stalled_updates()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, |
| + syncable::Id::CreateFromServerId("some_id")); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_TRUE(entry.Get(IS_DIR)); |
| + EXPECT_EQ(entry.Get(SERVER_VERSION), version); |
| + EXPECT_EQ(entry.Get(BASE_VERSION), version); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry.Get(SERVER_IS_DEL)); |
| + EXPECT_FALSE(entry.Get(IS_DEL)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, IllegalAndLegalUpdates) { |
| + Id root = ids_.root(); |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Should apply just fine. |
| + mock_server_->AddUpdateDirectory(1, 0, "in_root", 10, 10); |
| + |
| + // Name clash: this is a conflict. |
| + mock_server_->AddUpdateDirectory(2, 0, "in_root", 10, 10); |
| + |
| + // Unknown parent: should never be applied. "-80" is a legal server ID, |
| + // because any string sent by the server is a legal server ID in the sync |
| + // protocol, but it's not the ID of any item known to the client. This |
| + // update should succeed validation, but be stuck in the unapplied state |
| + // until an item with the server ID "-80" arrives. |
| + mock_server_->AddUpdateDirectory(3, -80, "bad_parent", 10, 10); |
| + |
| + syncer_->SyncShare(state_.get()); |
| + |
| + ConflictResolutionView conflict_view(state_.get()); |
| + SyncerStatus status(NULL, state_.get()); |
| + // Ids 2 and 3 are expected to be in conflict now. |
| + EXPECT_EQ(2, conflict_view.conflicting_updates()); |
| + EXPECT_EQ(0, status.stalled_updates()); |
| + |
| + // These entries will be used in the second set of updates. |
| + mock_server_->AddUpdateDirectory(4, 0, "newer_version", 20, 10); |
| + mock_server_->AddUpdateDirectory(5, 0, "circular1", 10, 10); |
| + mock_server_->AddUpdateDirectory(6, 5, "circular2", 10, 10); |
| + mock_server_->AddUpdateDirectory(9, 3, "bad_parent_child", 10, 10); |
| + mock_server_->AddUpdateDirectory(100, 9, "bad_parent_child2", 10, 10); |
| + mock_server_->AddUpdateDirectory(10, 0, "dir_to_bookmark", 10, 10); |
| + |
| + syncer_->SyncShare(state_.get()); |
| + // The three items with an unresolved parent should be unapplied (3, 9, 100). |
| + // The name clash should also still be in conflict. |
| + EXPECT_EQ(4, conflict_view.conflicting_updates()); |
| + EXPECT_EQ(0, status.stalled_updates()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + Entry name_clash(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(name_clash.good()); |
| + EXPECT_TRUE(name_clash.Get(IS_UNAPPLIED_UPDATE)); |
| + |
| + Entry bad_parent(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(bad_parent.good()); |
| + EXPECT_TRUE(name_clash.Get(IS_UNAPPLIED_UPDATE)) |
| + << "child of unknown parent should be in conflict"; |
| + |
| + Entry bad_parent_child(&trans, GET_BY_ID, ids_.FromNumber(9)); |
| + ASSERT_TRUE(bad_parent_child.good()); |
| + EXPECT_TRUE(bad_parent_child.Get(IS_UNAPPLIED_UPDATE)) |
| + << "grandchild of unknown parent should be in conflict"; |
| + |
| + Entry bad_parent_child2(&trans, GET_BY_ID, ids_.FromNumber(100)); |
| + ASSERT_TRUE(bad_parent_child2.good()); |
| + EXPECT_TRUE(bad_parent_child2.Get(IS_UNAPPLIED_UPDATE)) |
| + << "great-grandchild of unknown parent should be in conflict"; |
| + } |
| + |
| + // Updating 1 should unblock the clashing item 2. |
| + mock_server_->AddUpdateDirectory(1, 0, "new_name", 20, 20); |
| + |
| + // Moving 5 under 6 will create a cycle: a conflict. |
| + mock_server_->AddUpdateDirectory(5, 6, "circular3", 20, 20); |
| + |
| + // Flip the is_dir bit: should fail verify & be dropped. |
| + mock_server_->AddUpdateBookmark(10, 0, "dir_to_bookmark", 20, 20); |
| + syncer_->SyncShare(state_.get()); |
| + |
| + // Version number older than last known: should fail verify & be dropped. |
| + mock_server_->AddUpdateDirectory(4, 0, "old_version", 10, 10); |
| + syncer_->SyncShare(state_.get()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry still_a_dir(&trans, GET_BY_ID, ids_.FromNumber(10)); |
| + ASSERT_TRUE(still_a_dir.good()); |
| + EXPECT_FALSE(still_a_dir.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_EQ(10, still_a_dir.Get(BASE_VERSION)); |
| + EXPECT_EQ(10, still_a_dir.Get(SERVER_VERSION)); |
| + EXPECT_TRUE(still_a_dir.Get(IS_DIR)); |
| + |
| + Entry rename(&trans, GET_BY_PARENTID_AND_NAME, root, PSTR("new_name")); |
| + ASSERT_TRUE(rename.good()); |
| + EXPECT_FALSE(rename.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_EQ(ids_.FromNumber(1), rename.Get(ID)); |
| + EXPECT_EQ(20, rename.Get(BASE_VERSION)); |
| + |
| + Entry unblocked(&trans, GET_BY_PARENTID_AND_NAME, root, PSTR("in_root")); |
| + ASSERT_TRUE(unblocked.good()); |
| + EXPECT_FALSE(unblocked.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_EQ(ids_.FromNumber(2), unblocked.Get(ID)); |
| + EXPECT_EQ(10, unblocked.Get(BASE_VERSION)); |
| + |
| + Entry ignored_old_version(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(ignored_old_version.good()); |
| + EXPECT_EQ(ignored_old_version.Get(NAME), PSTR("newer_version")); |
| + EXPECT_FALSE(ignored_old_version.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_EQ(20, ignored_old_version.Get(BASE_VERSION)); |
| + |
| + Entry circular_parent_issue(&trans, GET_BY_ID, ids_.FromNumber(5)); |
| + ASSERT_TRUE(circular_parent_issue.good()); |
| + EXPECT_TRUE(circular_parent_issue.Get(IS_UNAPPLIED_UPDATE)) |
| + << "circular move should be in conflict"; |
| + EXPECT_EQ(circular_parent_issue.Get(PARENT_ID), root_id_); |
| + EXPECT_EQ(circular_parent_issue.Get(SERVER_PARENT_ID), ids_.FromNumber(6)); |
| + EXPECT_EQ(10, circular_parent_issue.Get(BASE_VERSION)); |
| + |
| + Entry circular_parent_target(&trans, GET_BY_ID, ids_.FromNumber(6)); |
| + ASSERT_TRUE(circular_parent_target.good()); |
| + EXPECT_FALSE(circular_parent_target.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_EQ(circular_parent_issue.Get(ID), |
| + circular_parent_target.Get(PARENT_ID)); |
| + EXPECT_EQ(10, circular_parent_target.Get(BASE_VERSION)); |
| + } |
| + |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + EXPECT_EQ(4, conflict_view.conflicting_updates()); |
| +} |
| + |
| +TEST_F(SyncerTest, CommitTimeRename) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Create a folder and an entry |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&trans, CREATE, root_id_, PSTR("Folder")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(IS_DIR, true); |
| + parent.Put(IS_UNSYNCED, true); |
| + MutableEntry entry(&trans, CREATE, parent.Get(ID), PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + |
| + // Mix in a directory creation too for later |
| + mock_server_->AddUpdateDirectory(2, 0, "dir_in_root", 10, 10); |
| + mock_server_->SetCommitTimeRename("renamed_"); |
| + syncer_->SyncShare(); |
| + |
| + // Verify it was correctly renamed |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry_folder(&trans, GET_BY_PATH, PSTR("renamed_Folder")); |
| + ASSERT_TRUE(entry_folder.good()); |
| + |
| + Entry entry_new(&trans, GET_BY_PATH, |
| + PSTR("renamed_Folder") + PathString(kPathSeparator) |
| + + PSTR("renamed_new_entry")); |
| + ASSERT_TRUE(entry_new.good()); |
| + |
| + // And that the unrelated directory creation worked without a rename |
| + Entry new_dir(&trans, GET_BY_PATH, PSTR("dir_in_root")); |
| + EXPECT_TRUE(new_dir.good()); |
| + } |
| +} |
| + |
| + |
| +TEST_F(SyncerTest, CommitTimeRenameI18N) { |
| + // This is utf-8 for the diacritized Internationalization |
| + const char* i18nString = "\xc3\x8e\xc3\xb1\x74\xc3\xa9\x72\xc3\xb1" |
| + "\xc3\xa5\x74\xc3\xae\xc3\xb6\xc3\xb1\xc3\xa5\x6c\xc3\xae" |
| + "\xc2\x9e\xc3\xa5\x74\xc3\xae\xc3\xb6\xc3\xb1"; |
| + |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Create a folder and entry |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&trans, CREATE, root_id_, PSTR("Folder")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(IS_DIR, true); |
| + parent.Put(IS_UNSYNCED, true); |
| + MutableEntry entry(&trans, CREATE, parent.Get(ID), PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + |
| + // Mix in a directory creation too for later |
| + mock_server_->AddUpdateDirectory(2, 0, "dir_in_root", 10, 10); |
| + mock_server_->SetCommitTimeRename(i18nString); |
| + syncer_->SyncShare(); |
| + |
| + // Verify it was correctly renamed |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + PathString expectedFolder; |
| + AppendUTF8ToPathString(i18nString, &expectedFolder); |
| + AppendUTF8ToPathString("Folder", &expectedFolder); |
| + Entry entry_folder(&trans, GET_BY_PATH, expectedFolder); |
| + ASSERT_TRUE(entry_folder.good()); |
| + PathString expected = expectedFolder + PathString(kPathSeparator); |
| + AppendUTF8ToPathString(i18nString, &expected); |
| + AppendUTF8ToPathString("new_entry", &expected); |
| + |
| + Entry entry_new(&trans, GET_BY_PATH, expected); |
| + ASSERT_TRUE(entry_new.good()); |
| + |
| + // And that the unrelated directory creation worked without a rename |
| + Entry new_dir(&trans, GET_BY_PATH, PSTR("dir_in_root")); |
| + EXPECT_TRUE(new_dir.good()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, CommitTimeRenameCollision) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Create a folder to collide with |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry collider(&trans, CREATE, root_id_, PSTR("renamed_Folder")); |
| + ASSERT_TRUE(collider.good()); |
| + collider.Put(IS_DIR, true); |
| + collider.Put(IS_UNSYNCED, true); |
| + } |
| + syncer_->SyncShare(); // Now we have a folder. |
| + |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry folder(&trans, CREATE, root_id_, PSTR("Folder")); |
| + ASSERT_TRUE(folder.good()); |
| + folder.Put(IS_DIR, true); |
| + folder.Put(IS_UNSYNCED, true); |
| + } |
| + |
| + mock_server_->set_next_new_id(30000); |
| + mock_server_->SetCommitTimeRename("renamed_"); |
| + syncer_->SyncShare(); // Should collide and rename aside. |
| + // This case will only occur if we got a commit time rename aside |
| + // and the server attempts to rename to an entry that we know about, but it |
| + // does not. |
| + |
| + // Verify it was correctly renamed; one of them should have a sanitized name. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry collider_folder(&trans, GET_BY_PARENTID_AND_NAME, root_id_, |
| + PSTR("renamed_Folder")); |
| + EXPECT_EQ(collider_folder.Get(UNSANITIZED_NAME), PSTR("")); |
| + ASSERT_TRUE(collider_folder.good()); |
| + |
| + // ID is generated by next_new_id_ and server mock prepending of strings. |
| + Entry entry_folder(&trans, GET_BY_ID, |
| + syncable::Id::CreateFromServerId("mock_server:30000")); |
| + ASSERT_TRUE(entry_folder.good()); |
| + // A little arbitrary but nothing we can do about that. |
| + EXPECT_EQ(entry_folder.Get(NAME), PSTR("renamed_Folder~1")); |
| + EXPECT_EQ(entry_folder.Get(UNSANITIZED_NAME), PSTR("renamed_Folder")); |
| + } |
| +} |
| + |
| + |
| +// A commit with a lost response produces an update that has to be reunited with |
| +// its parent. |
| +TEST_F(SyncerTest, CommitReuniteUpdateAdjustsChildren) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Create a folder in the root. |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("new_folder")); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(IS_DIR, true); |
| + entry.Put(IS_UNSYNCED, true); |
| + } |
| + |
| + // Verify it and pull the ID out of the folder |
| + syncable::Id folder_id; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("new_folder")); |
| + ASSERT_TRUE(entry.good()); |
| + folder_id = entry.Get(ID); |
| + ASSERT_TRUE(!folder_id.ServerKnows()); |
| + } |
| + |
| + // Create an entry in the newly created folder. |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, folder_id, PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + |
| + // Verify it and pull the ID out of the entry |
| + syncable::Id entry_id; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, syncable::GET_BY_PARENTID_AND_NAME, folder_id, |
| + PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + entry_id = entry.Get(ID); |
| + EXPECT_TRUE(!entry_id.ServerKnows()); |
| + VerifyTestDataInEntry(&trans, &entry); |
| + } |
| + |
| + // Now, to emulate a commit response failure, we just don't commit it. |
| + int64 new_version = 150; // any larger value |
| + int64 timestamp = 20; // arbitrary value. |
| + int64 size = 20; // arbitrary. |
| + syncable::Id new_folder_id = |
| + syncable::Id::CreateFromServerId("folder_server_id"); |
| + |
| + // the following update should cause the folder to both apply the update, as |
| + // well as reassociate the id |
| + mock_server_->AddUpdateDirectory(new_folder_id, root_id_, |
| + "new_folder", new_version, timestamp); |
| + mock_server_->SetLastUpdateOriginatorFields( |
| + dir->cache_guid(), folder_id.GetServerId()); |
| + |
| + // We don't want it accidentally committed, just the update applied. |
| + mock_server_->set_conflict_all_commits(true); |
| + |
| + // Alright! Apply that update! |
| + syncer_->SyncShare(); |
| + { |
| + // The folder's ID should have been updated. |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry folder(&trans, GET_BY_PATH, PSTR("new_folder")); |
| + ASSERT_TRUE(folder.good()); |
| + EXPECT_EQ(new_version, folder.Get(BASE_VERSION)); |
| + EXPECT_EQ(new_folder_id, folder.Get(ID)); |
| + EXPECT_TRUE(folder.Get(ID).ServerKnows()); |
| + |
| + // We changed the id of the parent, old lookups should fail. |
| + Entry bad_entry(&trans, syncable::GET_BY_PARENTID_AND_NAME, folder_id, |
| + PSTR("new_entry")); |
| + EXPECT_FALSE(bad_entry.good()); |
| + |
| + // The child's parent should have changed as well. |
| + Entry entry(&trans, syncable::GET_BY_PARENTID_AND_NAME, new_folder_id, |
| + PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_TRUE(!entry.Get(ID).ServerKnows()); |
| + VerifyTestDataInEntry(&trans, &entry); |
| + } |
| +} |
| + |
| +// A commit with a lost response produces an update that has to be reunited with |
| +// its parent. |
| +TEST_F(SyncerTest, CommitReuniteUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Create an entry in the root. |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + // Verify it and pull the ID out |
| + syncable::Id entry_id; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + entry_id = entry.Get(ID); |
| + EXPECT_TRUE(!entry_id.ServerKnows()); |
| + VerifyTestDataInEntry(&trans, &entry); |
| + } |
| + |
| + // Now, to emulate a commit response failure, we just don't commit it. |
| + int64 new_version = 150; // any larger value |
| + int64 timestamp = 20; // arbitrary value. |
| + syncable::Id new_entry_id = syncable::Id::CreateFromServerId("server_id"); |
| + |
| + // Generate an update from the server with a relevant ID reassignment. |
| + mock_server_->AddUpdateBookmark(new_entry_id, root_id_, |
| + "new_entry", new_version, timestamp); |
| + mock_server_->SetLastUpdateOriginatorFields( |
| + dir->cache_guid(), entry_id.GetServerId()); |
| + |
| + // We don't want it accidentally committed, just the update applied. |
| + mock_server_->set_conflict_all_commits(true); |
| + |
| + // Alright! Apply that update! |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_EQ(new_version, entry.Get(BASE_VERSION)); |
| + EXPECT_EQ(new_entry_id, entry.Get(ID)); |
| + } |
| +} |
| + |
| +// A commit with a lost response must work even if the local entry |
| +// was deleted before the update is applied. We should not duplicate the local |
| +// entry in this case, but just create another one alongside. |
| +// We may wish to examine this behavior in the future as it can create hanging |
| +// uploads that never finish, that must be cleaned up on the server side |
| +// after some time. |
| +TEST_F(SyncerTest, CommitReuniteUpdateDoesNotChokeOnDeletedLocalEntry) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + // Create a entry in the root. |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + // Verify it and pull the ID out |
| + syncable::Id entry_id; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + entry_id = entry.Get(ID); |
| + EXPECT_TRUE(!entry_id.ServerKnows()); |
| + VerifyTestDataInEntry(&trans, &entry); |
| + } |
| + |
| + // Now, to emulate a commit response failure, we just don't commit it. |
| + int64 new_version = 150; // any larger value |
| + int64 timestamp = 20; // arbitrary value. |
| + int64 size = 20; // arbitrary. |
| + syncable::Id new_entry_id = syncable::Id::CreateFromServerId("server_id"); |
| + |
| + // Generate an update from the server with a relevant ID reassignment. |
| + mock_server_->AddUpdateBookmark(new_entry_id, root_id_, |
| + "new_entry", new_version, timestamp); |
| + mock_server_->SetLastUpdateOriginatorFields( |
| + dir->cache_guid(), |
| + entry_id.GetServerId()); |
| + |
| + // We don't want it accidentally committed, just the update applied. |
| + mock_server_->set_conflict_all_commits(true); |
| + |
| + // Purposefully delete the entry now before the update application finishes. |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_PATH, PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(syncable::IS_DEL, true); |
| + } |
| + |
| + // Just don't CHECK fail in sync, have the update split. |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("new_entry")); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_DEL)); |
| + |
| + Entry old_entry(&trans, GET_BY_ID, entry_id); |
| + ASSERT_TRUE(old_entry.good()); |
| + EXPECT_TRUE(old_entry.Get(IS_DEL)); |
| + } |
| +} |
| + |
| +// TODO(chron): Add more unsanitized name tests |
| +TEST_F(SyncerTest, ConflictMatchingEntryHandlesUnsanitizedNames) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "A/A", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "B/B", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + |
| + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + A.Put(IS_UNSYNCED, true); |
| + A.Put(IS_UNAPPLIED_UPDATE, true); |
| + A.Put(SERVER_VERSION, 20); |
| + |
| + MutableEntry B(&wtrans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + B.Put(IS_UNAPPLIED_UPDATE, true); |
| + B.Put(SERVER_VERSION, 20); |
| + } |
| + LoopSyncShare(syncer_); |
| + syncer_events_.clear(); |
| + mock_server_->set_conflict_all_commits(false); |
| + |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + |
| + Entry A(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + EXPECT_EQ(A.Get(IS_UNSYNCED), false); |
| + EXPECT_EQ(A.Get(IS_UNAPPLIED_UPDATE), false); |
| + EXPECT_EQ(A.Get(SERVER_VERSION), 20); |
| + |
| + Entry B(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + EXPECT_EQ(B.Get(IS_UNSYNCED), false); |
| + EXPECT_EQ(B.Get(IS_UNAPPLIED_UPDATE), false); |
| + EXPECT_EQ(B.Get(SERVER_VERSION), 20); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, ConflictMatchingEntryHandlesNormalNames) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "B", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + |
| + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + A.Put(IS_UNSYNCED, true); |
| + A.Put(IS_UNAPPLIED_UPDATE, true); |
| + A.Put(SERVER_VERSION, 20); |
| + |
| + MutableEntry B(&wtrans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + B.Put(IS_UNAPPLIED_UPDATE, true); |
| + B.Put(SERVER_VERSION, 20); |
| + } |
| + LoopSyncShare(syncer_); |
| + syncer_events_.clear(); |
| + mock_server_->set_conflict_all_commits(false); |
| + |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + |
| + Entry A(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + EXPECT_EQ(A.Get(IS_UNSYNCED), false); |
| + EXPECT_EQ(A.Get(IS_UNAPPLIED_UPDATE), false); |
| + EXPECT_EQ(A.Get(SERVER_VERSION), 20); |
| + |
| + Entry B(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + EXPECT_EQ(B.Get(IS_UNSYNCED), false); |
| + EXPECT_EQ(B.Get(IS_UNAPPLIED_UPDATE), false); |
| + EXPECT_EQ(B.Get(SERVER_VERSION), 20); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, ReverseFolderOrderingTest) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(4, 3, "ggchild", 10, 10); |
| + mock_server_->AddUpdateDirectory(3, 2, "gchild", 10, 10); |
| + mock_server_->AddUpdateDirectory(5, 4, "gggchild", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 1, "child", 10, 10); |
| + mock_server_->AddUpdateDirectory(1, 0, "parent", 10, 10); |
| + LoopSyncShare(syncer_); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry child(&trans, syncable::GET_BY_PARENTID_AND_NAME, ids_.FromNumber(4), |
| + PSTR("gggchild")); |
| + ASSERT_TRUE(child.good()); |
| +} |
| + |
| +bool CreateFolderInBob(Directory* dir) { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, syncable::GET_BY_PARENTID_AND_NAME, trans.root_id(), |
| + PSTR("bob")); |
| + MutableEntry entry2(&trans, syncable::CREATE, bob.Get(syncable::ID), |
| + PSTR("bob")); |
| + CHECK(entry2.good()); |
| + entry2.Put(syncable::IS_DIR, true); |
| + entry2.Put(syncable::IS_UNSYNCED, true); |
| + return true; |
| +} |
| + |
| +TEST_F(SyncerTest, EntryCreatedInNewFolderMidSync) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, syncable::CREATE, trans.root_id(), PSTR("bob")); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(syncable::IS_DIR, true); |
| + entry.Put(syncable::IS_UNSYNCED, true); |
| + } |
| + mock_server_->SetMidCommitCallbackFunction(CreateFolderInBob); |
| + syncer_->SyncShare(BUILD_COMMIT_REQUEST, SYNCER_END); |
| + EXPECT_EQ(1, mock_server_->committed_ids().size()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + PathChar path[] = {*kPathSeparator, 'b', 'o', 'b', 0}; |
| + Entry entry(&trans, syncable::GET_BY_PATH, path); |
| + ASSERT_TRUE(entry.good()); |
| + PathChar path2[] = {*kPathSeparator, 'b', 'o', 'b', |
| + *kPathSeparator, 'b', 'o', 'b', 0}; |
| + Entry entry2(&trans, syncable::GET_BY_PATH, path2); |
| + ASSERT_TRUE(entry2.good()); |
| + } |
| +} |
| + |
| +bool TouchFredAndGingerInRoot(Directory* dir) { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry fred(&trans, syncable::GET_BY_PARENTID_AND_NAME, trans.root_id(), |
| + PSTR("fred")); |
| + CHECK(fred.good()); |
| + // Equivalent to touching the entry |
| + fred.Put(syncable::IS_UNSYNCED, true); |
| + fred.Put(syncable::SYNCING, false); |
| + MutableEntry ginger(&trans, syncable::GET_BY_PARENTID_AND_NAME, |
| + trans.root_id(), PSTR("ginger")); |
| + CHECK(ginger.good()); |
| + ginger.Put(syncable::IS_UNSYNCED, true); |
| + ginger.Put(syncable::SYNCING, false); |
| + return true; |
| +} |
| + |
| +TEST_F(SyncerTest, NegativeIDInUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(-10, 0, "bad", 40, 40); |
| + syncer_->SyncShare(); |
| + // The negative id would make us CHECK! |
| +} |
| + |
| +TEST_F(SyncerTest, UnappliedUpdateOnCreatedItemItemDoesNotCrash) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + // Create an item. |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry fred_match(&trans, CREATE, trans.root_id(), |
| + PSTR("fred_match")); |
| + ASSERT_TRUE(fred_match.good()); |
| + WriteTestDataToEntry(&trans, &fred_match); |
| + } |
| + // Commit it. |
| + syncer_->SyncShare(); |
| + EXPECT_EQ(1, mock_server_->committed_ids().size()); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncable::Id fred_match_id; |
| + { |
| + // Now receive a change from outside. |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry fred_match(&trans, GET_BY_PATH, PSTR("fred_match")); |
| + ASSERT_TRUE(fred_match.good()); |
| + EXPECT_TRUE(fred_match.Get(ID).ServerKnows()); |
| + fred_match_id = fred_match.Get(ID); |
| + mock_server_->AddUpdateBookmark(fred_match_id, trans.root_id(), |
| + "fred_match", 40, 40); |
| + } |
| + // Run the syncer. |
| + for (int i = 0 ; i < 30 ; ++i) { |
| + syncer_->SyncShare(); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, NameClashWithResolverInconsistentUpdates) { |
| + // I'm unsure what the client should really do when the scenario in this old |
| + // test occurs. The set of updates we've received are not consistent. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + const char* base_name = "name_clash_with_resolver"; |
| + const char* full_name = "name_clash_with_resolver.htm"; |
| + PathChar* base_name_p = PSTR("name_clash_with_resolver"); |
| + mock_server_->AddUpdateBookmark(1, 0, full_name, 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + mock_server_->AddUpdateBookmark(2, 0, full_name, 10, 10); |
| + mock_server_->set_conflict_n_commits(1); |
| + syncer_->SyncShare(); |
| + mock_server_->set_conflict_n_commits(1); |
| + syncer_->SyncShare(); |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + PathString id1name = id1.Get(NAME); |
| + |
| + EXPECT_EQ(base_name_p, id1name.substr(0, strlen(base_name))); |
| + EXPECT_EQ(PSTR(".htm"), id1name.substr(id1name.length() - 4)); |
| + EXPECT_LE(id1name.length(), 200ul); |
| + EXPECT_EQ(PSTR("name_clash_with_resolver.htm"), id2.Get(NAME)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, NameClashWithResolver) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + const char* base_name = "name_clash_with_resolver"; |
| + const char* full_name = "name_clash_with_resolver.htm"; |
| + PathChar* base_name_p = PSTR("name_clash_with_resolver"); |
| + PathChar* full_name_p = PSTR("name_clash_with_resolver.htm"); |
| + mock_server_->AddUpdateBookmark(1, 0, "fred", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(NAME, full_name_p); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + mock_server_->AddUpdateBookmark(2, 0, full_name, 10, 10); |
| + // We do NOT use LoopSyncShare here because of the way that |
| + // mock_server_->conflict_n_commits works. |
| + // It will only conflict the first n commits, so if we let the syncer loop, |
| + // the second commit of the update will succeed even though it shouldn't. |
| + mock_server_->set_conflict_n_commits(1); |
| + syncer_->SyncShare(state_.get()); |
| + mock_server_->set_conflict_n_commits(1); |
| + syncer_->SyncShare(state_.get()); |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + syncer_events_.clear(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + PathString id1name = id1.Get(NAME); |
| + |
| + EXPECT_EQ(base_name_p, id1name.substr(0, strlen(base_name))); |
| + EXPECT_EQ(PSTR(".htm"), id1name.substr(id1name.length() - 4)); |
| + EXPECT_LE(id1name.length(), 200ul); |
| + EXPECT_EQ(full_name_p, id2.Get(NAME)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, VeryLongNameClashWithResolver) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + string name; |
| + PathString name_w; |
| + name.resize(250, 'X'); |
| + name_w.resize(250, 'X'); |
| + name.append(".htm"); |
| + name_w.append(PSTR(".htm")); |
| + mock_server_->AddUpdateBookmark(1, 0, "fred", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(NAME, name_w); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + mock_server_->AddUpdateBookmark(2, 0, name, 10, 10); |
| + mock_server_->set_conflict_n_commits(1); |
| + // We do NOT use LoopSyncShare here because of the way that |
| + // mock_server_->conflict_n_commits works. |
| + // It will only conflict the first n commits, so if we let the syncer loop, |
| + // the second commit of the update will succeed even though it shouldn't. |
| + syncer_->SyncShare(state_.get()); |
| + mock_server_->set_conflict_n_commits(1); |
| + syncer_->SyncShare(state_.get()); |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + PathString id1name = id1.Get(NAME); |
| + EXPECT_EQ(PSTR(".htm"), id1name.substr(id1name.length() - 4)); |
| + EXPECT_EQ(name_w, id2.Get(NAME)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, NameClashWithResolverAndDotStartedName) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(1, 0, ".bob.htm", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(IS_UNSYNCED, true); |
| + entry.Put(NAME, PSTR(".htm")); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateBookmark(2, 0, ".htm", 10, 10); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + PathString id1name = id1.Get(NAME); |
| + EXPECT_EQ(PSTR(".htm"), id1name.substr(0, 4)); |
| + EXPECT_EQ(PSTR(".htm"), id2.Get(NAME)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, ThreeNamesClashWithResolver) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateBookmark(1, 0, "in_root.htm", 10, 10); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(entry.good()); |
| + ASSERT_FALSE(entry.Get(IS_DEL)); |
| + entry.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->AddUpdateBookmark(2, 0, "in_root.htm", 10, 10); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(entry.good()); |
| + ASSERT_FALSE(entry.Get(IS_DEL)); |
| + entry.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->AddUpdateBookmark(3, 0, "in_root.htm", 10, 10); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(entry.good()); |
| + ASSERT_FALSE(entry.Get(IS_DEL)); |
| + entry.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->AddUpdateBookmark(4, 0, "in_root.htm", 10, 10); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + Entry id3(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + Entry id4(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + ASSERT_TRUE(id3.good()); |
| + ASSERT_TRUE(id4.good()); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id3.Get(PARENT_ID)); |
| + EXPECT_EQ(root_id_, id4.Get(PARENT_ID)); |
| + PathString id1name = id1.Get(NAME); |
| + ASSERT_GE(id1name.length(), 4ul); |
| + EXPECT_EQ(PSTR("in_root"), id1name.substr(0, 7)); |
| + EXPECT_EQ(PSTR(".htm"), id1name.substr(id1name.length() - 4)); |
| + EXPECT_NE(PSTR("in_root.htm"), id1.Get(NAME)); |
| + PathString id2name = id2.Get(NAME); |
| + ASSERT_GE(id2name.length(), 4ul); |
| + EXPECT_EQ(PSTR("in_root"), id2name.substr(0, 7)); |
| + EXPECT_EQ(PSTR(".htm"), id2name.substr(id2name.length() - 4)); |
| + EXPECT_NE(PSTR("in_root.htm"), id2.Get(NAME)); |
| + PathString id3name = id3.Get(NAME); |
| + ASSERT_GE(id3name.length(), 4ul); |
| + EXPECT_EQ(PSTR("in_root"), id3name.substr(0, 7)); |
| + EXPECT_EQ(PSTR(".htm"), id3name.substr(id3name.length() - 4)); |
| + EXPECT_NE(PSTR("in_root.htm"), id3.Get(NAME)); |
| + EXPECT_EQ(PSTR("in_root.htm"), id4.Get(NAME)); |
| + } |
| +} |
| + |
| +/** |
| + * In the event that we have a double changed entry, that is |
| + * changed on both the client and the server, the conflict resolver |
| + * should just drop one of them and accept the other. |
| + */ |
| +TEST_F(SyncerTest, DoublyChangedWithResolver) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, root_id_, PSTR("Folder")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_DIR, true); |
| + parent.Put(syncable::ID, parent_id_); |
| + parent.Put(syncable::BASE_VERSION, 5); |
| + MutableEntry child(&wtrans, syncable::CREATE, parent_id_, PSTR("Pete.htm")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::ID, child_id_); |
| + child.Put(syncable::BASE_VERSION, 10); |
| + WriteTestDataToEntry(&wtrans, &child); |
| + } |
| + mock_server_->AddUpdateBookmark(child_id_, parent_id_, "Pete.htm", 11, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + LoopSyncShare(syncer_); |
| + syncable::Directory::ChildHandles children; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + dir->GetChildHandles(&trans, parent_id_, &children); |
| + // We expect the conflict resolver to just clobber the entry. |
| + Entry child(&trans, syncable::GET_BY_ID, child_id_); |
| + ASSERT_TRUE(child.good()); |
| + EXPECT_TRUE(child.Get(syncable::IS_UNSYNCED)); |
| + EXPECT_FALSE(child.Get(syncable::IS_UNAPPLIED_UPDATE)); |
| + } |
| + |
| + // Only one entry, since we just overwrite one. |
| + EXPECT_EQ(1, children.size()); |
| + syncer_events_.clear(); |
| +} |
| + |
| +// We got this repro case when someone was editing entries |
| +// while sync was occuring. The entry had changed out underneath |
| +// the user. |
| +TEST_F(SyncerTest, CommitsUpdateDoesntAlterEntry) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + int64 test_time = 123456; |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&wtrans, syncable::CREATE, root_id_, PSTR("Pete")); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(ID).ServerKnows()); |
| + entry.Put(syncable::IS_DIR, true); |
| + entry.Put(syncable::IS_UNSYNCED, true); |
| + entry.Put(syncable::MTIME, test_time); |
| + } |
| + syncer_->SyncShare(); |
| + syncable::Id id; |
| + int64 version; |
| + int64 server_position_in_parent; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, syncable::GET_BY_PARENTID_AND_NAME, trans.root_id(), |
| + PSTR("Pete")); |
| + ASSERT_TRUE(entry.good()); |
| + id = entry.Get(ID); |
| + EXPECT_TRUE(id.ServerKnows()); |
| + version = entry.Get(BASE_VERSION); |
| + server_position_in_parent = entry.Get(SERVER_POSITION_IN_PARENT); |
| + } |
| + mock_server_->AddUpdateDirectory(id, root_id_, "Pete", version, 10); |
| + mock_server_->SetLastUpdatePosition(server_position_in_parent); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, syncable::GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_EQ(entry.Get(MTIME), test_time); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, ParentAndChildBothMatch) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, CREATE, root_id_, PSTR("Folder")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(IS_DIR, true); |
| + parent.Put(IS_UNSYNCED, true); |
| + MutableEntry child(&wtrans, CREATE, parent.Get(ID), PSTR("test.htm")); |
| + ASSERT_TRUE(child.good()); |
| + WriteTestDataToEntry(&wtrans, &child); |
| + } |
| + mock_server_->AddUpdateDirectory(parent_id_, root_id_, "Folder", 10, 10); |
| + mock_server_->AddUpdateBookmark(child_id_, parent_id_, "test.htm", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Directory::ChildHandles children; |
| + dir->GetChildHandles(&trans, root_id_, &children); |
| + EXPECT_EQ(1, children.size()); |
| + dir->GetChildHandles(&trans, parent_id_, &children); |
| + EXPECT_EQ(1, children.size()); |
| + Directory::UnappliedUpdateMetaHandles unapplied; |
| + dir->GetUnappliedUpdateMetaHandles(&trans, &unapplied); |
| + EXPECT_EQ(0, unapplied.size()); |
| + syncable::Directory::UnsyncedMetaHandles unsynced; |
| + dir->GetUnsyncedMetaHandles(&trans, &unsynced); |
| + EXPECT_EQ(0, unsynced.size()); |
| + syncer_events_.clear(); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, CommittingNewDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("bob")); |
| + entry.Put(IS_UNSYNCED, true); |
| + entry.Put(IS_DEL, true); |
| + } |
| + syncer_->SyncShare(); |
| + EXPECT_EQ(0, mock_server_->committed_ids().size()); |
| +} |
| + |
| +// Original problem synopsis: |
| +// Check failed: entry->Get(BASE_VERSION) <= entry->Get(SERVER_VERSION) |
| +// Client creates entry, client finishes committing entry. Between |
| +// commit and getting update back, we delete the entry. |
| +// We get the update for the entry, but the local one was modified |
| +// so we store the entry but don't apply it. IS_UNAPPLIED_UPDATE is set. |
| +// We commit deletion and get a new version number. |
| +// We apply unapplied updates again before we get the update about the deletion. |
| +// This means we have an unapplied update where server_version < base_version. |
| +TEST_F(SyncerTest, UnappliedUpdateDuringCommit) { |
| + // This test is a little fake |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("bob")); |
| + entry.Put(ID, ids_.FromNumber(20)); |
| + entry.Put(BASE_VERSION, 1); |
| + entry.Put(SERVER_VERSION, 1); |
| + entry.Put(SERVER_PARENT_ID, ids_.FromNumber(9999)); // bad parent |
| + entry.Put(IS_UNSYNCED, true); |
| + entry.Put(IS_UNAPPLIED_UPDATE, true); |
| + entry.Put(IS_DEL, false); |
| + } |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + SyncerStatus status(NULL, state_.get()); |
| + EXPECT_EQ(0, status.conflicting_updates()); |
| + syncer_events_.clear(); |
| +} |
| + |
| +// Original problem synopsis: |
| +// Illegal parent |
| +// Unexpected error during sync if we: |
| +// make a new folder bob |
| +// wait for sync |
| +// make a new folder fred |
| +// move bob into fred |
| +// remove bob |
| +// remove fred |
| +// if no syncing occured midway, bob will have an illegal parent |
| +TEST_F(SyncerTest, DeletingEntryInFolder) { |
| + // This test is a little fake |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("existing")); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(IS_DIR, true); |
| + entry.Put(IS_UNSYNCED, true); |
| + } |
| + syncer_->SyncShare(state_.get()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry newfolder(&trans, CREATE, trans.root_id(), PSTR("new")); |
| + ASSERT_TRUE(newfolder.good()); |
| + newfolder.Put(IS_DIR, true); |
| + newfolder.Put(IS_UNSYNCED, true); |
| + |
| + MutableEntry existing(&trans, GET_BY_PATH, PSTR("existing")); |
| + ASSERT_TRUE(existing.good()); |
| + existing.Put(PARENT_ID, newfolder.Get(ID)); |
| + existing.Put(IS_UNSYNCED, true); |
| + EXPECT_TRUE(existing.Get(ID).ServerKnows()); |
| + |
| + newfolder.Put(IS_DEL, true); |
| + existing.Put(IS_DEL, true); |
| + } |
| + syncer_->SyncShare(state_.get()); |
| + SyncerStatus status(NULL, state_.get()); |
| + EXPECT_EQ(0, status.error_commits()); |
| + EXPECT_EQ(0, status.conflicting_commits()); |
| + EXPECT_EQ(0, status.BlockedItemsSize()); |
| +} |
| + |
| +// TODO(sync): Is this test useful anymore? |
| +TEST_F(SyncerTest, DeletingEntryWithLocalEdits) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry newfolder(&trans, CREATE, ids_.FromNumber(1), PSTR("local")); |
| + ASSERT_TRUE(newfolder.good()); |
| + newfolder.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + syncer_->SyncShare(SYNCER_BEGIN, APPLY_UPDATES); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry_by_path(&trans, syncable::GET_BY_PATH, |
| + PathString(PSTR("bob")) + kPathSeparator + PSTR("local")); |
| + ASSERT_TRUE(entry_by_path.good()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, FolderSwapUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(7801, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(1024, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + mock_server_->AddUpdateDirectory(1024, 0, "bob", 2, 20); |
| + mock_server_->AddUpdateDirectory(7801, 0, "fred", 2, 20); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(7801)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("fred"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(1024)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("bob"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, CorruptUpdateBadFolderSwapUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(7801, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(1024, 0, "fred", 1, 10); |
| + mock_server_->AddUpdateDirectory(4096, 0, "alice", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(7801)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("bob"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(1024)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("fred"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + Entry id3(&trans, GET_BY_ID, ids_.FromNumber(4096)); |
| + ASSERT_TRUE(id3.good()); |
| + EXPECT_EQ(PSTR("alice"), id3.Get(NAME)); |
| + EXPECT_EQ(root_id_, id3.Get(PARENT_ID)); |
| + } |
| + mock_server_->AddUpdateDirectory(1024, 0, "bob", 2, 20); |
| + mock_server_->AddUpdateDirectory(7801, 0, "fred", 2, 20); |
| + mock_server_->AddUpdateDirectory(4096, 0, "bob", 2, 20); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(7801)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("bob"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(1024)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("fred"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + Entry id3(&trans, GET_BY_ID, ids_.FromNumber(4096)); |
| + ASSERT_TRUE(id3.good()); |
| + EXPECT_EQ(PSTR("alice"), id3.Get(NAME)); |
| + EXPECT_EQ(root_id_, id3.Get(PARENT_ID)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// TODO(chron): New set of folder swap commit tests that don't rely |
| +// on transactional commits. |
| +TEST_F(SyncerTest, DISABLED_FolderSwapCommit) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(7801, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(1024, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry id1(&trans, GET_BY_ID, ids_.FromNumber(7801)); |
| + MutableEntry id2(&trans, GET_BY_ID, ids_.FromNumber(1024)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_FALSE(id1.Put(NAME, PSTR("fred"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("temp"))); |
| + EXPECT_TRUE(id2.Put(NAME, PSTR("bob"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("fred"))); |
| + id1.Put(IS_UNSYNCED, true); |
| + id2.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + ASSERT_EQ(2, mock_server_->commit_messages().size()); |
| + CommitMessage* m0 = mock_server_->commit_messages()[0]; |
| + CommitMessage* m1 = mock_server_->commit_messages()[1]; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(7801)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("fred"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_FALSE(id1.Get(IS_UNSYNCED)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(1024)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("bob"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + EXPECT_FALSE(id2.Get(IS_UNSYNCED)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// TODO(chron): New set of folder swap commit tests that don't rely |
| +// on transactional commits. |
| +TEST_F(SyncerTest, DISABLED_DualFolderSwapCommit) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + mock_server_->AddUpdateDirectory(3, 0, "sue", 1, 10); |
| + mock_server_->AddUpdateDirectory(4, 0, "greg", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + MutableEntry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + MutableEntry id3(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + MutableEntry id4(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + ASSERT_TRUE(id3.good()); |
| + ASSERT_TRUE(id4.good()); |
| + EXPECT_FALSE(id1.Put(NAME, PSTR("fred"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("temp"))); |
| + EXPECT_TRUE(id2.Put(NAME, PSTR("bob"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("fred"))); |
| + EXPECT_FALSE(id3.Put(NAME, PSTR("greg"))); |
| + EXPECT_TRUE(id3.Put(NAME, PSTR("temp"))); |
| + EXPECT_TRUE(id4.Put(NAME, PSTR("sue"))); |
| + EXPECT_TRUE(id3.Put(NAME, PSTR("greg"))); |
| + id1.Put(IS_UNSYNCED, true); |
| + id2.Put(IS_UNSYNCED, true); |
| + id3.Put(IS_UNSYNCED, true); |
| + id4.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + ASSERT_EQ(4, mock_server_->commit_messages().size()); |
| + CommitMessage* m0 = mock_server_->commit_messages()[0]; |
| + CommitMessage* m1 = mock_server_->commit_messages()[1]; |
| + CommitMessage* m2 = mock_server_->commit_messages()[2]; |
| + CommitMessage* m3 = mock_server_->commit_messages()[3]; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("fred"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_FALSE(id1.Get(IS_UNSYNCED)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("bob"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + EXPECT_FALSE(id2.Get(IS_UNSYNCED)); |
| + Entry id3(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(id3.good()); |
| + EXPECT_EQ(PSTR("greg"), id3.Get(NAME)); |
| + EXPECT_EQ(root_id_, id3.Get(PARENT_ID)); |
| + EXPECT_FALSE(id3.Get(IS_UNSYNCED)); |
| + Entry id4(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(id4.good()); |
| + EXPECT_EQ(PSTR("sue"), id4.Get(NAME)); |
| + EXPECT_EQ(root_id_, id4.Get(PARENT_ID)); |
| + EXPECT_FALSE(id4.Get(IS_UNSYNCED)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// TODO(chron): New set of folder swap commit tests that don't rely |
| +// on transactional commits. |
| +TEST_F(SyncerTest, DISABLED_TripleFolderRotateCommit) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + mock_server_->AddUpdateDirectory(3, 0, "sue", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + MutableEntry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + MutableEntry id3(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + ASSERT_TRUE(id3.good()); |
| + EXPECT_FALSE(id1.Put(NAME, PSTR("sue"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("temp"))); |
| + EXPECT_TRUE(id2.Put(NAME, PSTR("bob"))); |
| + EXPECT_TRUE(id3.Put(NAME, PSTR("fred"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("sue"))); |
| + id1.Put(IS_UNSYNCED, true); |
| + id2.Put(IS_UNSYNCED, true); |
| + id3.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + ASSERT_EQ(2, mock_server_->commit_messages().size()); |
| + CommitMessage* m0 = mock_server_->commit_messages()[0]; |
| + CommitMessage* m1 = mock_server_->commit_messages()[1]; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("sue"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_FALSE(id1.Get(IS_UNSYNCED)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("bob"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + EXPECT_FALSE(id2.Get(IS_UNSYNCED)); |
| + Entry id3(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(id3.good()); |
| + EXPECT_EQ(PSTR("fred"), id3.Get(NAME)); |
| + EXPECT_EQ(root_id_, id3.Get(PARENT_ID)); |
| + EXPECT_FALSE(id3.Get(IS_UNSYNCED)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// TODO(chron): New set of folder swap commit tests that don't rely |
| +// on transactional commits. |
| +TEST_F(SyncerTest, DISABLED_ServerAndClientSwap) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + mock_server_->AddUpdateDirectory(3, 0, "sue", 1, 10); |
| + mock_server_->AddUpdateDirectory(4, 0, "greg", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + MutableEntry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id1.good()); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_FALSE(id1.Put(NAME, PSTR("fred"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("temp"))); |
| + EXPECT_TRUE(id2.Put(NAME, PSTR("bob"))); |
| + EXPECT_TRUE(id1.Put(NAME, PSTR("fred"))); |
| + id1.Put(IS_UNSYNCED, true); |
| + id2.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateDirectory(3, 0, "greg", 2, 20); |
| + mock_server_->AddUpdateDirectory(4, 0, "sue", 2, 20); |
| + syncer_->SyncShare(); |
| + ASSERT_EQ(2, mock_server_->commit_messages().size()); |
| + CommitMessage* m0 = mock_server_->commit_messages()[0]; |
| + CommitMessage* m1 = mock_server_->commit_messages()[1]; |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry id1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(id1.good()); |
| + EXPECT_EQ(PSTR("fred"), id1.Get(NAME)); |
| + EXPECT_EQ(root_id_, id1.Get(PARENT_ID)); |
| + EXPECT_FALSE(id1.Get(IS_UNSYNCED)); |
| + Entry id2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(id2.good()); |
| + EXPECT_EQ(PSTR("bob"), id2.Get(NAME)); |
| + EXPECT_EQ(root_id_, id2.Get(PARENT_ID)); |
| + EXPECT_FALSE(id2.Get(IS_UNSYNCED)); |
| + Entry id3(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(id3.good()); |
| + EXPECT_EQ(PSTR("greg"), id3.Get(NAME)); |
| + EXPECT_EQ(root_id_, id3.Get(PARENT_ID)); |
| + EXPECT_FALSE(id3.Get(IS_UNSYNCED)); |
| + Entry id4(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(id4.good()); |
| + EXPECT_EQ(PSTR("sue"), id4.Get(NAME)); |
| + EXPECT_EQ(root_id_, id4.Get(PARENT_ID)); |
| + EXPECT_FALSE(id4.Get(IS_UNSYNCED)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, CommitManyItemsInOneGo) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + uint32 max_batches = 3; |
| + uint32 items_to_commit = kDefaultMaxCommitBatchSize * max_batches; |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + for (uint32 i = 0; i < items_to_commit; i++) { |
| + string nameutf8 = StringPrintf("%d", i); |
| + PathString name(nameutf8.begin(), nameutf8.end()); |
| + MutableEntry e(&trans, CREATE, trans.root_id(), name); |
| + e.Put(IS_UNSYNCED, true); |
| + e.Put(IS_DIR, true); |
| + } |
| + } |
| + uint32 num_loops = 0; |
| + while (syncer_->SyncShare()) { |
| + num_loops++; |
| + ASSERT_LT(num_loops, max_batches * 2); |
| + } |
| + EXPECT_GE(mock_server_->commit_messages().size(), max_batches); |
| +} |
| + |
| +TEST_F(SyncerTest, HugeConflict) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + PathString name = PSTR("f"); |
| + int item_count = 30; // We should be able to do 300 or 3000 w/o issue. |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + syncable::Id last_id = trans.root_id(); |
| + for (int i = 0; i < item_count ; i++) { |
| + MutableEntry e(&trans, CREATE, last_id, name); |
| + e.Put(IS_UNSYNCED, true); |
| + e.Put(IS_DIR, true); |
| + last_id = e.Get(ID); |
| + } |
| + } |
| + syncer_->SyncShare(); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry e(&trans, GET_BY_PARENTID_AND_NAME, root_id_, name); |
| + syncable::Id in_root = e.Get(ID); |
| + syncable::Id last_id = e.Get(ID); |
| + for (int i = 0; i < item_count - 1 ; i++) { |
| + MutableEntry e(&trans, GET_BY_PARENTID_AND_NAME, last_id, name); |
| + ASSERT_TRUE(e.good()); |
| + mock_server_->AddUpdateDirectory(in_root, root_id_, "BOB", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + if (0 == i) |
| + e.Put(IS_UNSYNCED, true); |
| + last_id = e.Get(ID); |
| + } |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + CHECK(dir.good()); |
| +} |
| + |
| +TEST_F(SyncerTest, CaseChangeNameClashConflict) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry e(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(e.good()); |
| + e.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateDirectory(1, 0, "BOB", 2, 20); |
| + syncer_->SyncShare(); // USED TO CAUSE AN ASSERT |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, UnsyncedItemAndUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + syncer_->SyncShare(); |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateDirectory(2, 0, "bob", 2, 20); |
| + syncer_->SyncShare(); // USED TO CAUSE AN ASSERT |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, FolderMergeWithChildNameClash) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + syncable::Id local_folder_id, root_id; |
| + mock_server_->AddUpdateDirectory(parent_id_, root_id_, "Folder2", 10, 10); |
| + mock_server_->AddUpdateBookmark(child_id_, parent_id_, "Bookmark", 10, 10); |
| + syncer_->SyncShare(); |
| + int64 local_folder_handle; |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, CREATE, root_id_, PSTR("Folder")); |
| + ASSERT_TRUE(parent.good()); |
| + local_folder_id = parent.Get(ID); |
| + local_folder_handle = parent.Get(META_HANDLE); |
| + parent.Put(IS_DIR, true); |
| + parent.Put(IS_UNSYNCED, true); |
| + MutableEntry child(&wtrans, CREATE, parent.Get(ID), PSTR("Bookmark")); |
| + ASSERT_TRUE(child.good()); |
| + WriteTestDataToEntry(&wtrans, &child); |
| + } |
| + mock_server_->AddUpdateDirectory(parent_id_, root_id_, "Folder", 20, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Directory::ChildHandles children; |
| + dir->GetChildHandles(&trans, root_id_, &children); |
| + ASSERT_EQ(2, children.size()); |
| + Entry parent(&trans, GET_BY_ID, parent_id_); |
| + ASSERT_TRUE(parent.good()); |
| + EXPECT_EQ(parent.Get(NAME), PSTR("Folder")); |
| + if (local_folder_handle == children[0]) { |
| + EXPECT_EQ(children[1], parent.Get(META_HANDLE)); |
| + } else { |
| + EXPECT_EQ(children[0], parent.Get(META_HANDLE)); |
| + EXPECT_EQ(children[1], local_folder_handle); |
| + } |
| + dir->GetChildHandles(&trans, local_folder_id, &children); |
| + EXPECT_EQ(1, children.size()); |
| + dir->GetChildHandles(&trans, parent_id_, &children); |
| + EXPECT_EQ(1, children.size()); |
| + Directory::UnappliedUpdateMetaHandles unapplied; |
| + dir->GetUnappliedUpdateMetaHandles(&trans, &unapplied); |
| + EXPECT_EQ(0, unapplied.size()); |
| + syncable::Directory::UnsyncedMetaHandles unsynced; |
| + dir->GetUnsyncedMetaHandles(&trans, &unsynced); |
| + EXPECT_EQ(2, unsynced.size()); |
| + } |
| + mock_server_->set_conflict_all_commits(false); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + syncable::Directory::UnsyncedMetaHandles unsynced; |
| + dir->GetUnsyncedMetaHandles(&trans, &unsynced); |
| + EXPECT_EQ(0, unsynced.size()); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, NewEntryAndAlteredServerEntrySharePath) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(1, 0, "Foo.htm", 10, 10); |
| + syncer_->SyncShare(); |
| + int64 local_folder_handle; |
| + syncable::Id local_folder_id; |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry new_entry(&wtrans, CREATE, wtrans.root_id(), PSTR("Bar.htm")); |
| + ASSERT_TRUE(new_entry.good()); |
| + local_folder_id = new_entry.Get(ID); |
| + local_folder_handle = new_entry.Get(META_HANDLE); |
| + new_entry.Put(IS_UNSYNCED, true); |
| + MutableEntry old(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(old.good()); |
| + WriteTestDataToEntry(&wtrans, &old); |
| + } |
| + mock_server_->AddUpdateBookmark(1, 0, "Bar.htm", 20, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_events_.clear(); |
| +} |
| + |
| +// Circular links should be resolved by the server. |
| +TEST_F(SyncerTest, SiblingDirectoriesBecomeCircular) { |
| + // we don't currently resolve this. This test ensures we don't |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "B", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + A.Put(IS_UNSYNCED, true); |
| + ASSERT_TRUE(A.Put(PARENT_ID, ids_.FromNumber(2))); |
| + ASSERT_TRUE(A.Put(NAME, PSTR("B"))); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 1, "A", 20, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_events_.clear(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + MutableEntry B(&wtrans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + EXPECT_EQ(A.Get(NAME), PSTR("B")); |
| + EXPECT_EQ(B.Get(NAME), PSTR("B")); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, ConflictSetClassificationError) { |
| + // This code used to cause a CHECK failure because we incorrectly thought |
| + // a set was only unapplied updates. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "B", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + A.Put(IS_UNSYNCED, true); |
| + A.Put(IS_UNAPPLIED_UPDATE, true); |
| + A.Put(SERVER_NAME, PSTR("B")); |
| + MutableEntry B(&wtrans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + B.Put(IS_UNAPPLIED_UPDATE, true); |
| + B.Put(SERVER_NAME, PSTR("A")); |
| + } |
| + syncer_->SyncShare(); |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, SwapEntryNames) { |
| + // Simple transaction test |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "B", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(A.good()); |
| + A.Put(IS_UNSYNCED, true); |
| + MutableEntry B(&wtrans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + B.Put(IS_UNSYNCED, true); |
| + ASSERT_TRUE(A.Put(NAME, PSTR("C"))); |
| + ASSERT_TRUE(B.Put(NAME, PSTR("A"))); |
| + ASSERT_TRUE(A.Put(NAME, PSTR("B"))); |
| + } |
| + syncer_->SyncShare(); |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, DualDeletionWithNewItemNameClash) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10); |
| + mock_server_->AddUpdateBookmark(2, 0, "B", 10, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry B(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + WriteTestDataToEntry(&trans, &B); |
| + B.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateBookmark(2, 0, "A", 11, 11); |
| + mock_server_->SetLastUpdateDeleted(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry B(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(B.good()); |
| + EXPECT_FALSE(B.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(B.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, FixDirectoryLoopConflict) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(2)); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 1, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, ResolveWeWroteTheyDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(1, 0, "bob", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + WriteTestDataToEntry(&trans, &bob); |
| + } |
| + mock_server_->AddUpdateBookmark(1, 0, "bob", 2, 10); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_PARENTID_AND_NAME, trans.root_id(), PSTR("bob")); |
| + ASSERT_TRUE(bob.good()); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(bob.Get(ID).ServerKnows()); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_DEL)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// This test is disabled because we actually enforce the opposite behavior in: |
| +// ConflictResolverMergesLocalDeleteAndServerUpdate for bookmarks. |
| +TEST_F(SyncerTest, DISABLED_ResolveWeDeletedTheyWrote) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(1, 0, "bob", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateBookmark(1, 0, "bob", 2, 10); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_PARENTID_AND_NAME, trans.root_id(), PSTR("bob")); |
| + ASSERT_TRUE(bob.good()); |
| + EXPECT_EQ(bob.Get(ID), ids_.FromNumber(1)); |
| + EXPECT_FALSE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_DEL)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, ServerDeletingFolderWeHaveMovedSomethingInto) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(2)); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_PARENTID_AND_NAME, trans.root_id(), PSTR("fred")); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// TODO(ncarter): This test is bogus, but it actually seems to hit an |
| +// interesting case the 4th time SyncShare is called. |
| +TEST_F(SyncerTest, DISABLED_ServerDeletingFolderWeHaveAnOpenEntryIn) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(state_.get()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + WriteTestDataToEntry(&trans, &bob); |
| + } |
| + syncer_->SyncShare(state_.get()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + EXPECT_FALSE(bob.Get(IS_UNSYNCED)); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(2)); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_events_.clear(); |
| + // These SyncShares would cause a CHECK because we'd think we were stuck. |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + EXPECT_EQ(0, syncer_events_.size()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_PARENTID_AND_NAME, trans.root_id(), PSTR("fred")); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, WeMovedSomethingIntoAFolderServerHasDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(2)); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_PATH, PSTR("fred")); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(ID).ServerKnows()); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +namespace { |
| + |
| +int move_bob_count; |
| + |
| +bool MoveBobIntoID2(Directory* dir) { |
| + int first_count = move_bob_count; |
| + if (--move_bob_count > 0) |
| + return false; |
| + int second_count = move_bob_count; |
| + if (move_bob_count == 0) { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + Entry alice(&trans, GET_BY_ID, TestIdFactory::FromNumber(2)); |
| + CHECK(alice.good()); |
| + CHECK(!alice.Get(IS_DEL)); |
| + MutableEntry bob(&trans, GET_BY_ID, TestIdFactory::FromNumber(1)); |
| + CHECK(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, alice.Get(ID)); |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +} // namespace |
| + |
| +TEST_F(SyncerTest, |
| + WeMovedSomethingIntoAFolderServerHasDeletedAndWeRenamed) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry fred(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(fred.good()); |
| + fred.Put(IS_UNSYNCED, true); |
| + fred.Put(NAME, PSTR("Alice")); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + // This test is a little brittle. We want to move the item into the folder |
| + // such that we think we're dealing with a simple conflict, but in reality |
| + // it's actually a conflict set. |
| + move_bob_count = 2; |
| + mock_server_->SetMidCommitCallbackFunction(MoveBobIntoID2); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry alice(&trans, GET_BY_PATH, PSTR("Alice")); |
| + ASSERT_TRUE(alice.good()); |
| + EXPECT_TRUE(alice.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(alice.Get(ID).ServerKnows()); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), alice.Get(ID)); |
| + EXPECT_EQ(alice.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(alice.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| + |
| +TEST_F(SyncerTest, |
| + WeMovedADirIntoAndCreatedAnEntryInAFolderServerHasDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + syncable::Id new_item_id; |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(2)); |
| + MutableEntry new_item(&trans, CREATE, ids_.FromNumber(2), PSTR("new_item")); |
| + WriteTestDataToEntry(&trans, &new_item); |
| + new_item_id = new_item.Get(ID); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_PATH, PSTR("fred")); |
| + ASSERT_TRUE(fred.good()); |
| + PathChar path[] = {'f', 'r', 'e', 'd', *kPathSeparator, |
| + 'n', 'e', 'w', '_', 'i', 't', 'e', 'm', 0}; |
| + Entry new_item(&trans, GET_BY_PATH, path); |
| + EXPECT_TRUE(new_item.good()); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(ID).ServerKnows()); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, ServerMovedSomethingIntoAFolderWeHaveDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 1, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), bob.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, ServerMovedAFolderIntoAFolderWeHaveDeletedAndMovedIntoIt) { |
| + // This test combines circular folders and deleted parents. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(2)); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 1, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_DEL)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), root_id_); |
| + EXPECT_EQ(bob.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, NewServerItemInAFolderWeHaveDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 1, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), bob.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, NewServerItemInAFolderHierarchyWeHaveDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 1, "joe", 1, 10); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + MutableEntry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + joe.Put(IS_UNSYNCED, true); |
| + joe.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateDirectory(3, 2, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(fred.good()); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(joe.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), joe.Get(ID)); |
| + EXPECT_EQ(joe.Get(PARENT_ID), bob.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(joe.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, NewServerItemInAFolderHierarchyWeHaveDeleted2) { |
| + // The difference here is that the hierarchy's not in the root. We have |
| + // another entry that shouldn't be touched. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(4, 0, "susan", 1, 10); |
| + mock_server_->AddUpdateDirectory(1, 4, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 1, "joe", 1, 10); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + MutableEntry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + joe.Put(IS_UNSYNCED, true); |
| + joe.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateDirectory(3, 2, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(fred.good()); |
| + Entry susan(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(susan.good()); |
| + EXPECT_FALSE(susan.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(joe.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), joe.Get(ID)); |
| + EXPECT_EQ(joe.Get(PARENT_ID), bob.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), susan.Get(ID)); |
| + EXPECT_EQ(susan.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(susan.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(joe.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +namespace { |
| + |
| +int countown_till_delete = 0; |
| + |
| +void DeleteSusanInRoot(Directory* dir) { |
| + ASSERT_GT(countown_till_delete, 0); |
| + if (0 != --countown_till_delete) |
| + return; |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry susan(&trans, GET_BY_PATH, PSTR("susan")); |
| + Directory::ChildHandles children; |
| + dir->GetChildHandles(&trans, susan.Get(ID), &children); |
| + ASSERT_EQ(0, children.size()); |
| + susan.Put(IS_DEL, true); |
| + susan.Put(IS_UNSYNCED, true); |
| +} |
| + |
| +} // namespace |
| + |
| +TEST_F(SyncerTest, NewServerItemInAFolderHierarchyWeHaveDeleted3) { |
| + // Same as 2, except we deleted the folder the set is in between set building |
| + // and conflict resolution. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(4, 0, "susan", 1, 10); |
| + mock_server_->AddUpdateDirectory(1, 4, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 1, "joe", 1, 10); |
| + LoopSyncShare(syncer_); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(IS_DEL, true); |
| + MutableEntry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + joe.Put(IS_UNSYNCED, true); |
| + joe.Put(IS_DEL, true); |
| + } |
| + mock_server_->AddUpdateDirectory(3, 2, "fred", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + countown_till_delete = 2; |
| + syncer_->pre_conflict_resolution_function_ = DeleteSusanInRoot; |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(fred.good()); |
| + Entry susan(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(susan.good()); |
| + EXPECT_FALSE(susan.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_TRUE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(joe.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_TRUE(susan.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(joe.Get(IS_UNSYNCED)); |
| + } |
| + EXPECT_EQ(0, countown_till_delete); |
| + syncer_->pre_conflict_resolution_function_ = 0; |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry joe(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(joe.good()); |
| + Entry fred(&trans, GET_BY_ID, ids_.FromNumber(3)); |
| + ASSERT_TRUE(fred.good()); |
| + Entry susan(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(susan.good()); |
| + EXPECT_TRUE(susan.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(joe.Get(IS_UNSYNCED)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), joe.Get(ID)); |
| + EXPECT_EQ(joe.Get(PARENT_ID), bob.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), susan.Get(ID)); |
| + EXPECT_EQ(susan.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(susan.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(joe.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, WeMovedSomethingIntoAFolderHierarchyServerHasDeleted) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 1, 10); |
| + mock_server_->AddUpdateDirectory(3, 2, "alice", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(3)); // Move into alice. |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->AddUpdateDirectory(3, 0, "alice", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + Entry fred(&trans, GET_BY_PATH, PSTR("fred")); |
| + ASSERT_TRUE(fred.good()); |
| + PathChar path[] = {'f', 'r', 'e', 'd', *kPathSeparator, |
| + 'a', 'l', 'i', 'c', 'e', 0}; |
| + Entry alice(&trans, GET_BY_PATH, path); |
| + ASSERT_TRUE(alice.good()); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(alice.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(ID).ServerKnows()); |
| + EXPECT_FALSE(alice.Get(ID).ServerKnows()); |
| + EXPECT_EQ(alice.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), alice.Get(ID)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(alice.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, WeMovedSomethingIntoAFolderHierarchyServerHasDeleted2) { |
| + // The difference here is that the hierarchy's not in the root. We have |
| + // another entry that shouldn't be touched. |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + mock_server_->AddUpdateDirectory(4, 0, "susan", 1, 10); |
| + mock_server_->AddUpdateDirectory(2, 4, "fred", 1, 10); |
| + mock_server_->AddUpdateDirectory(3, 2, "alice", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + bob.Put(PARENT_ID, ids_.FromNumber(3)); // Move into alice. |
| + } |
| + mock_server_->AddUpdateDirectory(2, 0, "fred", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->AddUpdateDirectory(3, 0, "alice", 2, 20); |
| + mock_server_->SetLastUpdateDeleted(); |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + PathChar path[] = {'s', 'u', 's', 'a', 'n', *kPathSeparator, |
| + 'f', 'r', 'e', 'd', 0}; |
| + Entry fred(&trans, GET_BY_PATH, path); |
| + ASSERT_TRUE(fred.good()); |
| + PathChar path2[] = {'s', 'u', 's', 'a', 'n', *kPathSeparator, |
| + 'f', 'r', 'e', 'd', *kPathSeparator, |
| + 'a', 'l', 'i', 'c', 'e', 0}; |
| + Entry alice(&trans, GET_BY_PATH, path2); |
| + ASSERT_TRUE(alice.good()); |
| + Entry susan(&trans, GET_BY_ID, ids_.FromNumber(4)); |
| + ASSERT_TRUE(susan.good()); |
| + Entry susan_by_path(&trans, GET_BY_PATH, PSTR("susan")); |
| + ASSERT_TRUE(susan.good()); |
| + EXPECT_FALSE(susan.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(fred.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(alice.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(bob.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(fred.Get(ID).ServerKnows()); |
| + EXPECT_FALSE(alice.Get(ID).ServerKnows()); |
| + EXPECT_EQ(alice.Get(PARENT_ID), fred.Get(ID)); |
| + EXPECT_EQ(bob.Get(PARENT_ID), alice.Get(ID)); |
| + EXPECT_EQ(fred.Get(PARENT_ID), susan.Get(ID)); |
| + EXPECT_EQ(susan.Get(PARENT_ID), root_id_); |
| + EXPECT_FALSE(fred.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(bob.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(alice.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(susan.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| + syncer_events_.clear(); |
| +} |
| + |
| +// This test is to reproduce a check failure. Sometimes we would get a |
| +// bad ID back when creating an entry. |
| +TEST_F(SyncerTest, DuplicateIDReturn) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry folder(&trans, CREATE, trans.root_id(), PSTR("bob")); |
| + ASSERT_TRUE(folder.good()); |
| + folder.Put(IS_UNSYNCED, true); |
| + folder.Put(IS_DIR, true); |
| + MutableEntry folder2(&trans, CREATE, trans.root_id(), PSTR("fred")); |
| + ASSERT_TRUE(folder2.good()); |
| + folder2.Put(IS_UNSYNCED, false); |
| + folder2.Put(IS_DIR, true); |
| + folder2.Put(BASE_VERSION, 3); |
| + folder2.Put(ID, syncable::Id::CreateFromServerId("mock_server:10000")); |
| + } |
| + mock_server_->set_next_new_id(10000); |
| + EXPECT_EQ(1, dir->unsynced_entity_count()); |
| + syncer_->SyncShare(); // we get back a bad id in here (should never happen). |
| + EXPECT_EQ(1, dir->unsynced_entity_count()); |
| + syncer_->SyncShare(); // another bad id in here. |
| + EXPECT_EQ(0, dir->unsynced_entity_count()); |
| + syncer_events_.clear(); |
| +} |
| + |
| +// This test is not very useful anymore. It used to trigger |
| +// a more interesting condition. |
| +TEST_F(SyncerTest, SimpleConflictOnAnEntry) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, CREATE, trans.root_id(), PSTR("bob")); |
| + ASSERT_TRUE(bob.good()); |
| + bob.Put(IS_UNSYNCED, true); |
| + WriteTestDataToEntry(&trans, &bob); |
| + } |
| + syncer_->SyncShare(); |
| + syncable::Id bobid; |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_PATH, PSTR("bob")); |
| + ASSERT_TRUE(bob.good()); |
| + EXPECT_FALSE(bob.Get(IS_UNSYNCED)); |
| + bob.Put(IS_UNSYNCED, true); |
| + bobid = bob.Get(ID); |
| + } |
| + mock_server_->AddUpdateBookmark(1, 0, "jim", 2, 20); |
| + mock_server_->set_conflict_all_commits(true); |
| + SyncRepeatedlyToTriggerConflictResolution(state_.get()); |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, DeletedEntryWithBadParentInLoopCalculation) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + ASSERT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "bob", 1, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry bob(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(bob.good()); |
| + // This is valid, because the parent could have gone away a long time ago. |
| + bob.Put(PARENT_ID, ids_.FromNumber(54)); |
| + bob.Put(IS_DEL, true); |
| + bob.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->AddUpdateDirectory(2, 1, "fred", 1, 10); |
| + syncer_->SyncShare(); |
| + syncer_->SyncShare(); |
| +} |
| + |
| +TEST_F(SyncerTest, ConflictResolverMergeOverwritesLocalEntry) { |
| + // This test would die because it would rename |
| + // a entry to a name that was taken in the namespace |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + |
| + ConflictSet conflict_set; |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + |
| + MutableEntry local_deleted(&trans, CREATE, trans.root_id(), PSTR("name")); |
| + local_deleted.Put(ID, ids_.FromNumber(1)); |
| + local_deleted.Put(BASE_VERSION, 1); |
| + local_deleted.Put(IS_DEL, true); |
| + local_deleted.Put(IS_UNSYNCED, true); |
| + |
| + MutableEntry in_the_way(&trans, CREATE, trans.root_id(), PSTR("name")); |
| + in_the_way.Put(ID, ids_.FromNumber(2)); |
| + in_the_way.Put(BASE_VERSION, 1); |
| + |
| + MutableEntry update(&trans, CREATE_NEW_UPDATE_ITEM, ids_.FromNumber(3)); |
| + update.Put(BASE_VERSION, 1); |
| + update.Put(SERVER_NAME, PSTR("name")); |
| + update.Put(PARENT_ID, ids_.FromNumber(0)); |
| + update.Put(IS_UNAPPLIED_UPDATE, true); |
| + |
| + conflict_set.push_back(ids_.FromNumber(1)); |
| + conflict_set.push_back(ids_.FromNumber(3)); |
| + } |
| + { |
| + SyncCycleState cycle_state; |
| + SyncerSession session(&cycle_state, state_.get()); |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + syncer_->conflict_resolver()->ProcessConflictSet(&trans, &conflict_set, 50, |
| + &session); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, ConflictResolverMergesLocalDeleteAndServerUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + |
| + MutableEntry local_deleted(&trans, CREATE, trans.root_id(), PSTR("name")); |
| + local_deleted.Put(ID, ids_.FromNumber(1)); |
| + local_deleted.Put(BASE_VERSION, 1); |
| + local_deleted.Put(IS_DEL, true); |
| + local_deleted.Put(IS_DIR, false); |
| + local_deleted.Put(IS_UNSYNCED, true); |
| + local_deleted.Put(IS_BOOKMARK_OBJECT, true); |
| + } |
| + |
| + mock_server_->AddUpdateBookmark(ids_.FromNumber(1), root_id_, "name", 10, 10); |
| + |
| + // We don't care about actually committing, just the resolution |
| + mock_server_->set_conflict_all_commits(true); |
| + syncer_->SyncShare(); |
| + |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry local_deleted(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + EXPECT_EQ(local_deleted.Get(BASE_VERSION), 10); |
| + EXPECT_EQ(local_deleted.Get(IS_UNAPPLIED_UPDATE), false); |
| + EXPECT_EQ(local_deleted.Get(IS_UNSYNCED), true); |
| + EXPECT_EQ(local_deleted.Get(IS_DEL), true); |
| + EXPECT_EQ(local_deleted.Get(IS_DIR), false); |
| + } |
| +} |
| + |
| +// See what happens if the IS_DIR bit gets flipped. This can cause us |
| +// all kinds of disasters. |
| +TEST_F(SyncerTest, UpdateFlipsTheFolderBit) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + |
| + // Local object: a deleted directory (container), revision 1, unsynced. |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + |
| + MutableEntry local_deleted(&trans, CREATE, trans.root_id(), PSTR("name")); |
| + local_deleted.Put(ID, ids_.FromNumber(1)); |
| + local_deleted.Put(BASE_VERSION, 1); |
| + local_deleted.Put(IS_DEL, true); |
| + local_deleted.Put(IS_DIR, true); |
| + local_deleted.Put(IS_UNSYNCED, true); |
| + } |
| + |
| + // Server update: entry-type object (not a container), revision 10. |
| + mock_server_->AddUpdateBookmark(ids_.FromNumber(1), root_id_, "name", 10, 10); |
| + |
| + // Don't attempt to commit |
| + mock_server_->set_conflict_all_commits(true); |
| + |
| + // The syncer should not attempt to apply the invalid update. |
| + syncer_->SyncShare(); |
| + |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry local_deleted(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + EXPECT_EQ(local_deleted.Get(BASE_VERSION), 1); |
| + EXPECT_EQ(local_deleted.Get(IS_UNAPPLIED_UPDATE), false); |
| + EXPECT_EQ(local_deleted.Get(IS_UNSYNCED), true); |
| + EXPECT_EQ(local_deleted.Get(IS_DEL), true); |
| + EXPECT_EQ(local_deleted.Get(IS_DIR), true); |
| + } |
| +} |
| + |
| +TEST(SyncerSyncProcessState, MergeSetsTest) { |
| + TestIdFactory id_factory; |
| + syncable::Id id[7]; |
| + for (int i = 1; i < 7; i++) { |
| + id[i] = id_factory.NewServerId(); |
| + } |
| + SyncProcessState c; |
| + c.MergeSets(id[1], id[2]); |
| + c.MergeSets(id[2], id[3]); |
| + c.MergeSets(id[4], id[5]); |
| + c.MergeSets(id[5], id[6]); |
| + EXPECT_EQ(6, c.IdToConflictSetSize()); |
| + for (int i = 1; i < 7; i++) { |
| + EXPECT_TRUE(NULL != c.IdToConflictSetGet(id[i])); |
| + EXPECT_EQ(c.IdToConflictSetGet(id[(i & ~3) + 1]), |
| + c.IdToConflictSetGet(id[i])); |
| + } |
| + c.MergeSets(id[1], id[6]); |
| + for (int i = 1; i < 7; i++) { |
| + EXPECT_TRUE(NULL != c.IdToConflictSetGet(id[i])); |
| + EXPECT_EQ(c.IdToConflictSetGet(id[1]), c.IdToConflictSetGet(id[i])); |
| + } |
| + |
| + // Check dupes don't cause double sets |
| + SyncProcessState identical_set; |
| + identical_set.MergeSets(id[1], id[1]); |
| + EXPECT_EQ(identical_set.IdToConflictSetSize(), 1); |
| + EXPECT_EQ(identical_set.IdToConflictSetGet(id[1])->size(), 1); |
| +} |
| + |
| +// Bug Synopsis: |
| +// Merge conflict resolution will merge a new local entry |
| +// with another entry that needs updates, resulting in CHECK. |
| +TEST_F(SyncerTest, MergingExistingItems) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateBookmark(1, 0, "base", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("Copy of base")); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + mock_server_->AddUpdateBookmark(1, 0, "Copy of base", 50, 50); |
| + SyncRepeatedlyToTriggerConflictResolution(state_.get()); |
| +} |
| + |
| +// In this test a long changelog contains a child at the start of the changelog |
| +// and a parent at the end. While these updates are in progress the client would |
| +// appear stuck. |
| +TEST_F(SyncerTest, LongChangelistCreatesFakeOrphanedEntries) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + const int DEPTH = 400; |
| + // First we an item in a folder in the root. However the folder won't come |
| + // till much later. |
| + mock_server_->AddUpdateDirectory(99999, 1, "stuck", 1, 1); |
| + mock_server_->SetNewestTimestamp(DEPTH); |
| + syncer_->SyncShare(state_.get()); |
| + |
| + // Very long changelist. We should never be stuck. |
| + for (int i = 0; i < DEPTH; i++) { |
| + mock_server_->SetNewTimestamp(i); |
| + mock_server_->SetNewestTimestamp(DEPTH); |
| + syncer_->SyncShare(state_.get()); |
| + EXPECT_FALSE(SyncerStuck(state_.get())); |
| + } |
| + // And finally the folder. |
| + mock_server_->AddUpdateDirectory(1, 0, "folder", 1, 1); |
| + mock_server_->SetNewestTimestamp(DEPTH); |
| + LoopSyncShare(syncer_); |
| + LoopSyncShare(syncer_); |
| + // Check that everything's as expected after the commit. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("folder")); |
| + ASSERT_TRUE(entry.good()); |
| + Entry child(&trans, GET_BY_PARENTID_AND_NAME, entry.Get(ID), PSTR("stuck")); |
| + EXPECT_TRUE(child.good()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, DontMergeTwoExistingItems) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateBookmark(1, 0, "base", 10, 10); |
| + mock_server_->AddUpdateBookmark(2, 0, "base2", 10, 10); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_TRUE(entry.Put(NAME, PSTR("Copy of base"))); |
| + entry.Put(IS_UNSYNCED, true); |
| + } |
| + mock_server_->AddUpdateBookmark(1, 0, "Copy of base", 50, 50); |
| + SyncRepeatedlyToTriggerConflictResolution(state_.get()); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry1(&trans, GET_BY_ID, ids_.FromNumber(1)); |
| + EXPECT_FALSE(entry1.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry1.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry1.Get(IS_DEL)); |
| + Entry entry2(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + EXPECT_FALSE(entry2.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_TRUE(entry2.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry2.Get(IS_DEL)); |
| + EXPECT_NE(entry1.Get(NAME), entry2.Get(NAME)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestUndeleteUpdate) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateDirectory(1, 0, "foo", 1, 1); |
| + mock_server_->AddUpdateDirectory(2, 1, "bar", 1, 2); |
| + syncer_->SyncShare(); |
| + mock_server_->AddUpdateDirectory(2, 1, "bar", 2, 3); |
| + mock_server_->SetLastUpdateDeleted(); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_TRUE(entry.Get(IS_DEL)); |
| + } |
| + mock_server_->AddUpdateDirectory(1, 0, "foo", 2, 4); |
| + mock_server_->SetLastUpdateDeleted(); |
| + syncer_->SyncShare(); |
| + // This used to be rejected as it's an undeletion. |
| + // Now, it results in moving the delete path aside. |
| + mock_server_->AddUpdateDirectory(2, 1, "bar", 3, 5); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_TRUE(entry.Get(IS_DEL)); |
| + EXPECT_FALSE(entry.Get(SERVER_IS_DEL)); |
| + EXPECT_TRUE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestMoveSanitizedNamedFolder) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "foo", 1, 1); |
| + mock_server_->AddUpdateDirectory(2, 0, ":::", 1, 2); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_TRUE(entry.Put(PARENT_ID, ids_.FromNumber(1))); |
| + EXPECT_TRUE(entry.Put(IS_UNSYNCED, true)); |
| + } |
| + syncer_->SyncShare(); |
| + // We use the same sync ts as before so our times match up. |
| + mock_server_->AddUpdateDirectory(2, 1, ":::", 2, 2); |
| + syncer_->SyncShare(); |
| +} |
| + |
| +TEST_F(SyncerTest, QuicklyMergeDualCreatedHierarchy) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + mock_server_->set_conflict_all_commits(true); |
| + int depth = 10; |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + syncable::Id parent = root_id_; |
| + for (int i = 0 ; i < depth ; ++i) { |
| + MutableEntry entry(&trans, CREATE, parent, PSTR("folder")); |
| + entry.Put(IS_DIR, true); |
| + entry.Put(IS_UNSYNCED, true); |
| + parent = entry.Get(ID); |
| + } |
| + } |
| + for (int i = 0 ; i < depth ; ++i) { |
| + mock_server_->AddUpdateDirectory(i + 1, i, "folder", 1, 1); |
| + } |
| + syncer_->SyncShare(state_.get()); |
| + syncer_->SyncShare(state_.get()); |
| + SyncerStatus status(NULL, state_.get()); |
| + EXPECT_LT(status.consecutive_problem_commits(), 5); |
| + EXPECT_EQ(0, dir->unsynced_entity_count()); |
| +} |
| + |
| +TEST(SortedCollectionsIntersect, SortedCollectionsIntersectTest) { |
| + int negative[] = {-3, -2, -1}; |
| + int straddle[] = {-1, 0, 1}; |
| + int positive[] = {1, 2, 3}; |
| + EXPECT_TRUE(SortedCollectionsIntersect(negative, negative + 3, |
| + straddle, straddle + 3)); |
| + EXPECT_FALSE(SortedCollectionsIntersect(negative, negative + 3, |
| + positive, positive + 3)); |
| + EXPECT_TRUE(SortedCollectionsIntersect(straddle, straddle + 3, |
| + positive, positive + 3)); |
| + EXPECT_FALSE(SortedCollectionsIntersect(straddle + 2, straddle + 3, |
| + positive, positive)); |
| + EXPECT_FALSE(SortedCollectionsIntersect(straddle, straddle + 3, |
| + positive + 1, positive + 1)); |
| + EXPECT_TRUE(SortedCollectionsIntersect(straddle, straddle + 3, |
| + positive, positive + 1)); |
| +} |
| + |
| +// Don't crash when this occurs. |
| +TEST_F(SyncerTest, UpdateWhereParentIsNotAFolder) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(1, 0, "B", 10, 10); |
| + mock_server_->AddUpdateDirectory(2, 1, "BookmarkParent", 10, 10); |
| + // Used to cause a CHECK |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction rtrans(dir, __FILE__, __LINE__); |
| + Entry good_entry(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1)); |
| + ASSERT_TRUE(good_entry.good()); |
| + EXPECT_FALSE(good_entry.Get(IS_UNAPPLIED_UPDATE)); |
| + Entry bad_parent(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(2)); |
| + ASSERT_TRUE(bad_parent.good()); |
| + EXPECT_TRUE(bad_parent.Get(IS_UNAPPLIED_UPDATE)); |
| + } |
| +} |
| + |
| +const char kRootId[] = "0"; |
| + |
| +TEST_F(SyncerTest, DirectoryUpdateTest) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory("in_root_id", kRootId, |
| + "in_root_name", 2, 2); |
| + mock_server_->AddUpdateDirectory("in_in_root_id", "in_root_id", |
| + "in_in_root_name", 3, 3); |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + // Entry will have been dropped. |
| + Entry by_path(&trans, GET_BY_PATH, PSTR("in_root_name")); |
| + EXPECT_TRUE(by_path.good()); |
| + Entry by_path2(&trans, GET_BY_PATH, PSTR("in_root_name") + |
| + PathString(kPathSeparator) + |
| + PSTR("in_in_root_name")); |
| + EXPECT_TRUE(by_path2.good()); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, DirectoryCommitTest) { |
| + syncable::Id in_root, in_dir; |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry parent(&wtrans, syncable::CREATE, root_id_, PSTR("foo")); |
| + ASSERT_TRUE(parent.good()); |
| + parent.Put(syncable::IS_UNSYNCED, true); |
| + parent.Put(syncable::IS_DIR, true); |
| + in_root = parent.Get(syncable::ID); |
| + MutableEntry child(&wtrans, syncable::CREATE, parent.Get(ID), PSTR("bar")); |
| + ASSERT_TRUE(child.good()); |
| + child.Put(syncable::IS_UNSYNCED, true); |
| + child.Put(syncable::IS_DIR, true); |
| + in_dir = parent.Get(syncable::ID); |
| + } |
| + syncer_->SyncShare(); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry by_path(&trans, GET_BY_PATH, PSTR("foo")); |
| + ASSERT_TRUE(by_path.good()); |
| + EXPECT_NE(by_path.Get(syncable::ID), in_root); |
| + Entry by_path2(&trans, GET_BY_PATH, PSTR("foo") + |
| + PathString(kPathSeparator) + |
| + PSTR("bar")); |
| + ASSERT_TRUE(by_path2.good()); |
| + EXPECT_NE(by_path2.Get(syncable::ID), in_dir); |
| + } |
| +} |
| + |
| +namespace { |
| + |
| +void CheckEntryVersion(syncable::DirectoryManager* dirmgr, PathString name) { |
| + ScopedDirLookup dir(dirmgr, name); |
| + ASSERT_TRUE(dir.good()); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_PATH, PSTR("foo")); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_EQ(entry.Get(BASE_VERSION), 1); |
| +} |
| + |
| +} // namespace |
| + |
| +TEST_F(SyncerTest, ConflictSetSizeReducedToOne) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateBookmark(2, 0, "in_root", 1, 1); |
| + syncer_->SyncShare(); |
| + { |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry oentry(&trans, GET_BY_PATH, PSTR("in_root")); |
| + ASSERT_TRUE(oentry.good()); |
| + oentry.Put(NAME, PSTR("old_in_root")); |
| + WriteTestDataToEntry(&trans, &oentry); |
| + MutableEntry entry(&trans, CREATE, trans.root_id(), PSTR("in_root")); |
| + ASSERT_TRUE(entry.good()); |
| + WriteTestDataToEntry(&trans, &entry); |
| + } |
| + mock_server_->set_conflict_all_commits(true); |
| + // This SyncShare call used to result in a CHECK failure. |
| + syncer_->SyncShare(); |
| + syncer_events_.clear(); |
| +} |
| + |
| +TEST_F(SyncerTest, TestClientCommand) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + using sync_pb::ClientCommand; |
| + |
| + ClientCommand* command = mock_server_->GetNextClientCommand(); |
| + command->set_set_sync_poll_interval(8); |
| + command->set_set_sync_long_poll_interval(800); |
| + mock_server_->AddUpdateDirectory(1, 0, "in_root", 1, 1); |
| + syncer_->SyncShare(); |
| + |
| + EXPECT_TRUE(last_client_command_.has_set_sync_poll_interval()); |
| + EXPECT_TRUE(last_client_command_.has_set_sync_long_poll_interval()); |
| + EXPECT_EQ(8, last_client_command_.set_sync_poll_interval()); |
| + EXPECT_EQ(800, last_client_command_.set_sync_long_poll_interval()); |
| + |
| + command = mock_server_->GetNextClientCommand(); |
| + command->set_set_sync_poll_interval(180); |
| + command->set_set_sync_long_poll_interval(190); |
| + mock_server_->AddUpdateDirectory(1, 0, "in_root", 1, 1); |
| + syncer_->SyncShare(); |
| + |
| + EXPECT_TRUE(last_client_command_.has_set_sync_poll_interval()); |
| + EXPECT_TRUE(last_client_command_.has_set_sync_long_poll_interval()); |
| + EXPECT_EQ(180, last_client_command_.set_sync_poll_interval()); |
| + EXPECT_EQ(190, last_client_command_.set_sync_long_poll_interval()); |
| +} |
| + |
| +TEST_F(SyncerTest, EnsureWeSendUpOldParent) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + mock_server_->AddUpdateDirectory(1, 0, "folder_one", 1, 1); |
| + mock_server_->AddUpdateDirectory(2, 0, "folder_two", 1, 1); |
| + syncer_->SyncShare(); |
| + { |
| + // A moved entry should send an old parent. |
| + WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&trans, GET_BY_PATH, PSTR("folder_one")); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(PARENT_ID, ids_.FromNumber(2)); |
| + entry.Put(IS_UNSYNCED, true); |
| + // A new entry should send no parent. |
| + MutableEntry create(&trans, CREATE, trans.root_id(), PSTR("new_folder")); |
| + create.Put(IS_UNSYNCED, true); |
| + } |
| + syncer_->SyncShare(); |
| + const sync_pb::CommitMessage& commit = mock_server_->last_sent_commit(); |
| + ASSERT_EQ(2, commit.entries_size()); |
| + EXPECT_EQ(commit.entries(0).parent_id_string(), "2"); |
| + EXPECT_EQ(commit.entries(0).old_parent_id(), "0"); |
| + EXPECT_FALSE(commit.entries(1).has_old_parent_id()); |
| +} |
| + |
| +TEST_F(SyncerTest, Test64BitVersionSupport) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + int64 really_big_int = std::numeric_limits<int64>::max() - 12; |
| + const PathString name(PSTR("ringo's dang orang ran rings around my o-ring")); |
| + |
| + // Try writing max int64 to the version fields of a meta entry. |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&wtrans, syncable::CREATE, wtrans.root_id(), name); |
| + ASSERT_TRUE(entry.good()); |
| + entry.Put(syncable::BASE_VERSION, really_big_int); |
| + entry.Put(syncable::SERVER_VERSION, really_big_int); |
| + entry.Put(syncable::ID, syncable::Id::CreateFromServerId("ID")); |
| + } |
| + // Now read it back out and make sure the value is max int64. |
| + ReadTransaction rtrans(dir, __FILE__, __LINE__); |
| + Entry entry(&rtrans, syncable::GET_BY_PATH, name); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_EQ(really_big_int, entry.Get(syncable::BASE_VERSION)); |
| +} |
| + |
| +TEST_F(SyncerTest, TestDSStoreDirectorySyncsNormally) { |
| + syncable::Id item_id = parent_id_; |
| + mock_server_->AddUpdateDirectory(item_id, |
| + root_id_, ".DS_Store", 1, 1); |
| + syncer_->SyncShare(); |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + CHECK(dir.good()); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry ds_dir(&trans, GET_BY_PATH, PSTR(".DS_Store")); |
| + ASSERT_TRUE(ds_dir.good()); |
| +} |
| + |
| +TEST_F(SyncerTest, TestSimpleUndelete) { |
| + Id id = ids_.MakeServer("undeletion item"), root = ids_.root(); |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + mock_server_->set_conflict_all_commits(true); |
| + // let there be an entry from the server. |
| + mock_server_->AddUpdateBookmark(id, root, "foo", 1, 10); |
| + syncer_->SyncShare(); |
| + // check it out and delete it |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&wtrans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry.Get(IS_DEL)); |
| + // delete it locally |
| + entry.Put(IS_DEL, true); |
| + } |
| + syncer_->SyncShare(); |
| + // Confirm we see IS_DEL and not SERVER_IS_DEL. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(entry.Get(IS_DEL)); |
| + EXPECT_FALSE(entry.Get(SERVER_IS_DEL)); |
| + } |
| + syncer_->SyncShare(); |
| + // Update from server confirming deletion |
| + mock_server_->AddUpdateBookmark(id, root, "foo", 2, 11); |
| + mock_server_->SetLastUpdateDeleted(); |
| + syncer_->SyncShare(); |
| + // IS_DEL AND SERVER_IS_DEL now both true. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(entry.Get(IS_DEL)); |
| + EXPECT_TRUE(entry.Get(SERVER_IS_DEL)); |
| + } |
| + // Undelete from server |
| + mock_server_->AddUpdateBookmark(id, root, "foo", 2, 12); |
| + syncer_->SyncShare(); |
| + // IS_DEL and SERVER_IS_DEL now both false. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry.Get(IS_DEL)); |
| + EXPECT_FALSE(entry.Get(SERVER_IS_DEL)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestUndeleteWithMissingDeleteUpdate) { |
| + Id id = ids_.MakeServer("undeletion item"), root = ids_.root(); |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + // let there be a entry, from the server. |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateBookmark(id, root, "foo", 1, 10); |
| + syncer_->SyncShare(); |
| + // check it out and delete it |
| + { |
| + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); |
| + MutableEntry entry(&wtrans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry.Get(IS_DEL)); |
| + // delete it locally |
| + entry.Put(IS_DEL, true); |
| + } |
| + syncer_->SyncShare(); |
| + // Confirm we see IS_DEL and not SERVER_IS_DEL. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_TRUE(entry.Get(IS_DEL)); |
| + EXPECT_FALSE(entry.Get(SERVER_IS_DEL)); |
| + } |
| + syncer_->SyncShare(); |
| + // Say we do not get an update from server confirming deletion. |
| + // Undelete from server |
| + mock_server_->AddUpdateBookmark(id, root, "foo", 2, 12); |
| + syncer_->SyncShare(); |
| + // IS_DEL and SERVER_IS_DEL now both false. |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry entry(&trans, GET_BY_ID, id); |
| + ASSERT_TRUE(entry.good()); |
| + EXPECT_FALSE(entry.Get(IS_UNAPPLIED_UPDATE)); |
| + EXPECT_FALSE(entry.Get(IS_UNSYNCED)); |
| + EXPECT_FALSE(entry.Get(IS_DEL)); |
| + EXPECT_FALSE(entry.Get(SERVER_IS_DEL)); |
| + } |
| +} |
| + |
| +TEST_F(SyncerTest, TestUndeleteIgnoreCorrectlyUnappliedUpdate) { |
| + Id id1 = ids_.MakeServer("first"), id2 = ids_.MakeServer("second"); |
| + Id root = ids_.root(); |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + // duplicate! expect path clashing! |
| + mock_server_->set_conflict_all_commits(true); |
| + mock_server_->AddUpdateBookmark(id1, root, "foo", 1, 10); |
| + mock_server_->AddUpdateBookmark(id2, root, "foo", 1, 10); |
| + syncer_->SyncShare(); |
| + mock_server_->AddUpdateBookmark(id2, root, "foo2", 1, 10); |
| + syncer_->SyncShare(); // Now just don't explode. |
| +} |
| + |
| +TEST_F(SyncerTest, CopySyncProcessState) { |
| + scoped_ptr<SyncProcessState> b; |
| + { |
| + SyncProcessState a; |
| + a.MergeSets(ids_.FromNumber(1), ids_.FromNumber(2)); |
| + a.MergeSets(ids_.FromNumber(2), ids_.FromNumber(3)); |
| + a.MergeSets(ids_.FromNumber(4), ids_.FromNumber(5)); |
| + EXPECT_EQ(a.ConflictSetsSize(), 2); |
| + { |
| + SyncProcessState b = a; |
| + b = b; |
| + EXPECT_EQ(b.ConflictSetsSize(), 2); |
| + } |
| + EXPECT_EQ(a.ConflictSetsSize(), 2); |
| + a.MergeSets(ids_.FromNumber(3), ids_.FromNumber(4)); |
| + EXPECT_EQ(a.ConflictSetsSize(), 1); |
| + b.reset(new SyncProcessState(a)); |
| + } |
| + EXPECT_EQ(b->ConflictSetsSize(), 1); |
| +} |
| + |
| +TEST_F(SyncerTest, SingletonTagUpdates) { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + // As a hurdle, introduce an item whose name is the same as the |
| + // tag value we'll use later. |
| + int64 hurdle_handle = CreateUnsyncedDirectory(PSTR("bob"), "id_bob"); |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Entry hurdle(&trans, GET_BY_HANDLE, hurdle_handle); |
| + ASSERT_TRUE(hurdle.good()); |
| + ASSERT_TRUE(!hurdle.Get(IS_DEL)); |
| + ASSERT_TRUE(hurdle.Get(SINGLETON_TAG).empty()); |
| + ASSERT_TRUE(hurdle.GetName().value() == PSTR("bob")); |
| + |
| + // Try to lookup by the tagname. These should fail. |
| + Entry tag_alpha(&trans, GET_BY_TAG, PSTR("alpha")); |
| + EXPECT_FALSE(tag_alpha.good()); |
| + Entry tag_bob(&trans, GET_BY_TAG, PSTR("bob")); |
| + EXPECT_FALSE(tag_bob.good()); |
| + } |
| + |
| + // Now download some tagged items as updates. |
| + mock_server_->AddUpdateDirectory(1, 0, "update1", 1, 10); |
| + mock_server_->SetLastUpdateSingletonTag("alpha"); |
| + mock_server_->AddUpdateDirectory(2, 0, "update2", 2, 20); |
| + mock_server_->SetLastUpdateSingletonTag("bob"); |
| + syncer_->SyncShare(); |
| + |
| + { |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + |
| + // The new items should be applied as new entries, and we should be able |
| + // to look them up by their tag values. |
| + Entry tag_alpha(&trans, GET_BY_TAG, PSTR("alpha")); |
| + ASSERT_TRUE(tag_alpha.good()); |
| + ASSERT_TRUE(!tag_alpha.Get(IS_DEL)); |
| + ASSERT_TRUE(tag_alpha.Get(SINGLETON_TAG) == PSTR("alpha")); |
| + ASSERT_TRUE(tag_alpha.GetName().value() == PSTR("update1")); |
| + Entry tag_bob(&trans, GET_BY_TAG, PSTR("bob")); |
| + ASSERT_TRUE(tag_bob.good()); |
| + ASSERT_TRUE(!tag_bob.Get(IS_DEL)); |
| + ASSERT_TRUE(tag_bob.Get(SINGLETON_TAG) == PSTR("bob")); |
| + ASSERT_TRUE(tag_bob.GetName().value() == PSTR("update2")); |
| + // The old item should be unchanged. |
| + Entry hurdle(&trans, GET_BY_HANDLE, hurdle_handle); |
| + ASSERT_TRUE(hurdle.good()); |
| + ASSERT_TRUE(!hurdle.Get(IS_DEL)); |
| + ASSERT_TRUE(hurdle.Get(SINGLETON_TAG).empty()); |
| + ASSERT_TRUE(hurdle.GetName().value() == PSTR("bob")); |
| + } |
| +} |
| + |
| +namespace { |
| + |
| +class SyncerPositionUpdateTest : public SyncerTest { |
| + public: |
| + SyncerPositionUpdateTest() : next_update_id_(1), next_revision_(1) {} |
| + |
| + protected: |
| + void ExpectLocalItemsInServerOrder() { |
| + if (position_map_.empty()) |
| + return; |
| + |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + |
| + Id prev_id; |
| + DCHECK(prev_id.IsRoot()); |
| + PosMap::iterator next = position_map_.begin(); |
| + for (PosMap::iterator i = next++; i != position_map_.end(); ++i) { |
| + Id id = i->second; |
| + Entry entry_with_id(&trans, GET_BY_ID, id); |
| + EXPECT_TRUE(entry_with_id.good()); |
| + EXPECT_EQ(entry_with_id.Get(PREV_ID), prev_id); |
| + EXPECT_EQ(entry_with_id.Get(SERVER_POSITION_IN_PARENT), i->first); |
| + if (next == position_map_.end()) { |
| + EXPECT_TRUE(entry_with_id.Get(NEXT_ID).IsRoot()); |
| + } else { |
| + EXPECT_EQ(entry_with_id.Get(NEXT_ID), next->second); |
| + next++; |
| + } |
| + prev_id = id; |
| + } |
| + } |
| + |
| + void AddRootItemWithPosition(int64 position) { |
| + string id = string("ServerId") + Int64ToString(next_update_id_++); |
| + string name = "my name is my id -- " + id; |
| + int revision = next_revision_++; |
| + mock_server_->AddUpdateDirectory(id, kRootId, name, revision, revision); |
| + mock_server_->SetLastUpdatePosition(position); |
| + position_map_.insert( |
| + PosMap::value_type(position, Id::CreateFromServerId(id))); |
| + } |
| + private: |
| + typedef multimap<int64, Id> PosMap; |
| + PosMap position_map_; |
| + int next_update_id_; |
| + int next_revision_; |
| + DISALLOW_COPY_AND_ASSIGN(SyncerPositionUpdateTest); |
| +}; |
| + |
| +} // namespace |
| + |
| +TEST_F(SyncerPositionUpdateTest, InOrderPositive) { |
| + // Add a bunch of items in increasing order, starting with just |
| + // positive position values. |
| + AddRootItemWithPosition(100); |
| + AddRootItemWithPosition(199); |
| + AddRootItemWithPosition(200); |
| + AddRootItemWithPosition(201); |
| + AddRootItemWithPosition(400); |
| + |
| + syncer_->SyncShare(); |
| + ExpectLocalItemsInServerOrder(); |
| +} |
| + |
| +TEST_F(SyncerPositionUpdateTest, InOrderNegative) { |
| + // Test negative position values, but in increasing order. |
| + AddRootItemWithPosition(-400); |
| + AddRootItemWithPosition(-201); |
| + AddRootItemWithPosition(-200); |
| + AddRootItemWithPosition(-150); |
| + AddRootItemWithPosition(100); |
| + |
| + syncer_->SyncShare(); |
| + ExpectLocalItemsInServerOrder(); |
| +} |
| + |
| +TEST_F(SyncerPositionUpdateTest, ReverseOrder) { |
| + // Test when items are sent in the reverse order. |
| + AddRootItemWithPosition(400); |
| + AddRootItemWithPosition(201); |
| + AddRootItemWithPosition(200); |
| + AddRootItemWithPosition(100); |
| + AddRootItemWithPosition(-150); |
| + AddRootItemWithPosition(-201); |
| + AddRootItemWithPosition(-200); |
| + AddRootItemWithPosition(-400); |
| + |
| + syncer_->SyncShare(); |
| + ExpectLocalItemsInServerOrder(); |
| +} |
| + |
| +TEST_F(SyncerPositionUpdateTest, RandomOrderInBatches) { |
| + // Mix it all up, interleaving position values, |
| + // and try multiple batches of updates. |
| + AddRootItemWithPosition(400); |
| + AddRootItemWithPosition(201); |
| + AddRootItemWithPosition(-400); |
| + AddRootItemWithPosition(100); |
| + |
| + syncer_->SyncShare(); |
| + ExpectLocalItemsInServerOrder(); |
| + |
| + AddRootItemWithPosition(-150); |
| + AddRootItemWithPosition(-200); |
| + AddRootItemWithPosition(200); |
| + AddRootItemWithPosition(-201); |
| + |
| + syncer_->SyncShare(); |
| + ExpectLocalItemsInServerOrder(); |
| + |
| + AddRootItemWithPosition(-144); |
| + |
| + syncer_->SyncShare(); |
| + ExpectLocalItemsInServerOrder(); |
| +} |
| + |
| +namespace { |
| + |
| +class SyncerPositionTiebreakingTest : public SyncerTest { |
| + public: |
| + SyncerPositionTiebreakingTest() |
| + : low_id_(Id::CreateFromServerId("A")), |
| + mid_id_(Id::CreateFromServerId("M")), |
| + high_id_(Id::CreateFromServerId("Z")), |
| + next_revision_(1) { |
| + DCHECK(low_id_ < mid_id_); |
| + DCHECK(mid_id_ < high_id_); |
| + DCHECK(low_id_ < high_id_); |
| + } |
| + |
| + // Adds the item by its Id, using a constant value for the position |
| + // so that the syncer has to resolve the order some other way. |
| + void Add(const Id& id) { |
| + int revision = next_revision_++; |
| + mock_server_->AddUpdateDirectory(id.GetServerId(), kRootId, |
| + id.GetServerId(), revision, revision); |
| + // The update position doesn't vary. |
| + mock_server_->SetLastUpdatePosition(90210); |
| + } |
| + |
| + void ExpectLocalOrderIsByServerId() { |
| + ScopedDirLookup dir(syncdb_.manager(), syncdb_.name()); |
| + EXPECT_TRUE(dir.good()); |
| + ReadTransaction trans(dir, __FILE__, __LINE__); |
| + Id null_id; |
| + Entry low(&trans, GET_BY_ID, low_id_); |
| + Entry mid(&trans, GET_BY_ID, mid_id_); |
| + Entry high(&trans, GET_BY_ID, high_id_); |
| + EXPECT_TRUE(low.good()); |
| + EXPECT_TRUE(mid.good()); |
| + EXPECT_TRUE(high.good()); |
| + EXPECT_EQ(low.Get(PREV_ID), null_id); |
| + EXPECT_EQ(mid.Get(PREV_ID), low_id_); |
| + EXPECT_EQ(high.Get(PREV_ID), mid_id_); |
| + EXPECT_EQ(high.Get(NEXT_ID), null_id); |
| + EXPECT_EQ(mid.Get(NEXT_ID), high_id_); |
| + EXPECT_EQ(low.Get(NEXT_ID), mid_id_); |
| + } |
| + |
| + protected: |
| + // When there's a tiebreak on the numeric position, it's supposed to be |
| + // broken by string comparison of the ids. These ids are in increasing |
| + // order. |
| + const Id low_id_; |
| + const Id mid_id_; |
| + const Id high_id_; |
| + |
| + private: |
| + int next_revision_; |
| + DISALLOW_COPY_AND_ASSIGN(SyncerPositionTiebreakingTest); |
| +}; |
| + |
| +} // namespace |
| + |
| +TEST_F(SyncerPositionTiebreakingTest, LowMidHigh) { |
| + Add(low_id_); |
| + Add(mid_id_); |
| + Add(high_id_); |
| + syncer_->SyncShare(); |
| + ExpectLocalOrderIsByServerId(); |
| +} |
| + |
| +TEST_F(SyncerPositionTiebreakingTest, LowHighMid) { |
| + Add(low_id_); |
| + Add(high_id_); |
| + Add(mid_id_); |
| + syncer_->SyncShare(); |
| + ExpectLocalOrderIsByServerId(); |
| +} |
| + |
| +TEST_F(SyncerPositionTiebreakingTest, HighMidLow) { |
| + Add(high_id_); |
| + Add(mid_id_); |
| + Add(low_id_); |
| + syncer_->SyncShare(); |
| + ExpectLocalOrderIsByServerId(); |
| +} |
| + |
| +TEST_F(SyncerPositionTiebreakingTest, HighLowMid) { |
| + Add(high_id_); |
| + Add(low_id_); |
| + Add(mid_id_); |
| + syncer_->SyncShare(); |
| + ExpectLocalOrderIsByServerId(); |
| +} |
| + |
| +TEST_F(SyncerPositionTiebreakingTest, MidHighLow) { |
| + Add(mid_id_); |
| + Add(high_id_); |
| + Add(low_id_); |
| + syncer_->SyncShare(); |
| + ExpectLocalOrderIsByServerId(); |
| +} |
| + |
| +TEST_F(SyncerPositionTiebreakingTest, MidLowHigh) { |
| + Add(mid_id_); |
| + Add(low_id_); |
| + Add(high_id_); |
| + syncer_->SyncShare(); |
| + ExpectLocalOrderIsByServerId(); |
| +} |
| + |
| +const SyncerTest::CommitOrderingTest |
| +SyncerTest::CommitOrderingTest::LAST_COMMIT_ITEM = {-1, TestIdFactory::root()}; |
|
idana
2009/09/10 05:44:37
Add a blank line.
|
| +} // namespace browser_sync |
| Property changes on: chrome\browser\sync\engine\syncer_unittest.cc |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |