Home | History | Annotate | Download | only in history
      1 // Copyright (c) 2011 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 <stdio.h>
      6 
      7 #include <fstream>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "app/sql/connection.h"
     12 #include "app/sql/statement.h"
     13 #include "app/sql/transaction.h"
     14 #include "base/file_path.h"
     15 #include "base/file_util.h"
     16 #include "base/memory/scoped_ptr.h"
     17 #include "base/path_service.h"
     18 #include "base/string_util.h"
     19 #include "base/time.h"
     20 #include "base/utf_string_conversions.h"
     21 #include "chrome/browser/history/in_memory_url_index.h"
     22 #include "chrome/browser/history/in_memory_database.h"
     23 #include "chrome/common/chrome_paths.h"
     24 #include "testing/gtest/include/gtest/gtest.h"
     25 
     26 // The test version of the history url database table ('url') is contained in
     27 // a database file created from a text file('url_history_provider_test.db.txt').
     28 // The only difference between this table and a live 'urls' table from a
     29 // profile is that the last_visit_time column in the test table contains a
     30 // number specifying the number of days relative to 'today' to which the
     31 // absolute time should be set during the test setup stage.
     32 //
     33 // The format of the test database text file is of a SQLite .dump file.
     34 // Note that only lines whose first character is an upper-case letter are
     35 // processed when creating the test database.
     36 
     37 namespace history {
     38 
     39 class InMemoryURLIndexTest : public testing::Test,
     40                              public InMemoryDatabase {
     41  public:
     42   InMemoryURLIndexTest() { InitFromScratch(); }
     43 
     44  protected:
     45   // Test setup.
     46   virtual void SetUp() {
     47     // Create and populate a working copy of the URL history database.
     48     FilePath history_proto_path;
     49     PathService::Get(chrome::DIR_TEST_DATA, &history_proto_path);
     50     history_proto_path = history_proto_path.Append(
     51         FILE_PATH_LITERAL("History"));
     52     history_proto_path = history_proto_path.Append(TestDBName());
     53     EXPECT_TRUE(file_util::PathExists(history_proto_path));
     54 
     55     std::ifstream proto_file(history_proto_path.value().c_str());
     56     static const size_t kCommandBufferMaxSize = 2048;
     57     char sql_cmd_line[kCommandBufferMaxSize];
     58 
     59     sql::Connection& db(GetDB());
     60     {
     61       sql::Transaction transaction(&db);
     62       transaction.Begin();
     63       while (!proto_file.eof()) {
     64         proto_file.getline(sql_cmd_line, kCommandBufferMaxSize);
     65         if (!proto_file.eof()) {
     66           // We only process lines which begin with a upper-case letter.
     67           // TODO(mrossetti): Can iswupper() be used here?
     68           if (sql_cmd_line[0] >= 'A' && sql_cmd_line[0] <= 'Z') {
     69             std::string sql_cmd(sql_cmd_line);
     70             sql::Statement sql_stmt(db.GetUniqueStatement(sql_cmd_line));
     71             EXPECT_TRUE(sql_stmt.Run());
     72           }
     73         }
     74       }
     75       transaction.Commit();
     76     }
     77     proto_file.close();
     78 
     79     // Update the last_visit_time table column
     80     // such that it represents a time relative to 'now'.
     81     sql::Statement statement(db.GetUniqueStatement(
     82         "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls;"));
     83     EXPECT_TRUE(statement);
     84     base::Time time_right_now = base::Time::NowFromSystemTime();
     85     base::TimeDelta day_delta = base::TimeDelta::FromDays(1);
     86     {
     87       sql::Transaction transaction(&db);
     88       transaction.Begin();
     89       while (statement.Step()) {
     90         URLRow row;
     91         FillURLRow(statement, &row);
     92         base::Time last_visit = time_right_now;
     93         for (int64 i = row.last_visit().ToInternalValue(); i > 0; --i)
     94           last_visit -= day_delta;
     95         row.set_last_visit(last_visit);
     96         UpdateURLRow(row.id(), row);
     97       }
     98       transaction.Commit();
     99     }
    100   }
    101 
    102   virtual FilePath::StringType TestDBName() const {
    103       return FILE_PATH_LITERAL("url_history_provider_test.db.txt");
    104   }
    105 
    106   URLRow MakeURLRow(const char* url,
    107                     const char* title,
    108                     int visit_count,
    109                     int last_visit_ago,
    110                     int typed_count) {
    111     URLRow row(GURL(url), 0);
    112     row.set_title(UTF8ToUTF16(title));
    113     row.set_visit_count(visit_count);
    114     row.set_typed_count(typed_count);
    115     row.set_last_visit(base::Time::NowFromSystemTime() -
    116                        base::TimeDelta::FromDays(last_visit_ago));
    117     return row;
    118   }
    119 
    120   InMemoryURLIndex::String16Vector Make1Term(const char* term) {
    121     InMemoryURLIndex::String16Vector terms;
    122     terms.push_back(UTF8ToUTF16(term));
    123     return terms;
    124   }
    125 
    126   InMemoryURLIndex::String16Vector Make2Terms(const char* term_1,
    127                                               const char* term_2) {
    128     InMemoryURLIndex::String16Vector terms;
    129     terms.push_back(UTF8ToUTF16(term_1));
    130     terms.push_back(UTF8ToUTF16(term_2));
    131     return terms;
    132   }
    133 
    134   scoped_ptr<InMemoryURLIndex> url_index_;
    135 };
    136 
    137 class LimitedInMemoryURLIndexTest : public InMemoryURLIndexTest {
    138  protected:
    139   FilePath::StringType TestDBName() const {
    140     return FILE_PATH_LITERAL("url_history_provider_test_limited.db.txt");
    141   }
    142 };
    143 
    144 class ExpandedInMemoryURLIndexTest : public InMemoryURLIndexTest {
    145  protected:
    146   virtual void SetUp() {
    147     InMemoryURLIndexTest::SetUp();
    148     // Add 600 more history items.
    149     // NOTE: Keep the string length constant at least the length of the format
    150     // string plus 5 to account for a 3 digit number and terminator.
    151     char url_format[] = "http://www.google.com/%d";
    152     const size_t kMaxLen = arraysize(url_format) + 5;
    153     char url_string[kMaxLen + 1];
    154     for (int i = 0; i < 600; ++i) {
    155       base::snprintf(url_string, kMaxLen, url_format, i);
    156       URLRow row(MakeURLRow(url_string, "Google Search", 20, 0, 20));
    157       AddURL(row);
    158     }
    159   }
    160 };
    161 
    162 TEST_F(InMemoryURLIndexTest, Construction) {
    163   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    164   EXPECT_TRUE(url_index_.get());
    165 }
    166 
    167 TEST_F(LimitedInMemoryURLIndexTest, Initialization) {
    168   // Verify that the database contains the expected number of items, which
    169   // is the pre-filtered count, i.e. all of the items.
    170   sql::Statement statement(GetDB().GetUniqueStatement("SELECT * FROM urls;"));
    171   EXPECT_TRUE(statement);
    172   uint64 row_count = 0;
    173   while (statement.Step()) ++row_count;
    174   EXPECT_EQ(1U, row_count);
    175   url_index_.reset(new InMemoryURLIndex);
    176   url_index_->Init(this, "en,ja,hi,zh");
    177   EXPECT_EQ(1, url_index_->history_item_count_);
    178 
    179   // history_info_map_ should have the same number of items as were filtered.
    180   EXPECT_EQ(1U, url_index_->history_info_map_.size());
    181   EXPECT_EQ(36U, url_index_->char_word_map_.size());
    182   EXPECT_EQ(17U, url_index_->word_map_.size());
    183 }
    184 
    185 TEST_F(InMemoryURLIndexTest, Retrieval) {
    186   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    187   url_index_->Init(this, "en,ja,hi,zh");
    188   InMemoryURLIndex::String16Vector terms;
    189   // The term will be lowercased by the search.
    190 
    191   // See if a very specific term gives a single result.
    192   terms.push_back(ASCIIToUTF16("DrudgeReport"));
    193   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms);
    194   EXPECT_EQ(1U, matches.size());
    195 
    196   // Verify that we got back the result we expected.
    197   EXPECT_EQ(5, matches[0].url_info.id());
    198   EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
    199   EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
    200 
    201   // Search which should result in multiple results.
    202   terms.clear();
    203   terms.push_back(ASCIIToUTF16("drudge"));
    204   matches = url_index_->HistoryItemsForTerms(terms);
    205   ASSERT_EQ(2U, matches.size());
    206   // The results should be in descending score order.
    207   EXPECT_GE(matches[0].raw_score, matches[1].raw_score);
    208 
    209   // Search which should result in nearly perfect result.
    210   terms.clear();
    211   terms.push_back(ASCIIToUTF16("https"));
    212   terms.push_back(ASCIIToUTF16("NearlyPerfectResult"));
    213   matches = url_index_->HistoryItemsForTerms(terms);
    214   ASSERT_EQ(1U, matches.size());
    215   // The results should have a very high score.
    216   EXPECT_GT(matches[0].raw_score, 900);
    217   EXPECT_EQ(32, matches[0].url_info.id());
    218   EXPECT_EQ("https://nearlyperfectresult.com/",
    219             matches[0].url_info.url().spec());  // Note: URL gets lowercased.
    220   EXPECT_EQ(ASCIIToUTF16("Practically Perfect Search Result"),
    221             matches[0].url_info.title());
    222 
    223   // Search which should result in very poor result.
    224   terms.clear();
    225   terms.push_back(ASCIIToUTF16("z"));
    226   terms.push_back(ASCIIToUTF16("y"));
    227   terms.push_back(ASCIIToUTF16("x"));
    228   matches = url_index_->HistoryItemsForTerms(terms);
    229   ASSERT_EQ(1U, matches.size());
    230   // The results should have a poor score.
    231   EXPECT_LT(matches[0].raw_score, 500);
    232   EXPECT_EQ(33, matches[0].url_info.id());
    233   EXPECT_EQ("http://quiteuselesssearchresultxyz.com/",
    234             matches[0].url_info.url().spec());  // Note: URL gets lowercased.
    235   EXPECT_EQ(ASCIIToUTF16("Practically Useless Search Result"),
    236             matches[0].url_info.title());
    237 
    238   // Search which will match at the end of an URL with encoded characters.
    239   terms.clear();
    240   terms.push_back(ASCIIToUTF16("ice"));
    241   matches = url_index_->HistoryItemsForTerms(terms);
    242   ASSERT_EQ(1U, matches.size());
    243 }
    244 
    245 TEST_F(ExpandedInMemoryURLIndexTest, ShortCircuit) {
    246   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    247   url_index_->Init(this, "en,ja,hi,zh");
    248   InMemoryURLIndex::String16Vector terms;
    249 
    250   // A search for 'w' should short-circuit and not return any matches.
    251   terms.push_back(ASCIIToUTF16("w"));
    252   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms);
    253   EXPECT_TRUE(matches.empty());
    254 
    255   // A search for 'working' should not short-circuit.
    256   terms.clear();
    257   terms.push_back(ASCIIToUTF16("working"));
    258   matches = url_index_->HistoryItemsForTerms(terms);
    259   EXPECT_EQ(1U, matches.size());
    260 }
    261 
    262 TEST_F(InMemoryURLIndexTest, TitleSearch) {
    263   url_index_.reset(new InMemoryURLIndex());
    264   url_index_->Init(this, "en,ja,hi,zh");
    265   // Signal if someone has changed the test DB.
    266   EXPECT_EQ(25U, url_index_->history_info_map_.size());
    267   InMemoryURLIndex::String16Vector terms;
    268 
    269   // Ensure title is being searched.
    270   terms.push_back(ASCIIToUTF16("MORTGAGE"));
    271   terms.push_back(ASCIIToUTF16("RATE"));
    272   terms.push_back(ASCIIToUTF16("DROPS"));
    273   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms);
    274   EXPECT_EQ(1U, matches.size());
    275 
    276   // Verify that we got back the result we expected.
    277   EXPECT_EQ(1, matches[0].url_info.id());
    278   EXPECT_EQ("http://www.reuters.com/article/idUSN0839880620100708",
    279             matches[0].url_info.url().spec());
    280   EXPECT_EQ(ASCIIToUTF16(
    281       "UPDATE 1-US 30-yr mortgage rate drops to new record low | Reuters"),
    282       matches[0].url_info.title());
    283 }
    284 
    285 TEST_F(InMemoryURLIndexTest, Char16Utilities) {
    286   string16 term = ASCIIToUTF16("drudgereport");
    287   string16 expected = ASCIIToUTF16("drugepot");
    288   EXPECT_EQ(expected.size(),
    289             InMemoryURLIndex::Char16SetFromString16(term).size());
    290   InMemoryURLIndex::Char16Vector c_vector =
    291       InMemoryURLIndex::Char16VectorFromString16(term);
    292   ASSERT_EQ(expected.size(), c_vector.size());
    293 
    294   InMemoryURLIndex::Char16Vector::const_iterator c_iter = c_vector.begin();
    295   for (string16::const_iterator s_iter = expected.begin();
    296        s_iter != expected.end(); ++s_iter, ++c_iter)
    297     EXPECT_EQ(*s_iter, *c_iter);
    298 }
    299 
    300 TEST_F(InMemoryURLIndexTest, StaticFunctions) {
    301   // Test WordVectorFromString16
    302   string16 string_a(ASCIIToUTF16("http://www.google.com/ frammy the brammy"));
    303   InMemoryURLIndex::String16Vector string_vec =
    304       InMemoryURLIndex::WordVectorFromString16(string_a, false);
    305   ASSERT_EQ(7U, string_vec.size());
    306   // See if we got the words we expected.
    307   EXPECT_EQ(UTF8ToUTF16("http"), string_vec[0]);
    308   EXPECT_EQ(UTF8ToUTF16("www"), string_vec[1]);
    309   EXPECT_EQ(UTF8ToUTF16("google"), string_vec[2]);
    310   EXPECT_EQ(UTF8ToUTF16("com"), string_vec[3]);
    311   EXPECT_EQ(UTF8ToUTF16("frammy"), string_vec[4]);
    312   EXPECT_EQ(UTF8ToUTF16("the"), string_vec[5]);
    313   EXPECT_EQ(UTF8ToUTF16("brammy"), string_vec[6]);
    314 
    315   string_vec = InMemoryURLIndex::WordVectorFromString16(string_a, true);
    316   ASSERT_EQ(5U, string_vec.size());
    317   EXPECT_EQ(UTF8ToUTF16("http://"), string_vec[0]);
    318   EXPECT_EQ(UTF8ToUTF16("www.google.com/"), string_vec[1]);
    319   EXPECT_EQ(UTF8ToUTF16("frammy"), string_vec[2]);
    320   EXPECT_EQ(UTF8ToUTF16("the"), string_vec[3]);
    321   EXPECT_EQ(UTF8ToUTF16("brammy"), string_vec[4]);
    322 
    323   // Test WordSetFromString16
    324   string16 string_b(ASCIIToUTF16(
    325       "http://web.google.com/search Google Web Search"));
    326   InMemoryURLIndex::String16Set string_set =
    327       InMemoryURLIndex::WordSetFromString16(string_b);
    328   EXPECT_EQ(5U, string_set.size());
    329   // See if we got the words we expected.
    330   EXPECT_TRUE(string_set.find(UTF8ToUTF16("com")) != string_set.end());
    331   EXPECT_TRUE(string_set.find(UTF8ToUTF16("google")) != string_set.end());
    332   EXPECT_TRUE(string_set.find(UTF8ToUTF16("http")) != string_set.end());
    333   EXPECT_TRUE(string_set.find(UTF8ToUTF16("search")) != string_set.end());
    334   EXPECT_TRUE(string_set.find(UTF8ToUTF16("web")) != string_set.end());
    335 
    336   // Test SortAndDeoverlap
    337   TermMatches matches_a;
    338   matches_a.push_back(TermMatch(1, 13, 10));
    339   matches_a.push_back(TermMatch(2, 23, 10));
    340   matches_a.push_back(TermMatch(3, 3, 10));
    341   matches_a.push_back(TermMatch(4, 40, 5));
    342   TermMatches matches_b = InMemoryURLIndex::SortAndDeoverlap(matches_a);
    343   // Nothing should have been eliminated.
    344   EXPECT_EQ(matches_a.size(), matches_b.size());
    345   // The order should now be 3, 1, 2, 4.
    346   EXPECT_EQ(3, matches_b[0].term_num);
    347   EXPECT_EQ(1, matches_b[1].term_num);
    348   EXPECT_EQ(2, matches_b[2].term_num);
    349   EXPECT_EQ(4, matches_b[3].term_num);
    350   matches_a.push_back(TermMatch(5, 18, 10));
    351   matches_a.push_back(TermMatch(6, 38, 5));
    352   matches_b = InMemoryURLIndex::SortAndDeoverlap(matches_a);
    353   // Two matches should have been eliminated.
    354   EXPECT_EQ(matches_a.size() - 2, matches_b.size());
    355   // The order should now be 3, 1, 2, 6.
    356   EXPECT_EQ(3, matches_b[0].term_num);
    357   EXPECT_EQ(1, matches_b[1].term_num);
    358   EXPECT_EQ(2, matches_b[2].term_num);
    359   EXPECT_EQ(6, matches_b[3].term_num);
    360 
    361   // Test MatchTermInString
    362   TermMatches matches_c = InMemoryURLIndex::MatchTermInString(
    363       UTF8ToUTF16("x"), UTF8ToUTF16("axbxcxdxex fxgx/hxixjx.kx"), 123);
    364   ASSERT_EQ(11U, matches_c.size());
    365   const size_t expected_offsets[] = { 1, 3, 5, 7, 9, 12, 14, 17, 19, 21, 24 };
    366   for (int i = 0; i < 11; ++i)
    367     EXPECT_EQ(expected_offsets[i], matches_c[i].offset);
    368 }
    369 
    370 TEST_F(InMemoryURLIndexTest, OffsetsAndTermMatches) {
    371   // Test OffsetsFromTermMatches
    372   history::TermMatches matches_a;
    373   matches_a.push_back(history::TermMatch(1, 1, 2));
    374   matches_a.push_back(history::TermMatch(2, 4, 3));
    375   matches_a.push_back(history::TermMatch(3, 9, 1));
    376   matches_a.push_back(history::TermMatch(3, 10, 1));
    377   matches_a.push_back(history::TermMatch(4, 14, 5));
    378   std::vector<size_t> offsets =
    379       InMemoryURLIndex::OffsetsFromTermMatches(matches_a);
    380   const size_t expected_offsets_a[] = {1, 4, 9, 10, 14};
    381   ASSERT_EQ(offsets.size(), arraysize(expected_offsets_a));
    382   for (size_t i = 0; i < offsets.size(); ++i)
    383     EXPECT_EQ(expected_offsets_a[i], offsets[i]);
    384 
    385   // Test ReplaceOffsetsInTermMatches
    386   offsets[2] = string16::npos;
    387   history::TermMatches matches_b =
    388       InMemoryURLIndex::ReplaceOffsetsInTermMatches(matches_a, offsets);
    389   const size_t expected_offsets_b[] = {1, 4, 10, 14};
    390   ASSERT_EQ(arraysize(expected_offsets_b), matches_b.size());
    391   for (size_t i = 0; i < matches_b.size(); ++i)
    392     EXPECT_EQ(expected_offsets_b[i], matches_b[i].offset);
    393 }
    394 
    395 TEST_F(InMemoryURLIndexTest, TypedCharacterCaching) {
    396   // Verify that match results for previously typed characters are retained
    397   // (in the term_char_word_set_cache_) and reused, if possible, in future
    398   // autocompletes.
    399   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    400   url_index_->Init(this, "en,ja,hi,zh");
    401 
    402   // Verify that we can find something that already exists.
    403   InMemoryURLIndex::String16Vector terms;
    404   string16 term = ASCIIToUTF16("drudgerepo");
    405   terms.push_back(term);
    406   EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(terms).size());
    407 
    408   {
    409     // Exercise the term matching cache with the same term.
    410     InMemoryURLIndex::Char16Vector uni_chars =
    411         InMemoryURLIndex::Char16VectorFromString16(term);
    412     EXPECT_EQ(uni_chars.size(), 7U);  // Equivalent to 'degopru'
    413     EXPECT_EQ(6U, url_index_->CachedResultsIndexForTerm(uni_chars));
    414   }
    415 
    416   {
    417     // Back off a character.
    418     InMemoryURLIndex::Char16Vector uni_chars =
    419         InMemoryURLIndex::Char16VectorFromString16(ASCIIToUTF16("drudgerep"));
    420     EXPECT_EQ(6U, uni_chars.size());  // Equivalent to 'degpru'
    421     EXPECT_EQ(5U, url_index_->CachedResultsIndexForTerm(uni_chars));
    422   }
    423 
    424   {
    425     // Add a couple of characters.
    426     InMemoryURLIndex::Char16Vector uni_chars =
    427         InMemoryURLIndex::Char16VectorFromString16(
    428             ASCIIToUTF16("drudgereporta"));
    429     EXPECT_EQ(9U, uni_chars.size());  // Equivalent to 'adegoprtu'
    430     EXPECT_EQ(6U, url_index_->CachedResultsIndexForTerm(uni_chars));
    431   }
    432 
    433   {
    434     // Use different string.
    435     InMemoryURLIndex::Char16Vector uni_chars =
    436         InMemoryURLIndex::Char16VectorFromString16(ASCIIToUTF16("abcde"));
    437     EXPECT_EQ(5U, uni_chars.size());
    438     EXPECT_EQ(static_cast<size_t>(-1),
    439               url_index_->CachedResultsIndexForTerm(uni_chars));
    440   }
    441 }
    442 
    443 TEST_F(InMemoryURLIndexTest, Scoring) {
    444   URLRow row_a(MakeURLRow("http://abcdef", "fedcba", 3, 30, 1));
    445   // Test scores based on position.
    446   ScoredHistoryMatch scored_a(
    447       InMemoryURLIndex::ScoredMatchForURL(row_a, Make1Term("abc")));
    448   ScoredHistoryMatch scored_b(
    449       InMemoryURLIndex::ScoredMatchForURL(row_a, Make1Term("bcd")));
    450   EXPECT_GT(scored_a.raw_score, scored_b.raw_score);
    451   // Test scores based on length.
    452   ScoredHistoryMatch scored_c(
    453       InMemoryURLIndex::ScoredMatchForURL(row_a, Make1Term("abcd")));
    454   EXPECT_LT(scored_a.raw_score, scored_c.raw_score);
    455   // Test scores based on order.
    456   ScoredHistoryMatch scored_d(
    457       InMemoryURLIndex::ScoredMatchForURL(row_a, Make2Terms("abc", "def")));
    458   ScoredHistoryMatch scored_e(
    459       InMemoryURLIndex::ScoredMatchForURL(row_a, Make2Terms("def", "abc")));
    460   EXPECT_GT(scored_d.raw_score, scored_e.raw_score);
    461   // Test scores based on visit_count.
    462   URLRow row_b(MakeURLRow("http://abcdef", "fedcba", 10, 30, 1));
    463   ScoredHistoryMatch scored_f(
    464       InMemoryURLIndex::ScoredMatchForURL(row_b, Make1Term("abc")));
    465   EXPECT_GT(scored_f.raw_score, scored_a.raw_score);
    466   // Test scores based on last_visit.
    467   URLRow row_c(MakeURLRow("http://abcdef", "fedcba", 3, 10, 1));
    468   ScoredHistoryMatch scored_g(
    469       InMemoryURLIndex::ScoredMatchForURL(row_c, Make1Term("abc")));
    470   EXPECT_GT(scored_g.raw_score, scored_a.raw_score);
    471   // Test scores based on typed_count.
    472   URLRow row_d(MakeURLRow("http://abcdef", "fedcba", 3, 30, 10));
    473   ScoredHistoryMatch scored_h(
    474       InMemoryURLIndex::ScoredMatchForURL(row_d, Make1Term("abc")));
    475   EXPECT_GT(scored_h.raw_score, scored_a.raw_score);
    476 }
    477 
    478 TEST_F(InMemoryURLIndexTest, AddNewRows) {
    479   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    480   url_index_->Init(this, "en,ja,hi,zh");
    481   InMemoryURLIndex::String16Vector terms;
    482 
    483   // Verify that the row we're going to add does not already exist.
    484   URLID new_row_id = 87654321;
    485   // Newly created URLRows get a last_visit time of 'right now' so it should
    486   // qualify as a quick result candidate.
    487   terms.push_back(ASCIIToUTF16("brokeandalone"));
    488   EXPECT_TRUE(url_index_->HistoryItemsForTerms(terms).empty());
    489 
    490   // Add a new row.
    491   URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"), new_row_id);
    492   new_row.set_last_visit(base::Time::Now());
    493   url_index_->UpdateURL(new_row_id, new_row);
    494 
    495   // Verify that we can retrieve it.
    496   EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(terms).size());
    497 
    498   // Add it again just to be sure that is harmless.
    499   url_index_->UpdateURL(new_row_id, new_row);
    500   EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(terms).size());
    501 }
    502 
    503 TEST_F(InMemoryURLIndexTest, DeleteRows) {
    504   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    505   url_index_->Init(this, "en,ja,hi,zh");
    506   InMemoryURLIndex::String16Vector terms;
    507 
    508   // Make sure we actually get an existing result.
    509   terms.push_back(ASCIIToUTF16("DrudgeReport"));
    510   ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms);
    511   ASSERT_EQ(1U, matches.size());
    512 
    513   // Determine the row id for that result, delete that id, then search again.
    514   url_index_->DeleteURL(matches[0].url_info.id());
    515   EXPECT_TRUE(url_index_->HistoryItemsForTerms(terms).empty());
    516 }
    517 
    518 TEST_F(InMemoryURLIndexTest, CacheFilePath) {
    519   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL(
    520       "/flammmy/frammy/"))));
    521   FilePath full_file_path;
    522   url_index_->GetCacheFilePath(&full_file_path);
    523   std::vector<FilePath::StringType> expected_parts;
    524   FilePath(FILE_PATH_LITERAL("/flammmy/frammy/History Provider Cache")).
    525       GetComponents(&expected_parts);
    526   std::vector<FilePath::StringType> actual_parts;
    527   full_file_path.GetComponents(&actual_parts);
    528   ASSERT_EQ(expected_parts.size(), actual_parts.size());
    529   size_t count = expected_parts.size();
    530   for (size_t i = 0; i < count; ++i)
    531     EXPECT_EQ(expected_parts[i], actual_parts[i]);
    532 }
    533 
    534 TEST_F(InMemoryURLIndexTest, CacheSaveRestore) {
    535   // Save the cache to a protobuf, restore it, and compare the results.
    536   url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
    537   InMemoryURLIndex& url_index(*(url_index_.get()));
    538   url_index.Init(this, "en,ja,hi,zh");
    539   in_memory_url_index::InMemoryURLIndexCacheItem index_cache;
    540   url_index.SavePrivateData(&index_cache);
    541 
    542   // Capture our private data so we can later compare for equality.
    543   int history_item_count(url_index.history_item_count_);
    544   InMemoryURLIndex::String16Vector word_list(url_index.word_list_);
    545   InMemoryURLIndex::WordMap word_map(url_index.word_map_);
    546   InMemoryURLIndex::CharWordIDMap char_word_map(url_index.char_word_map_);
    547   InMemoryURLIndex::WordIDHistoryMap word_id_history_map(
    548       url_index.word_id_history_map_);
    549   InMemoryURLIndex::HistoryInfoMap history_info_map(
    550       url_index.history_info_map_);
    551 
    552   // Prove that there is really something there.
    553   EXPECT_GT(url_index.history_item_count_, 0);
    554   EXPECT_FALSE(url_index.word_list_.empty());
    555   EXPECT_FALSE(url_index.word_map_.empty());
    556   EXPECT_FALSE(url_index.char_word_map_.empty());
    557   EXPECT_FALSE(url_index.word_id_history_map_.empty());
    558   EXPECT_FALSE(url_index.history_info_map_.empty());
    559 
    560   // Clear and then prove it's clear.
    561   url_index.ClearPrivateData();
    562   EXPECT_EQ(0, url_index.history_item_count_);
    563   EXPECT_TRUE(url_index.word_list_.empty());
    564   EXPECT_TRUE(url_index.word_map_.empty());
    565   EXPECT_TRUE(url_index.char_word_map_.empty());
    566   EXPECT_TRUE(url_index.word_id_history_map_.empty());
    567   EXPECT_TRUE(url_index.history_info_map_.empty());
    568 
    569   // Restore the cache.
    570   EXPECT_TRUE(url_index.RestorePrivateData(index_cache));
    571 
    572   // Compare the restored and captured for equality.
    573   EXPECT_EQ(history_item_count, url_index.history_item_count_);
    574   EXPECT_EQ(word_list.size(), url_index.word_list_.size());
    575   EXPECT_EQ(word_map.size(), url_index.word_map_.size());
    576   EXPECT_EQ(char_word_map.size(), url_index.char_word_map_.size());
    577   EXPECT_EQ(word_id_history_map.size(), url_index.word_id_history_map_.size());
    578   EXPECT_EQ(history_info_map.size(), url_index.history_info_map_.size());
    579   // WordList must be index-by-index equal.
    580   size_t count = word_list.size();
    581   for (size_t i = 0; i < count; ++i)
    582     EXPECT_EQ(word_list[i], url_index.word_list_[i]);
    583   for (InMemoryURLIndex::CharWordIDMap::const_iterator expected =
    584         char_word_map.begin(); expected != char_word_map.end(); ++expected) {
    585     InMemoryURLIndex::CharWordIDMap::const_iterator actual =
    586         url_index.char_word_map_.find(expected->first);
    587     ASSERT_TRUE(url_index.char_word_map_.end() != actual);
    588     const InMemoryURLIndex::WordIDSet& expected_set(expected->second);
    589     const InMemoryURLIndex::WordIDSet& actual_set(actual->second);
    590     ASSERT_EQ(expected_set.size(), actual_set.size());
    591     for (InMemoryURLIndex::WordIDSet::const_iterator set_iter =
    592         expected_set.begin(); set_iter != expected_set.end(); ++set_iter)
    593       EXPECT_GT(actual_set.count(*set_iter), 0U);
    594   }
    595   for (InMemoryURLIndex::WordIDHistoryMap::const_iterator expected =
    596       word_id_history_map.begin(); expected != word_id_history_map.end();
    597       ++expected) {
    598     InMemoryURLIndex::WordIDHistoryMap::const_iterator actual =
    599         url_index.word_id_history_map_.find(expected->first);
    600     ASSERT_TRUE(url_index.word_id_history_map_.end() != actual);
    601     const InMemoryURLIndex::HistoryIDSet& expected_set(expected->second);
    602     const InMemoryURLIndex::HistoryIDSet& actual_set(actual->second);
    603     ASSERT_EQ(expected_set.size(), actual_set.size());
    604     for (InMemoryURLIndex::HistoryIDSet::const_iterator set_iter =
    605         expected_set.begin(); set_iter != expected_set.end(); ++set_iter)
    606       EXPECT_GT(actual_set.count(*set_iter), 0U);
    607   }
    608   for (InMemoryURLIndex::HistoryInfoMap::const_iterator expected =
    609       history_info_map.begin(); expected != history_info_map.end();
    610       ++expected) {
    611     InMemoryURLIndex::HistoryInfoMap::const_iterator actual =
    612         url_index.history_info_map_.find(expected->first);
    613     ASSERT_FALSE(url_index.history_info_map_.end() == actual);
    614     const URLRow& expected_row(expected->second);
    615     const URLRow& actual_row(actual->second);
    616     EXPECT_EQ(expected_row.visit_count(), actual_row.visit_count());
    617     EXPECT_EQ(expected_row.typed_count(), actual_row.typed_count());
    618     EXPECT_EQ(expected_row.last_visit(), actual_row.last_visit());
    619     EXPECT_EQ(expected_row.url(), actual_row.url());
    620   }
    621 }
    622 
    623 }  // namespace history
    624