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
37namespace history {
38
39class 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
137class 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
144class 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
162TEST_F(InMemoryURLIndexTest, Construction) {
163  url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy"))));
164  EXPECT_TRUE(url_index_.get());
165}
166
167TEST_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
185TEST_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
245TEST_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
262TEST_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
285TEST_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
300TEST_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
370TEST_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
395TEST_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
443TEST_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
478TEST_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
503TEST_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
518TEST_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
534TEST_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