1 // Copyright (c) 2012 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.
5 #include "base/basictypes.h"
7 #include "base/strings/string_piece.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/synchronization/waitable_event.h"
10 #include "base/test/test_timeouts.h"
11 #include "base/time/time.h"
12 #include "chrome/browser/sync/glue/typed_url_model_associator.h"
13 #include "chrome/browser/sync/profile_sync_service_mock.h"
14 #include "components/history/core/browser/history_types.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/test/test_browser_thread_bundle.h"
17 #include "sync/protocol/typed_url_specifics.pb.h"
18 #include "testing/gtest/include/gtest/gtest.h"
21 using browser_sync::TypedUrlModelAssociator;
22 using content::BrowserThread;
25 class SyncTypedUrlModelAssociatorTest : public testing::Test {
27 static history::URLRow MakeTypedUrlRow(const char* url,
32 history::VisitVector* visits) {
34 history::URLRow history_url(gurl);
35 history_url.set_title(base::UTF8ToUTF16(title));
36 history_url.set_typed_count(typed_count);
37 history_url.set_last_visit(
38 base::Time::FromInternalValue(last_visit));
39 history_url.set_hidden(hidden);
40 visits->push_back(history::VisitRow(
41 history_url.id(), history_url.last_visit(), 0,
42 ui::PAGE_TRANSITION_RELOAD, 0));
43 history_url.set_visit_count(visits->size());
47 static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
51 sync_pb::TypedUrlSpecifics typed_url;
52 typed_url.set_url(url);
53 typed_url.set_title(title);
54 typed_url.set_hidden(hidden);
55 typed_url.add_visits(last_visit);
56 typed_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
60 static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
61 // Only compare synced fields (ignore typed_count and visit_count as those
62 // are maintained by the history subsystem).
63 return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
64 (lhs.title().compare(rhs.title()) == 0) &&
65 (lhs.hidden() == rhs.hidden());
69 static void CreateModelAssociatorAsync(base::WaitableEvent* startup,
70 base::WaitableEvent* aborted,
71 base::WaitableEvent* done,
72 TypedUrlModelAssociator** associator,
73 ProfileSyncServiceMock* mock) {
74 // Grab the done lock - when we exit, this will be released and allow the
76 *associator = new TypedUrlModelAssociator(mock, NULL, NULL);
78 // Signal frontend to call AbortAssociation and proceed after it's called.
81 syncer::SyncError error = (*associator)->AssociateModels(NULL, NULL);
82 EXPECT_TRUE(error.IsSet());
83 EXPECT_EQ("Association was aborted.", error.message());
90 TEST_F(SyncTypedUrlModelAssociatorTest, MergeUrls) {
91 history::VisitVector visits1;
92 history::URLRow row1(MakeTypedUrlRow("http://pie.com/", "pie",
93 2, 3, false, &visits1));
94 sync_pb::TypedUrlSpecifics specs1(MakeTypedUrlSpecifics("http://pie.com/",
97 history::URLRow new_row1(GURL("http://pie.com/"));
98 std::vector<history::VisitInfo> new_visits1;
99 EXPECT_TRUE(TypedUrlModelAssociator::MergeUrls(specs1, row1, &visits1,
100 &new_row1, &new_visits1) == TypedUrlModelAssociator::DIFF_NONE);
102 history::VisitVector visits2;
103 history::URLRow row2(MakeTypedUrlRow("http://pie.com/", "pie",
104 2, 3, false, &visits2));
105 sync_pb::TypedUrlSpecifics specs2(MakeTypedUrlSpecifics("http://pie.com/",
108 history::VisitVector expected_visits2;
109 history::URLRow expected2(MakeTypedUrlRow("http://pie.com/", "pie",
110 2, 3, true, &expected_visits2));
111 history::URLRow new_row2(GURL("http://pie.com/"));
112 std::vector<history::VisitInfo> new_visits2;
113 EXPECT_TRUE(TypedUrlModelAssociator::MergeUrls(specs2, row2, &visits2,
114 &new_row2, &new_visits2) ==
115 TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED);
116 EXPECT_TRUE(URLsEqual(new_row2, expected2));
118 history::VisitVector visits3;
119 history::URLRow row3(MakeTypedUrlRow("http://pie.com/", "pie",
120 2, 3, false, &visits3));
121 sync_pb::TypedUrlSpecifics specs3(MakeTypedUrlSpecifics("http://pie.com/",
124 history::VisitVector expected_visits3;
125 history::URLRow expected3(MakeTypedUrlRow("http://pie.com/", "pie2",
126 2, 3, true, &expected_visits3));
127 history::URLRow new_row3(GURL("http://pie.com/"));
128 std::vector<history::VisitInfo> new_visits3;
129 EXPECT_EQ(TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED |
130 TypedUrlModelAssociator::DIFF_NONE,
131 TypedUrlModelAssociator::MergeUrls(specs3, row3, &visits3,
132 &new_row3, &new_visits3));
133 EXPECT_TRUE(URLsEqual(new_row3, expected3));
135 // Create one node in history DB with timestamp of 3, and one node in sync
136 // DB with timestamp of 4. Result should contain one new item (4).
137 history::VisitVector visits4;
138 history::URLRow row4(MakeTypedUrlRow("http://pie.com/", "pie",
139 2, 3, false, &visits4));
140 sync_pb::TypedUrlSpecifics specs4(MakeTypedUrlSpecifics("http://pie.com/",
143 history::VisitVector expected_visits4;
144 history::URLRow expected4(MakeTypedUrlRow("http://pie.com/", "pie2",
145 2, 4, false, &expected_visits4));
146 history::URLRow new_row4(GURL("http://pie.com/"));
147 std::vector<history::VisitInfo> new_visits4;
148 EXPECT_EQ(TypedUrlModelAssociator::DIFF_UPDATE_NODE |
149 TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED |
150 TypedUrlModelAssociator::DIFF_LOCAL_VISITS_ADDED,
151 TypedUrlModelAssociator::MergeUrls(specs4, row4, &visits4,
152 &new_row4, &new_visits4));
153 EXPECT_EQ(1U, new_visits4.size());
154 EXPECT_EQ(specs4.visits(0), new_visits4[0].first.ToInternalValue());
155 EXPECT_TRUE(URLsEqual(new_row4, expected4));
156 EXPECT_EQ(2U, visits4.size());
158 history::VisitVector visits5;
159 history::URLRow row5(MakeTypedUrlRow("http://pie.com/", "pie",
160 1, 4, false, &visits5));
161 sync_pb::TypedUrlSpecifics specs5(MakeTypedUrlSpecifics("http://pie.com/",
164 history::VisitVector expected_visits5;
165 history::URLRow expected5(MakeTypedUrlRow("http://pie.com/", "pie",
166 2, 3, false, &expected_visits5));
167 history::URLRow new_row5(GURL("http://pie.com/"));
168 std::vector<history::VisitInfo> new_visits5;
170 // UPDATE_NODE should be set because row5 has a newer last_visit timestamp.
171 EXPECT_EQ(TypedUrlModelAssociator::DIFF_UPDATE_NODE |
172 TypedUrlModelAssociator::DIFF_NONE,
173 TypedUrlModelAssociator::MergeUrls(specs5, row5, &visits5,
174 &new_row5, &new_visits5));
175 EXPECT_TRUE(URLsEqual(new_row5, expected5));
176 EXPECT_EQ(0U, new_visits5.size());
179 TEST_F(SyncTypedUrlModelAssociatorTest, MergeUrlsAfterExpiration) {
180 // Tests to ensure that we don't resurrect expired URLs (URLs that have been
181 // deleted from the history DB but still exist in the sync DB).
183 // First, create a history row that has two visits, with timestamps 2 and 3.
184 history::VisitVector(history_visits);
185 history_visits.push_back(history::VisitRow(
186 0, base::Time::FromInternalValue(2), 0, ui::PAGE_TRANSITION_TYPED,
188 history::URLRow history_url(MakeTypedUrlRow("http://pie.com/", "pie",
189 2, 3, false, &history_visits));
191 // Now, create a sync node with visits at timestamps 1, 2, 3, 4.
192 sync_pb::TypedUrlSpecifics node(MakeTypedUrlSpecifics("http://pie.com/",
197 node.add_visit_transitions(2);
198 node.add_visit_transitions(3);
199 node.add_visit_transitions(4);
200 history::URLRow new_history_url(history_url.url());
201 std::vector<history::VisitInfo> new_visits;
202 EXPECT_EQ(TypedUrlModelAssociator::DIFF_NONE |
203 TypedUrlModelAssociator::DIFF_LOCAL_VISITS_ADDED,
204 TypedUrlModelAssociator::MergeUrls(
205 node, history_url, &history_visits, &new_history_url,
207 EXPECT_TRUE(URLsEqual(history_url, new_history_url));
208 EXPECT_EQ(1U, new_visits.size());
209 EXPECT_EQ(4U, new_visits[0].first.ToInternalValue());
210 // We should not sync the visit with timestamp #1 since it is earlier than
211 // any other visit for this URL in the history DB. But we should sync visit
213 EXPECT_EQ(3U, history_visits.size());
214 EXPECT_EQ(2U, history_visits[0].visit_time.ToInternalValue());
215 EXPECT_EQ(3U, history_visits[1].visit_time.ToInternalValue());
216 EXPECT_EQ(4U, history_visits[2].visit_time.ToInternalValue());
219 TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsSame) {
220 history::VisitVector old_visits;
221 sync_pb::TypedUrlSpecifics new_url;
223 const int64 visits[] = { 1024, 2065, 65534, 1237684 };
225 for (size_t c = 0; c < arraysize(visits); ++c) {
226 old_visits.push_back(history::VisitRow(
227 0, base::Time::FromInternalValue(visits[c]), 0,
228 ui::PAGE_TRANSITION_TYPED, 0));
229 new_url.add_visits(visits[c]);
230 new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
233 std::vector<history::VisitInfo> new_visits;
234 history::VisitVector removed_visits;
236 TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
237 &new_visits, &removed_visits);
238 EXPECT_TRUE(new_visits.empty());
239 EXPECT_TRUE(removed_visits.empty());
242 TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsRemove) {
243 history::VisitVector old_visits;
244 sync_pb::TypedUrlSpecifics new_url;
246 const int64 visits_left[] = { 1, 2, 1024, 1500, 2065, 6000,
247 65534, 1237684, 2237684 };
248 const int64 visits_right[] = { 1024, 2065, 65534, 1237684 };
250 // DiffVisits will not remove the first visit, because we never delete visits
251 // from the start of the array (since those visits can get truncated by the
252 // size-limiting code).
253 const int64 visits_removed[] = { 1500, 6000, 2237684 };
255 for (size_t c = 0; c < arraysize(visits_left); ++c) {
256 old_visits.push_back(history::VisitRow(
257 0, base::Time::FromInternalValue(visits_left[c]), 0,
258 ui::PAGE_TRANSITION_TYPED, 0));
261 for (size_t c = 0; c < arraysize(visits_right); ++c) {
262 new_url.add_visits(visits_right[c]);
263 new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
266 std::vector<history::VisitInfo> new_visits;
267 history::VisitVector removed_visits;
269 TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
270 &new_visits, &removed_visits);
271 EXPECT_TRUE(new_visits.empty());
272 ASSERT_EQ(removed_visits.size(), arraysize(visits_removed));
273 for (size_t c = 0; c < arraysize(visits_removed); ++c) {
274 EXPECT_EQ(removed_visits[c].visit_time.ToInternalValue(),
279 TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsAdd) {
280 history::VisitVector old_visits;
281 sync_pb::TypedUrlSpecifics new_url;
283 const int64 visits_left[] = { 1024, 2065, 65534, 1237684 };
284 const int64 visits_right[] = { 1, 1024, 1500, 2065, 6000,
285 65534, 1237684, 2237684 };
287 const int64 visits_added[] = { 1, 1500, 6000, 2237684 };
289 for (size_t c = 0; c < arraysize(visits_left); ++c) {
290 old_visits.push_back(history::VisitRow(
291 0, base::Time::FromInternalValue(visits_left[c]), 0,
292 ui::PAGE_TRANSITION_TYPED, 0));
295 for (size_t c = 0; c < arraysize(visits_right); ++c) {
296 new_url.add_visits(visits_right[c]);
297 new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
300 std::vector<history::VisitInfo> new_visits;
301 history::VisitVector removed_visits;
303 TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
304 &new_visits, &removed_visits);
305 EXPECT_TRUE(removed_visits.empty());
306 ASSERT_TRUE(new_visits.size() == arraysize(visits_added));
307 for (size_t c = 0; c < arraysize(visits_added); ++c) {
308 EXPECT_EQ(new_visits[c].first.ToInternalValue(),
310 EXPECT_EQ(new_visits[c].second, ui::PAGE_TRANSITION_TYPED);
314 static history::VisitRow CreateVisit(ui::PageTransition type,
316 return history::VisitRow(0, base::Time::FromInternalValue(timestamp), 0,
320 TEST_F(SyncTypedUrlModelAssociatorTest, WriteTypedUrlSpecifics) {
321 history::VisitVector visits;
322 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, 1));
323 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, 2));
324 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, 3));
326 history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
327 1, 100, false, &visits));
328 sync_pb::TypedUrlSpecifics typed_url;
329 TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
330 // RELOAD visits should be removed.
331 EXPECT_EQ(2, typed_url.visits_size());
332 EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
333 EXPECT_EQ(1, typed_url.visits(0));
334 EXPECT_EQ(3, typed_url.visits(1));
335 EXPECT_EQ(ui::PAGE_TRANSITION_TYPED,
336 ui::PageTransitionFromInt(typed_url.visit_transitions(0)));
337 EXPECT_EQ(ui::PAGE_TRANSITION_LINK,
338 ui::PageTransitionFromInt(typed_url.visit_transitions(1)));
341 TEST_F(SyncTypedUrlModelAssociatorTest, TooManyVisits) {
342 history::VisitVector visits;
343 int64 timestamp = 1000;
344 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++));
345 for (int i = 0 ; i < 100; ++i) {
346 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++));
348 history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
349 1, timestamp++, false, &visits));
350 sync_pb::TypedUrlSpecifics typed_url;
351 TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
352 // # visits should be capped at 100.
353 EXPECT_EQ(100, typed_url.visits_size());
354 EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
355 EXPECT_EQ(1000, typed_url.visits(0));
356 // Visit with timestamp of 1001 should be omitted since we should have
357 // skipped that visit to stay under the cap.
358 EXPECT_EQ(1002, typed_url.visits(1));
359 EXPECT_EQ(ui::PAGE_TRANSITION_TYPED,
360 ui::PageTransitionFromInt(typed_url.visit_transitions(0)));
361 EXPECT_EQ(ui::PAGE_TRANSITION_LINK,
362 ui::PageTransitionFromInt(typed_url.visit_transitions(1)));
365 TEST_F(SyncTypedUrlModelAssociatorTest, TooManyTypedVisits) {
366 history::VisitVector visits;
367 int64 timestamp = 1000;
368 for (int i = 0 ; i < 102; ++i) {
369 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++));
370 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++));
371 visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, timestamp++));
373 history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
374 1, timestamp++, false, &visits));
375 sync_pb::TypedUrlSpecifics typed_url;
376 TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
377 // # visits should be capped at 100.
378 EXPECT_EQ(100, typed_url.visits_size());
379 EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
380 // First two typed visits should be skipped.
381 EXPECT_EQ(1006, typed_url.visits(0));
383 // Ensure there are no non-typed visits since that's all that should fit.
384 for (int i = 0; i < typed_url.visits_size(); ++i) {
385 EXPECT_EQ(ui::PAGE_TRANSITION_TYPED,
386 ui::PageTransitionFromInt(typed_url.visit_transitions(i)));
390 TEST_F(SyncTypedUrlModelAssociatorTest, NoTypedVisits) {
391 history::VisitVector visits;
392 history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
393 1, 1000, false, &visits));
394 sync_pb::TypedUrlSpecifics typed_url;
395 TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
396 // URLs with no typed URL visits should be translated to a URL with one
398 EXPECT_EQ(1, typed_url.visits_size());
399 EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
400 // First two typed visits should be skipped.
401 EXPECT_EQ(1000, typed_url.visits(0));
402 EXPECT_EQ(ui::PAGE_TRANSITION_RELOAD,
403 ui::PageTransitionFromInt(typed_url.visit_transitions(0)));
406 // This test verifies that we can abort model association from the UI thread.
407 // We start up the model associator on the DB thread, block until we abort the
408 // association on the UI thread, then ensure that AssociateModels() returns
410 TEST_F(SyncTypedUrlModelAssociatorTest, TestAbort) {
411 content::TestBrowserThreadBundle thread_bundle(
412 content::TestBrowserThreadBundle::REAL_DB_THREAD);
413 base::WaitableEvent startup(false, false);
414 base::WaitableEvent aborted(false, false);
415 base::WaitableEvent done(false, false);
416 TestingProfile profile;
417 ProfileSyncServiceMock mock(&profile);
418 TypedUrlModelAssociator* associator(NULL);
419 // Fire off to the DB thread to create the model associator and start
420 // model association.
421 base::Closure callback = base::Bind(
422 &CreateModelAssociatorAsync, &startup, &aborted, &done, &associator,
424 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, callback);
425 // Wait for the model associator to get created and start assocation.
426 ASSERT_TRUE(startup.TimedWait(TestTimeouts::action_timeout()));
427 // Abort the model assocation - this should be callable from any thread.
428 associator->AbortAssociation();
429 // Tell the remote thread to continue.
431 // Block until CreateModelAssociator() exits.
432 ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));