Home | History | Annotate | Download | only in history
      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 <algorithm>
      6 #include <fstream>
      7 
      8 #include "base/auto_reset.h"
      9 #include "base/file_util.h"
     10 #include "base/files/file_path.h"
     11 #include "base/files/scoped_temp_dir.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/path_service.h"
     14 #include "base/strings/string16.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
     18 #include "chrome/browser/chrome_notification_types.h"
     19 #include "chrome/browser/history/history_backend.h"
     20 #include "chrome/browser/history/history_database.h"
     21 #include "chrome/browser/history/history_notifications.h"
     22 #include "chrome/browser/history/history_service.h"
     23 #include "chrome/browser/history/history_service_factory.h"
     24 #include "chrome/browser/history/in_memory_url_index.h"
     25 #include "chrome/browser/history/in_memory_url_index_types.h"
     26 #include "chrome/browser/history/url_index_private_data.h"
     27 #include "chrome/common/chrome_paths.h"
     28 #include "chrome/test/base/history_index_restore_observer.h"
     29 #include "chrome/test/base/testing_profile.h"
     30 #include "components/bookmarks/test/bookmark_test_helpers.h"
     31 #include "components/history/core/browser/history_client.h"
     32 #include "content/public/browser/notification_details.h"
     33 #include "content/public/browser/notification_source.h"
     34 #include "content/public/test/test_browser_thread.h"
     35 #include "sql/transaction.h"
     36 #include "testing/gtest/include/gtest/gtest.h"
     37 
     38 using base::ASCIIToUTF16;
     39 using content::BrowserThread;
     40 
     41 namespace {
     42 const size_t kMaxMatches = 3;
     43 }  // namespace
     44 
     45 // The test version of the history url database table ('url') is contained in
     46 // a database file created from a text file('url_history_provider_test.db.txt').
     47 // The only difference between this table and a live 'urls' table from a
     48 // profile is that the last_visit_time column in the test table contains a
     49 // number specifying the number of days relative to 'today' to which the
     50 // absolute time should be set during the test setup stage.
     51 //
     52 // The format of the test database text file is of a SQLite .dump file.
     53 // Note that only lines whose first character is an upper-case letter are
     54 // processed when creating the test database.
     55 
     56 namespace history {
     57 
     58 // -----------------------------------------------------------------------------
     59 
     60 // Observer class so the unit tests can wait while the cache is being saved.
     61 class CacheFileSaverObserver : public InMemoryURLIndex::SaveCacheObserver {
     62  public:
     63   explicit CacheFileSaverObserver(base::MessageLoop* loop);
     64   virtual void OnCacheSaveFinished(bool succeeded) OVERRIDE;
     65 
     66   base::MessageLoop* loop_;
     67   bool succeeded_;
     68   DISALLOW_COPY_AND_ASSIGN(CacheFileSaverObserver);
     69 };
     70 
     71 CacheFileSaverObserver::CacheFileSaverObserver(base::MessageLoop* loop)
     72     : loop_(loop),
     73       succeeded_(false) {
     74   DCHECK(loop);
     75 }
     76 
     77 void CacheFileSaverObserver::OnCacheSaveFinished(bool succeeded) {
     78   succeeded_ = succeeded;
     79   loop_->Quit();
     80 }
     81 
     82 // -----------------------------------------------------------------------------
     83 
     84 class InMemoryURLIndexTest : public testing::Test {
     85  public:
     86   InMemoryURLIndexTest();
     87 
     88  protected:
     89   // Test setup.
     90   virtual void SetUp();
     91 
     92   // Allows the database containing the test data to be customized by
     93   // subclasses.
     94   virtual base::FilePath::StringType TestDBName() const;
     95 
     96   // Validates that the given |term| is contained in |cache| and that it is
     97   // marked as in-use.
     98   void CheckTerm(const URLIndexPrivateData::SearchTermCacheMap& cache,
     99                  base::string16 term) const;
    100 
    101   // Pass-through function to simplify our friendship with HistoryService.
    102   sql::Connection& GetDB();
    103 
    104   // Pass-through functions to simplify our friendship with InMemoryURLIndex.
    105   URLIndexPrivateData* GetPrivateData() const;
    106   void ClearPrivateData();
    107   void set_history_dir(const base::FilePath& dir_path);
    108   bool GetCacheFilePath(base::FilePath* file_path) const;
    109   void PostRestoreFromCacheFileTask();
    110   void PostSaveToCacheFileTask();
    111   void Observe(int notification_type,
    112                const content::NotificationSource& source,
    113                const content::NotificationDetails& details);
    114   const std::set<std::string>& scheme_whitelist();
    115 
    116 
    117   // Pass-through functions to simplify our friendship with URLIndexPrivateData.
    118   bool UpdateURL(const URLRow& row);
    119   bool DeleteURL(const GURL& url);
    120 
    121   // Data verification helper functions.
    122   void ExpectPrivateDataNotEmpty(const URLIndexPrivateData& data);
    123   void ExpectPrivateDataEmpty(const URLIndexPrivateData& data);
    124   void ExpectPrivateDataEqual(const URLIndexPrivateData& expected,
    125                               const URLIndexPrivateData& actual);
    126 
    127   base::MessageLoopForUI message_loop_;
    128   content::TestBrowserThread ui_thread_;
    129   content::TestBrowserThread file_thread_;
    130   TestingProfile profile_;
    131   HistoryService* history_service_;
    132 
    133   scoped_ptr<InMemoryURLIndex> url_index_;
    134   HistoryDatabase* history_database_;
    135 };
    136 
    137 InMemoryURLIndexTest::InMemoryURLIndexTest()
    138     : ui_thread_(content::BrowserThread::UI, &message_loop_),
    139       file_thread_(content::BrowserThread::FILE, &message_loop_) {
    140 }
    141 
    142 sql::Connection& InMemoryURLIndexTest::GetDB() {
    143   return history_database_->GetDB();
    144 }
    145 
    146 URLIndexPrivateData* InMemoryURLIndexTest::GetPrivateData() const {
    147   DCHECK(url_index_->private_data());
    148   return url_index_->private_data();
    149 }
    150 
    151 void InMemoryURLIndexTest::ClearPrivateData() {
    152   return url_index_->ClearPrivateData();
    153 }
    154 
    155 void InMemoryURLIndexTest::set_history_dir(const base::FilePath& dir_path) {
    156   return url_index_->set_history_dir(dir_path);
    157 }
    158 
    159 bool InMemoryURLIndexTest::GetCacheFilePath(base::FilePath* file_path) const {
    160   DCHECK(file_path);
    161   return url_index_->GetCacheFilePath(file_path);
    162 }
    163 
    164 void InMemoryURLIndexTest::PostRestoreFromCacheFileTask() {
    165   url_index_->PostRestoreFromCacheFileTask();
    166 }
    167 
    168 void InMemoryURLIndexTest::PostSaveToCacheFileTask() {
    169   url_index_->PostSaveToCacheFileTask();
    170 }
    171 
    172 void InMemoryURLIndexTest::Observe(
    173     int notification_type,
    174     const content::NotificationSource& source,
    175     const content::NotificationDetails& details) {
    176   url_index_->Observe(notification_type, source, details);
    177 }
    178 
    179 const std::set<std::string>& InMemoryURLIndexTest::scheme_whitelist() {
    180   return url_index_->scheme_whitelist();
    181 }
    182 
    183 bool InMemoryURLIndexTest::UpdateURL(const URLRow& row) {
    184   return GetPrivateData()->UpdateURL(
    185       history_service_, row, url_index_->languages_,
    186       url_index_->scheme_whitelist_);
    187 }
    188 
    189 bool InMemoryURLIndexTest::DeleteURL(const GURL& url) {
    190   return GetPrivateData()->DeleteURL(url);
    191 }
    192 
    193 void InMemoryURLIndexTest::SetUp() {
    194   // We cannot access the database until the backend has been loaded.
    195   ASSERT_TRUE(profile_.CreateHistoryService(true, false));
    196   profile_.CreateBookmarkModel(true);
    197   test::WaitForBookmarkModelToLoad(
    198       BookmarkModelFactory::GetForProfile(&profile_));
    199   profile_.BlockUntilHistoryProcessesPendingRequests();
    200   profile_.BlockUntilHistoryIndexIsRefreshed();
    201   history_service_ = HistoryServiceFactory::GetForProfile(
    202       &profile_, Profile::EXPLICIT_ACCESS);
    203   ASSERT_TRUE(history_service_);
    204   HistoryBackend* backend = history_service_->history_backend_.get();
    205   history_database_ = backend->db();
    206 
    207   // Create and populate a working copy of the URL history database.
    208   base::FilePath history_proto_path;
    209   PathService::Get(chrome::DIR_TEST_DATA, &history_proto_path);
    210   history_proto_path = history_proto_path.Append(
    211       FILE_PATH_LITERAL("History"));
    212   history_proto_path = history_proto_path.Append(TestDBName());
    213   EXPECT_TRUE(base::PathExists(history_proto_path));
    214 
    215   std::ifstream proto_file(history_proto_path.value().c_str());
    216   static const size_t kCommandBufferMaxSize = 2048;
    217   char sql_cmd_line[kCommandBufferMaxSize];
    218 
    219   sql::Connection& db(GetDB());
    220   ASSERT_TRUE(db.is_open());
    221   {
    222     sql::Transaction transaction(&db);
    223     transaction.Begin();
    224     while (!proto_file.eof()) {
    225       proto_file.getline(sql_cmd_line, kCommandBufferMaxSize);
    226       if (!proto_file.eof()) {
    227         // We only process lines which begin with a upper-case letter.
    228         // TODO(mrossetti): Can iswupper() be used here?
    229         if (sql_cmd_line[0] >= 'A' && sql_cmd_line[0] <= 'Z') {
    230           std::string sql_cmd(sql_cmd_line);
    231           sql::Statement sql_stmt(db.GetUniqueStatement(sql_cmd_line));
    232           EXPECT_TRUE(sql_stmt.Run());
    233         }
    234       }
    235     }
    236     transaction.Commit();
    237   }
    238 
    239   // Update the last_visit_time table column in the "urls" table
    240   // such that it represents a time relative to 'now'.
    241   sql::Statement statement(db.GetUniqueStatement(
    242       "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls;"));
    243   ASSERT_TRUE(statement.is_valid());
    244   base::Time time_right_now = base::Time::NowFromSystemTime();
    245   base::TimeDelta day_delta = base::TimeDelta::FromDays(1);
    246   {
    247     sql::Transaction transaction(&db);
    248     transaction.Begin();
    249     while (statement.Step()) {
    250       URLRow row;
    251       history_database_->FillURLRow(statement, &row);
    252       base::Time last_visit = time_right_now;
    253       for (int64 i = row.last_visit().ToInternalValue(); i > 0; --i)
    254         last_visit -= day_delta;
    255       row.set_last_visit(last_visit);
    256       history_database_->UpdateURLRow(row.id(), row);
    257     }
    258     transaction.Commit();
    259   }
    260 
    261   // Update the visit_time table column in the "visits" table
    262   // such that it represents a time relative to 'now'.
    263   statement.Assign(db.GetUniqueStatement(
    264       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits;"));
    265   ASSERT_TRUE(statement.is_valid());
    266   {
    267     sql::Transaction transaction(&db);
    268     transaction.Begin();
    269     while (statement.Step()) {
    270       VisitRow row;
    271       history_database_->FillVisitRow(statement, &row);
    272       base::Time last_visit = time_right_now;
    273       for (int64 i = row.visit_time.ToInternalValue(); i > 0; --i)
    274         last_visit -= day_delta;
    275       row.visit_time = last_visit;
    276       history_database_->UpdateVisitRow(row);
    277     }
    278     transaction.Commit();
    279   }
    280 
    281   url_index_.reset(new InMemoryURLIndex(
    282       &profile_, base::FilePath(), "en,ja,hi,zh",
    283       history_service_->history_client()));
    284   url_index_->Init();
    285   url_index_->RebuildFromHistory(history_database_);
    286 }
    287 
    288 base::FilePath::StringType InMemoryURLIndexTest::TestDBName() const {
    289     return FILE_PATH_LITERAL("url_history_provider_test.db.txt");
    290 }
    291 
    292 void InMemoryURLIndexTest::CheckTerm(
    293     const URLIndexPrivateData::SearchTermCacheMap& cache,
    294     base::string16 term) const {
    295   URLIndexPrivateData::SearchTermCacheMap::const_iterator cache_iter(
    296       cache.find(term));
    297   ASSERT_TRUE(cache.end() != cache_iter)
    298       << "Cache does not contain '" << term << "' but should.";
    299   URLIndexPrivateData::SearchTermCacheItem cache_item = cache_iter->second;
    300   EXPECT_TRUE(cache_item.used_)
    301       << "Cache item '" << term << "' should be marked as being in use.";
    302 }
    303 
    304 void InMemoryURLIndexTest::ExpectPrivateDataNotEmpty(
    305     const URLIndexPrivateData& data) {
    306   EXPECT_FALSE(data.word_list_.empty());
    307   // available_words_ will be empty since we have freshly built the
    308   // data set for these tests.
    309   EXPECT_TRUE(data.available_words_.empty());
    310   EXPECT_FALSE(data.word_map_.empty());
    311   EXPECT_FALSE(data.char_word_map_.empty());
    312   EXPECT_FALSE(data.word_id_history_map_.empty());
    313   EXPECT_FALSE(data.history_id_word_map_.empty());
    314   EXPECT_FALSE(data.history_info_map_.empty());
    315 }
    316 
    317 void InMemoryURLIndexTest::ExpectPrivateDataEmpty(
    318     const URLIndexPrivateData& data) {
    319   EXPECT_TRUE(data.word_list_.empty());
    320   EXPECT_TRUE(data.available_words_.empty());
    321   EXPECT_TRUE(data.word_map_.empty());
    322   EXPECT_TRUE(data.char_word_map_.empty());
    323   EXPECT_TRUE(data.word_id_history_map_.empty());
    324   EXPECT_TRUE(data.history_id_word_map_.empty());
    325   EXPECT_TRUE(data.history_info_map_.empty());
    326 }
    327 
    328 // Helper function which compares two maps for equivalence. The maps' values
    329 // are associative containers and their contents are compared as well.
    330 template<typename T>
    331 void ExpectMapOfContainersIdentical(const T& expected, const T& actual) {
    332   ASSERT_EQ(expected.size(), actual.size());
    333   for (typename T::const_iterator expected_iter = expected.begin();
    334        expected_iter != expected.end(); ++expected_iter) {
    335     typename T::const_iterator actual_iter = actual.find(expected_iter->first);
    336     ASSERT_TRUE(actual.end() != actual_iter);
    337     typename T::mapped_type const& expected_values(expected_iter->second);
    338     typename T::mapped_type const& actual_values(actual_iter->second);
    339     ASSERT_EQ(expected_values.size(), actual_values.size());
    340     for (typename T::mapped_type::const_iterator set_iter =
    341          expected_values.begin(); set_iter != expected_values.end(); ++set_iter)
    342       EXPECT_EQ(actual_values.count(*set_iter),
    343                 expected_values.count(*set_iter));
    344   }
    345 }
    346 
    347 void InMemoryURLIndexTest::ExpectPrivateDataEqual(
    348     const URLIndexPrivateData& expected,
    349     const URLIndexPrivateData& actual) {
    350   EXPECT_EQ(expected.word_list_.size(), actual.word_list_.size());
    351   EXPECT_EQ(expected.word_map_.size(), actual.word_map_.size());
    352   EXPECT_EQ(expected.char_word_map_.size(), actual.char_word_map_.size());
    353   EXPECT_EQ(expected.word_id_history_map_.size(),
    354             actual.word_id_history_map_.size());
    355   EXPECT_EQ(expected.history_id_word_map_.size(),
    356             actual.history_id_word_map_.size());
    357   EXPECT_EQ(expected.history_info_map_.size(), actual.history_info_map_.size());
    358   EXPECT_EQ(expected.word_starts_map_.size(), actual.word_starts_map_.size());
    359   // WordList must be index-by-index equal.
    360   size_t count = expected.word_list_.size();
    361   for (size_t i = 0; i < count; ++i)
    362     EXPECT_EQ(expected.word_list_[i], actual.word_list_[i]);
    363 
    364   ExpectMapOfContainersIdentical(expected.char_word_map_,
    365                                  actual.char_word_map_);
    366   ExpectMapOfContainersIdentical(expected.word_id_history_map_,
    367                                  actual.word_id_history_map_);
    368   ExpectMapOfContainersIdentical(expected.history_id_word_map_,
    369                                  actual.history_id_word_map_);
    370 
    371   for (HistoryInfoMap::const_iterator expected_info =
    372       expected.history_info_map_.begin();
    373       expected_info != expected.history_info_map_.end(); ++expected_info) {
    374     HistoryInfoMap::const_iterator actual_info =
    375         actual.history_info_map_.find(expected_info->first);
    376     // NOTE(yfriedman): ASSERT_NE can't be used due to incompatibility between
    377     // gtest and STLPort in the Android build. See
    378     // http://code.google.com/p/googletest/issues/detail?id=359
    379     ASSERT_TRUE(actual_info != actual.history_info_map_.end());
    380     const URLRow& expected_row(expected_info->second.url_row);
    381     const URLRow& actual_row(actual_info->second.url_row);
    382     EXPECT_EQ(expected_row.visit_count(), actual_row.visit_count());
    383     EXPECT_EQ(expected_row.typed_count(), actual_row.typed_count());
    384     EXPECT_EQ(expected_row.last_visit(), actual_row.last_visit());
    385     EXPECT_EQ(expected_row.url(), actual_row.url());
    386     const VisitInfoVector& expected_visits(expected_info->second.visits);
    387     const VisitInfoVector& actual_visits(actual_info->second.visits);
    388     EXPECT_EQ(expected_visits.size(), actual_visits.size());
    389     for (size_t i = 0;
    390          i < std::min(expected_visits.size(), actual_visits.size()); ++i) {
    391       EXPECT_EQ(expected_visits[i].first, actual_visits[i].first);
    392       EXPECT_EQ(expected_visits[i].second, actual_visits[i].second);
    393     }
    394   }
    395 
    396   for (WordStartsMap::const_iterator expected_starts =
    397       expected.word_starts_map_.begin();
    398       expected_starts != expected.word_starts_map_.end();
    399       ++expected_starts) {
    400     WordStartsMap::const_iterator actual_starts =
    401         actual.word_starts_map_.find(expected_starts->first);
    402     // NOTE(yfriedman): ASSERT_NE can't be used due to incompatibility between
    403     // gtest and STLPort in the Android build. See
    404     // http://code.google.com/p/googletest/issues/detail?id=359
    405     ASSERT_TRUE(actual_starts != actual.word_starts_map_.end());
    406     const RowWordStarts& expected_word_starts(expected_starts->second);
    407     const RowWordStarts& actual_word_starts(actual_starts->second);
    408     EXPECT_EQ(expected_word_starts.url_word_starts_.size(),
    409               actual_word_starts.url_word_starts_.size());
    410     EXPECT_TRUE(std::equal(expected_word_starts.url_word_starts_.begin(),
    411                            expected_word_starts.url_word_starts_.end(),
    412                            actual_word_starts.url_word_starts_.begin()));
    413     EXPECT_EQ(expected_word_starts.title_word_starts_.size(),
    414               actual_word_starts.title_word_starts_.size());
    415     EXPECT_TRUE(std::equal(expected_word_starts.title_word_starts_.begin(),
    416                            expected_word_starts.title_word_starts_.end(),
    417                            actual_word_starts.title_word_starts_.begin()));
    418   }
    419 }
    420 
    421 //------------------------------------------------------------------------------
    422 
    423 class LimitedInMemoryURLIndexTest : public InMemoryURLIndexTest {
    424  protected:
    425   virtual base::FilePath::StringType TestDBName() const OVERRIDE;
    426 };
    427 
    428 base::FilePath::StringType LimitedInMemoryURLIndexTest::TestDBName() const {
    429   return FILE_PATH_LITERAL("url_history_provider_test_limited.db.txt");
    430 }
    431 
    432 TEST_F(LimitedInMemoryURLIndexTest, Initialization) {
    433   // Verify that the database contains the expected number of items, which
    434   // is the pre-filtered count, i.e. all of the items.
    435   sql::Statement statement(GetDB().GetUniqueStatement("SELECT * FROM urls;"));
    436   ASSERT_TRUE(statement.is_valid());
    437   uint64 row_count = 0;
    438   while (statement.Step()) ++row_count;
    439   EXPECT_EQ(1U, row_count);
    440   url_index_.reset(new InMemoryURLIndex(
    441       &profile_, base::FilePath(), "en,ja,hi,zh",
    442       history_service_->history_client()));
    443   url_index_->Init();
    444   url_index_->RebuildFromHistory(history_database_);
    445   URLIndexPrivateData& private_data(*GetPrivateData());
    446 
    447   // history_info_map_ should have the same number of items as were filtered.
    448   EXPECT_EQ(1U, private_data.history_info_map_.size());
    449   EXPECT_EQ(35U, private_data.char_word_map_.size());
    450   EXPECT_EQ(17U, private_data.word_map_.size());
    451 }
    452 
    453 #if defined(OS_WIN)
    454 // Flaky on windows trybots: http://crbug.com/351500
    455 #define MAYBE_Retrieval DISABLED_Retrieval
    456 #else
    457 #define MAYBE_Retrieval Retrieval
    458 #endif
    459 TEST_F(InMemoryURLIndexTest, MAYBE_Retrieval) {
    460   // See if a very specific term gives a single result.
    461   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    462       ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches);
    463   ASSERT_EQ(1U, matches.size());
    464 
    465   // Verify that we got back the result we expected.
    466   EXPECT_EQ(5, matches[0].url_info.id());
    467   EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
    468   EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
    469   EXPECT_TRUE(matches[0].can_inline());
    470 
    471   // Make sure a trailing space prevents inline-ability but still results
    472   // in the expected result.
    473   matches = url_index_->HistoryItemsForTerms(
    474       ASCIIToUTF16("DrudgeReport "), base::string16::npos, kMaxMatches);
    475   ASSERT_EQ(1U, matches.size());
    476   EXPECT_EQ(5, matches[0].url_info.id());
    477   EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
    478   EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
    479   EXPECT_FALSE(matches[0].can_inline());
    480 
    481   // Search which should result in multiple results.
    482   matches = url_index_->HistoryItemsForTerms(
    483       ASCIIToUTF16("drudge"), base::string16::npos, kMaxMatches);
    484   ASSERT_EQ(2U, matches.size());
    485   // The results should be in descending score order.
    486   EXPECT_GE(matches[0].raw_score(), matches[1].raw_score());
    487 
    488   // Search which should result in nearly perfect result.
    489   matches = url_index_->HistoryItemsForTerms(
    490       ASCIIToUTF16("Nearly Perfect Result"), base::string16::npos, kMaxMatches);
    491   ASSERT_EQ(1U, matches.size());
    492   // The results should have a very high score.
    493   EXPECT_GT(matches[0].raw_score(), 900);
    494   EXPECT_EQ(32, matches[0].url_info.id());
    495   EXPECT_EQ("https://nearlyperfectresult.com/",
    496             matches[0].url_info.url().spec());  // Note: URL gets lowercased.
    497   EXPECT_EQ(ASCIIToUTF16("Practically Perfect Search Result"),
    498             matches[0].url_info.title());
    499   EXPECT_FALSE(matches[0].can_inline());
    500 
    501   // Search which should result in very poor result.
    502   matches = url_index_->HistoryItemsForTerms(
    503       ASCIIToUTF16("qui c"), base::string16::npos, kMaxMatches);
    504   ASSERT_EQ(1U, matches.size());
    505   // The results should have a poor score.
    506   EXPECT_LT(matches[0].raw_score(), 500);
    507   EXPECT_EQ(33, matches[0].url_info.id());
    508   EXPECT_EQ("http://quiteuselesssearchresultxyz.com/",
    509             matches[0].url_info.url().spec());  // Note: URL gets lowercased.
    510   EXPECT_EQ(ASCIIToUTF16("Practically Useless Search Result"),
    511             matches[0].url_info.title());
    512   EXPECT_FALSE(matches[0].can_inline());
    513 
    514   // Search which will match at the end of an URL with encoded characters.
    515   matches = url_index_->HistoryItemsForTerms(
    516       ASCIIToUTF16("Mice"), base::string16::npos, kMaxMatches);
    517   ASSERT_EQ(1U, matches.size());
    518   EXPECT_EQ(30, matches[0].url_info.id());
    519   EXPECT_FALSE(matches[0].can_inline());
    520 
    521   // Check that URLs are not escaped an escape time.
    522   matches = url_index_->HistoryItemsForTerms(
    523        ASCIIToUTF16("1% wikipedia"), base::string16::npos, kMaxMatches);
    524   ASSERT_EQ(1U, matches.size());
    525   EXPECT_EQ(35, matches[0].url_info.id());
    526   EXPECT_EQ("http://en.wikipedia.org/wiki/1%25_rule_(Internet_culture)",
    527             matches[0].url_info.url().spec());
    528 
    529   // Verify that a single term can appear multiple times in the URL and as long
    530   // as one starts the URL it is still inlined.
    531   matches = url_index_->HistoryItemsForTerms(
    532       ASCIIToUTF16("fubar"), base::string16::npos, kMaxMatches);
    533   ASSERT_EQ(1U, matches.size());
    534   EXPECT_EQ(34, matches[0].url_info.id());
    535   EXPECT_EQ("http://fubarfubarandfubar.com/", matches[0].url_info.url().spec());
    536   EXPECT_EQ(ASCIIToUTF16("Situation Normal -- FUBARED"),
    537             matches[0].url_info.title());
    538   EXPECT_TRUE(matches[0].can_inline());
    539 }
    540 
    541 TEST_F(InMemoryURLIndexTest, CursorPositionRetrieval) {
    542   // See if a very specific term with no cursor gives an empty result.
    543   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    544       ASCIIToUTF16("DrudReport"), base::string16::npos, kMaxMatches);
    545   ASSERT_EQ(0U, matches.size());
    546 
    547   // The same test with the cursor at the end should give an empty result.
    548   matches = url_index_->HistoryItemsForTerms(
    549       ASCIIToUTF16("DrudReport"), 10u, kMaxMatches);
    550   ASSERT_EQ(0U, matches.size());
    551 
    552   // If the cursor is between Drud and Report, we should find the desired
    553   // result.
    554   matches = url_index_->HistoryItemsForTerms(
    555       ASCIIToUTF16("DrudReport"), 4u, kMaxMatches);
    556   ASSERT_EQ(1U, matches.size());
    557   EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
    558   EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
    559 
    560   // Now check multi-word inputs.  No cursor should fail to find a
    561   // result on this input.
    562   matches = url_index_->HistoryItemsForTerms(
    563       ASCIIToUTF16("MORTGAGERATE DROPS"), base::string16::npos, kMaxMatches);
    564   ASSERT_EQ(0U, matches.size());
    565 
    566   // Ditto with cursor at end.
    567   matches = url_index_->HistoryItemsForTerms(
    568       ASCIIToUTF16("MORTGAGERATE DROPS"), 18u, kMaxMatches);
    569   ASSERT_EQ(0U, matches.size());
    570 
    571   // If the cursor is between MORTAGE And RATE, we should find the
    572   // desired result.
    573   matches = url_index_->HistoryItemsForTerms(
    574       ASCIIToUTF16("MORTGAGERATE DROPS"), 8u, kMaxMatches);
    575   ASSERT_EQ(1U, matches.size());
    576   EXPECT_EQ("http://www.reuters.com/article/idUSN0839880620100708",
    577             matches[0].url_info.url().spec());
    578   EXPECT_EQ(ASCIIToUTF16(
    579       "UPDATE 1-US 30-yr mortgage rate drops to new record low | Reuters"),
    580             matches[0].url_info.title());
    581 }
    582 
    583 TEST_F(InMemoryURLIndexTest, URLPrefixMatching) {
    584   // "drudgere" - found, can inline
    585   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    586       ASCIIToUTF16("drudgere"), base::string16::npos, kMaxMatches);
    587   ASSERT_EQ(1U, matches.size());
    588   EXPECT_TRUE(matches[0].can_inline());
    589 
    590   // "drudgere" - found, can inline
    591   matches = url_index_->HistoryItemsForTerms(
    592       ASCIIToUTF16("drudgere"), base::string16::npos, kMaxMatches);
    593   ASSERT_EQ(1U, matches.size());
    594   EXPECT_TRUE(matches[0].can_inline());
    595 
    596   // "www.atdmt" - not found
    597   matches = url_index_->HistoryItemsForTerms(
    598       ASCIIToUTF16("www.atdmt"), base::string16::npos, kMaxMatches);
    599   EXPECT_EQ(0U, matches.size());
    600 
    601   // "atdmt" - found, cannot inline
    602   matches = url_index_->HistoryItemsForTerms(
    603       ASCIIToUTF16("atdmt"), base::string16::npos, kMaxMatches);
    604   ASSERT_EQ(1U, matches.size());
    605   EXPECT_FALSE(matches[0].can_inline());
    606 
    607   // "view.atdmt" - found, can inline
    608   matches = url_index_->HistoryItemsForTerms(
    609       ASCIIToUTF16("view.atdmt"), base::string16::npos, kMaxMatches);
    610   ASSERT_EQ(1U, matches.size());
    611   EXPECT_TRUE(matches[0].can_inline());
    612 
    613   // "view.atdmt" - found, can inline
    614   matches = url_index_->HistoryItemsForTerms(
    615       ASCIIToUTF16("view.atdmt"), base::string16::npos, kMaxMatches);
    616   ASSERT_EQ(1U, matches.size());
    617   EXPECT_TRUE(matches[0].can_inline());
    618 
    619   // "cnn.com" - found, can inline
    620   matches = url_index_->HistoryItemsForTerms(
    621       ASCIIToUTF16("cnn.com"), base::string16::npos, kMaxMatches);
    622   ASSERT_EQ(2U, matches.size());
    623   // One match should be inline-able, the other not.
    624   EXPECT_TRUE(matches[0].can_inline() != matches[1].can_inline());
    625 
    626   // "www.cnn.com" - found, can inline
    627   matches = url_index_->HistoryItemsForTerms(
    628       ASCIIToUTF16("www.cnn.com"), base::string16::npos, kMaxMatches);
    629   ASSERT_EQ(1U, matches.size());
    630   EXPECT_TRUE(matches[0].can_inline());
    631 
    632   // "ww.cnn.com" - found because we allow mid-term matches in hostnames
    633   matches = url_index_->HistoryItemsForTerms(
    634       ASCIIToUTF16("ww.cnn.com"), base::string16::npos, kMaxMatches);
    635   ASSERT_EQ(1U, matches.size());
    636 
    637   // "www.cnn.com" - found, can inline
    638   matches = url_index_->HistoryItemsForTerms(
    639       ASCIIToUTF16("www.cnn.com"), base::string16::npos, kMaxMatches);
    640   ASSERT_EQ(1U, matches.size());
    641   EXPECT_TRUE(matches[0].can_inline());
    642 
    643   // "tp://www.cnn.com" - not found because we don't allow tp as a mid-term
    644   // match
    645   matches = url_index_->HistoryItemsForTerms(
    646       ASCIIToUTF16("tp://www.cnn.com"), base::string16::npos, kMaxMatches);
    647   ASSERT_EQ(0U, matches.size());
    648 }
    649 
    650 TEST_F(InMemoryURLIndexTest, ProperStringMatching) {
    651   // Search for the following with the expected results:
    652   // "atdmt view" - found
    653   // "atdmt.view" - not found
    654   // "view.atdmt" - found
    655   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    656       ASCIIToUTF16("atdmt view"), base::string16::npos, kMaxMatches);
    657   ASSERT_EQ(1U, matches.size());
    658   matches = url_index_->HistoryItemsForTerms(
    659        ASCIIToUTF16("atdmt.view"), base::string16::npos, kMaxMatches);
    660   ASSERT_EQ(0U, matches.size());
    661   matches = url_index_->HistoryItemsForTerms(
    662       ASCIIToUTF16("view.atdmt"), base::string16::npos, kMaxMatches);
    663   ASSERT_EQ(1U, matches.size());
    664 }
    665 
    666 TEST_F(InMemoryURLIndexTest, HugeResultSet) {
    667   // Create a huge set of qualifying history items.
    668   for (URLID row_id = 5000; row_id < 6000; ++row_id) {
    669     URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"), row_id);
    670     new_row.set_last_visit(base::Time::Now());
    671     EXPECT_TRUE(UpdateURL(new_row));
    672   }
    673 
    674   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    675       ASCIIToUTF16("b"), base::string16::npos, kMaxMatches);
    676   URLIndexPrivateData& private_data(*GetPrivateData());
    677   ASSERT_EQ(kMaxMatches, matches.size());
    678   // There are 7 matches already in the database.
    679   ASSERT_EQ(1008U, private_data.pre_filter_item_count_);
    680   ASSERT_EQ(500U, private_data.post_filter_item_count_);
    681   ASSERT_EQ(kMaxMatches, private_data.post_scoring_item_count_);
    682 }
    683 
    684 #if defined(OS_WIN)
    685 // Flaky on windows trybots: http://crbug.com/351500
    686 #define MAYBE_TitleSearch DISABLED_TitleSearch
    687 #else
    688 #define MAYBE_TitleSearch TitleSearch
    689 #endif
    690 TEST_F(InMemoryURLIndexTest, MAYBE_TitleSearch) {
    691   // Signal if someone has changed the test DB.
    692   EXPECT_EQ(29U, GetPrivateData()->history_info_map_.size());
    693 
    694   // Ensure title is being searched.
    695   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    696       ASCIIToUTF16("MORTGAGE RATE DROPS"), base::string16::npos, kMaxMatches);
    697   ASSERT_EQ(1U, matches.size());
    698 
    699   // Verify that we got back the result we expected.
    700   EXPECT_EQ(1, matches[0].url_info.id());
    701   EXPECT_EQ("http://www.reuters.com/article/idUSN0839880620100708",
    702             matches[0].url_info.url().spec());
    703   EXPECT_EQ(ASCIIToUTF16(
    704       "UPDATE 1-US 30-yr mortgage rate drops to new record low | Reuters"),
    705       matches[0].url_info.title());
    706 }
    707 
    708 TEST_F(InMemoryURLIndexTest, TitleChange) {
    709   // Verify current title terms retrieves desired item.
    710   base::string16 original_terms =
    711       ASCIIToUTF16("lebronomics could high taxes influence");
    712   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    713       original_terms, base::string16::npos, kMaxMatches);
    714   ASSERT_EQ(1U, matches.size());
    715 
    716   // Verify that we got back the result we expected.
    717   const URLID expected_id = 3;
    718   EXPECT_EQ(expected_id, matches[0].url_info.id());
    719   EXPECT_EQ("http://www.businessandmedia.org/articles/2010/20100708120415.aspx",
    720             matches[0].url_info.url().spec());
    721   EXPECT_EQ(ASCIIToUTF16(
    722       "LeBronomics: Could High Taxes Influence James' Team Decision?"),
    723       matches[0].url_info.title());
    724   URLRow old_row(matches[0].url_info);
    725 
    726   // Verify new title terms retrieves nothing.
    727   base::string16 new_terms = ASCIIToUTF16("does eat oats little lambs ivy");
    728   matches = url_index_->HistoryItemsForTerms(
    729       new_terms, base::string16::npos, kMaxMatches);
    730   ASSERT_EQ(0U, matches.size());
    731 
    732   // Update the row.
    733   old_row.set_title(ASCIIToUTF16("Does eat oats and little lambs eat ivy"));
    734   EXPECT_TRUE(UpdateURL(old_row));
    735 
    736   // Verify we get the row using the new terms but not the original terms.
    737   matches = url_index_->HistoryItemsForTerms(
    738       new_terms, base::string16::npos, kMaxMatches);
    739   ASSERT_EQ(1U, matches.size());
    740   EXPECT_EQ(expected_id, matches[0].url_info.id());
    741   matches = url_index_->HistoryItemsForTerms(
    742       original_terms, base::string16::npos, kMaxMatches);
    743   ASSERT_EQ(0U, matches.size());
    744 }
    745 
    746 TEST_F(InMemoryURLIndexTest, NonUniqueTermCharacterSets) {
    747   // The presence of duplicate characters should succeed. Exercise by cycling
    748   // through a string with several duplicate characters.
    749   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    750       ASCIIToUTF16("ABRA"), base::string16::npos, kMaxMatches);
    751   ASSERT_EQ(1U, matches.size());
    752   EXPECT_EQ(28, matches[0].url_info.id());
    753   EXPECT_EQ("http://www.ddj.com/windows/184416623",
    754             matches[0].url_info.url().spec());
    755 
    756   matches = url_index_->HistoryItemsForTerms(
    757       ASCIIToUTF16("ABRACAD"), base::string16::npos, kMaxMatches);
    758   ASSERT_EQ(1U, matches.size());
    759   EXPECT_EQ(28, matches[0].url_info.id());
    760 
    761   matches = url_index_->HistoryItemsForTerms(
    762       ASCIIToUTF16("ABRACADABRA"), base::string16::npos, kMaxMatches);
    763   ASSERT_EQ(1U, matches.size());
    764   EXPECT_EQ(28, matches[0].url_info.id());
    765 
    766   matches = url_index_->HistoryItemsForTerms(
    767       ASCIIToUTF16("ABRACADABR"), base::string16::npos, kMaxMatches);
    768   ASSERT_EQ(1U, matches.size());
    769   EXPECT_EQ(28, matches[0].url_info.id());
    770 
    771   matches = url_index_->HistoryItemsForTerms(
    772       ASCIIToUTF16("ABRACA"), base::string16::npos, kMaxMatches);
    773   ASSERT_EQ(1U, matches.size());
    774   EXPECT_EQ(28, matches[0].url_info.id());
    775 }
    776 
    777 TEST_F(InMemoryURLIndexTest, TypedCharacterCaching) {
    778   // Verify that match results for previously typed characters are retained
    779   // (in the term_char_word_set_cache_) and reused, if possible, in future
    780   // autocompletes.
    781 
    782   URLIndexPrivateData::SearchTermCacheMap& cache(
    783       GetPrivateData()->search_term_cache_);
    784 
    785   // The cache should be empty at this point.
    786   EXPECT_EQ(0U, cache.size());
    787 
    788   // Now simulate typing search terms into the omnibox and check the state of
    789   // the cache as each item is 'typed'.
    790 
    791   // Simulate typing "r" giving "r" in the simulated omnibox. The results for
    792   // 'r' will be not cached because it is only 1 character long.
    793   url_index_->HistoryItemsForTerms(
    794       ASCIIToUTF16("r"), base::string16::npos, kMaxMatches);
    795   EXPECT_EQ(0U, cache.size());
    796 
    797   // Simulate typing "re" giving "r re" in the simulated omnibox.
    798   // 're' should be cached at this point but not 'r' as it is a single
    799   // character.
    800   url_index_->HistoryItemsForTerms(
    801       ASCIIToUTF16("r re"), base::string16::npos, kMaxMatches);
    802   ASSERT_EQ(1U, cache.size());
    803   CheckTerm(cache, ASCIIToUTF16("re"));
    804 
    805   // Simulate typing "reco" giving "r re reco" in the simulated omnibox.
    806   // 're' and 'reco' should be cached at this point but not 'r' as it is a
    807   // single character.
    808   url_index_->HistoryItemsForTerms(
    809       ASCIIToUTF16("r re reco"), base::string16::npos, kMaxMatches);
    810   ASSERT_EQ(2U, cache.size());
    811   CheckTerm(cache, ASCIIToUTF16("re"));
    812   CheckTerm(cache, ASCIIToUTF16("reco"));
    813 
    814   // Simulate typing "mort".
    815   // Since we now have only one search term, the cached results for 're' and
    816   // 'reco' should be purged, giving us only 1 item in the cache (for 'mort').
    817   url_index_->HistoryItemsForTerms(
    818       ASCIIToUTF16("mort"), base::string16::npos, kMaxMatches);
    819   ASSERT_EQ(1U, cache.size());
    820   CheckTerm(cache, ASCIIToUTF16("mort"));
    821 
    822   // Simulate typing "reco" giving "mort reco" in the simulated omnibox.
    823   url_index_->HistoryItemsForTerms(
    824       ASCIIToUTF16("mort reco"), base::string16::npos, kMaxMatches);
    825   ASSERT_EQ(2U, cache.size());
    826   CheckTerm(cache, ASCIIToUTF16("mort"));
    827   CheckTerm(cache, ASCIIToUTF16("reco"));
    828 
    829   // Simulate a <DELETE> by removing the 'reco' and adding back the 'rec'.
    830   url_index_->HistoryItemsForTerms(
    831       ASCIIToUTF16("mort rec"), base::string16::npos, kMaxMatches);
    832   ASSERT_EQ(2U, cache.size());
    833   CheckTerm(cache, ASCIIToUTF16("mort"));
    834   CheckTerm(cache, ASCIIToUTF16("rec"));
    835 }
    836 
    837 TEST_F(InMemoryURLIndexTest, AddNewRows) {
    838   // Verify that the row we're going to add does not already exist.
    839   URLID new_row_id = 87654321;
    840   // Newly created URLRows get a last_visit time of 'right now' so it should
    841   // qualify as a quick result candidate.
    842   EXPECT_TRUE(url_index_->HistoryItemsForTerms(
    843       ASCIIToUTF16("brokeandalone"), base::string16::npos, kMaxMatches)
    844           .empty());
    845 
    846   // Add a new row.
    847   URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"), new_row_id++);
    848   new_row.set_last_visit(base::Time::Now());
    849   EXPECT_TRUE(UpdateURL(new_row));
    850 
    851   // Verify that we can retrieve it.
    852   EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(
    853       ASCIIToUTF16("brokeandalone"), base::string16::npos, kMaxMatches).size());
    854 
    855   // Add it again just to be sure that is harmless and that it does not update
    856   // the index.
    857   EXPECT_FALSE(UpdateURL(new_row));
    858   EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(
    859       ASCIIToUTF16("brokeandalone"), base::string16::npos, kMaxMatches).size());
    860 
    861   // Make up an URL that does not qualify and try to add it.
    862   URLRow unqualified_row(GURL("http://www.brokeandaloneinmanitoba.com/"),
    863                          new_row_id++);
    864   EXPECT_FALSE(UpdateURL(new_row));
    865 }
    866 
    867 TEST_F(InMemoryURLIndexTest, DeleteRows) {
    868   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    869       ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches);
    870   ASSERT_EQ(1U, matches.size());
    871 
    872   // Delete the URL then search again.
    873   EXPECT_TRUE(DeleteURL(matches[0].url_info.url()));
    874   EXPECT_TRUE(url_index_->HistoryItemsForTerms(
    875       ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches).empty());
    876 
    877   // Make up an URL that does not exist in the database and delete it.
    878   GURL url("http://www.hokeypokey.com/putyourrightfootin.html");
    879   EXPECT_FALSE(DeleteURL(url));
    880 }
    881 
    882 TEST_F(InMemoryURLIndexTest, ExpireRow) {
    883   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
    884       ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches);
    885   ASSERT_EQ(1U, matches.size());
    886 
    887   // Determine the row id for the result, remember that id, broadcast a
    888   // delete notification, then ensure that the row has been deleted.
    889   URLsDeletedDetails deleted_details;
    890   deleted_details.all_history = false;
    891   deleted_details.rows.push_back(matches[0].url_info);
    892   Observe(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
    893           content::Source<InMemoryURLIndexTest>(this),
    894           content::Details<history::HistoryDetails>(&deleted_details));
    895   EXPECT_TRUE(url_index_->HistoryItemsForTerms(
    896       ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches).empty());
    897 }
    898 
    899 TEST_F(InMemoryURLIndexTest, WhitelistedURLs) {
    900   struct TestData {
    901     const std::string url_spec;
    902     const bool expected_is_whitelisted;
    903   } data[] = {
    904     // URLs with whitelisted schemes.
    905     { "about:histograms", true },
    906     { "chrome://settings", true },
    907     { "file://localhost/Users/joeschmoe/sekrets", true },
    908     { "ftp://public.mycompany.com/myfile.txt", true },
    909     { "http://www.google.com/translate", true },
    910     { "https://www.gmail.com/", true },
    911     { "mailto:support (at) google.com", true },
    912     // URLs with unacceptable schemes.
    913     { "aaa://www.dummyhost.com;frammy", false },
    914     { "aaas://www.dummyhost.com;frammy", false },
    915     { "acap://suzie (at) somebody.com", false },
    916     { "cap://cal.example.com/Company/Holidays", false },
    917     { "cid:foo4*foo1 (at) bar.net", false },
    918     { "crid://example.com/foobar", false },
    919     { "data:image/png;base64,iVBORw0KGgoAAAANSUhE=", false },
    920     { "dict://dict.org/d:shortcake:", false },
    921     { "dns://192.168.1.1/ftp.example.org?type=A", false },
    922     { "fax:+358.555.1234567", false },
    923     { "geo:13.4125,103.8667", false },
    924     { "go:Mercedes%20Benz", false },
    925     { "gopher://farnsworth.ca:666/gopher", false },
    926     { "h323:farmer-john;sixpence", false },
    927     { "iax:johnQ (at) example.com/12022561414", false },
    928     { "icap://icap.net/service?mode=translate&lang=french", false },
    929     { "im:fred (at) example.com", false },
    930     { "imap://michael (at) minbari.org/users.*", false },
    931     { "info:ddc/22/eng//004.678", false },
    932     { "ipp://example.com/printer/fox", false },
    933     { "iris:dreg1//example.com/local/myhosts", false },
    934     { "iris.beep:dreg1//example.com/local/myhosts", false },
    935     { "iris.lws:dreg1//example.com/local/myhosts", false },
    936     { "iris.xpc:dreg1//example.com/local/myhosts", false },
    937     { "iris.xpcs:dreg1//example.com/local/myhosts", false },
    938     { "ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US", false },
    939     { "mid:foo4%25foo1 (at) bar.net", false },
    940     { "modem:+3585551234567;type=v32b?7e1;type=v110", false },
    941     { "msrp://atlanta.example.com:7654/jshA7weztas;tcp", false },
    942     { "msrps://atlanta.example.com:7654/jshA7weztas;tcp", false },
    943     { "news:colorectal.info.banned", false },
    944     { "nfs://server/d/e/f", false },
    945     { "nntp://www.example.com:6543/info.comp.lies/1234", false },
    946     { "pop://rg;AUTH=+APOP (at) mail.mycompany.com:8110", false },
    947     { "pres:fred (at) example.com", false },
    948     { "prospero://host.dom//pros/name", false },
    949     { "rsync://syler (at) lost.com/Source", false },
    950     { "rtsp://media.example.com:554/twister/audiotrack", false },
    951     { "service:acap://some.where.net;authentication=KERBEROSV4", false },
    952     { "shttp://www.terces.com/secret", false },
    953     { "sieve://example.com//script", false },
    954     { "sip:+1-212-555-1212:1234 (at) gateway.com;user=phone", false },
    955     { "sips:+1-212-555-1212:1234 (at) gateway.com;user=phone", false },
    956     { "sms:+15105551212?body=hello%20there", false },
    957     { "snmp://tester5 (at) example.com:8161/bridge1;800002b804616263", false },
    958     { "soap.beep://stockquoteserver.example.com/StockQuote", false },
    959     { "soap.beeps://stockquoteserver.example.com/StockQuote", false },
    960     { "tag:blogger.com,1999:blog-555", false },
    961     { "tel:+358-555-1234567;postd=pp22", false },
    962     { "telnet://mayor_margie:one2rule4All (at) www.mycity.com:6789/", false },
    963     { "tftp://example.com/mystartupfile", false },
    964     { "tip://123.123.123.123/?urn:xopen:xid", false },
    965     { "tv:nbc.com", false },
    966     { "urn:foo:A123,456", false },
    967     { "vemmi://zeus.mctel.fr/demo", false },
    968     { "wais://www.mydomain.net:8765/mydatabase", false },
    969     { "xmpp:node (at) example.com", false },
    970     { "xmpp://guest (at) example.com", false },
    971   };
    972 
    973   URLIndexPrivateData& private_data(*GetPrivateData());
    974   const std::set<std::string>& whitelist(scheme_whitelist());
    975   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
    976     GURL url(data[i].url_spec);
    977     EXPECT_EQ(data[i].expected_is_whitelisted,
    978               private_data.URLSchemeIsWhitelisted(url, whitelist));
    979   }
    980 }
    981 
    982 TEST_F(InMemoryURLIndexTest, ReadVisitsFromHistory) {
    983   const HistoryInfoMap& history_info_map = GetPrivateData()->history_info_map_;
    984 
    985   // Check (for URL with id 1) that the number of visits and their
    986   // transition types are what we expect.  We don't bother checking
    987   // the timestamps because it's too much trouble.  (The timestamps go
    988   // through a transformation in InMemoryURLIndexTest::SetUp().  We
    989   // assume that if the count and transitions show up with the right
    990   // information, we're getting the right information from the history
    991   // database file.)
    992   HistoryInfoMap::const_iterator entry = history_info_map.find(1);
    993   ASSERT_TRUE(entry != history_info_map.end());
    994   {
    995     const VisitInfoVector& visits = entry->second.visits;
    996     EXPECT_EQ(3u, visits.size());
    997     EXPECT_EQ(0u, visits[0].second);
    998     EXPECT_EQ(1u, visits[1].second);
    999     EXPECT_EQ(0u, visits[2].second);
   1000   }
   1001 
   1002   // Ditto but for URL with id 35.
   1003   entry = history_info_map.find(35);
   1004   ASSERT_TRUE(entry != history_info_map.end());
   1005   {
   1006     const VisitInfoVector& visits = entry->second.visits;
   1007     EXPECT_EQ(2u, visits.size());
   1008     EXPECT_EQ(1u, visits[0].second);
   1009     EXPECT_EQ(1u, visits[1].second);
   1010   }
   1011 
   1012   // The URL with id 32 has many visits listed in the database, but we
   1013   // should only read the most recent 10 (which are all transition type 0).
   1014   entry = history_info_map.find(32);
   1015   ASSERT_TRUE(entry != history_info_map.end());
   1016   {
   1017     const VisitInfoVector& visits = entry->second.visits;
   1018     EXPECT_EQ(10u, visits.size());
   1019     for (size_t i = 0; i < visits.size(); ++i)
   1020       EXPECT_EQ(0u, visits[i].second);
   1021   }
   1022 }
   1023 
   1024 TEST_F(InMemoryURLIndexTest, CacheSaveRestore) {
   1025   base::ScopedTempDir temp_directory;
   1026   ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
   1027   set_history_dir(temp_directory.path());
   1028 
   1029   URLIndexPrivateData& private_data(*GetPrivateData());
   1030 
   1031   // Ensure that there is really something there to be saved.
   1032   EXPECT_FALSE(private_data.word_list_.empty());
   1033   // available_words_ will already be empty since we have freshly built the
   1034   // data set for this test.
   1035   EXPECT_TRUE(private_data.available_words_.empty());
   1036   EXPECT_FALSE(private_data.word_map_.empty());
   1037   EXPECT_FALSE(private_data.char_word_map_.empty());
   1038   EXPECT_FALSE(private_data.word_id_history_map_.empty());
   1039   EXPECT_FALSE(private_data.history_id_word_map_.empty());
   1040   EXPECT_FALSE(private_data.history_info_map_.empty());
   1041   EXPECT_FALSE(private_data.word_starts_map_.empty());
   1042 
   1043   // Make sure the data we have was built from history.  (Version 0
   1044   // means rebuilt from history.)
   1045   EXPECT_EQ(0, private_data.restored_cache_version_);
   1046 
   1047   // Capture the current private data for later comparison to restored data.
   1048   scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
   1049   const base::Time rebuild_time = private_data.last_time_rebuilt_from_history_;
   1050 
   1051   // Save then restore our private data.
   1052   CacheFileSaverObserver save_observer(&message_loop_);
   1053   url_index_->set_save_cache_observer(&save_observer);
   1054   PostSaveToCacheFileTask();
   1055   message_loop_.Run();
   1056   EXPECT_TRUE(save_observer.succeeded_);
   1057 
   1058   // Clear and then prove it's clear before restoring.
   1059   ClearPrivateData();
   1060   EXPECT_TRUE(private_data.word_list_.empty());
   1061   EXPECT_TRUE(private_data.available_words_.empty());
   1062   EXPECT_TRUE(private_data.word_map_.empty());
   1063   EXPECT_TRUE(private_data.char_word_map_.empty());
   1064   EXPECT_TRUE(private_data.word_id_history_map_.empty());
   1065   EXPECT_TRUE(private_data.history_id_word_map_.empty());
   1066   EXPECT_TRUE(private_data.history_info_map_.empty());
   1067   EXPECT_TRUE(private_data.word_starts_map_.empty());
   1068 
   1069   HistoryIndexRestoreObserver restore_observer(
   1070       base::Bind(&base::MessageLoop::Quit, base::Unretained(&message_loop_)));
   1071   url_index_->set_restore_cache_observer(&restore_observer);
   1072   PostRestoreFromCacheFileTask();
   1073   message_loop_.Run();
   1074   EXPECT_TRUE(restore_observer.succeeded());
   1075 
   1076   URLIndexPrivateData& new_data(*GetPrivateData());
   1077 
   1078   // Make sure the data we have was reloaded from cache.  (Version 0
   1079   // means rebuilt from history; anything else means restored from
   1080   // a cache version.)  Also, the rebuild time should not have changed.
   1081   EXPECT_GT(new_data.restored_cache_version_, 0);
   1082   EXPECT_EQ(rebuild_time, new_data.last_time_rebuilt_from_history_);
   1083 
   1084   // Compare the captured and restored for equality.
   1085   ExpectPrivateDataEqual(*old_data.get(), new_data);
   1086 }
   1087 
   1088 TEST_F(InMemoryURLIndexTest, RebuildFromHistoryIfCacheOld) {
   1089   base::ScopedTempDir temp_directory;
   1090   ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
   1091   set_history_dir(temp_directory.path());
   1092 
   1093   URLIndexPrivateData& private_data(*GetPrivateData());
   1094 
   1095   // Ensure that there is really something there to be saved.
   1096   EXPECT_FALSE(private_data.word_list_.empty());
   1097   // available_words_ will already be empty since we have freshly built the
   1098   // data set for this test.
   1099   EXPECT_TRUE(private_data.available_words_.empty());
   1100   EXPECT_FALSE(private_data.word_map_.empty());
   1101   EXPECT_FALSE(private_data.char_word_map_.empty());
   1102   EXPECT_FALSE(private_data.word_id_history_map_.empty());
   1103   EXPECT_FALSE(private_data.history_id_word_map_.empty());
   1104   EXPECT_FALSE(private_data.history_info_map_.empty());
   1105   EXPECT_FALSE(private_data.word_starts_map_.empty());
   1106 
   1107   // Make sure the data we have was built from history.  (Version 0
   1108   // means rebuilt from history.)
   1109   EXPECT_EQ(0, private_data.restored_cache_version_);
   1110 
   1111   // Overwrite the build time so that we'll think the data is too old
   1112   // and rebuild the cache from history.
   1113   const base::Time fake_rebuild_time =
   1114       private_data.last_time_rebuilt_from_history_ -
   1115       base::TimeDelta::FromDays(30);
   1116   private_data.last_time_rebuilt_from_history_ = fake_rebuild_time;
   1117 
   1118   // Capture the current private data for later comparison to restored data.
   1119   scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
   1120 
   1121   // Save then restore our private data.
   1122   CacheFileSaverObserver save_observer(&message_loop_);
   1123   url_index_->set_save_cache_observer(&save_observer);
   1124   PostSaveToCacheFileTask();
   1125   message_loop_.Run();
   1126   EXPECT_TRUE(save_observer.succeeded_);
   1127 
   1128   // Clear and then prove it's clear before restoring.
   1129   ClearPrivateData();
   1130   EXPECT_TRUE(private_data.word_list_.empty());
   1131   EXPECT_TRUE(private_data.available_words_.empty());
   1132   EXPECT_TRUE(private_data.word_map_.empty());
   1133   EXPECT_TRUE(private_data.char_word_map_.empty());
   1134   EXPECT_TRUE(private_data.word_id_history_map_.empty());
   1135   EXPECT_TRUE(private_data.history_id_word_map_.empty());
   1136   EXPECT_TRUE(private_data.history_info_map_.empty());
   1137   EXPECT_TRUE(private_data.word_starts_map_.empty());
   1138 
   1139   HistoryIndexRestoreObserver restore_observer(
   1140       base::Bind(&base::MessageLoop::Quit, base::Unretained(&message_loop_)));
   1141   url_index_->set_restore_cache_observer(&restore_observer);
   1142   PostRestoreFromCacheFileTask();
   1143   message_loop_.Run();
   1144   EXPECT_TRUE(restore_observer.succeeded());
   1145 
   1146   URLIndexPrivateData& new_data(*GetPrivateData());
   1147 
   1148   // Make sure the data we have was rebuilt from history.  (Version 0
   1149   // means rebuilt from history; anything else means restored from
   1150   // a cache version.)
   1151   EXPECT_EQ(0, new_data.restored_cache_version_);
   1152   EXPECT_NE(fake_rebuild_time, new_data.last_time_rebuilt_from_history_);
   1153 
   1154   // Compare the captured and restored for equality.
   1155   ExpectPrivateDataEqual(*old_data.get(), new_data);
   1156 }
   1157 
   1158 class InMemoryURLIndexCacheTest : public testing::Test {
   1159  public:
   1160   InMemoryURLIndexCacheTest() {}
   1161 
   1162  protected:
   1163   virtual void SetUp() OVERRIDE;
   1164 
   1165   // Pass-through functions to simplify our friendship with InMemoryURLIndex.
   1166   void set_history_dir(const base::FilePath& dir_path);
   1167   bool GetCacheFilePath(base::FilePath* file_path) const;
   1168 
   1169   base::ScopedTempDir temp_dir_;
   1170   scoped_ptr<InMemoryURLIndex> url_index_;
   1171 };
   1172 
   1173 void InMemoryURLIndexCacheTest::SetUp() {
   1174   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
   1175   HistoryClient history_client;
   1176   base::FilePath path(temp_dir_.path());
   1177   url_index_.reset(new InMemoryURLIndex(
   1178       NULL, path, "en,ja,hi,zh", &history_client));
   1179 }
   1180 
   1181 void InMemoryURLIndexCacheTest::set_history_dir(
   1182     const base::FilePath& dir_path) {
   1183   return url_index_->set_history_dir(dir_path);
   1184 }
   1185 
   1186 bool InMemoryURLIndexCacheTest::GetCacheFilePath(
   1187     base::FilePath* file_path) const {
   1188   DCHECK(file_path);
   1189   return url_index_->GetCacheFilePath(file_path);
   1190 }
   1191 
   1192 TEST_F(InMemoryURLIndexCacheTest, CacheFilePath) {
   1193   base::FilePath expectedPath =
   1194       temp_dir_.path().Append(FILE_PATH_LITERAL("History Provider Cache"));
   1195   std::vector<base::FilePath::StringType> expected_parts;
   1196   expectedPath.GetComponents(&expected_parts);
   1197   base::FilePath full_file_path;
   1198   ASSERT_TRUE(GetCacheFilePath(&full_file_path));
   1199   std::vector<base::FilePath::StringType> actual_parts;
   1200   full_file_path.GetComponents(&actual_parts);
   1201   ASSERT_EQ(expected_parts.size(), actual_parts.size());
   1202   size_t count = expected_parts.size();
   1203   for (size_t i = 0; i < count; ++i)
   1204     EXPECT_EQ(expected_parts[i], actual_parts[i]);
   1205   // Must clear the history_dir_ to satisfy the dtor's DCHECK.
   1206   set_history_dir(base::FilePath());
   1207 }
   1208 
   1209 }  // namespace history
   1210