summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/engine/syncer_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/engine/syncer_unittest.cc')
-rw-r--r--chrome/browser/sync/engine/syncer_unittest.cc4588
1 files changed, 4588 insertions, 0 deletions
diff --git a/chrome/browser/sync/engine/syncer_unittest.cc b/chrome/browser/sync/engine/syncer_unittest.cc
new file mode 100644
index 0000000..27bdb9b
--- /dev/null
+++ b/chrome/browser/sync/engine/syncer_unittest.cc
@@ -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"
+#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
+
+
+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()};
+} // namespace browser_sync