Home | History | Annotate | Download | only in glue
      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.
      4 
      5 #include "base/basictypes.h"
      6 #include "base/bind.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/history/history_types.h"
     13 #include "chrome/browser/sync/glue/typed_url_model_associator.h"
     14 #include "chrome/browser/sync/profile_sync_service_mock.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"
     19 #include "url/gurl.h"
     20 
     21 using browser_sync::TypedUrlModelAssociator;
     22 using content::BrowserThread;
     23 
     24 namespace {
     25 class SyncTypedUrlModelAssociatorTest : public testing::Test {
     26  public:
     27   static history::URLRow MakeTypedUrlRow(const char* url,
     28                                          const char* title,
     29                                          int typed_count,
     30                                          int64 last_visit,
     31                                          bool hidden,
     32                                          history::VisitVector* visits) {
     33     GURL gurl(url);
     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         content::PAGE_TRANSITION_RELOAD, 0));
     43     history_url.set_visit_count(visits->size());
     44     return history_url;
     45   }
     46 
     47   static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
     48                                                           const char* title,
     49                                                           int64 last_visit,
     50                                                           bool hidden) {
     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(content::PAGE_TRANSITION_TYPED);
     57     return typed_url;
     58   }
     59 
     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());
     66   }
     67 };
     68 
     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
     75   // test to finish.
     76   *associator = new TypedUrlModelAssociator(mock, NULL, NULL);
     77 
     78   // Signal frontend to call AbortAssociation and proceed after it's called.
     79   startup->Signal();
     80   aborted->Wait();
     81   syncer::SyncError error = (*associator)->AssociateModels(NULL, NULL);
     82   EXPECT_TRUE(error.IsSet());
     83   EXPECT_EQ("datatype error was encountered: Association was aborted.",
     84             error.message());
     85   delete *associator;
     86   done->Signal();
     87 }
     88 
     89 } // namespace
     90 
     91 TEST_F(SyncTypedUrlModelAssociatorTest, MergeUrls) {
     92   history::VisitVector visits1;
     93   history::URLRow row1(MakeTypedUrlRow("http://pie.com/", "pie",
     94                                        2, 3, false, &visits1));
     95   sync_pb::TypedUrlSpecifics specs1(MakeTypedUrlSpecifics("http://pie.com/",
     96                                                           "pie",
     97                                                           3, false));
     98   history::URLRow new_row1(GURL("http://pie.com/"));
     99   std::vector<history::VisitInfo> new_visits1;
    100   EXPECT_TRUE(TypedUrlModelAssociator::MergeUrls(specs1, row1, &visits1,
    101       &new_row1, &new_visits1) == TypedUrlModelAssociator::DIFF_NONE);
    102 
    103   history::VisitVector visits2;
    104   history::URLRow row2(MakeTypedUrlRow("http://pie.com/", "pie",
    105                                        2, 3, false, &visits2));
    106   sync_pb::TypedUrlSpecifics specs2(MakeTypedUrlSpecifics("http://pie.com/",
    107                                                           "pie",
    108                                                           3, true));
    109   history::VisitVector expected_visits2;
    110   history::URLRow expected2(MakeTypedUrlRow("http://pie.com/", "pie",
    111                                             2, 3, true, &expected_visits2));
    112   history::URLRow new_row2(GURL("http://pie.com/"));
    113   std::vector<history::VisitInfo> new_visits2;
    114   EXPECT_TRUE(TypedUrlModelAssociator::MergeUrls(specs2, row2, &visits2,
    115                                                  &new_row2, &new_visits2) ==
    116       TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED);
    117   EXPECT_TRUE(URLsEqual(new_row2, expected2));
    118 
    119   history::VisitVector visits3;
    120   history::URLRow row3(MakeTypedUrlRow("http://pie.com/", "pie",
    121                                        2, 3, false, &visits3));
    122   sync_pb::TypedUrlSpecifics specs3(MakeTypedUrlSpecifics("http://pie.com/",
    123                                                           "pie2",
    124                                                           3, true));
    125   history::VisitVector expected_visits3;
    126   history::URLRow expected3(MakeTypedUrlRow("http://pie.com/", "pie2",
    127                                             2, 3, true, &expected_visits3));
    128   history::URLRow new_row3(GURL("http://pie.com/"));
    129   std::vector<history::VisitInfo> new_visits3;
    130   EXPECT_EQ(TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED |
    131             TypedUrlModelAssociator::DIFF_NONE,
    132             TypedUrlModelAssociator::MergeUrls(specs3, row3, &visits3,
    133                                                &new_row3, &new_visits3));
    134   EXPECT_TRUE(URLsEqual(new_row3, expected3));
    135 
    136   // Create one node in history DB with timestamp of 3, and one node in sync
    137   // DB with timestamp of 4. Result should contain one new item (4).
    138   history::VisitVector visits4;
    139   history::URLRow row4(MakeTypedUrlRow("http://pie.com/", "pie",
    140                                        2, 3, false, &visits4));
    141   sync_pb::TypedUrlSpecifics specs4(MakeTypedUrlSpecifics("http://pie.com/",
    142                                                           "pie2",
    143                                                           4, false));
    144   history::VisitVector expected_visits4;
    145   history::URLRow expected4(MakeTypedUrlRow("http://pie.com/", "pie2",
    146                                             2, 4, false, &expected_visits4));
    147   history::URLRow new_row4(GURL("http://pie.com/"));
    148   std::vector<history::VisitInfo> new_visits4;
    149   EXPECT_EQ(TypedUrlModelAssociator::DIFF_UPDATE_NODE |
    150             TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED |
    151             TypedUrlModelAssociator::DIFF_LOCAL_VISITS_ADDED,
    152             TypedUrlModelAssociator::MergeUrls(specs4, row4, &visits4,
    153                                                &new_row4, &new_visits4));
    154   EXPECT_EQ(1U, new_visits4.size());
    155   EXPECT_EQ(specs4.visits(0), new_visits4[0].first.ToInternalValue());
    156   EXPECT_TRUE(URLsEqual(new_row4, expected4));
    157   EXPECT_EQ(2U, visits4.size());
    158 
    159   history::VisitVector visits5;
    160   history::URLRow row5(MakeTypedUrlRow("http://pie.com/", "pie",
    161                                        1, 4, false, &visits5));
    162   sync_pb::TypedUrlSpecifics specs5(MakeTypedUrlSpecifics("http://pie.com/",
    163                                                           "pie",
    164                                                           3, false));
    165   history::VisitVector expected_visits5;
    166   history::URLRow expected5(MakeTypedUrlRow("http://pie.com/", "pie",
    167                                             2, 3, false, &expected_visits5));
    168   history::URLRow new_row5(GURL("http://pie.com/"));
    169   std::vector<history::VisitInfo> new_visits5;
    170 
    171   // UPDATE_NODE should be set because row5 has a newer last_visit timestamp.
    172   EXPECT_EQ(TypedUrlModelAssociator::DIFF_UPDATE_NODE |
    173             TypedUrlModelAssociator::DIFF_NONE,
    174             TypedUrlModelAssociator::MergeUrls(specs5, row5, &visits5,
    175                                                &new_row5, &new_visits5));
    176   EXPECT_TRUE(URLsEqual(new_row5, expected5));
    177   EXPECT_EQ(0U, new_visits5.size());
    178 }
    179 
    180 TEST_F(SyncTypedUrlModelAssociatorTest, MergeUrlsAfterExpiration) {
    181   // Tests to ensure that we don't resurrect expired URLs (URLs that have been
    182   // deleted from the history DB but still exist in the sync DB).
    183 
    184   // First, create a history row that has two visits, with timestamps 2 and 3.
    185   history::VisitVector(history_visits);
    186   history_visits.push_back(history::VisitRow(
    187       0, base::Time::FromInternalValue(2), 0, content::PAGE_TRANSITION_TYPED,
    188       0));
    189   history::URLRow history_url(MakeTypedUrlRow("http://pie.com/", "pie",
    190                                               2, 3, false, &history_visits));
    191 
    192   // Now, create a sync node with visits at timestamps 1, 2, 3, 4.
    193   sync_pb::TypedUrlSpecifics node(MakeTypedUrlSpecifics("http://pie.com/",
    194                                                         "pie", 1, false));
    195   node.add_visits(2);
    196   node.add_visits(3);
    197   node.add_visits(4);
    198   node.add_visit_transitions(2);
    199   node.add_visit_transitions(3);
    200   node.add_visit_transitions(4);
    201   history::URLRow new_history_url(history_url.url());
    202   std::vector<history::VisitInfo> new_visits;
    203   EXPECT_EQ(TypedUrlModelAssociator::DIFF_NONE |
    204             TypedUrlModelAssociator::DIFF_LOCAL_VISITS_ADDED,
    205             TypedUrlModelAssociator::MergeUrls(
    206                 node, history_url, &history_visits, &new_history_url,
    207                 &new_visits));
    208   EXPECT_TRUE(URLsEqual(history_url, new_history_url));
    209   EXPECT_EQ(1U, new_visits.size());
    210   EXPECT_EQ(4U, new_visits[0].first.ToInternalValue());
    211   // We should not sync the visit with timestamp #1 since it is earlier than
    212   // any other visit for this URL in the history DB. But we should sync visit
    213   // #4.
    214   EXPECT_EQ(3U, history_visits.size());
    215   EXPECT_EQ(2U, history_visits[0].visit_time.ToInternalValue());
    216   EXPECT_EQ(3U, history_visits[1].visit_time.ToInternalValue());
    217   EXPECT_EQ(4U, history_visits[2].visit_time.ToInternalValue());
    218 }
    219 
    220 TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsSame) {
    221   history::VisitVector old_visits;
    222   sync_pb::TypedUrlSpecifics new_url;
    223 
    224   const int64 visits[] = { 1024, 2065, 65534, 1237684 };
    225 
    226   for (size_t c = 0; c < arraysize(visits); ++c) {
    227     old_visits.push_back(history::VisitRow(
    228         0, base::Time::FromInternalValue(visits[c]), 0,
    229         content::PAGE_TRANSITION_TYPED, 0));
    230     new_url.add_visits(visits[c]);
    231     new_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
    232   }
    233 
    234   std::vector<history::VisitInfo> new_visits;
    235   history::VisitVector removed_visits;
    236 
    237   TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
    238                                       &new_visits, &removed_visits);
    239   EXPECT_TRUE(new_visits.empty());
    240   EXPECT_TRUE(removed_visits.empty());
    241 }
    242 
    243 TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsRemove) {
    244   history::VisitVector old_visits;
    245   sync_pb::TypedUrlSpecifics new_url;
    246 
    247   const int64 visits_left[] = { 1, 2, 1024, 1500, 2065, 6000,
    248                                 65534, 1237684, 2237684 };
    249   const int64 visits_right[] = { 1024, 2065, 65534, 1237684 };
    250 
    251   // DiffVisits will not remove the first visit, because we never delete visits
    252   // from the start of the array (since those visits can get truncated by the
    253   // size-limiting code).
    254   const int64 visits_removed[] = { 1500, 6000, 2237684 };
    255 
    256   for (size_t c = 0; c < arraysize(visits_left); ++c) {
    257     old_visits.push_back(history::VisitRow(
    258         0, base::Time::FromInternalValue(visits_left[c]), 0,
    259         content::PAGE_TRANSITION_TYPED, 0));
    260   }
    261 
    262   for (size_t c = 0; c < arraysize(visits_right); ++c) {
    263     new_url.add_visits(visits_right[c]);
    264     new_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
    265   }
    266 
    267   std::vector<history::VisitInfo> new_visits;
    268   history::VisitVector removed_visits;
    269 
    270   TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
    271                                       &new_visits, &removed_visits);
    272   EXPECT_TRUE(new_visits.empty());
    273   ASSERT_EQ(removed_visits.size(), arraysize(visits_removed));
    274   for (size_t c = 0; c < arraysize(visits_removed); ++c) {
    275     EXPECT_EQ(removed_visits[c].visit_time.ToInternalValue(),
    276               visits_removed[c]);
    277   }
    278 }
    279 
    280 TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsAdd) {
    281   history::VisitVector old_visits;
    282   sync_pb::TypedUrlSpecifics new_url;
    283 
    284   const int64 visits_left[] = { 1024, 2065, 65534, 1237684 };
    285   const int64 visits_right[] = { 1, 1024, 1500, 2065, 6000,
    286                                 65534, 1237684, 2237684 };
    287 
    288   const int64 visits_added[] = { 1, 1500, 6000, 2237684 };
    289 
    290   for (size_t c = 0; c < arraysize(visits_left); ++c) {
    291     old_visits.push_back(history::VisitRow(
    292         0, base::Time::FromInternalValue(visits_left[c]), 0,
    293         content::PAGE_TRANSITION_TYPED, 0));
    294   }
    295 
    296   for (size_t c = 0; c < arraysize(visits_right); ++c) {
    297     new_url.add_visits(visits_right[c]);
    298     new_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
    299   }
    300 
    301   std::vector<history::VisitInfo> new_visits;
    302   history::VisitVector removed_visits;
    303 
    304   TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
    305                                       &new_visits, &removed_visits);
    306   EXPECT_TRUE(removed_visits.empty());
    307   ASSERT_TRUE(new_visits.size() == arraysize(visits_added));
    308   for (size_t c = 0; c < arraysize(visits_added); ++c) {
    309     EXPECT_EQ(new_visits[c].first.ToInternalValue(),
    310               visits_added[c]);
    311     EXPECT_EQ(new_visits[c].second, content::PAGE_TRANSITION_TYPED);
    312   }
    313 }
    314 
    315 static history::VisitRow CreateVisit(content::PageTransition type,
    316                                      int64 timestamp) {
    317   return history::VisitRow(0, base::Time::FromInternalValue(timestamp), 0,
    318                            type, 0);
    319 }
    320 
    321 TEST_F(SyncTypedUrlModelAssociatorTest, WriteTypedUrlSpecifics) {
    322   history::VisitVector visits;
    323   visits.push_back(CreateVisit(content::PAGE_TRANSITION_TYPED, 1));
    324   visits.push_back(CreateVisit(content::PAGE_TRANSITION_RELOAD, 2));
    325   visits.push_back(CreateVisit(content::PAGE_TRANSITION_LINK, 3));
    326 
    327   history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
    328                                       1, 100, false, &visits));
    329   sync_pb::TypedUrlSpecifics typed_url;
    330   TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
    331   // RELOAD visits should be removed.
    332   EXPECT_EQ(2, typed_url.visits_size());
    333   EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
    334   EXPECT_EQ(1, typed_url.visits(0));
    335   EXPECT_EQ(3, typed_url.visits(1));
    336   EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
    337       static_cast<content::PageTransition>(typed_url.visit_transitions(0)));
    338   EXPECT_EQ(content::PAGE_TRANSITION_LINK,
    339       static_cast<content::PageTransition>(typed_url.visit_transitions(1)));
    340 }
    341 
    342 TEST_F(SyncTypedUrlModelAssociatorTest, TooManyVisits) {
    343   history::VisitVector visits;
    344   int64 timestamp = 1000;
    345   visits.push_back(CreateVisit(content::PAGE_TRANSITION_TYPED, timestamp++));
    346   for (int i = 0 ; i < 100; ++i) {
    347     visits.push_back(CreateVisit(content::PAGE_TRANSITION_LINK, timestamp++));
    348   }
    349   history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
    350                                       1, timestamp++, false, &visits));
    351   sync_pb::TypedUrlSpecifics typed_url;
    352   TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
    353   // # visits should be capped at 100.
    354   EXPECT_EQ(100, typed_url.visits_size());
    355   EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
    356   EXPECT_EQ(1000, typed_url.visits(0));
    357   // Visit with timestamp of 1001 should be omitted since we should have
    358   // skipped that visit to stay under the cap.
    359   EXPECT_EQ(1002, typed_url.visits(1));
    360   EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
    361       static_cast<content::PageTransition>(typed_url.visit_transitions(0)));
    362   EXPECT_EQ(content::PAGE_TRANSITION_LINK,
    363       static_cast<content::PageTransition>(typed_url.visit_transitions(1)));
    364 }
    365 
    366 TEST_F(SyncTypedUrlModelAssociatorTest, TooManyTypedVisits) {
    367   history::VisitVector visits;
    368   int64 timestamp = 1000;
    369   for (int i = 0 ; i < 102; ++i) {
    370     visits.push_back(CreateVisit(content::PAGE_TRANSITION_TYPED, timestamp++));
    371     visits.push_back(CreateVisit(content::PAGE_TRANSITION_LINK, timestamp++));
    372     visits.push_back(CreateVisit(content::PAGE_TRANSITION_RELOAD, timestamp++));
    373   }
    374   history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
    375                                       1, timestamp++, false, &visits));
    376   sync_pb::TypedUrlSpecifics typed_url;
    377   TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
    378   // # visits should be capped at 100.
    379   EXPECT_EQ(100, typed_url.visits_size());
    380   EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
    381   // First two typed visits should be skipped.
    382   EXPECT_EQ(1006, typed_url.visits(0));
    383 
    384   // Ensure there are no non-typed visits since that's all that should fit.
    385   for (int i = 0; i < typed_url.visits_size(); ++i) {
    386     EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
    387         static_cast<content::PageTransition>(typed_url.visit_transitions(i)));
    388   }
    389 }
    390 
    391 TEST_F(SyncTypedUrlModelAssociatorTest, NoTypedVisits) {
    392   history::VisitVector visits;
    393   history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
    394                                       1, 1000, false, &visits));
    395   sync_pb::TypedUrlSpecifics typed_url;
    396   TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
    397   // URLs with no typed URL visits should be translated to a URL with one
    398   // reload visit.
    399   EXPECT_EQ(1, typed_url.visits_size());
    400   EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
    401   // First two typed visits should be skipped.
    402   EXPECT_EQ(1000, typed_url.visits(0));
    403   EXPECT_EQ(content::PAGE_TRANSITION_RELOAD,
    404       static_cast<content::PageTransition>(typed_url.visit_transitions(0)));
    405 }
    406 
    407 // This test verifies that we can abort model association from the UI thread.
    408 // We start up the model associator on the DB thread, block until we abort the
    409 // association on the UI thread, then ensure that AssociateModels() returns
    410 // false.
    411 TEST_F(SyncTypedUrlModelAssociatorTest, TestAbort) {
    412   content::TestBrowserThreadBundle thread_bundle(
    413       content::TestBrowserThreadBundle::REAL_DB_THREAD);
    414   base::WaitableEvent startup(false, false);
    415   base::WaitableEvent aborted(false, false);
    416   base::WaitableEvent done(false, false);
    417   TestingProfile profile;
    418   ProfileSyncServiceMock mock(&profile);
    419   TypedUrlModelAssociator* associator(NULL);
    420   // Fire off to the DB thread to create the model associator and start
    421   // model association.
    422   base::Closure callback = base::Bind(
    423       &CreateModelAssociatorAsync, &startup, &aborted, &done, &associator,
    424                                    &mock);
    425   BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, callback);
    426   // Wait for the model associator to get created and start assocation.
    427   ASSERT_TRUE(startup.TimedWait(TestTimeouts::action_timeout()));
    428   // Abort the model assocation - this should be callable from any thread.
    429   associator->AbortAssociation();
    430   // Tell the remote thread to continue.
    431   aborted.Signal();
    432   // Block until CreateModelAssociator() exits.
    433   ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
    434 }
    435