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