root/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. MakeTypedUrlRow
  2. MakeTypedUrlSpecifics
  3. URLsEqual
  4. CreateModelAssociatorAsync
  5. TEST_F
  6. TEST_F
  7. TEST_F
  8. TEST_F
  9. TEST_F
  10. CreateVisit
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/sync/glue/typed_url_model_associator.h"
#include "chrome/browser/sync/profile_sync_service_mock.h"
#include "content/public/test/test_browser_thread.h"
#include "sync/protocol/typed_url_specifics.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using browser_sync::TypedUrlModelAssociator;
using content::BrowserThread;

namespace {
class SyncTypedUrlModelAssociatorTest : public testing::Test {
 public:
  static history::URLRow MakeTypedUrlRow(const char* url,
                                         const char* title,
                                         int typed_count,
                                         int64 last_visit,
                                         bool hidden,
                                         history::VisitVector* visits) {
    GURL gurl(url);
    history::URLRow history_url(gurl);
    history_url.set_title(base::UTF8ToUTF16(title));
    history_url.set_typed_count(typed_count);
    history_url.set_last_visit(
        base::Time::FromInternalValue(last_visit));
    history_url.set_hidden(hidden);
    visits->push_back(history::VisitRow(
        history_url.id(), history_url.last_visit(), 0,
        content::PAGE_TRANSITION_RELOAD, 0));
    history_url.set_visit_count(visits->size());
    return history_url;
  }

  static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
                                                          const char* title,
                                                          int64 last_visit,
                                                          bool hidden) {
    sync_pb::TypedUrlSpecifics typed_url;
    typed_url.set_url(url);
    typed_url.set_title(title);
    typed_url.set_hidden(hidden);
    typed_url.add_visits(last_visit);
    typed_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
    return typed_url;
  }

  static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
    // Only compare synced fields (ignore typed_count and visit_count as those
    // are maintained by the history subsystem).
    return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
           (lhs.title().compare(rhs.title()) == 0) &&
           (lhs.hidden() == rhs.hidden());
  }
};

static void CreateModelAssociatorAsync(base::WaitableEvent* startup,
                                       base::WaitableEvent* aborted,
                                       base::WaitableEvent* done,
                                       TypedUrlModelAssociator** associator,
                                       ProfileSyncServiceMock* mock) {
  // Grab the done lock - when we exit, this will be released and allow the
  // test to finish.
  *associator = new TypedUrlModelAssociator(mock, NULL, NULL);

  // Signal frontend to call AbortAssociation and proceed after it's called.
  startup->Signal();
  aborted->Wait();
  syncer::SyncError error = (*associator)->AssociateModels(NULL, NULL);
  EXPECT_TRUE(error.IsSet());
  EXPECT_EQ("datatype error was encountered: Association was aborted.",
            error.message());
  delete *associator;
  done->Signal();
}

} // namespace

