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:
ProfileSyncServiceSessionTest()56 ProfileSyncServiceSessionTest()
57 : window_bounds_(0, 1, 2, 3),
58 notified_of_update_(false) {}
sync_service()59 ProfileSyncService* sync_service() { return sync_service_.get(); }
60
ids()61 TestIdFactory* ids() { return sync_service_->id_factory(); }
62
63 protected:
service()64 SessionService* service() { return helper_.service(); }
65
SetUp()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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
TearDown()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
StartSyncService(Task * task,bool will_fail_association)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:
CreateRootTask(ProfileSyncServiceSessionTest * test)150 explicit CreateRootTask(ProfileSyncServiceSessionTest* test)
151 : test_(test), success_(false) {
152 }
153
~CreateRootTask()154 virtual ~CreateRootTask() {}
Run()155 virtual void Run() {
156 success_ = ProfileSyncServiceTestHelper::CreateRoot(
157 syncable::SESSIONS,
158 test_->sync_service()->GetUserShare(),
159 test_->ids());
160 }
161
success()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.
TEST_F(ProfileSyncServiceSessionTest,WriteSessionToNode)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.
TEST_F(ProfileSyncServiceSessionTest,WriteFilledSessionToNode)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.
TEST_F(ProfileSyncServiceSessionTest,FailModelAssociation)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.
TEST_F(ProfileSyncServiceSessionTest,WriteForeignSessionToNode)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.
TEST_F(ProfileSyncServiceSessionTest,UpdatedSyncNodeActionUpdate)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.
TEST_F(ProfileSyncServiceSessionTest,UpdatedSyncNodeActionAdd)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.
TEST_F(ProfileSyncServiceSessionTest,UpdatedSyncNodeActionDelete)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.
TEST_F(ProfileSyncServiceSessionTest,TabNodePoolEmpty)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
TEST_F(ProfileSyncServiceSessionTest,TabNodePoolNonEmpty)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