1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <map> 6 #include <string> 7 8 #include "base/memory/scoped_ptr.h" 9 #include "base/memory/scoped_temp_dir.h" 10 #include "base/message_loop.h" 11 #include "base/stl_util-inl.h" 12 #include "base/task.h" 13 #include "chrome/browser/sessions/session_service.h" 14 #include "chrome/browser/sessions/session_service_test_helper.h" 15 #include "chrome/browser/sync/abstract_profile_sync_service_test.h" 16 #include "chrome/browser/sync/engine/syncapi.h" 17 #include "chrome/browser/sync/glue/session_change_processor.h" 18 #include "chrome/browser/sync/glue/session_data_type_controller.h" 19 #include "chrome/browser/sync/glue/session_model_associator.h" 20 #include "chrome/browser/sync/glue/sync_backend_host.h" 21 #include "chrome/browser/sync/profile_sync_factory_mock.h" 22 #include "chrome/browser/sync/profile_sync_test_util.h" 23 #include "chrome/browser/sync/protocol/session_specifics.pb.h" 24 #include "chrome/browser/sync/protocol/sync.pb.h" 25 #include "chrome/browser/sync/syncable/directory_manager.h" 26 #include "chrome/browser/sync/syncable/model_type.h" 27 #include "chrome/browser/sync/syncable/syncable.h" 28 #include "chrome/browser/sync/test_profile_sync_service.h" 29 #include "chrome/common/net/gaia/gaia_constants.h" 30 #include "chrome/test/browser_with_test_window_test.h" 31 #include "chrome/test/file_test_utils.h" 32 #include "chrome/test/profile_mock.h" 33 #include "chrome/test/sync/engine/test_id_factory.h" 34 #include "chrome/test/testing_profile.h" 35 #include "content/common/notification_observer.h" 36 #include "content/common/notification_registrar.h" 37 #include "content/common/notification_service.h" 38 #include "testing/gmock/include/gmock/gmock.h" 39 #include "testing/gtest/include/gtest/gtest.h" 40 41 using browser_sync::SessionChangeProcessor; 42 using browser_sync::SessionDataTypeController; 43 using browser_sync::SessionModelAssociator; 44 using browser_sync::SyncBackendHost; 45 using sync_api::SyncManager; 46 using testing::_; 47 using testing::Return; 48 using browser_sync::TestIdFactory; 49 50 namespace browser_sync { 51 52 class ProfileSyncServiceSessionTest 53 : public BrowserWithTestWindowTest, 54 public NotificationObserver { 55 public: 56 ProfileSyncServiceSessionTest() 57 : window_bounds_(0, 1, 2, 3), 58 notified_of_update_(false) {} 59 ProfileSyncService* sync_service() { return sync_service_.get(); } 60 61 TestIdFactory* ids() { return sync_service_->id_factory(); } 62 63 protected: 64 SessionService* service() { return helper_.service(); } 65 66 virtual void SetUp() { 67 // BrowserWithTestWindowTest implementation. 68 BrowserWithTestWindowTest::SetUp(); 69 profile()->CreateRequestContext(); 70 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 71 SessionService* session_service = new SessionService(temp_dir_.path()); 72 helper_.set_service(session_service); 73 service()->SetWindowType(window_id_, Browser::TYPE_NORMAL); 74 service()->SetWindowBounds(window_id_, window_bounds_, false); 75 registrar_.Add(this, NotificationType::FOREIGN_SESSION_UPDATED, 76 NotificationService::AllSources()); 77 } 78 79 void Observe(NotificationType type, 80 const NotificationSource& source, 81 const NotificationDetails& details) { 82 switch (type.value) { 83 case NotificationType::FOREIGN_SESSION_UPDATED: 84 notified_of_update_ = true; 85 break; 86 default: 87 NOTREACHED(); 88 break; 89 } 90 } 91 92 virtual void TearDown() { 93 helper_.set_service(NULL); 94 profile()->set_session_service(NULL); 95 sync_service_.reset(); 96 { 97 // The request context gets deleted on the I/O thread. To prevent a leak 98 // supply one here. 99 BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); 100 profile()->ResetRequestContext(); 101 } 102 MessageLoop::current()->RunAllPending(); 103 } 104 105 bool StartSyncService(Task* task, bool will_fail_association) { 106 if (sync_service_.get()) 107 return false; 108 sync_service_.reset(new TestProfileSyncService( 109 &factory_, profile(), "test user", false, task)); 110 profile()->set_session_service(helper_.service()); 111 112 // Register the session data type. 113 model_associator_ = 114 new SessionModelAssociator(sync_service_.get(), 115 true /* setup_for_test */); 116 change_processor_ = new SessionChangeProcessor( 117 sync_service_.get(), model_associator_, 118 true /* setup_for_test */); 119 EXPECT_CALL(factory_, CreateSessionSyncComponents(_, _)). 120 WillOnce(Return(ProfileSyncFactory::SyncComponents( 121 model_associator_, change_processor_))); 122 EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). 123 WillOnce(ReturnNewDataTypeManager()); 124 sync_service_->RegisterDataTypeController( 125 new SessionDataTypeController(&factory_, 126 profile(), 127 sync_service_.get())); 128 profile()->GetTokenService()->IssueAuthTokenForTest( 129 GaiaConstants::kSyncService, "token"); 130 sync_service_->Initialize(); 131 MessageLoop::current()->Run(); 132 return true; 133 } 134 135 // Path used in testing. 136 ScopedTempDir temp_dir_; 137 SessionServiceTestHelper helper_; 138 SessionModelAssociator* model_associator_; 139 SessionChangeProcessor* change_processor_; 140 SessionID window_id_; 141 ProfileSyncFactoryMock factory_; 142 scoped_ptr<TestProfileSyncService> sync_service_; 143 const gfx::Rect window_bounds_; 144 bool notified_of_update_; 145 NotificationRegistrar registrar_; 146 }; 147 148 class CreateRootTask : public Task { 149 public: 150 explicit CreateRootTask(ProfileSyncServiceSessionTest* test) 151 : test_(test), success_(false) { 152 } 153 154 virtual ~CreateRootTask() {} 155 virtual void Run() { 156 success_ = ProfileSyncServiceTestHelper::CreateRoot( 157 syncable::SESSIONS, 158 test_->sync_service()->GetUserShare(), 159 test_->ids()); 160 } 161 162 bool success() { return success_; } 163 164 private: 165 ProfileSyncServiceSessionTest* test_; 166 bool success_; 167 }; 168 169 // Test that we can write this machine's session to a node and retrieve it. 170 TEST_F(ProfileSyncServiceSessionTest, WriteSessionToNode) { 171 CreateRootTask task(this); 172 ASSERT_TRUE(StartSyncService(&task, false)); 173 ASSERT_TRUE(task.success()); 174 ASSERT_EQ(model_associator_->GetSessionService(), helper_.service()); 175 176 // Check that the DataTypeController associated the models. 177 bool has_nodes; 178 ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); 179 ASSERT_TRUE(has_nodes); 180 std::string machine_tag = model_associator_->GetCurrentMachineTag(); 181 int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); 182 ASSERT_NE(sync_api::kInvalidId, sync_id); 183 184 // Check that we can get the correct session specifics back from the node. 185 sync_api::ReadTransaction trans(sync_service_->GetUserShare()); 186 sync_api::ReadNode node(&trans); 187 ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS, 188 machine_tag)); 189 const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics()); 190 ASSERT_EQ(machine_tag, specifics.session_tag()); 191 ASSERT_TRUE(specifics.has_header()); 192 const sync_pb::SessionHeader& header_s = specifics.header(); 193 ASSERT_EQ(0, header_s.window_size()); 194 } 195 196 // Test that we can fill this machine's session, write it to a node, 197 // and then retrieve it. 198 TEST_F(ProfileSyncServiceSessionTest, WriteFilledSessionToNode) { 199 CreateRootTask task(this); 200 ASSERT_TRUE(StartSyncService(&task, false)); 201 ASSERT_TRUE(task.success()); 202 203 // Check that the DataTypeController associated the models. 204 bool has_nodes; 205 ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); 206 ASSERT_TRUE(has_nodes); 207 AddTab(browser(), GURL("http://foo/1")); 208 NavigateAndCommitActiveTab(GURL("http://foo/2")); 209 AddTab(browser(), GURL("http://bar/1")); 210 NavigateAndCommitActiveTab(GURL("http://bar/2")); 211 212 ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); 213 ASSERT_TRUE(has_nodes); 214 std::string machine_tag = model_associator_->GetCurrentMachineTag(); 215 int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); 216 ASSERT_NE(sync_api::kInvalidId, sync_id); 217 218 // Check that this machine's data is not included in the foreign windows. 219 std::vector<const ForeignSession*> foreign_sessions; 220 model_associator_->GetAllForeignSessions(&foreign_sessions); 221 ASSERT_EQ(foreign_sessions.size(), 0U); 222 223 // Get the tabs for this machine from the node and check that they were 224 // filled. 225 SessionModelAssociator::TabLinksMap tab_map = model_associator_->tab_map_; 226 ASSERT_EQ(2U, tab_map.size()); 227 // Tabs are ordered by sessionid in tab_map, so should be able to traverse 228 // the tree based on order of tabs created 229 SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin(); 230 ASSERT_EQ(2, iter->second.tab()->controller().entry_count()); 231 ASSERT_EQ(GURL("http://foo/1"), iter->second.tab()->controller(). 232 GetEntryAtIndex(0)->virtual_url()); 233 ASSERT_EQ(GURL("http://foo/2"), iter->second.tab()->controller(). 234 GetEntryAtIndex(1)->virtual_url()); 235 iter++; 236 ASSERT_EQ(2, iter->second.tab()->controller().entry_count()); 237 ASSERT_EQ(GURL("http://bar/1"), iter->second.tab()->controller(). 238 GetEntryAtIndex(0)->virtual_url()); 239 ASSERT_EQ(GURL("http://bar/2"), iter->second.tab()->controller(). 240 GetEntryAtIndex(1)->virtual_url()); 241 } 242 243 // Test that we fail on a failed model association. 244 TEST_F(ProfileSyncServiceSessionTest, FailModelAssociation) { 245 ASSERT_TRUE(StartSyncService(NULL, true)); 246 ASSERT_TRUE(sync_service_->unrecoverable_error_detected()); 247 } 248 249 // Write a foreign session to a node, and then retrieve it. 250 TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { 251 CreateRootTask task(this); 252 ASSERT_TRUE(StartSyncService(&task, false)); 253 ASSERT_TRUE(task.success()); 254 255 // Check that the DataTypeController associated the models. 256 bool has_nodes; 257 ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); 258 ASSERT_TRUE(has_nodes); 259 260 // Fill an instance of session specifics with a foreign session's data. 261 sync_pb::SessionSpecifics meta_specifics; 262 std::string machine_tag = "session_sync123"; 263 meta_specifics.set_session_tag(machine_tag); 264 sync_pb::SessionHeader* header_s = meta_specifics.mutable_header(); 265 sync_pb::SessionWindow* window_s = header_s->add_window(); 266 window_s->add_tab(0); 267 window_s->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); 268 window_s->set_selected_tab_index(1); 269 270 sync_pb::SessionSpecifics tab_specifics; 271 tab_specifics.set_session_tag(machine_tag); 272 sync_pb::SessionTab* tab = tab_specifics.mutable_tab(); 273 tab->set_tab_visual_index(13); 274 tab->set_current_navigation_index(3); 275 tab->set_pinned(true); 276 tab->set_extension_app_id("app_id"); 277 sync_pb::TabNavigation* navigation = tab->add_navigation(); 278 navigation->set_index(12); 279 navigation->set_virtual_url("http://foo/1"); 280 navigation->set_referrer("referrer"); 281 navigation->set_title("title"); 282 navigation->set_page_transition(sync_pb::TabNavigation_PageTransition_TYPED); 283 284 // Update the server with the session specifics. 285 { 286 model_associator_->AssociateForeignSpecifics(meta_specifics, 0); 287 model_associator_->AssociateForeignSpecifics(tab_specifics, 0); 288 } 289 290 // Check that the foreign session was associated and retrieve the data. 291 std::vector<const ForeignSession*> foreign_sessions; 292 model_associator_->GetAllForeignSessions(&foreign_sessions); 293 ASSERT_EQ(1U, foreign_sessions.size()); 294 ASSERT_EQ(machine_tag, foreign_sessions[0]->foreign_session_tag); 295 ASSERT_EQ(1U, foreign_sessions[0]->windows.size()); 296 ASSERT_EQ(1U, foreign_sessions[0]->windows[0]->tabs.size()); 297 ASSERT_EQ(1U, foreign_sessions[0]->windows[0]->tabs[0]->navigations.size()); 298 ASSERT_EQ(foreign_sessions[0]->foreign_session_tag, machine_tag); 299 ASSERT_EQ(1, foreign_sessions[0]->windows[0]->selected_tab_index); 300 ASSERT_EQ(1, foreign_sessions[0]->windows[0]->type); 301 ASSERT_EQ(13, foreign_sessions[0]->windows[0]->tabs[0]->tab_visual_index); 302 ASSERT_EQ(3, 303 foreign_sessions[0]->windows[0]->tabs[0]->current_navigation_index); 304 ASSERT_TRUE(foreign_sessions[0]->windows[0]->tabs[0]->pinned); 305 ASSERT_EQ("app_id", 306 foreign_sessions[0]->windows[0]->tabs[0]->extension_app_id); 307 ASSERT_EQ(12, 308 foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].index()); 309 ASSERT_EQ(GURL("referrer"), 310 foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].referrer()); 311 ASSERT_EQ(string16(ASCIIToUTF16("title")), 312 foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].title()); 313 ASSERT_EQ(PageTransition::TYPED, 314 foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].transition()); 315 ASSERT_EQ(GURL("http://foo/1"), 316 foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].virtual_url()); 317 } 318 319 // Test the DataTypeController on update. 320 TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionUpdate) { 321 CreateRootTask task(this); 322 ASSERT_TRUE(StartSyncService(&task, false)); 323 ASSERT_TRUE(task.success()); 324 int64 node_id = model_associator_->GetSyncIdFromSessionTag( 325 model_associator_->GetCurrentMachineTag()); 326 scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord); 327 record->action = SyncManager::ChangeRecord::ACTION_UPDATE; 328 record->id = node_id; 329 ASSERT_FALSE(notified_of_update_); 330 { 331 sync_api::WriteTransaction trans(sync_service_->GetUserShare()); 332 change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1); 333 } 334 ASSERT_TRUE(notified_of_update_); 335 } 336 337 // Test the DataTypeController on add. 338 TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionAdd) { 339 CreateRootTask task(this); 340 ASSERT_TRUE(StartSyncService(&task, false)); 341 ASSERT_TRUE(task.success()); 342 343 int64 node_id = model_associator_->GetSyncIdFromSessionTag( 344 model_associator_->GetCurrentMachineTag()); 345 scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord); 346 record->action = SyncManager::ChangeRecord::ACTION_ADD; 347 record->id = node_id; 348 ASSERT_FALSE(notified_of_update_); 349 { 350 sync_api::WriteTransaction trans(sync_service_->GetUserShare()); 351 change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1); 352 } 353 ASSERT_TRUE(notified_of_update_); 354 } 355 356 // Test the DataTypeController on delete. 357 TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionDelete) { 358 CreateRootTask task(this); 359 ASSERT_TRUE(StartSyncService(&task, false)); 360 ASSERT_TRUE(task.success()); 361 362 int64 node_id = model_associator_->GetSyncIdFromSessionTag( 363 model_associator_->GetCurrentMachineTag()); 364 scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord); 365 record->action = SyncManager::ChangeRecord::ACTION_DELETE; 366 record->id = node_id; 367 ASSERT_FALSE(notified_of_update_); 368 { 369 sync_api::WriteTransaction trans(sync_service_->GetUserShare()); 370 change_processor_->ApplyChangesFromSyncModel(&trans, record.get(), 1); 371 } 372 ASSERT_TRUE(notified_of_update_); 373 } 374 // Test the TabNodePool when it starts off empty. 375 TEST_F(ProfileSyncServiceSessionTest, TabNodePoolEmpty) { 376 CreateRootTask task(this); 377 ASSERT_TRUE(StartSyncService(&task, false)); 378 ASSERT_TRUE(task.success()); 379 380 std::vector<int64> node_ids; 381 ASSERT_EQ(0U, model_associator_->tab_pool_.capacity()); 382 ASSERT_TRUE(model_associator_->tab_pool_.empty()); 383 ASSERT_TRUE(model_associator_->tab_pool_.full()); 384 const size_t num_ids = 10; 385 for (size_t i = 0; i < num_ids; ++i) { 386 int64 id = model_associator_->tab_pool_.GetFreeTabNode(); 387 ASSERT_GT(id, -1); 388 node_ids.push_back(id); 389 } 390 ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); 391 ASSERT_TRUE(model_associator_->tab_pool_.empty()); 392 ASSERT_FALSE(model_associator_->tab_pool_.full()); 393 for (size_t i = 0; i < num_ids; ++i) { 394 model_associator_->tab_pool_.FreeTabNode(node_ids[i]); 395 } 396 ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); 397 ASSERT_FALSE(model_associator_->tab_pool_.empty()); 398 ASSERT_TRUE(model_associator_->tab_pool_.full()); 399 } 400 401 // Test the TabNodePool when it starts off with nodes 402 TEST_F(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty) { 403 CreateRootTask task(this); 404 ASSERT_TRUE(StartSyncService(&task, false)); 405 ASSERT_TRUE(task.success()); 406 407 const size_t num_starting_nodes = 3; 408 for (size_t i = 0; i < num_starting_nodes; ++i) { 409 model_associator_->tab_pool_.AddTabNode(i); 410 } 411 412 std::vector<int64> node_ids; 413 ASSERT_EQ(num_starting_nodes, model_associator_->tab_pool_.capacity()); 414 ASSERT_FALSE(model_associator_->tab_pool_.empty()); 415 ASSERT_TRUE(model_associator_->tab_pool_.full()); 416 const size_t num_ids = 10; 417 for (size_t i = 0; i < num_ids; ++i) { 418 int64 id = model_associator_->tab_pool_.GetFreeTabNode(); 419 ASSERT_GT(id, -1); 420 node_ids.push_back(id); 421 } 422 ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); 423 ASSERT_TRUE(model_associator_->tab_pool_.empty()); 424 ASSERT_FALSE(model_associator_->tab_pool_.full()); 425 for (size_t i = 0; i < num_ids; ++i) { 426 model_associator_->tab_pool_.FreeTabNode(node_ids[i]); 427 } 428 ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); 429 ASSERT_FALSE(model_associator_->tab_pool_.empty()); 430 ASSERT_TRUE(model_associator_->tab_pool_.full()); 431 } 432 433 } // namespace browser_sync 434