TEST_F(SyncTypedUrlModelAssociatorTest, MergeUrls) {
  history::VisitVector visits1;
  history::URLRow row1(MakeTypedUrlRow("http://pie.com/", "pie",
                                       2, 3, false, &visits1));
  sync_pb::TypedUrlSpecifics specs1(MakeTypedUrlSpecifics("http://pie.com/",
                                                          "pie",
                                                          3, false));
  history::URLRow new_row1(GURL("http://pie.com/"));
  std::vector<history::VisitInfo> new_visits1;
  EXPECT_TRUE(TypedUrlModelAssociator::MergeUrls(specs1, row1, &visits1,
      &new_row1, &new_visits1) == TypedUrlModelAssociator::DIFF_NONE);

  history::VisitVector visits2;
  history::URLRow row2(MakeTypedUrlRow("http://pie.com/", "pie",
                                       2, 3, false, &visits2));
  sync_pb::TypedUrlSpecifics specs2(MakeTypedUrlSpecifics("http://pie.com/",
                                                          "pie",
                                                          3, true));
  history::VisitVector expected_visits2;
  history::URLRow expected2(MakeTypedUrlRow("http://pie.com/", "pie",
                                            2, 3, true, &expected_visits2));
  history::URLRow new_row2(GURL("http://pie.com/"));
  std::vector<history::VisitInfo> new_visits2;
  EXPECT_TRUE(TypedUrlModelAssociator::MergeUrls(specs2, row2, &visits2,
                                                 &new_row2, &new_visits2) ==
      TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED);
  EXPECT_TRUE(URLsEqual(new_row2, expected2));

  history::VisitVector visits3;
  history::URLRow row3(MakeTypedUrlRow("http://pie.com/", "pie",
                                       2, 3, false, &visits3));
  sync_pb::TypedUrlSpecifics specs3(MakeTypedUrlSpecifics("http://pie.com/",
                                                          "pie2",
                                                          3, true));
  history::VisitVector expected_visits3;
  history::URLRow expected3(MakeTypedUrlRow("http://pie.com/", "pie2",
                                            2, 3, true, &expected_visits3));
  history::URLRow new_row3(GURL("http://pie.com/"));
  std::vector<history::VisitInfo> new_visits3;
  EXPECT_EQ(TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED |
            TypedUrlModelAssociator::DIFF_NONE,
            TypedUrlModelAssociator::MergeUrls(specs3, row3, &visits3,
                                               &new_row3, &new_visits3));
  EXPECT_TRUE(URLsEqual(new_row3, expected3));

  // Create one node in history DB with timestamp of 3, and one node in sync
  // DB with timestamp of 4. Result should contain one new item (4).
  history::VisitVector visits4;
  history::URLRow row4(MakeTypedUrlRow("http://pie.com/", "pie",
                                       2, 3, false, &visits4));
  sync_pb::TypedUrlSpecifics specs4(MakeTypedUrlSpecifics("http://pie.com/",
                                                          "pie2",
                                                          4, false));
  history::VisitVector expected_visits4;
  history::URLRow expected4(MakeTypedUrlRow("http://pie.com/", "pie2",
                                            2, 4, false, &expected_visits4));
  history::URLRow new_row4(GURL("http://pie.com/"));
  std::vector<history::VisitInfo> new_visits4;
  EXPECT_EQ(TypedUrlModelAssociator::DIFF_UPDATE_NODE |
            TypedUrlModelAssociator::DIFF_LOCAL_ROW_CHANGED |
            TypedUrlModelAssociator::DIFF_LOCAL_VISITS_ADDED,
            TypedUrlModelAssociator::MergeUrls(specs4, row4, &visits4,
                                               &new_row4, &new_visits4));
  EXPECT_EQ(1U, new_visits4.size());
  EXPECT_EQ(specs4.visits(0), new_visits4[0].first.ToInternalValue());
  EXPECT_TRUE(URLsEqual(new_row4, expected4));
  EXPECT_EQ(2U, visits4.size());

  history::VisitVector visits5;
  history::URLRow row5(MakeTypedUrlRow("http://pie.com/", "pie",
                                       1, 4, false, &visits5));
  sync_pb::TypedUrlSpecifics specs5(MakeTypedUrlSpecifics("http://pie.com/",
                                                          "pie",
                                                          3, false));
  history::VisitVector expected_visits5;
  history::URLRow expected5(MakeTypedUrlRow("http://pie.com/", "pie",
                                            2, 3, false, &expected_visits5));
  history::URLRow new_row5(GURL("http://pie.com/"));
  std::vector<history::VisitInfo> new_visits5;

  // UPDATE_NODE should be set because row5 has a newer last_visit timestamp.
  EXPECT_EQ(TypedUrlModelAssociator::DIFF_UPDATE_NODE |
            TypedUrlModelAssociator::DIFF_NONE,
            TypedUrlModelAssociator::MergeUrls(specs5, row5, &visits5,
                                               &new_row5, &new_visits5));
  EXPECT_TRUE(URLsEqual(new_row5, expected5));
  EXPECT_EQ(0U, new_visits5.size());
}

TEST_F(SyncTypedUrlModelAssociatorTest, MergeUrlsAfterExpiration) {
  // Tests to ensure that we don't resurrect expired URLs (URLs that have been
  // deleted from the history DB but still exist in the sync DB).

  // First, create a history row that has two visits, with timestamps 2 and 3.
  history::VisitVector(history_visits);
  history_visits.push_back(history::VisitRow(
      0, base::Time::FromInternalValue(2), 0, content::PAGE_TRANSITION_TYPED,
      0));
  history::URLRow history_url(MakeTypedUrlRow("http://pie.com/", "pie",
                                              2, 3, false, &history_visits));

  // Now, create a sync node with visits at timestamps 1, 2, 3, 4.
  sync_pb::TypedUrlSpecifics node(MakeTypedUrlSpecifics("http://pie.com/",
                                                        "pie", 1, false));
  node.add_visits(2);
  node.add_visits(3);
  node.add_visits(4);
  node.add_visit_transitions(2);
  node.add_visit_transitions(3);
  node.add_visit_transitions(4);
  history::URLRow new_history_url(history_url.url());
  std::vector<history::VisitInfo> new_visits;
  EXPECT_EQ(TypedUrlModelAssociator::DIFF_NONE |
            TypedUrlModelAssociator::DIFF_LOCAL_VISITS_ADDED,
            TypedUrlModelAssociator::MergeUrls(
                node, history_url, &history_visits, &new_history_url,
                &new_visits));
  EXPECT_TRUE(URLsEqual(history_url, new_history_url));
  EXPECT_EQ(1U, new_visits.size());
  EXPECT_EQ(4U, new_visits[0].first.ToInternalValue());
  // We should not sync the visit with timestamp #1 since it is earlier than
  // any other visit for this URL in the history DB. But we should sync visit
  // #4.
  EXPECT_EQ(3U, history_visits.size());
  EXPECT_EQ(2U, history_visits[0].visit_time.ToInternalValue());
  EXPECT_EQ(3U, history_visits[1].visit_time.ToInternalValue());
  EXPECT_EQ(4U, history_visits[2].visit_time.ToInternalValue());
}

TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsSame) {
  history::VisitVector old_visits;
  sync_pb::TypedUrlSpecifics new_url;

  const int64 visits[] = { 1024, 2065, 65534, 1237684 };

  for (size_t c = 0; c < arraysize(visits); ++c) {
    old_visits.push_back(history::VisitRow(
        0, base::Time::FromInternalValue(visits[c]), 0,
        content::PAGE_TRANSITION_TYPED, 0));
    new_url.add_visits(visits[c]);
    new_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
  }

  std::vector<history::VisitInfo> new_visits;
  history::VisitVector removed_visits;

  TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
                                      &new_visits, &removed_visits);
  EXPECT_TRUE(new_visits.empty());
  EXPECT_TRUE(removed_visits.empty());
}

TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsRemove) {
  history::VisitVector old_visits;
  sync_pb::TypedUrlSpecifics new_url;

  const int64 visits_left[] = { 1, 2, 1024, 1500, 2065, 6000,
                                65534, 1237684, 2237684 };
  const int64 visits_right[] = { 1024, 2065, 65534, 1237684 };

  // DiffVisits will not remove the first visit, because we never delete visits
  // from the start of the array (since those visits can get truncated by the
  // size-limiting code).
  const int64 visits_removed[] = { 1500, 6000, 2237684 };

  for (size_t c = 0; c < arraysize(visits_left); ++c) {
    old_visits.push_back(history::VisitRow(
        0, base::Time::FromInternalValue(visits_left[c]), 0,
        content::PAGE_TRANSITION_TYPED, 0));
  }

  for (size_t c = 0; c < arraysize(visits_right); ++c) {
    new_url.add_visits(visits_right[c]);
    new_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
  }

  std::vector<history::VisitInfo> new_visits;
  history::VisitVector removed_visits;

  TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
                                      &new_visits, &removed_visits);
  EXPECT_TRUE(new_visits.empty());
  ASSERT_EQ(removed_visits.size(), arraysize(visits_removed));
  for (size_t c = 0; c < arraysize(visits_removed); ++c) {
    EXPECT_EQ(removed_visits[c].visit_time.ToInternalValue(),
              visits_removed[c]);
  }
}

TEST_F(SyncTypedUrlModelAssociatorTest, DiffVisitsAdd) {
  history::VisitVector old_visits;
  sync_pb::TypedUrlSpecifics new_url;

  const int64 visits_left[] = { 1024, 2065, 65534, 1237684 };
  const int64 visits_right[] = { 1, 1024, 1500, 2065, 6000,
                                65534, 1237684, 2237684 };

  const int64 visits_added[] = { 1, 1500, 6000, 2237684 };

  for (size_t c = 0; c < arraysize(visits_left); ++c) {
    old_visits.push_back(history::VisitRow(
        0, base::Time::FromInternalValue(visits_left[c]), 0,
        content::PAGE_TRANSITION_TYPED, 0));
  }

  for (size_t c = 0; c < arraysize(visits_right); ++c) {
    new_url.add_visits(visits_right[c]);
    new_url.add_visit_transitions(content::PAGE_TRANSITION_TYPED);
  }

  std::vector<history::VisitInfo> new_visits;
  history::VisitVector removed_visits;

  TypedUrlModelAssociator::DiffVisits(old_visits, new_url,
                                      &new_visits, &removed_visits);
  EXPECT_TRUE(removed_visits.empty());
  ASSERT_TRUE(new_visits.size() == arraysize(visits_added));
  for (size_t c = 0; c < arraysize(visits_added); ++c) {
    EXPECT_EQ(new_visits[c].first.ToInternalValue(),
              visits_added[c]);
    EXPECT_EQ(new_visits[c].second, content::PAGE_TRANSITION_TYPED);
  }
}

static history::VisitRow CreateVisit(content::PageTransition type,
                                     int64 timestamp) {
  return history::VisitRow(0, base::Time::FromInternalValue(timestamp), 0,
                           type, 0);
}

