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