// Copyright 2014 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 "components/sync_sessions/tab_node_pool.h" #include #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace sync_sessions { namespace { using testing::ElementsAre; using testing::UnorderedElementsAre; const int kTabNodeId1 = 10; const int kTabNodeId2 = 5; const SessionID kTabId1 = SessionID::FromSerializedValue(1010); const SessionID kTabId2 = SessionID::FromSerializedValue(1020); const SessionID kTabId3 = SessionID::FromSerializedValue(1030); const SessionID kTabId4 = SessionID::FromSerializedValue(1040); const SessionID kTabId5 = SessionID::FromSerializedValue(1050); const SessionID kTabId6 = SessionID::FromSerializedValue(1060); class SyncTabNodePoolTest : public testing::Test { protected: SyncTabNodePoolTest() = default; int GetMaxUsedTabNodeId() const { return pool_.GetMaxUsedTabNodeIdForTest(); } void AddFreeTabNodes(const std::vector& node_ids) { const SessionID kTmpTabId = SessionID::FromSerializedValue(123); for (int node_id : node_ids) { pool_.ReassociateTabNode(node_id, kTmpTabId); pool_.FreeTab(kTmpTabId); } } TabNodePool pool_; }; TEST_F(SyncTabNodePoolTest, MaxTabNodeIdShouldIncrease) { EXPECT_EQ(-1, GetMaxUsedTabNodeId()); pool_.ReassociateTabNode(10, kTabId1); EXPECT_EQ(10, GetMaxUsedTabNodeId()); pool_.ReassociateTabNode(5, kTabId2); EXPECT_EQ(10, GetMaxUsedTabNodeId()); pool_.ReassociateTabNode(20, kTabId3); EXPECT_EQ(20, GetMaxUsedTabNodeId()); } TEST_F(SyncTabNodePoolTest, MaxTabNodeIdShouldDecrease) { pool_.ReassociateTabNode(10, kTabId1); pool_.ReassociateTabNode(5, kTabId2); pool_.ReassociateTabNode(20, kTabId3); EXPECT_EQ(20, GetMaxUsedTabNodeId()); pool_.FreeTab(kTabId3); ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(20)); EXPECT_EQ(10, GetMaxUsedTabNodeId()); pool_.FreeTab(kTabId1); ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(10)); EXPECT_EQ(5, GetMaxUsedTabNodeId()); pool_.FreeTab(kTabId2); ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(5)); EXPECT_EQ(-1, GetMaxUsedTabNodeId()); } TEST_F(SyncTabNodePoolTest, MaxTabNodeIdShouldNotChange) { pool_.ReassociateTabNode(10, kTabId1); pool_.ReassociateTabNode(5, kTabId2); pool_.ReassociateTabNode(20, kTabId3); EXPECT_EQ(20, GetMaxUsedTabNodeId()); pool_.FreeTab(kTabId1); ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(10)); EXPECT_EQ(20, GetMaxUsedTabNodeId()); pool_.FreeTab(kTabId2); ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(5)); EXPECT_EQ(20, GetMaxUsedTabNodeId()); } TEST_F(SyncTabNodePoolTest, Reassociation) { // Reassociate tab node 1 with tab id 1. pool_.ReassociateTabNode(kTabNodeId1, kTabId1); EXPECT_EQ(kTabId1, pool_.GetTabIdFromTabNodeId(kTabNodeId1)); EXPECT_FALSE(pool_.GetTabIdFromTabNodeId(kTabNodeId2).is_valid()); // Introduce a new tab node associated with the same tab. The old tab node // should get added to the free pool pool_.ReassociateTabNode(kTabNodeId2, kTabId1); EXPECT_FALSE(pool_.GetTabIdFromTabNodeId(kTabNodeId1).is_valid()); EXPECT_EQ(kTabId1, pool_.GetTabIdFromTabNodeId(kTabNodeId2)); // Reassociating the same tab node/tab should have no effect. pool_.ReassociateTabNode(kTabNodeId2, kTabId1); EXPECT_FALSE(pool_.GetTabIdFromTabNodeId(kTabNodeId1).is_valid()); EXPECT_EQ(kTabId1, pool_.GetTabIdFromTabNodeId(kTabNodeId2)); // Reassociating the new tab node with a new tab should just update the // association tables. pool_.ReassociateTabNode(kTabNodeId2, kTabId2); EXPECT_FALSE(pool_.GetTabIdFromTabNodeId(kTabNodeId1).is_valid()); EXPECT_EQ(kTabId2, pool_.GetTabIdFromTabNodeId(kTabNodeId2)); // Reassociating the first tab node should make the pool empty. pool_.ReassociateTabNode(kTabNodeId1, kTabId1); EXPECT_EQ(kTabId1, pool_.GetTabIdFromTabNodeId(kTabNodeId1)); EXPECT_EQ(kTabId2, pool_.GetTabIdFromTabNodeId(kTabNodeId2)); } TEST_F(SyncTabNodePoolTest, ReassociateThenFree) { // Verify old tab nodes are reassociated correctly. pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1); pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2); pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3); // Free tabs 2 and 3. pool_.FreeTab(kTabId2); pool_.FreeTab(kTabId3); EXPECT_EQ(TabNodePool::kInvalidTabNodeID, pool_.GetTabNodeIdFromTabId(kTabId2)); EXPECT_EQ(TabNodePool::kInvalidTabNodeID, pool_.GetTabNodeIdFromTabId(kTabId3)); EXPECT_NE(TabNodePool::kInvalidTabNodeID, pool_.GetTabNodeIdFromTabId(kTabId1)); // Free node pool should have 1 (for kTabId2) and 2 (for kTabId3). EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId4)); EXPECT_EQ(2, pool_.AssociateWithFreeTabNode(kTabId5)); } TEST_F(SyncTabNodePoolTest, AssociateWithFreeTabNode) { ASSERT_EQ(TabNodePool::kInvalidTabNodeID, pool_.GetTabNodeIdFromTabId(kTabId1)); ASSERT_EQ(TabNodePool::kInvalidTabNodeID, pool_.GetTabNodeIdFromTabId(kTabId2)); EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId1)); EXPECT_EQ(0, pool_.GetTabNodeIdFromTabId(kTabId1)); ASSERT_EQ(TabNodePool::kInvalidTabNodeID, pool_.GetTabNodeIdFromTabId(kTabId2)); EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId2)); EXPECT_EQ(1, pool_.GetTabNodeIdFromTabId(kTabId2)); pool_.FreeTab(kTabId1); EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId3)); } TEST_F(SyncTabNodePoolTest, AssociateWithFreeTabNodesContiguous) { pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId1); EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId2)); EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId3)); // Tab node 2 is already used, so it should be skipped. EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId4)); } // Tests that, when *both* a free tab node and a "hole" exists, // AssociateWithFreeTabNode() returns the smallest of them. TEST_F(SyncTabNodePoolTest, AssociateWithFreeTabNodeReturnsMinimum) { // Set up the pool such that tab node 1 is freed, and nodes 0 and 2 are holes // (missing). pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId1); pool_.ReassociateTabNode(/*tab_node_id=*/3, kTabId2); pool_.FreeTab(kTabId1); EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId3)); EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId4)); EXPECT_EQ(2, pool_.AssociateWithFreeTabNode(kTabId5)); } TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesMiddle) { pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1); pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2); pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3); pool_.FreeTab(kTabId2); EXPECT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(1)); EXPECT_EQ(2, GetMaxUsedTabNodeId()); EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId4)); EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId5)); } TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesMax) { pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1); pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2); pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3); pool_.FreeTab(kTabId3); EXPECT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(2)); EXPECT_EQ(1, GetMaxUsedTabNodeId()); EXPECT_EQ(2, pool_.AssociateWithFreeTabNode(kTabId4)); EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId5)); } TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesMultiple) { pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1); pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2); pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3); pool_.FreeTab(kTabId1); pool_.FreeTab(kTabId2); EXPECT_THAT(pool_.CleanupFreeTabNodes(), UnorderedElementsAre(0, 1)); EXPECT_EQ(2, GetMaxUsedTabNodeId()); EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId4)); EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId5)); EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId6)); } TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesAll) { pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1); pool_.FreeTab(kTabId1); EXPECT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(0)); EXPECT_EQ(-1, GetMaxUsedTabNodeId()); EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId4)); } } // namespace } // namespace sync_sessions