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