blob: eb97e1501b4c70ad3dee1c981f4b5fcb7d1eb25f [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync_sessions/synced_session_tracker.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "components/sessions/core/serialized_navigation_entry_test_helper.h"
#include "components/sync_sessions/mock_sync_sessions_client.h"
#include "components/sync_sessions/synced_tab_delegate.h"
#include "components/sync_sessions/test_matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::AssertionFailure;
using testing::AssertionResult;
using testing::AssertionSuccess;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::IsNull;
using testing::Ne;
using testing::NotNull;
using testing::Pointee;
using testing::Return;
namespace sync_sessions {
namespace {
const char kValidUrl[] = "http://www.example.com";
const char kSessionName[] = "sessionname";
const sync_pb::SyncEnums::DeviceType kDeviceType =
sync_pb::SyncEnums_DeviceType_TYPE_PHONE;
const char kTag[] = "tag";
const char kTag2[] = "tag2";
const char kTag3[] = "tag3";
const char kTitle[] = "title";
const int kTabNode1 = 0;
const int kTabNode2 = 1;
const int kTabNode3 = 2;
const int kTabNode4 = 3;
const int kTabNode5 = 4;
const SessionID kWindow1 = SessionID::FromSerializedValue(1);
const SessionID kWindow2 = SessionID::FromSerializedValue(2);
const SessionID kWindow3 = SessionID::FromSerializedValue(3);
const SessionID kTab1 = SessionID::FromSerializedValue(15);
const SessionID kTab2 = SessionID::FromSerializedValue(25);
const SessionID kTab3 = SessionID::FromSerializedValue(35);
const SessionID kTab4 = SessionID::FromSerializedValue(45);
const SessionID kTab5 = SessionID::FromSerializedValue(55);
const SessionID kTab6 = SessionID::FromSerializedValue(65);
const SessionID kTab7 = SessionID::FromSerializedValue(75);
MATCHER_P(HasSessionTag, expected_tag, "") {
return arg->session_tag == expected_tag;
}
} // namespace
class SyncedSessionTrackerTest : public testing::Test {
public:
SyncedSessionTrackerTest() : tracker_(&sessions_client_) {}
~SyncedSessionTrackerTest() override {}
TabNodePool* GetLocalTabNodePool() {
return &tracker_.LookupTrackedSession(tracker_.local_session_tag_)
->tab_node_pool;
}
// Returns whether |tab_node_id| refers to a valid tab node that is associated
// with a tab.
bool IsLocalTabNodeAssociated(int tab_node_id) const {
return tracker_
.LookupTabIdFromTabNodeId(tracker_.local_session_tag_, tab_node_id)
.is_valid();
}
// Verify that each tab within a session is allocated one SessionTab object,
// and that that tab object is owned either by the Session itself or the
// |unmapped_tabs_| tab holder.
AssertionResult VerifyTabIntegrity(const std::string& session_tag) {
const SyncedSessionTracker::TrackedSession* session =
tracker_.LookupTrackedSession(session_tag);
if (!session) {
return AssertionFailure()
<< "Not tracked session with tag " << session_tag;
}
// First get all the tabs associated with this session.
int total_tab_count = session->synced_tab_map.size();
// Now traverse the SyncedSession tree to verify the mapped tabs all match
// up.
int mapped_tab_count = 0;
for (auto& window_pair : session->synced_session.windows) {
mapped_tab_count += window_pair.second->wrapped_window.tabs.size();
for (auto& tab : window_pair.second->wrapped_window.tabs) {
const auto tab_map_it = session->synced_tab_map.find(tab->tab_id);
if (tab_map_it == session->synced_tab_map.end()) {
return AssertionFailure() << "Tab ID " << tab->tab_id.id()
<< " has no corresponding synced tab entry";
}
if (tab_map_it->second != tab.get()) {
return AssertionFailure()
<< "Mapped tab " << tab->tab_id.id()
<< " does not match synced tab map " << tab->tab_id.id();
}
}
}
// Wrap up by verifying all unmapped tabs are tracked.
int unmapped_tab_count = session->unmapped_tabs.size();
for (const auto& tab_pair : session->unmapped_tabs) {
if (tab_pair.first != tab_pair.second->tab_id) {
return AssertionFailure()
<< "Unmapped tab " << tab_pair.second->tab_id.id()
<< " associated with wrong tab " << tab_pair.first;
}
const auto tab_map_it =
session->synced_tab_map.find(tab_pair.second->tab_id);
if (tab_map_it == session->synced_tab_map.end()) {
return AssertionFailure() << "Unmapped tab " << tab_pair.second->tab_id
<< " has no corresponding synced tab entry";
}
if (tab_map_it->second != tab_pair.second.get()) {
return AssertionFailure()
<< "Unmapped tab " << tab_pair.second->tab_id.id()
<< " does not match synced tab map " << tab_map_it->second;
}
}
return mapped_tab_count + unmapped_tab_count == total_tab_count
? AssertionSuccess()
: AssertionFailure()
<< " Tab count mismatch. Total: " << total_tab_count
<< ". Mapped + Unmapped: " << mapped_tab_count << " + "
<< unmapped_tab_count;
}
protected:
testing::NiceMock<MockSyncSessionsClient> sessions_client_;
testing::NiceMock<
base::MockCallback<base::RepeatingCallback<bool(int /*tab_node_id*/)>>>
is_tab_node_unsynced_cb_;
SyncedSessionTracker tracker_;
};
TEST_F(SyncedSessionTrackerTest, GetSession) {
SyncedSession* session1 = tracker_.GetSession(kTag);
SyncedSession* session2 = tracker_.GetSession(kTag2);
ASSERT_EQ(session1, tracker_.LookupSession(kTag));
ASSERT_EQ(session1, tracker_.GetSession(kTag));
ASSERT_NE(session1, session2);
// Should clean up memory on its own.
}
TEST_F(SyncedSessionTrackerTest, GetTabUnmapped) {
sessions::SessionTab* tab = tracker_.GetTab(kTag, kTab1);
ASSERT_EQ(tab, tracker_.GetTab(kTag, kTab1));
// Should clean up memory on its own.
}
TEST_F(SyncedSessionTrackerTest, PutWindowInSession) {
tracker_.PutWindowInSession(kTag, kWindow1);
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(1U, session->windows.size());
// Doing it again should have no effect.
tracker_.PutWindowInSession(kTag, kWindow1);
ASSERT_EQ(1U, session->windows.size());
// Should clean up memory on its own.
}
TEST_F(SyncedSessionTrackerTest, PutTabInWindow) {
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(1U, session->windows.size());
ASSERT_EQ(1U, session->windows.at(kWindow1)->wrapped_window.tabs.size());
ASSERT_EQ(tracker_.GetTab(kTag, kTab1),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
// Should clean up memory on its own.
}
TEST_F(SyncedSessionTrackerTest, LookupAllSessions) {
EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
IsEmpty());
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::RAW),
ElementsAre(HasSessionTag(kTag)));
EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
IsEmpty());
sessions::SessionTab* tab = tracker_.GetTab(kTag, kTab1);
ASSERT_TRUE(tab);
tab->navigations.push_back(
sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
kTitle));
EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
ElementsAre(HasSessionTag(kTag)));
tracker_.GetSession(kTag2);
tracker_.PutWindowInSession(kTag2, kWindow1);
tracker_.PutTabInWindow(kTag2, kWindow1, kTab2);
sessions::SessionTab* tab2 = tracker_.GetTab(kTag2, kTab2);
ASSERT_TRUE(tab2);
tab2->navigations.push_back(
sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
kTitle));
EXPECT_THAT(tracker_.LookupAllSessions(SyncedSessionTracker::PRESENTABLE),
ElementsAre(HasSessionTag(kTag), HasSessionTag(kTag2)));
}
TEST_F(SyncedSessionTrackerTest, LookupAllForeignSessions) {
const char kInvalidUrl[] = "invalid.url";
ON_CALL(sessions_client_, ShouldSyncURL(GURL(kInvalidUrl)))
.WillByDefault(testing::Return(false));
EXPECT_THAT(
tracker_.LookupAllForeignSessions(SyncedSessionTracker::PRESENTABLE),
IsEmpty());
tracker_.GetSession(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
sessions::SessionTab* tab = tracker_.GetTab(kTag, kTab1);
ASSERT_TRUE(tab);
tab->navigations.push_back(
sessions::SerializedNavigationEntryTestHelper::CreateNavigation(kValidUrl,
kTitle));
tracker_.GetSession(kTag2);
tracker_.GetSession(kTag3);
tracker_.PutWindowInSession(kTag3, kWindow1);
tracker_.PutTabInWindow(kTag3, kWindow1, kTab1);
tab = tracker_.GetTab(kTag3, kTab1);
ASSERT_TRUE(tab);
tab->navigations.push_back(
sessions::SerializedNavigationEntryTestHelper::CreateNavigation(
kInvalidUrl, kTitle));
// Only the session with a valid window and tab gets returned.
EXPECT_THAT(
tracker_.LookupAllForeignSessions(SyncedSessionTracker::PRESENTABLE),
ElementsAre(HasSessionTag(kTag)));
EXPECT_THAT(tracker_.LookupAllForeignSessions(SyncedSessionTracker::RAW),
ElementsAre(HasSessionTag(kTag), HasSessionTag(kTag2),
HasSessionTag(kTag3)));
}
TEST_F(SyncedSessionTrackerTest, LookupSessionWindows) {
std::vector<const sessions::SessionWindow*> windows;
ASSERT_FALSE(tracker_.LookupSessionWindows(kTag, &windows));
tracker_.GetSession(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutWindowInSession(kTag, kWindow2);
tracker_.GetSession(kTag2);
tracker_.PutWindowInSession(kTag2, kWindow1);
tracker_.PutWindowInSession(kTag2, kWindow2);
ASSERT_TRUE(tracker_.LookupSessionWindows(kTag, &windows));
ASSERT_EQ(2U, windows.size()); // Only windows from kTag session.
ASSERT_NE((sessions::SessionWindow*)nullptr, windows[0]);
ASSERT_NE((sessions::SessionWindow*)nullptr, windows[1]);
ASSERT_NE(windows[1], windows[0]);
}
TEST_F(SyncedSessionTrackerTest, LookupSessionTab) {
ASSERT_THAT(tracker_.LookupSessionTab(kTag, SessionID::InvalidValue()),
IsNull());
ASSERT_THAT(tracker_.LookupSessionTab(kTag, kTab1), IsNull());
tracker_.GetSession(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
ASSERT_THAT(tracker_.LookupSessionTab(kTag, kTab1), NotNull());
}
TEST_F(SyncedSessionTrackerTest, Complex) {
std::vector<sessions::SessionTab *> tabs1, tabs2;
sessions::SessionTab* temp_tab;
ASSERT_TRUE(tracker_.Empty());
ASSERT_EQ(0U, tracker_.num_synced_sessions());
ASSERT_EQ(0U, tracker_.num_synced_tabs(kTag));
tabs1.push_back(tracker_.GetTab(kTag, kTab1));
tabs1.push_back(tracker_.GetTab(kTag, kTab2));
tabs1.push_back(tracker_.GetTab(kTag, kTab3));
ASSERT_EQ(3U, tracker_.num_synced_tabs(kTag));
ASSERT_EQ(1U, tracker_.num_synced_sessions());
temp_tab = tracker_.GetTab(kTag, kTab1); // Already created.
ASSERT_EQ(3U, tracker_.num_synced_tabs(kTag));
ASSERT_EQ(1U, tracker_.num_synced_sessions());
ASSERT_EQ(tabs1[0], temp_tab);
tabs2.push_back(tracker_.GetTab(kTag2, kTab1));
ASSERT_EQ(1U, tracker_.num_synced_tabs(kTag2));
ASSERT_EQ(2U, tracker_.num_synced_sessions());
ASSERT_FALSE(tracker_.DeleteForeignSession(kTag3));
SyncedSession* session = tracker_.GetSession(kTag);
ASSERT_EQ(2U, tracker_.num_synced_sessions());
SyncedSession* session2 = tracker_.GetSession(kTag2);
ASSERT_EQ(2U, tracker_.num_synced_sessions());
SyncedSession* session3 = tracker_.GetSession(kTag3);
session3->device_type = sync_pb::SyncEnums_DeviceType_TYPE_LINUX;
ASSERT_EQ(3U, tracker_.num_synced_sessions());
ASSERT_TRUE(session);
ASSERT_TRUE(session2);
ASSERT_TRUE(session3);
ASSERT_NE(session, session2);
ASSERT_NE(session2, session3);
ASSERT_TRUE(tracker_.DeleteForeignSession(kTag3));
ASSERT_EQ(2U, tracker_.num_synced_sessions());
tracker_.PutWindowInSession(kTag, kWindow1); // Create a window.
tracker_.PutTabInWindow(kTag, kWindow1, kTab3); // No longer unmapped.
ASSERT_EQ(3U, tracker_.num_synced_tabs(kTag)); // Has not changed.
ASSERT_EQ(tabs1[0], tracker_.LookupSessionTab(kTag, kTab1));
ASSERT_EQ(tabs1[2], tracker_.LookupSessionTab(kTag, kTab3));
ASSERT_THAT(tracker_.LookupSessionTab(kTag, kTab4), IsNull());
std::vector<const sessions::SessionWindow*> windows;
ASSERT_TRUE(tracker_.LookupSessionWindows(kTag, &windows));
ASSERT_EQ(1U, windows.size());
ASSERT_TRUE(tracker_.LookupSessionWindows(kTag2, &windows));
ASSERT_EQ(0U, windows.size());
// The sessions don't have valid tabs, lookup should not succeed.
std::vector<const SyncedSession*> sessions;
EXPECT_THAT(
tracker_.LookupAllForeignSessions(SyncedSessionTracker::PRESENTABLE),
IsEmpty());
EXPECT_THAT(tracker_.LookupAllForeignSessions(SyncedSessionTracker::RAW),
ElementsAre(HasSessionTag(kTag), HasSessionTag(kTag2)));
tracker_.Clear();
ASSERT_EQ(0U, tracker_.num_synced_tabs(kTag));
ASSERT_EQ(0U, tracker_.num_synced_tabs(kTag2));
ASSERT_EQ(0U, tracker_.num_synced_sessions());
}
TEST_F(SyncedSessionTrackerTest, ManyGetTabs) {
ASSERT_TRUE(tracker_.Empty());
const int kMaxSessions = 10;
const int kMaxTabs = 1000;
const int kMaxAttempts = 10000;
for (int j = 0; j < kMaxSessions; ++j) {
std::string tag = base::StringPrintf("tag%d", j);
for (int i = 0; i < kMaxAttempts; ++i) {
// More attempts than tabs means we'll sometimes get the same tabs,
// sometimes have to allocate new tabs.
int rand_tab_num = base::RandInt(0, kMaxTabs);
sessions::SessionTab* tab = tracker_.GetTab(
tag, SessionID::FromSerializedValue(rand_tab_num + 1));
ASSERT_TRUE(tab);
}
}
}
TEST_F(SyncedSessionTrackerTest, LookupTabNodeIds) {
tracker_.OnTabNodeSeen(kTag, 1, kTab1);
tracker_.OnTabNodeSeen(kTag, 2, kTab2);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), ElementsAre(1, 2));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag2), IsEmpty());
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), ElementsAre(1, 2));
tracker_.OnTabNodeSeen(kTag, 3, kTab3);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), ElementsAre(1, 2, 3));
tracker_.OnTabNodeSeen(kTag2, 21, kTab4);
tracker_.OnTabNodeSeen(kTag2, 22, kTab5);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag2), ElementsAre(21, 22));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), ElementsAre(1, 2, 3));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag3), IsEmpty());
tracker_.PutWindowInSession(kTag3, kWindow2);
tracker_.PutTabInWindow(kTag3, kWindow2, kTab2);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag3), IsEmpty());
EXPECT_FALSE(tracker_.DeleteForeignSession(kTag3));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag3), IsEmpty());
EXPECT_FALSE(tracker_.DeleteForeignSession(kTag));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), IsEmpty());
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag2), ElementsAre(21, 22));
tracker_.OnTabNodeSeen(kTag2, 21, kTab6);
tracker_.OnTabNodeSeen(kTag2, 23, kTab7);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag2), ElementsAre(21, 22, 23));
EXPECT_FALSE(tracker_.DeleteForeignSession(kTag2));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag2), IsEmpty());
}
TEST_F(SyncedSessionTrackerTest, LookupUnmappedTabs) {
EXPECT_THAT(tracker_.LookupUnmappedTabs(kTag), IsEmpty());
sessions::SessionTab* tab = tracker_.GetTab(kTag, kTab1);
ASSERT_THAT(tab, NotNull());
EXPECT_THAT(tracker_.LookupUnmappedTabs(kTag), ElementsAre(tab));
EXPECT_THAT(tracker_.LookupUnmappedTabs(kTag2), IsEmpty());
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_THAT(tracker_.LookupUnmappedTabs(kTag), IsEmpty());
}
TEST_F(SyncedSessionTrackerTest, SessionTracking) {
ASSERT_TRUE(tracker_.Empty());
// Create some session information that is stale.
SyncedSession* session1 = tracker_.GetSession(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
tracker_.GetTab(kTag, kTab3)->window_id =
SessionID::FromSerializedValue(1); // Unmapped.
tracker_.GetTab(kTag, kTab4)->window_id =
SessionID::FromSerializedValue(1); // Unmapped.
tracker_.PutWindowInSession(kTag, kWindow2);
tracker_.PutTabInWindow(kTag, kWindow2, kTab5);
tracker_.PutTabInWindow(kTag, kWindow2, kTab6);
ASSERT_EQ(2U, session1->windows.size());
ASSERT_EQ(2U, session1->windows[kWindow1]->wrapped_window.tabs.size());
ASSERT_EQ(2U, session1->windows[kWindow2]->wrapped_window.tabs.size());
ASSERT_EQ(6U, tracker_.num_synced_tabs(kTag));
// Create a session that should not be affected.
SyncedSession* session2 = tracker_.GetSession(kTag2);
tracker_.PutWindowInSession(kTag2, kWindow3);
tracker_.PutTabInWindow(kTag2, kWindow3, kTab2);
ASSERT_EQ(1U, session2->windows.size());
ASSERT_EQ(1U, session2->windows[kWindow3]->wrapped_window.tabs.size());
ASSERT_EQ(1U, tracker_.num_synced_tabs(kTag2));
// Reset tracking and get the current windows/tabs.
// We simulate moving a tab from one window to another, then closing the
// first window (including its one remaining tab), and opening a new tab
// on the remaining window.
// New tab, arrived before meta node so unmapped.
tracker_.GetTab(kTag, kTab7);
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
// Tab 1 is closed.
tracker_.PutTabInWindow(kTag, kWindow1, kTab3); // No longer unmapped.
// Tab 3 was unmapped and does not get used.
tracker_.PutTabInWindow(kTag, kWindow1, kTab5); // Moved from window 1.
// Window 1 was closed, along with tab 5.
tracker_.PutTabInWindow(kTag, kWindow1, kTab7); // No longer unmapped.
// Session 2 should not be affected.
tracker_.CleanupSession(kTag);
// Verify that only those parts of the session not owned have been removed.
ASSERT_EQ(1U, session1->windows.size());
ASSERT_EQ(4U, session1->windows[kWindow1]->wrapped_window.tabs.size());
ASSERT_EQ(1U, session2->windows.size());
ASSERT_EQ(1U, session2->windows[kWindow3]->wrapped_window.tabs.size());
ASSERT_EQ(2U, tracker_.num_synced_sessions());
ASSERT_EQ(4U, tracker_.num_synced_tabs(kTag));
ASSERT_EQ(1U, tracker_.num_synced_tabs(kTag2));
ASSERT_TRUE(VerifyTabIntegrity(kTag));
// All memory should be properly deallocated by destructor for the
// SyncedSessionTracker.
}
TEST_F(SyncedSessionTrackerTest, DeleteForeignTab) {
int tab_node_id_1 = 1;
int tab_node_id_2 = 2;
std::set<int> result;
tracker_.OnTabNodeSeen(kTag, tab_node_id_1, kTab1);
tracker_.OnTabNodeSeen(kTag, tab_node_id_2, kTab2);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag),
ElementsAre(tab_node_id_1, tab_node_id_2));
tracker_.DeleteForeignTab(kTag, tab_node_id_1);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), ElementsAre(tab_node_id_2));
tracker_.DeleteForeignTab(kTag, tab_node_id_2);
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag), IsEmpty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, CleanupLocalTabs) {
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
// Start with two restored tab nodes.
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
tracker_.ReassociateLocalTab(kTabNode2, kTab2);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
// Associate with no tabs. The tab pool should now be full.
tracker_.ResetSessionTracking(kTag);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
// Associate with only 1 tab open. A tab node should be reused.
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_EQ(kTabNode1, tracker_.AssociateLocalTabWithFreeTabNode(kTab1));
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
// Simulate a tab opening, which should use the last free tab node.
EXPECT_EQ(kTabNode2, tracker_.AssociateLocalTabWithFreeTabNode(kTab2));
EXPECT_EQ(kTabNode2, tracker_.LookupTabNodeFromTabId(kTag, kTab2));
// Simulate another tab opening, which should create a new associated tab
// node.
EXPECT_EQ(kTabNode3, tracker_.AssociateLocalTabWithFreeTabNode(kTab3));
EXPECT_EQ(kTabNode3, tracker_.LookupTabNodeFromTabId(kTag, kTab3));
// Previous tabs should still be associated.
EXPECT_EQ(kTabNode1, tracker_.LookupTabNodeFromTabId(kTag, kTab1));
EXPECT_EQ(kTabNode2, tracker_.LookupTabNodeFromTabId(kTag, kTab2));
// Associate with no tabs. All tabs should be freed again, and the pool
// should now be full.
tracker_.ResetSessionTracking(kTag);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, CleanupLocalTabsWithDeferredRecycling) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(kDeferRecyclingOfSyncTabNodesIfUnsynced);
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
// Start with four restored tab nodes, one of which is mapped (|kTab1|).
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
tracker_.ReassociateLocalTab(kTabNode2, kTab2);
tracker_.ReassociateLocalTab(kTabNode3, kTab3);
tracker_.ReassociateLocalTab(kTabNode4, kTab4);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
// Modification times are very recent except for one tab, |kTab3|, which is
// old, and hence is expected to be recycled immediately, even if it's
// unsynced.
tracker_.GetTab(kTag, kTab1)->timestamp = base::Time::Now();
tracker_.GetTab(kTag, kTab2)->timestamp = base::Time::Now();
tracker_.GetTab(kTag, kTab3)->timestamp =
base::Time::Now() - base::TimeDelta::FromDays(100);
tracker_.GetTab(kTag, kTab4)->timestamp = base::Time::Now();
// Among the unmapped (closed) ones, |kTab2| and |kTab3| are unsynced.
// However, |kTab3| is old so it's irrelevant whether it's unsynced and the
// callback shouldn't even run.
EXPECT_CALL(is_tab_node_unsynced_cb_, Run(kTabNode2)).WillOnce(Return(true));
EXPECT_CALL(is_tab_node_unsynced_cb_, Run(kTabNode3)).Times(0);
EXPECT_CALL(is_tab_node_unsynced_cb_, Run(kTabNode4)).WillOnce(Return(false));
// During cleanup, only two tabs should be freed:
// - |kTabNode3| because of its age, although it's unsynced.
// - |kTabNode4| because it's synced.
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_EQ(kTabNode1, tracker_.LookupTabNodeFromTabId(kTag, kTab1));
EXPECT_EQ(kTabNode2, tracker_.LookupTabNodeFromTabId(kTag, kTab2));
EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
tracker_.LookupTabNodeFromTabId(kTag, kTab3));
EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
tracker_.LookupTabNodeFromTabId(kTag, kTab4));
// |kTabNode2| now becomes synced (commit succeeded), which means it should be
// freed during cleanup.
EXPECT_CALL(is_tab_node_unsynced_cb_, Run(kTabNode2)).WillOnce(Return(false));
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
tracker_.LookupTabNodeFromTabId(kTag, kTab2));
EXPECT_EQ(kTabNode2, tracker_.AssociateLocalTabWithFreeTabNode(kTab5));
}
TEST_F(SyncedSessionTrackerTest, ReassociateTabMapped) {
// First create the tab normally.
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
// Map it to a window with the same tab id as it was created with.
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(1U, session->windows.size());
ASSERT_EQ(1U, session->windows.at(kWindow1)->wrapped_window.tabs.size());
ASSERT_EQ(tracker_.GetTab(kTag, kTab1),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
// Then reassociate with a new tab id.
tracker_.ReassociateLocalTab(kTabNode1, kTab2);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
// Reset tracking, and put the new tab id into the window.
tracker_.ResetSessionTracking(kTag);
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab2));
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
// Now that it's been mapped, it should be accessible both via the
// GetSession as well as the GetTab.
ASSERT_EQ(tracker_.GetTab(kTag, kTab2),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
ASSERT_EQ(tracker_.LookupTabNodeIds(kTag).size(),
tracker_.LookupTabNodeIds(kTag).count(kTabNode1));
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, ReassociateTabMappedTwice) {
// First create the tab normally.
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
// Map it to a window with the same tab id as it was created with.
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(1U, session->windows.size());
ASSERT_EQ(1U, session->windows.at(kWindow1)->wrapped_window.tabs.size());
EXPECT_EQ(tracker_.GetTab(kTag, kTab1),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
// Then reassociate with a new tab id.
tracker_.ReassociateLocalTab(kTabNode1, kTab2);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
// Tab 1 should no longer be associated with any SessionTab object. At this
// point there's no need to verify it's unmapped state.
EXPECT_THAT(tracker_.LookupSessionTab(kTag, kTab1), IsNull());
// Reset tracking and add back both the old tab and the new tab (both of which
// refer to the same tab node id).
tracker_.ResetSessionTracking(kTag);
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab2));
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
// Now that it's been mapped, it should be accessible both via the
// GetSession as well as the GetTab.
EXPECT_EQ(tracker_.GetTab(kTag, kTab2),
session->windows.at(kWindow1)->wrapped_window.tabs[1].get());
EXPECT_EQ(tracker_.LookupTabNodeIds(kTag).size(),
tracker_.LookupTabNodeIds(kTag).count(kTabNode1));
// Attempting to access the original tab will create a new SessionTab object.
EXPECT_NE(tracker_.GetTab(kTag, kTab1), tracker_.GetTab(kTag, kTab2));
EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
tracker_.LookupTabNodeFromTabId(kTag, kTab1));
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, ReassociateTabUnmapped) {
// First create the old tab in an unmapped state.
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
// Map it to a window, but reassociated with a new tab id.
tracker_.ResetSessionTracking(kTag);
tracker_.ReassociateLocalTab(kTabNode1, kTab2);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab2));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
// Now that it's been mapped, it should be accessible both via the
// GetSession as well as GetTab.
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(tracker_.GetTab(kTag, kTab2),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
ASSERT_EQ(tracker_.LookupTabNodeIds(kTag).size(),
tracker_.LookupTabNodeIds(kTag).count(kTabNode1));
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, ReassociateTabOldUnmappedNewMapped) {
// First create the old tab in an unmapped state.
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
// Map an unseen tab to a window, then reassociate the existing tab to the
// mapped tab id.
tracker_.ResetSessionTracking(kTag);
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
tracker_.ReassociateLocalTab(kTabNode1, kTab2);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
// Now that it's been mapped, it should be accessible both via the
// GetSession as well as GetTab.
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(tracker_.GetTab(kTag, kTab2),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
ASSERT_EQ(tracker_.LookupTabNodeIds(kTag).size(),
tracker_.LookupTabNodeIds(kTag).count(kTabNode1));
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, ReassociateTabSameTabId) {
// First create the tab normally.
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
// Map it to a window.
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(1U, session->windows.size());
ASSERT_EQ(1U, session->windows.at(kWindow1)->wrapped_window.tabs.size());
ASSERT_EQ(tracker_.GetTab(kTag, kTab1),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
// Reassociate, using the same tab id.
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
// Reset tracking, and put the tab id back into the same window.
tracker_.ResetSessionTracking(kTag);
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
EXPECT_TRUE(
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
// Now that it's been mapped, it should be accessible both via the
// GetSession as well as the GetTab.
ASSERT_EQ(tracker_.GetTab(kTag, kTab1),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
ASSERT_EQ(tracker_.LookupTabNodeIds(kTag).size(),
tracker_.LookupTabNodeIds(kTag).count(kTabNode1));
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, ReassociateTabOldMappedNewUnmapped) {
// First create an unmapped tab.
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode1));
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab1));
// Now, map the first one, deleting the second one.
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
const SyncedSession* session = tracker_.LookupSession(kTag);
ASSERT_EQ(1U, session->windows.size());
ASSERT_EQ(1U, session->windows.at(kWindow1)->wrapped_window.tabs.size());
ASSERT_EQ(tracker_.GetTab(kTag, kTab1),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
// Create a second unmapped tab.
tracker_.ReassociateLocalTab(kTabNode2, kTab2);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode2));
EXPECT_TRUE(tracker_.IsTabUnmappedForTesting(kTab2));
// Reassociate the second tab with node of the first tab.
tracker_.ReassociateLocalTab(kTabNode1, kTab2);
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
EXPECT_FALSE(IsLocalTabNodeAssociated(kTabNode2));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
// Now map the new one.
tracker_.ResetSessionTracking(kTag);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
// Now that it's been mapped, it should be accessible both via the
// GetSession as well as the GetTab.
ASSERT_EQ(tracker_.GetTab(kTag, kTab2),
session->windows.at(kWindow1)->wrapped_window.tabs[0].get());
ASSERT_TRUE(VerifyTabIntegrity(kTag));
}
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithHeader) {
sync_pb::SessionSpecifics header;
header.set_session_tag(kTag);
header.mutable_header()->add_window()->set_window_id(kWindow1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab2.id());
UpdateTrackerWithSpecifics(header, base::Time::Now(), &tracker_);
EXPECT_THAT(
tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, {{kWindow1.id(), {kTab1.id(), kTab2.id()}}}));
EXPECT_THAT(tracker_.LookupSessionTab(kTag, kTab1), NotNull());
EXPECT_THAT(tracker_.LookupSessionTab(kTag, kTab2), NotNull());
}
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithIdenticalHeader) {
sync_pb::SessionSpecifics header;
header.set_session_tag(kTag);
header.mutable_header()->add_window()->set_window_id(kWindow1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab2.id());
UpdateTrackerWithSpecifics(header, base::Time::Now(), &tracker_);
ASSERT_THAT(
tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, {{kWindow1.id(), {kTab1.id(), kTab2.id()}}}));
// Update with an exact header entity.
UpdateTrackerWithSpecifics(header, base::Time::Now(), &tracker_);
EXPECT_THAT(
tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, {{kWindow1.id(), {kTab1.id(), kTab2.id()}}}));
EXPECT_THAT(tracker_.LookupSessionTab(kTag, kTab1), NotNull());
}
// Verifies that an invalid header (with duplicated tab IDs) is discarded.
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithHeaderWithDuplicateTabIds) {
sync_pb::SessionSpecifics header;
header.set_session_tag(kTag);
header.mutable_header()->add_window()->set_window_id(kWindow1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab1.id());
UpdateTrackerWithSpecifics(header, base::Time::Now(), &tracker_);
EXPECT_THAT(tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, /*window_id_to_tabs=*/{}));
}
// Verifies that an invalid tab (with invalid ID) is discarded.
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithInvalidTab) {
const int kInvalidTabId = -1;
sync_pb::SessionSpecifics tab;
tab.set_session_tag(kTag);
tab.mutable_tab()->set_tab_id(kInvalidTabId);
UpdateTrackerWithSpecifics(tab, base::Time::Now(), &tracker_);
EXPECT_THAT(tracker_.LookupSessionTab(
kTag, SessionID::FromSerializedValue(kInvalidTabId)),
IsNull());
}
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithTab) {
sync_pb::SessionSpecifics tab;
tab.set_session_tag(kTag);
tab.set_tab_node_id(kTabNode1);
tab.mutable_tab()->set_window_id(kWindow1.id());
tab.mutable_tab()->set_tab_id(kTab1.id());
UpdateTrackerWithSpecifics(tab, base::Time::Now(), &tracker_);
const sessions::SessionTab* tracked_tab =
tracker_.LookupSessionTab(kTag, kTab1);
ASSERT_THAT(tracked_tab, NotNull());
EXPECT_EQ(kWindow1, tracked_tab->window_id);
EXPECT_EQ(false, tracked_tab->pinned);
EXPECT_THAT(tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, /*window_id_to_tabs*/ {}));
// Overwrite some attribute.
tab.mutable_tab()->set_pinned(true);
UpdateTrackerWithSpecifics(tab, base::Time::Now(), &tracker_);
tracked_tab = tracker_.LookupSessionTab(kTag, kTab1);
ASSERT_THAT(tracked_tab, NotNull());
EXPECT_EQ(kWindow1, tracked_tab->window_id);
EXPECT_EQ(true, tracked_tab->pinned);
EXPECT_THAT(tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, /*window_id_to_tabs*/ {}));
}
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithTabThenHeader) {
sync_pb::SessionSpecifics tab;
tab.set_session_tag(kTag);
tab.set_tab_node_id(kTabNode1);
tab.mutable_tab()->set_window_id(kWindow1.id());
tab.mutable_tab()->set_tab_id(kTab1.id());
UpdateTrackerWithSpecifics(tab, base::Time::Now(), &tracker_);
EXPECT_THAT(tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, /*window_id_to_tabs*/ {}));
sync_pb::SessionSpecifics header;
header.set_session_tag(kTag);
header.mutable_header()->add_window()->set_window_id(kWindow1.id());
header.mutable_header()->mutable_window(0)->add_tab(kTab1.id());
UpdateTrackerWithSpecifics(header, base::Time::Now(), &tracker_);
const sessions::SessionTab* tracked_tab =
tracker_.LookupSessionTab(kTag, kTab1);
ASSERT_THAT(tracked_tab, NotNull());
EXPECT_EQ(kWindow1, tracked_tab->window_id);
EXPECT_THAT(tracker_.LookupSession(kTag),
MatchesSyncedSession(
kTag, {{kWindow1.id(), std::vector<int>{kTab1.id()}}}));
}
TEST_F(SyncedSessionTrackerTest, UpdateTrackerWithTwoTabsSameId) {
sync_pb::SessionSpecifics tab1;
tab1.set_session_tag(kTag);
tab1.set_tab_node_id(kTabNode1);
tab1.mutable_tab()->set_window_id(kWindow1.id());
tab1.mutable_tab()->set_tab_id(kTab1.id());
tab1.mutable_tab()->set_pinned(false);
UpdateTrackerWithSpecifics(tab1, base::Time::Now(), &tracker_);
sync_pb::SessionSpecifics tab2;
tab2.set_session_tag(kTag);
tab2.set_tab_node_id(kTabNode2);
tab2.mutable_tab()->set_window_id(kWindow1.id());
tab2.mutable_tab()->set_tab_id(kTab1.id());
tab2.mutable_tab()->set_pinned(true);
UpdateTrackerWithSpecifics(tab2, base::Time::Now(), &tracker_);
EXPECT_THAT(tracker_.LookupSession(kTag),
MatchesSyncedSession(kTag, /*window_id_to_tabs*/ {}));
EXPECT_THAT(tracker_.LookupTabNodeIds(kTag),
ElementsAre(kTabNode1, kTabNode2));
const sessions::SessionTab* tracked_tab =
tracker_.LookupSessionTab(kTag, kTab1);
ASSERT_THAT(tracked_tab, NotNull());
EXPECT_EQ(kWindow1, tracked_tab->window_id);
EXPECT_EQ(true, tracked_tab->pinned);
}
TEST_F(SyncedSessionTrackerTest, SerializeTrackerToSpecifics) {
tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
tracker_.PutWindowInSession(kTag, kWindow1);
tracker_.GetSession(kTag)->windows[kWindow1]->window_type =
sync_pb::SessionWindow_BrowserType_TYPE_TABBED;
tracker_.PutTabInWindow(kTag, kWindow1, kTab1);
tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
// Unmapped tab.
tracker_.GetTab(kTag, kTab3);
// |kTabNode4| will be unassociated, because |kTab1| is associated twice.
tracker_.ReassociateLocalTab(kTabNode4, kTab1);
tracker_.ReassociateLocalTab(kTabNode1, kTab1);
// Regular associations.
tracker_.ReassociateLocalTab(kTabNode2, kTab2);
tracker_.ReassociateLocalTab(kTabNode3, kTab3);
base::MockCallback<base::RepeatingCallback<void(
const std::string& session_name, sync_pb::SessionSpecifics* specifics)>>
callback;
EXPECT_CALL(callback, Run(kSessionName,
Pointee(MatchesHeader(kTag, {kWindow1.id()},
{kTab1.id(), kTab2.id()}))));
EXPECT_CALL(callback, Run(kSessionName,
Pointee(MatchesTab(kTag, kWindow1.id(), kTab1.id(),
kTabNode1, /*urls=*/_))));
EXPECT_CALL(callback, Run(kSessionName,
Pointee(MatchesTab(kTag, kWindow1.id(), kTab2.id(),
kTabNode2, /*urls=*/_))));
EXPECT_CALL(
callback,
Run(kSessionName, Pointee(MatchesTab(kTag, Ne(kWindow1.id()), kTab3.id(),
kTabNode3, /*urls=*/_))));
SerializeTrackerToSpecifics(tracker_, callback.Get());
EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&callback));
// Serialize the header only.
EXPECT_CALL(callback, Run(kSessionName,
Pointee(MatchesHeader(kTag, {kWindow1.id()},
{kTab1.id(), kTab2.id()}))));
SerializePartialTrackerToSpecifics(
tracker_, {{kTag, {TabNodePool::kInvalidTabNodeID}}}, callback.Get());
EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&callback));
// Serialize a known and associated tab.
EXPECT_CALL(callback, Run(kSessionName,
Pointee(MatchesTab(kTag, kWindow1.id(), kTab1.id(),
kTabNode1, /*urls=*/_))));
SerializePartialTrackerToSpecifics(tracker_, {{kTag, {kTabNode1}}},
callback.Get());
EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&callback));
// Attempt to serialize unknown entities.
EXPECT_CALL(callback, Run(_, _)).Times(0);
SerializePartialTrackerToSpecifics(tracker_, {{kTag, {kTabNode5}}},
callback.Get());
SerializePartialTrackerToSpecifics(
tracker_, {{kTag2, {TabNodePool::kInvalidTabNodeID, kTabNode1}}},
callback.Get());
}
TEST_F(SyncedSessionTrackerTest, SerializeTrackerToSpecificsWithEmptyHeader) {
sync_pb::SessionSpecifics tab;
tab.set_session_tag(kTag);
tab.set_tab_node_id(kTabNode1);
tab.mutable_tab()->set_window_id(kWindow1.id());
tab.mutable_tab()->set_tab_id(kTab1.id());
UpdateTrackerWithSpecifics(tab, base::Time::Now(), &tracker_);
sync_pb::SessionSpecifics header;
header.set_session_tag(kTag);
header.mutable_header()->set_client_name(kSessionName);
UpdateTrackerWithSpecifics(header, base::Time::Now(), &tracker_);
base::MockCallback<base::RepeatingCallback<void(
const std::string& session_name, sync_pb::SessionSpecifics* specifics)>>
callback;
EXPECT_CALL(callback,
Run(kSessionName, Pointee(MatchesHeader(kTag, /*window_ids=*/{},
/*tab_ids=*/{}))));
EXPECT_CALL(
callback,
Run(kSessionName, Pointee(MatchesTab(kTag, /*window_id=*/0, kTab1.id(),
kTabNode1, /*urls=*/_))));
SerializeTrackerToSpecifics(tracker_, callback.Get());
}
} // namespace sync_sessions