| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sync/syncable/parent_child_index.h" |
| |
| #include <list> |
| |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "sync/syncable/entry_kernel.h" |
| #include "sync/syncable/syncable_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace syncer { |
| namespace syncable { |
| |
| namespace { |
| |
| static const std::string kCacheGuid = "8HhNIHlEOCGQbIAALr9QEg=="; |
| |
| class ParentChildIndexTest : public testing::Test { |
| public: |
| virtual void TearDown() { |
| // To make memory management easier, we take ownership of all EntryKernels |
| // returned by our factory methods and delete them here. |
| STLDeleteElements(&owned_entry_kernels_); |
| } |
| |
| // Unfortunately, we can't use the regular Entry factory methods, because the |
| // ParentChildIndex deals in EntryKernels. |
| |
| static syncable::Id GetBookmarkRootId() { |
| return syncable::Id::CreateFromServerId("bookmark_folder"); |
| } |
| |
| static syncable::Id GetBookmarkId(int n) { |
| return syncable::Id::CreateFromServerId("b" + base::IntToString(n)); |
| } |
| |
| static syncable::Id GetClientUniqueId(int n) { |
| return syncable::Id::CreateFromServerId("c" + base::IntToString(n)); |
| } |
| |
| EntryKernel* MakeRoot() { |
| // Mimics the root node. |
| EntryKernel* root = new EntryKernel(); |
| root->put(META_HANDLE, 1); |
| root->put(BASE_VERSION, -1); |
| root->put(SERVER_VERSION, 0); |
| root->put(IS_DIR, true); |
| root->put(ID, syncable::Id()); |
| root->put(PARENT_ID, syncable::Id()); |
| root->put(SERVER_PARENT_ID, syncable::Id()); |
| |
| owned_entry_kernels_.push_back(root); |
| return root; |
| } |
| |
| EntryKernel* MakeBookmarkRoot() { |
| // Mimics a server-created bookmark folder. |
| EntryKernel* folder = new EntryKernel; |
| folder->put(META_HANDLE, 1); |
| folder->put(BASE_VERSION, 9); |
| folder->put(SERVER_VERSION, 9); |
| folder->put(IS_DIR, true); |
| folder->put(ID, GetBookmarkRootId()); |
| folder->put(SERVER_PARENT_ID, syncable::Id()); |
| folder->put(PARENT_ID, syncable::Id()); |
| folder->put(UNIQUE_SERVER_TAG, "google_chrome_bookmarks"); |
| |
| owned_entry_kernels_.push_back(folder); |
| return folder; |
| } |
| |
| EntryKernel* MakeBookmark(int n, int pos, bool is_dir) { |
| // Mimics a regular bookmark or folder. |
| EntryKernel* bm = new EntryKernel(); |
| bm->put(META_HANDLE, n); |
| bm->put(BASE_VERSION, 10); |
| bm->put(SERVER_VERSION, 10); |
| bm->put(IS_DIR, is_dir); |
| bm->put(ID, GetBookmarkId(n)); |
| bm->put(PARENT_ID, GetBookmarkRootId()); |
| bm->put(SERVER_PARENT_ID, GetBookmarkRootId()); |
| |
| bm->put(UNIQUE_BOOKMARK_TAG, |
| syncable::GenerateSyncableBookmarkHash(kCacheGuid, |
| bm->ref(ID).GetServerId())); |
| |
| UniquePosition unique_pos = |
| UniquePosition::FromInt64(pos, bm->ref(UNIQUE_BOOKMARK_TAG)); |
| bm->put(UNIQUE_POSITION, unique_pos); |
| bm->put(SERVER_UNIQUE_POSITION, unique_pos); |
| |
| owned_entry_kernels_.push_back(bm); |
| return bm; |
| } |
| |
| EntryKernel* MakeUniqueClientItem(int n) { |
| EntryKernel* item = new EntryKernel(); |
| item->put(META_HANDLE, n); |
| item->put(BASE_VERSION, 10); |
| item->put(SERVER_VERSION, 10); |
| item->put(IS_DIR, false); |
| item->put(ID, GetClientUniqueId(n)); |
| item->put(PARENT_ID, syncable::Id()); |
| item->put(SERVER_PARENT_ID, syncable::Id()); |
| item->put(UNIQUE_CLIENT_TAG, base::IntToString(n)); |
| |
| owned_entry_kernels_.push_back(item); |
| return item; |
| } |
| |
| ParentChildIndex index_; |
| |
| private: |
| std::list<EntryKernel*> owned_entry_kernels_; |
| }; |
| |
| TEST_F(ParentChildIndexTest, TestRootNode) { |
| EntryKernel* root = MakeRoot(); |
| EXPECT_FALSE(ParentChildIndex::ShouldInclude(root)); |
| } |
| |
| TEST_F(ParentChildIndexTest, TestBookmarkRootFolder) { |
| EntryKernel* bm_folder = MakeBookmarkRoot(); |
| EXPECT_TRUE(ParentChildIndex::ShouldInclude(bm_folder)); |
| } |
| |
| // Tests iteration over a set of siblings. |
| TEST_F(ParentChildIndexTest, ChildInsertionAndIteration) { |
| EntryKernel* bm_folder = MakeBookmarkRoot(); |
| index_.Insert(bm_folder); |
| |
| // Make some folder and non-folder entries. |
| EntryKernel* b1 = MakeBookmark(1, 1, false); |
| EntryKernel* b2 = MakeBookmark(2, 2, false); |
| EntryKernel* b3 = MakeBookmark(3, 3, true); |
| EntryKernel* b4 = MakeBookmark(4, 4, false); |
| |
| // Insert them out-of-order to test different cases. |
| index_.Insert(b3); // Only child. |
| index_.Insert(b4); // Right-most child. |
| index_.Insert(b1); // Left-most child. |
| index_.Insert(b2); // Between existing items. |
| |
| // Double-check they've been added. |
| EXPECT_TRUE(index_.Contains(b1)); |
| EXPECT_TRUE(index_.Contains(b2)); |
| EXPECT_TRUE(index_.Contains(b3)); |
| EXPECT_TRUE(index_.Contains(b4)); |
| |
| // Check the ordering. |
| const OrderedChildSet* children = index_.GetChildren(GetBookmarkRootId()); |
| ASSERT_TRUE(children); |
| ASSERT_EQ(children->size(), 4UL); |
| OrderedChildSet::const_iterator it = children->begin(); |
| EXPECT_EQ(*it, b1); |
| it++; |
| EXPECT_EQ(*it, b2); |
| it++; |
| EXPECT_EQ(*it, b3); |
| it++; |
| EXPECT_EQ(*it, b4); |
| it++; |
| EXPECT_TRUE(it == children->end()); |
| } |
| |
| // Tests iteration when hierarchy is involved. |
| TEST_F(ParentChildIndexTest, ChildInsertionAndIterationWithHierarchy) { |
| EntryKernel* bm_folder = MakeBookmarkRoot(); |
| index_.Insert(bm_folder); |
| |
| // Just below the root, we have folders f1 and f2. |
| EntryKernel* f1 = MakeBookmark(1, 1, false); |
| EntryKernel* f2 = MakeBookmark(2, 2, false); |
| EntryKernel* f3 = MakeBookmark(3, 3, false); |
| |
| // Under folder f1, we have two bookmarks. |
| EntryKernel* f1_b1 = MakeBookmark(101, 1, false); |
| f1_b1->put(PARENT_ID, GetBookmarkId(1)); |
| EntryKernel* f1_b2 = MakeBookmark(102, 2, false); |
| f1_b2->put(PARENT_ID, GetBookmarkId(1)); |
| |
| // Under folder f2, there is one bookmark. |
| EntryKernel* f2_b1 = MakeBookmark(201, 1, false); |
| f2_b1->put(PARENT_ID, GetBookmarkId(2)); |
| |
| // Under folder f3, there is nothing. |
| |
| // Insert in a strange order, because we can. |
| index_.Insert(f1_b2); |
| index_.Insert(f2); |
| index_.Insert(f2_b1); |
| index_.Insert(f1); |
| index_.Insert(f1_b1); |
| index_.Insert(f3); |
| |
| OrderedChildSet::const_iterator it; |
| |
| // Iterate over children of the bookmark root. |
| const OrderedChildSet* top_children = index_.GetChildren(GetBookmarkRootId()); |
| ASSERT_TRUE(top_children); |
| ASSERT_EQ(top_children->size(), 3UL); |
| it = top_children->begin(); |
| EXPECT_EQ(*it, f1); |
| it++; |
| EXPECT_EQ(*it, f2); |
| it++; |
| EXPECT_EQ(*it, f3); |
| it++; |
| EXPECT_TRUE(it == top_children->end()); |
| |
| // Iterate over children of the first folder. |
| const OrderedChildSet* f1_children = index_.GetChildren(GetBookmarkId(1)); |
| ASSERT_TRUE(f1_children); |
| ASSERT_EQ(f1_children->size(), 2UL); |
| it = f1_children->begin(); |
| EXPECT_EQ(*it, f1_b1); |
| it++; |
| EXPECT_EQ(*it, f1_b2); |
| it++; |
| EXPECT_TRUE(it == f1_children->end()); |
| |
| // Iterate over children of the second folder. |
| const OrderedChildSet* f2_children = index_.GetChildren(GetBookmarkId(2)); |
| ASSERT_TRUE(f2_children); |
| ASSERT_EQ(f2_children->size(), 1UL); |
| it = f2_children->begin(); |
| EXPECT_EQ(*it, f2_b1); |
| it++; |
| EXPECT_TRUE(it == f2_children->end()); |
| |
| // Check for children of the third folder. |
| const OrderedChildSet* f3_children = index_.GetChildren(GetBookmarkId(3)); |
| EXPECT_FALSE(f3_children); |
| } |
| |
| // Tests removing items. |
| TEST_F(ParentChildIndexTest, RemoveWithHierarchy) { |
| EntryKernel* bm_folder = MakeBookmarkRoot(); |
| index_.Insert(bm_folder); |
| |
| // Just below the root, we have folders f1 and f2. |
| EntryKernel* f1 = MakeBookmark(1, 1, false); |
| EntryKernel* f2 = MakeBookmark(2, 2, false); |
| EntryKernel* f3 = MakeBookmark(3, 3, false); |
| |
| // Under folder f1, we have two bookmarks. |
| EntryKernel* f1_b1 = MakeBookmark(101, 1, false); |
| f1_b1->put(PARENT_ID, GetBookmarkId(1)); |
| EntryKernel* f1_b2 = MakeBookmark(102, 2, false); |
| f1_b2->put(PARENT_ID, GetBookmarkId(1)); |
| |
| // Under folder f2, there is one bookmark. |
| EntryKernel* f2_b1 = MakeBookmark(201, 1, false); |
| f2_b1->put(PARENT_ID, GetBookmarkId(2)); |
| |
| // Under folder f3, there is nothing. |
| |
| // Insert in any order. |
| index_.Insert(f2_b1); |
| index_.Insert(f3); |
| index_.Insert(f1_b2); |
| index_.Insert(f1); |
| index_.Insert(f2); |
| index_.Insert(f1_b1); |
| |
| // Check that all are in the index. |
| EXPECT_TRUE(index_.Contains(f1)); |
| EXPECT_TRUE(index_.Contains(f2)); |
| EXPECT_TRUE(index_.Contains(f3)); |
| EXPECT_TRUE(index_.Contains(f1_b1)); |
| EXPECT_TRUE(index_.Contains(f1_b2)); |
| EXPECT_TRUE(index_.Contains(f2_b1)); |
| |
| // Remove them all in any order. |
| index_.Remove(f3); |
| EXPECT_FALSE(index_.Contains(f3)); |
| index_.Remove(f1_b2); |
| EXPECT_FALSE(index_.Contains(f1_b2)); |
| index_.Remove(f2_b1); |
| EXPECT_FALSE(index_.Contains(f2_b1)); |
| index_.Remove(f1); |
| EXPECT_FALSE(index_.Contains(f1)); |
| index_.Remove(f2); |
| EXPECT_FALSE(index_.Contains(f2)); |
| index_.Remove(f1_b1); |
| EXPECT_FALSE(index_.Contains(f1_b1)); |
| } |
| |
| // Test that involves two non-ordered items. |
| TEST_F(ParentChildIndexTest, UnorderedChildren) { |
| // Make two unique client tag items under the root node. |
| EntryKernel* u1 = MakeUniqueClientItem(1); |
| EntryKernel* u2 = MakeUniqueClientItem(2); |
| |
| EXPECT_FALSE(u1->ShouldMaintainPosition()); |
| EXPECT_FALSE(u2->ShouldMaintainPosition()); |
| |
| index_.Insert(u1); |
| index_.Insert(u2); |
| |
| const OrderedChildSet* children = index_.GetChildren(syncable::Id()); |
| EXPECT_EQ(children->count(u1), 1UL); |
| EXPECT_EQ(children->count(u2), 1UL); |
| EXPECT_EQ(children->size(), 2UL); |
| } |
| |
| // Test ordered and non-ordered entries under the same parent. |
| // TODO(rlarocque): We should not need to support this. |
| TEST_F(ParentChildIndexTest, OrderedAndUnorderedChildren) { |
| EntryKernel* bm_folder = MakeBookmarkRoot(); |
| index_.Insert(bm_folder); |
| |
| EntryKernel* b1 = MakeBookmark(1, 1, false); |
| EntryKernel* b2 = MakeBookmark(2, 2, false); |
| EntryKernel* u1 = MakeUniqueClientItem(1); |
| u1->put(PARENT_ID, GetBookmarkRootId()); |
| |
| index_.Insert(b1); |
| index_.Insert(u1); |
| index_.Insert(b2); |
| |
| const OrderedChildSet* children = index_.GetChildren(GetBookmarkRootId()); |
| ASSERT_TRUE(children); |
| EXPECT_EQ(children->size(), 3UL); |
| |
| // Ensure that the non-positionable item is moved to the far right. |
| OrderedChildSet::const_iterator it = children->begin(); |
| EXPECT_EQ(*it, b1); |
| it++; |
| EXPECT_EQ(*it, b2); |
| it++; |
| EXPECT_EQ(*it, u1); |
| it++; |
| EXPECT_TRUE(it == children->end()); |
| } |
| |
| } // namespace |
| } // namespace syncable |
| } // namespace syncer |
| |