TEST_F(SyncTypedUrlModelAssociatorTest, WriteTypedUrlSpecifics) {
  history::VisitVector visits;
  visits.push_back(CreateVisit(content::PAGE_TRANSITION_TYPED, 1));
  visits.push_back(CreateVisit(content::PAGE_TRANSITION_RELOAD, 2));
  visits.push_back(CreateVisit(content::PAGE_TRANSITION_LINK, 3));

  history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
                                      1, 100, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // RELOAD visits should be removed.
  EXPECT_EQ(2, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  EXPECT_EQ(1, typed_url.visits(0));
  EXPECT_EQ(3, typed_url.visits(1));
  EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
      static_cast<content::PageTransition>(typed_url.visit_transitions(0)));
  EXPECT_EQ(content::PAGE_TRANSITION_LINK,
      static_cast<content::PageTransition>(typed_url.visit_transitions(1)));
}

TEST_F(SyncTypedUrlModelAssociatorTest, TooManyVisits) {
  history::VisitVector visits;
  int64 timestamp = 1000;
  visits.push_back(CreateVisit(content::PAGE_TRANSITION_TYPED, timestamp++));
  for (int i = 0 ; i < 100; ++i) {
    visits.push_back(CreateVisit(content::PAGE_TRANSITION_LINK, timestamp++));
  }
  history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
                                      1, timestamp++, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // # visits should be capped at 100.
  EXPECT_EQ(100, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  EXPECT_EQ(1000, typed_url.visits(0));
  // Visit with timestamp of 1001 should be omitted since we should have
  // skipped that visit to stay under the cap.
  EXPECT_EQ(1002, typed_url.visits(1));
  EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
      static_cast<content::PageTransition>(typed_url.visit_transitions(0)));
  EXPECT_EQ(content::PAGE_TRANSITION_LINK,
      static_cast<content::PageTransition>(typed_url.visit_transitions(1)));
}

TEST_F(SyncTypedUrlModelAssociatorTest, TooManyTypedVisits) {
  history::VisitVector visits;
  int64 timestamp = 1000;
  for (int i = 0 ; i < 102; ++i) {
    visits.push_back(CreateVisit(content::PAGE_TRANSITION_TYPED, timestamp++));
    visits.push_back(CreateVisit(content::PAGE_TRANSITION_LINK, timestamp++));
    visits.push_back(CreateVisit(content::PAGE_TRANSITION_RELOAD, timestamp++));
  }
  history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
                                      1, timestamp++, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // # visits should be capped at 100.
  EXPECT_EQ(100, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  // First two typed visits should be skipped.
  EXPECT_EQ(1006, typed_url.visits(0));

  // Ensure there are no non-typed visits since that's all that should fit.
  for (int i = 0; i < typed_url.visits_size(); ++i) {
    EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
        static_cast<content::PageTransition>(typed_url.visit_transitions(i)));
  }
}

TEST_F(SyncTypedUrlModelAssociatorTest, NoTypedVisits) {
  history::VisitVector visits;
  history::URLRow url(MakeTypedUrlRow("http://pie.com/", "pie",
                                      1, 1000, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  TypedUrlModelAssociator::WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // URLs with no typed URL visits should be translated to a URL with one
  // reload visit.
  EXPECT_EQ(1, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  // First two typed visits should be skipped.
  EXPECT_EQ(1000, typed_url.visits(0));
  EXPECT_EQ(content::PAGE_TRANSITION_RELOAD,
      static_cast<content::PageTransition>(typed_url.visit_transitions(0)));
}

// This test verifies that we can abort model association from the UI thread.
// We start up the model associator on the DB thread, block until we abort the
// association on the UI thread, then ensure that AssociateModels() returns
// false.
TEST_F(SyncTypedUrlModelAssociatorTest, TestAbort) {
  content::TestBrowserThread db_thread(BrowserThread::DB);
  base::WaitableEvent startup(false, false);
  base::WaitableEvent aborted(false, false);
  base::WaitableEvent done(false, false);
  TestingProfile profile;
  ProfileSyncServiceMock mock(&profile);
  TypedUrlModelAssociator* associator(NULL);
  // Fire off to the DB thread to create the model associator and start
  // model association.
  db_thread.Start();
  base::Closure callback = base::Bind(
      &CreateModelAssociatorAsync, &startup, &aborted, &done, &associator,
                                   &mock);
  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, callback);
  // Wait for the model associator to get created and start assocation.
  ASSERT_TRUE(startup.TimedWait(TestTimeouts::action_timeout()));
  // Abort the model assocation - this should be callable from any thread.
  associator->AbortAssociation();
  // Tell the remote thread to continue.
  aborted.Signal();
  // Block until CreateModelAssociator() exits.
  ASSERT_TRUE(done.TimedWait(TestTimeouts::action_timeout()));
}

/* [<][>][^][v][top][bottom][index][help] */