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/bookmarks/bookmark_test_helpers.h"
19#include "chrome/browser/chrome_notification_types.h"
20#include "chrome/browser/history/history_backend.h"
21#include "chrome/browser/history/history_database.h"
22#include "chrome/browser/history/history_notifications.h"
23#include "chrome/browser/history/history_service.h"
24#include "chrome/browser/history/history_service_factory.h"
25#include "chrome/browser/history/in_memory_url_index.h"
26#include "chrome/browser/history/in_memory_url_index_types.h"
27#include "chrome/browser/history/url_index_private_data.h"
28#include "chrome/common/chrome_paths.h"
29#include "chrome/test/base/history_index_restore_observer.h"
30#include "chrome/test/base/testing_profile.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
37using 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
50namespace history {
51
52// -----------------------------------------------------------------------------
53
54// Observer class so the unit tests can wait while the cache is being saved.
55class 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
65CacheFileSaverObserver::CacheFileSaverObserver(base::MessageLoop* loop)
66    : loop_(loop),
67      succeeded_(false) {
68  DCHECK(loop);
69}
70
71void CacheFileSaverObserver::OnCacheSaveFinished(bool succeeded) {
72  succeeded_ = succeeded;
73  loop_->Quit();
74}
75
76// -----------------------------------------------------------------------------
77
78class 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                 base::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
131InMemoryURLIndexTest::InMemoryURLIndexTest()
132    : ui_thread_(content::BrowserThread::UI, &message_loop_),
133      file_thread_(content::BrowserThread::FILE, &message_loop_) {
134}
135
136sql::Connection& InMemoryURLIndexTest::GetDB() {
137  return history_database_->GetDB();
138}
139
140URLIndexPrivateData* InMemoryURLIndexTest::GetPrivateData() const {
141  DCHECK(url_index_->private_data());
142  return url_index_->private_data();
143}
144
145void InMemoryURLIndexTest::ClearPrivateData() {
146  return url_index_->ClearPrivateData();
147}
148
149void InMemoryURLIndexTest::set_history_dir(const base::FilePath& dir_path) {
150  return url_index_->set_history_dir(dir_path);
151}
152
153bool InMemoryURLIndexTest::GetCacheFilePath(base::FilePath* file_path) const {
154  DCHECK(file_path);
155  return url_index_->GetCacheFilePath(file_path);
156}
157
158void InMemoryURLIndexTest::PostRestoreFromCacheFileTask() {
159  url_index_->PostRestoreFromCacheFileTask();
160}
161
162void InMemoryURLIndexTest::PostSaveToCacheFileTask() {
163  url_index_->PostSaveToCacheFileTask();
164}
165
166void 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
173const std::set<std::string>& InMemoryURLIndexTest::scheme_whitelist() {
174  return url_index_->scheme_whitelist();
175}
176
177bool InMemoryURLIndexTest::UpdateURL(const URLRow& row) {
178  return GetPrivateData()->UpdateURL(
179      history_service_, row, url_index_->languages_,
180      url_index_->scheme_whitelist_);
181}
182
183bool InMemoryURLIndexTest::DeleteURL(const GURL& url) {
184  return GetPrivateData()->DeleteURL(url);
185}
186
187void 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  test::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
280base::FilePath::StringType InMemoryURLIndexTest::TestDBName() const {
281    return FILE_PATH_LITERAL("url_history_provider_test.db.txt");
282}
283
284void InMemoryURLIndexTest::CheckTerm(
285    const URLIndexPrivateData::SearchTermCacheMap& cache,
286    base::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
296void 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
309void 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.
322template<typename T>
323void 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
339void 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
415class LimitedInMemoryURLIndexTest : public InMemoryURLIndexTest {
416 protected:
417  virtual base::FilePath::StringType TestDBName() const OVERRIDE;
418};
419
420base::FilePath::StringType LimitedInMemoryURLIndexTest::TestDBName() const {
421  return FILE_PATH_LITERAL("url_history_provider_test_limited.db.txt");
422}
423
424TEST_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
444TEST_F(InMemoryURLIndexTest, Retrieval) {
445  // See if a very specific term gives a single result.
446  ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
447      ASCIIToUTF16("DrudgeReport"), base::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                                             base::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                                             base::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"), base::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                                             base::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                                             base::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                                             base::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                                             base::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
526TEST_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"), base::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"), base::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
566TEST_F(InMemoryURLIndexTest, URLPrefixMatching) {
567  // "drudgere" - found, can inline
568  ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
569      ASCIIToUTF16("drudgere"), base::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                                             base::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                                             base::string16::npos);
582  EXPECT_EQ(0U, matches.size());
583
584  // "atdmt" - found, cannot inline
585  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("atdmt"),
586                                             base::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                                             base::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                                             base::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                                             base::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                                             base::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                                             base::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                                       base::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                                       base::string16::npos);
632  ASSERT_EQ(0U, matches.size());
633}
634
635TEST_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"), base::string16::npos);
642  ASSERT_EQ(1U, matches.size());
643  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("atdmt.view"),
644                                             base::string16::npos);
645  ASSERT_EQ(0U, matches.size());
646  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
647                                             base::string16::npos);
648  ASSERT_EQ(1U, matches.size());
649}
650
651TEST_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"), base::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
670TEST_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"), base::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
688TEST_F(InMemoryURLIndexTest, TitleChange) {
689  // Verify current title terms retrieves desired item.
690  base::string16 original_terms =
691      ASCIIToUTF16("lebronomics could high taxes influence");
692  ScoredHistoryMatches matches =
693      url_index_->HistoryItemsForTerms(original_terms, base::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  base::string16 new_terms = ASCIIToUTF16("does eat oats little lambs ivy");
708  matches = url_index_->HistoryItemsForTerms(new_terms, base::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, base::string16::npos);
717  ASSERT_EQ(1U, matches.size());
718  EXPECT_EQ(expected_id, matches[0].url_info.id());
719  matches =
720      url_index_->HistoryItemsForTerms(original_terms, base::string16::npos);
721  ASSERT_EQ(0U, matches.size());
722}
723
724TEST_F(InMemoryURLIndexTest, NonUniqueTermCharacterSets) {
725  // The presence of duplicate characters should succeed. Exercise by cycling
726  // through a string with several duplicate characters.
727  ScoredHistoryMatches matches =
728      url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRA"),
729                                       base::string16::npos);
730  ASSERT_EQ(1U, matches.size());
731  EXPECT_EQ(28, matches[0].url_info.id());
732  EXPECT_EQ("http://www.ddj.com/windows/184416623",
733            matches[0].url_info.url().spec());
734
735  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACAD"),
736                                             base::string16::npos);
737  ASSERT_EQ(1U, matches.size());
738  EXPECT_EQ(28, matches[0].url_info.id());
739
740  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACADABRA"),
741                                             base::string16::npos);
742  ASSERT_EQ(1U, matches.size());
743  EXPECT_EQ(28, matches[0].url_info.id());
744
745  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACADABR"),
746                                             base::string16::npos);
747  ASSERT_EQ(1U, matches.size());
748  EXPECT_EQ(28, matches[0].url_info.id());
749
750  matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACA"),
751                                             base::string16::npos);
752  ASSERT_EQ(1U, matches.size());
753  EXPECT_EQ(28, matches[0].url_info.id());
754}
755
756TEST_F(InMemoryURLIndexTest, TypedCharacterCaching) {
757  // Verify that match results for previously typed characters are retained
758  // (in the term_char_word_set_cache_) and reused, if possible, in future
759  // autocompletes.
760  typedef URLIndexPrivateData::SearchTermCacheMap::iterator CacheIter;
761  typedef URLIndexPrivateData::SearchTermCacheItem CacheItem;
762
763  URLIndexPrivateData::SearchTermCacheMap& cache(
764      GetPrivateData()->search_term_cache_);
765
766  // The cache should be empty at this point.
767  EXPECT_EQ(0U, cache.size());
768
769  // Now simulate typing search terms into the omnibox and check the state of
770  // the cache as each item is 'typed'.
771
772  // Simulate typing "r" giving "r" in the simulated omnibox. The results for
773  // 'r' will be not cached because it is only 1 character long.
774  url_index_->HistoryItemsForTerms(ASCIIToUTF16("r"), base::string16::npos);
775  EXPECT_EQ(0U, cache.size());
776
777  // Simulate typing "re" giving "r re" in the simulated omnibox.
778  // 're' should be cached at this point but not 'r' as it is a single
779  // character.
780  url_index_->HistoryItemsForTerms(ASCIIToUTF16("r re"), base::string16::npos);
781  ASSERT_EQ(1U, cache.size());
782  CheckTerm(cache, ASCIIToUTF16("re"));
783
784  // Simulate typing "reco" giving "r re reco" in the simulated omnibox.
785  // 're' and 'reco' should be cached at this point but not 'r' as it is a
786  // single character.
787  url_index_->HistoryItemsForTerms(ASCIIToUTF16("r re reco"),
788                                   base::string16::npos);
789  ASSERT_EQ(2U, cache.size());
790  CheckTerm(cache, ASCIIToUTF16("re"));
791  CheckTerm(cache, ASCIIToUTF16("reco"));
792
793  // Simulate typing "mort".
794  // Since we now have only one search term, the cached results for 're' and
795  // 'reco' should be purged, giving us only 1 item in the cache (for 'mort').
796  url_index_->HistoryItemsForTerms(ASCIIToUTF16("mort"), base::string16::npos);
797  ASSERT_EQ(1U, cache.size());
798  CheckTerm(cache, ASCIIToUTF16("mort"));
799
800  // Simulate typing "reco" giving "mort reco" in the simulated omnibox.
801  url_index_->HistoryItemsForTerms(ASCIIToUTF16("mort reco"),
802                                   base::string16::npos);
803  ASSERT_EQ(2U, cache.size());
804  CheckTerm(cache, ASCIIToUTF16("mort"));
805  CheckTerm(cache, ASCIIToUTF16("reco"));
806
807  // Simulate a <DELETE> by removing the 'reco' and adding back the 'rec'.
808  url_index_->HistoryItemsForTerms(ASCIIToUTF16("mort rec"),
809                                   base::string16::npos);
810  ASSERT_EQ(2U, cache.size());
811  CheckTerm(cache, ASCIIToUTF16("mort"));
812  CheckTerm(cache, ASCIIToUTF16("rec"));
813}
814
815TEST_F(InMemoryURLIndexTest, AddNewRows) {
816  // Verify that the row we're going to add does not already exist.
817  URLID new_row_id = 87654321;
818  // Newly created URLRows get a last_visit time of 'right now' so it should
819  // qualify as a quick result candidate.
820  EXPECT_TRUE(url_index_->HistoryItemsForTerms(
821      ASCIIToUTF16("brokeandalone"), base::string16::npos).empty());
822
823  // Add a new row.
824  URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"), new_row_id++);
825  new_row.set_last_visit(base::Time::Now());
826  EXPECT_TRUE(UpdateURL(new_row));
827
828  // Verify that we can retrieve it.
829  EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(
830      ASCIIToUTF16("brokeandalone"), base::string16::npos).size());
831
832  // Add it again just to be sure that is harmless and that it does not update
833  // the index.
834  EXPECT_FALSE(UpdateURL(new_row));
835  EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(
836      ASCIIToUTF16("brokeandalone"), base::string16::npos).size());
837
838  // Make up an URL that does not qualify and try to add it.
839  URLRow unqualified_row(GURL("http://www.brokeandaloneinmanitoba.com/"),
840                         new_row_id++);
841  EXPECT_FALSE(UpdateURL(new_row));
842}
843
844TEST_F(InMemoryURLIndexTest, DeleteRows) {
845  ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
846      ASCIIToUTF16("DrudgeReport"), base::string16::npos);
847  ASSERT_EQ(1U, matches.size());
848
849  // Delete the URL then search again.
850  EXPECT_TRUE(DeleteURL(matches[0].url_info.url()));
851  EXPECT_TRUE(url_index_->HistoryItemsForTerms(
852      ASCIIToUTF16("DrudgeReport"), base::string16::npos).empty());
853
854  // Make up an URL that does not exist in the database and delete it.
855  GURL url("http://www.hokeypokey.com/putyourrightfootin.html");
856  EXPECT_FALSE(DeleteURL(url));
857}
858
859TEST_F(InMemoryURLIndexTest, ExpireRow) {
860  ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
861      ASCIIToUTF16("DrudgeReport"), base::string16::npos);
862  ASSERT_EQ(1U, matches.size());
863
864  // Determine the row id for the result, remember that id, broadcast a
865  // delete notification, then ensure that the row has been deleted.
866  URLsDeletedDetails deleted_details;
867  deleted_details.all_history = false;
868  deleted_details.rows.push_back(matches[0].url_info);
869  Observe(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
870          content::Source<InMemoryURLIndexTest>(this),
871          content::Details<history::HistoryDetails>(&deleted_details));
872  EXPECT_TRUE(url_index_->HistoryItemsForTerms(
873      ASCIIToUTF16("DrudgeReport"), base::string16::npos).empty());
874}
875
876TEST_F(InMemoryURLIndexTest, WhitelistedURLs) {
877  struct TestData {
878    const std::string url_spec;
879    const bool expected_is_whitelisted;
880  } data[] = {
881    // URLs with whitelisted schemes.
882    { "about:histograms", true },
883    { "chrome://settings", true },
884    { "file://localhost/Users/joeschmoe/sekrets", true },
885    { "ftp://public.mycompany.com/myfile.txt", true },
886    { "http://www.google.com/translate", true },
887    { "https://www.gmail.com/", true },
888    { "mailto:support@google.com", true },
889    // URLs with unacceptable schemes.
890    { "aaa://www.dummyhost.com;frammy", false },
891    { "aaas://www.dummyhost.com;frammy", false },
892    { "acap://suzie@somebody.com", false },
893    { "cap://cal.example.com/Company/Holidays", false },
894    { "cid:foo4*foo1@bar.net", false },
895    { "crid://example.com/foobar", false },
896    { "data:image/png;base64,iVBORw0KGgoAAAANSUhE=", false },
897    { "dict://dict.org/d:shortcake:", false },
898    { "dns://192.168.1.1/ftp.example.org?type=A", false },
899    { "fax:+358.555.1234567", false },
900    { "geo:13.4125,103.8667", false },
901    { "go:Mercedes%20Benz", false },
902    { "gopher://farnsworth.ca:666/gopher", false },
903    { "h323:farmer-john;sixpence", false },
904    { "iax:johnQ@example.com/12022561414", false },
905    { "icap://icap.net/service?mode=translate&lang=french", false },
906    { "im:fred@example.com", false },
907    { "imap://michael@minbari.org/users.*", false },
908    { "info:ddc/22/eng//004.678", false },
909    { "ipp://example.com/printer/fox", false },
910    { "iris:dreg1//example.com/local/myhosts", false },
911    { "iris.beep:dreg1//example.com/local/myhosts", false },
912    { "iris.lws:dreg1//example.com/local/myhosts", false },
913    { "iris.xpc:dreg1//example.com/local/myhosts", false },
914    { "iris.xpcs:dreg1//example.com/local/myhosts", false },
915    { "ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US", false },
916    { "mid:foo4%25foo1@bar.net", false },
917    { "modem:+3585551234567;type=v32b?7e1;type=v110", false },
918    { "msrp://atlanta.example.com:7654/jshA7weztas;tcp", false },
919    { "msrps://atlanta.example.com:7654/jshA7weztas;tcp", false },
920    { "news:colorectal.info.banned", false },
921    { "nfs://server/d/e/f", false },
922    { "nntp://www.example.com:6543/info.comp.lies/1234", false },
923    { "pop://rg;AUTH=+APOP@mail.mycompany.com:8110", false },
924    { "pres:fred@example.com", false },
925    { "prospero://host.dom//pros/name", false },
926    { "rsync://syler@lost.com/Source", false },
927    { "rtsp://media.example.com:554/twister/audiotrack", false },
928    { "service:acap://some.where.net;authentication=KERBEROSV4", false },
929    { "shttp://www.terces.com/secret", false },
930    { "sieve://example.com//script", false },
931    { "sip:+1-212-555-1212:1234@gateway.com;user=phone", false },
932    { "sips:+1-212-555-1212:1234@gateway.com;user=phone", false },
933    { "sms:+15105551212?body=hello%20there", false },
934    { "snmp://tester5@example.com:8161/bridge1;800002b804616263", false },
935    { "soap.beep://stockquoteserver.example.com/StockQuote", false },
936    { "soap.beeps://stockquoteserver.example.com/StockQuote", false },
937    { "tag:blogger.com,1999:blog-555", false },
938    { "tel:+358-555-1234567;postd=pp22", false },
939    { "telnet://mayor_margie:one2rule4All@www.mycity.com:6789/", false },
940    { "tftp://example.com/mystartupfile", false },
941    { "tip://123.123.123.123/?urn:xopen:xid", false },
942    { "tv:nbc.com", false },
943    { "urn:foo:A123,456", false },
944    { "vemmi://zeus.mctel.fr/demo", false },
945    { "wais://www.mydomain.net:8765/mydatabase", false },
946    { "xmpp:node@example.com", false },
947    { "xmpp://guest@example.com", false },
948  };
949
950  URLIndexPrivateData& private_data(*GetPrivateData());
951  const std::set<std::string>& whitelist(scheme_whitelist());
952  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) {
953    GURL url(data[i].url_spec);
954    EXPECT_EQ(data[i].expected_is_whitelisted,
955              private_data.URLSchemeIsWhitelisted(url, whitelist));
956  }
957}
958
959TEST_F(InMemoryURLIndexTest, ReadVisitsFromHistory) {
960  const HistoryInfoMap& history_info_map = GetPrivateData()->history_info_map_;
961
962  // Check (for URL with id 1) that the number of visits and their
963  // transition types are what we expect.  We don't bother checking
964  // the timestamps because it's too much trouble.  (The timestamps go
965  // through a transformation in InMemoryURLIndexTest::SetUp().  We
966  // assume that if the count and transitions show up with the right
967  // information, we're getting the right information from the history
968  // database file.)
969  HistoryInfoMap::const_iterator entry = history_info_map.find(1);
970  ASSERT_TRUE(entry != history_info_map.end());
971  {
972    const VisitInfoVector& visits = entry->second.visits;
973    EXPECT_EQ(3u, visits.size());
974    EXPECT_EQ(0u, visits[0].second);
975    EXPECT_EQ(1u, visits[1].second);
976    EXPECT_EQ(0u, visits[2].second);
977  }
978
979  // Ditto but for URL with id 35.
980  entry = history_info_map.find(35);
981  ASSERT_TRUE(entry != history_info_map.end());
982  {
983    const VisitInfoVector& visits = entry->second.visits;
984    EXPECT_EQ(2u, visits.size());
985    EXPECT_EQ(1u, visits[0].second);
986    EXPECT_EQ(1u, visits[1].second);
987  }
988
989  // The URL with id 32 has many visits listed in the database, but we
990  // should only read the most recent 10 (which are all transition type 0).
991  entry = history_info_map.find(32);
992  ASSERT_TRUE(entry != history_info_map.end());
993  {
994    const VisitInfoVector& visits = entry->second.visits;
995    EXPECT_EQ(10u, visits.size());
996    for (size_t i = 0; i < visits.size(); ++i)
997      EXPECT_EQ(0u, visits[i].second);
998  }
999}
1000
1001TEST_F(InMemoryURLIndexTest, CacheSaveRestore) {
1002  base::ScopedTempDir temp_directory;
1003  ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
1004  set_history_dir(temp_directory.path());
1005
1006  URLIndexPrivateData& private_data(*GetPrivateData());
1007
1008  // Ensure that there is really something there to be saved.
1009  EXPECT_FALSE(private_data.word_list_.empty());
1010  // available_words_ will already be empty since we have freshly built the
1011  // data set for this test.
1012  EXPECT_TRUE(private_data.available_words_.empty());
1013  EXPECT_FALSE(private_data.word_map_.empty());
1014  EXPECT_FALSE(private_data.char_word_map_.empty());
1015  EXPECT_FALSE(private_data.word_id_history_map_.empty());
1016  EXPECT_FALSE(private_data.history_id_word_map_.empty());
1017  EXPECT_FALSE(private_data.history_info_map_.empty());
1018  EXPECT_FALSE(private_data.word_starts_map_.empty());
1019
1020  // Make sure the data we have was built from history.  (Version 0
1021  // means rebuilt from history.)
1022  EXPECT_EQ(0, private_data.restored_cache_version_);
1023
1024  // Capture the current private data for later comparison to restored data.
1025  scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
1026  const base::Time rebuild_time = private_data.last_time_rebuilt_from_history_;
1027
1028  // Save then restore our private data.
1029  CacheFileSaverObserver save_observer(&message_loop_);
1030  url_index_->set_save_cache_observer(&save_observer);
1031  PostSaveToCacheFileTask();
1032  message_loop_.Run();
1033  EXPECT_TRUE(save_observer.succeeded_);
1034
1035  // Clear and then prove it's clear before restoring.
1036  ClearPrivateData();
1037  EXPECT_TRUE(private_data.word_list_.empty());
1038  EXPECT_TRUE(private_data.available_words_.empty());
1039  EXPECT_TRUE(private_data.word_map_.empty());
1040  EXPECT_TRUE(private_data.char_word_map_.empty());
1041  EXPECT_TRUE(private_data.word_id_history_map_.empty());
1042  EXPECT_TRUE(private_data.history_id_word_map_.empty());
1043  EXPECT_TRUE(private_data.history_info_map_.empty());
1044  EXPECT_TRUE(private_data.word_starts_map_.empty());
1045
1046  HistoryIndexRestoreObserver restore_observer(
1047      base::Bind(&base::MessageLoop::Quit, base::Unretained(&message_loop_)));
1048  url_index_->set_restore_cache_observer(&restore_observer);
1049  PostRestoreFromCacheFileTask();
1050  message_loop_.Run();
1051  EXPECT_TRUE(restore_observer.succeeded());
1052
1053  URLIndexPrivateData& new_data(*GetPrivateData());
1054
1055  // Make sure the data we have was reloaded from cache.  (Version 0
1056  // means rebuilt from history; anything else means restored from
1057  // a cache version.)  Also, the rebuild time should not have changed.
1058  EXPECT_GT(new_data.restored_cache_version_, 0);
1059  EXPECT_EQ(rebuild_time, new_data.last_time_rebuilt_from_history_);
1060
1061  // Compare the captured and restored for equality.
1062  ExpectPrivateDataEqual(*old_data.get(), new_data);
1063}
1064
1065TEST_F(InMemoryURLIndexTest, RebuildFromHistoryIfCacheOld) {
1066  base::ScopedTempDir temp_directory;
1067  ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
1068  set_history_dir(temp_directory.path());
1069
1070  URLIndexPrivateData& private_data(*GetPrivateData());
1071
1072  // Ensure that there is really something there to be saved.
1073  EXPECT_FALSE(private_data.word_list_.empty());
1074  // available_words_ will already be empty since we have freshly built the
1075  // data set for this test.
1076  EXPECT_TRUE(private_data.available_words_.empty());
1077  EXPECT_FALSE(private_data.word_map_.empty());
1078  EXPECT_FALSE(private_data.char_word_map_.empty());
1079  EXPECT_FALSE(private_data.word_id_history_map_.empty());
1080  EXPECT_FALSE(private_data.history_id_word_map_.empty());
1081  EXPECT_FALSE(private_data.history_info_map_.empty());
1082  EXPECT_FALSE(private_data.word_starts_map_.empty());
1083
1084  // Make sure the data we have was built from history.  (Version 0
1085  // means rebuilt from history.)
1086  EXPECT_EQ(0, private_data.restored_cache_version_);
1087
1088  // Overwrite the build time so that we'll think the data is too old
1089  // and rebuild the cache from history.
1090  const base::Time fake_rebuild_time =
1091      base::Time::Now() - base::TimeDelta::FromDays(30);
1092  private_data.last_time_rebuilt_from_history_ = fake_rebuild_time;
1093
1094  // Capture the current private data for later comparison to restored data.
1095  scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
1096
1097  // Save then restore our private data.
1098  CacheFileSaverObserver save_observer(&message_loop_);
1099  url_index_->set_save_cache_observer(&save_observer);
1100  PostSaveToCacheFileTask();
1101  message_loop_.Run();
1102  EXPECT_TRUE(save_observer.succeeded_);
1103
1104  // Clear and then prove it's clear before restoring.
1105  ClearPrivateData();
1106  EXPECT_TRUE(private_data.word_list_.empty());
1107  EXPECT_TRUE(private_data.available_words_.empty());
1108  EXPECT_TRUE(private_data.word_map_.empty());
1109  EXPECT_TRUE(private_data.char_word_map_.empty());
1110  EXPECT_TRUE(private_data.word_id_history_map_.empty());
1111  EXPECT_TRUE(private_data.history_id_word_map_.empty());
1112  EXPECT_TRUE(private_data.history_info_map_.empty());
1113  EXPECT_TRUE(private_data.word_starts_map_.empty());
1114
1115  HistoryIndexRestoreObserver restore_observer(
1116      base::Bind(&base::MessageLoop::Quit, base::Unretained(&message_loop_)));
1117  url_index_->set_restore_cache_observer(&restore_observer);
1118  PostRestoreFromCacheFileTask();
1119  message_loop_.Run();
1120  EXPECT_TRUE(restore_observer.succeeded());
1121
1122  URLIndexPrivateData& new_data(*GetPrivateData());
1123
1124  // Make sure the data we have was rebuilt from history.  (Version 0
1125  // means rebuilt from history; anything else means restored from
1126  // a cache version.)
1127  EXPECT_EQ(0, new_data.restored_cache_version_);
1128  EXPECT_NE(fake_rebuild_time, new_data.last_time_rebuilt_from_history_);
1129
1130  // Compare the captured and restored for equality.
1131  ExpectPrivateDataEqual(*old_data.get(), new_data);
1132}
1133
1134class InMemoryURLIndexCacheTest : public testing::Test {
1135 public:
1136  InMemoryURLIndexCacheTest() {}
1137
1138 protected:
1139  virtual void SetUp() OVERRIDE;
1140
1141  // Pass-through functions to simplify our friendship with InMemoryURLIndex.
1142  void set_history_dir(const base::FilePath& dir_path);
1143  bool GetCacheFilePath(base::FilePath* file_path) const;
1144
1145  base::ScopedTempDir temp_dir_;
1146  scoped_ptr<InMemoryURLIndex> url_index_;
1147};
1148
1149void InMemoryURLIndexCacheTest::SetUp() {
1150  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
1151  base::FilePath path(temp_dir_.path());
1152  url_index_.reset(
1153      new InMemoryURLIndex(NULL, path, "en,ja,hi,zh"));
1154}
1155
1156void InMemoryURLIndexCacheTest::set_history_dir(
1157    const base::FilePath& dir_path) {
1158  return url_index_->set_history_dir(dir_path);
1159}
1160
1161bool InMemoryURLIndexCacheTest::GetCacheFilePath(
1162    base::FilePath* file_path) const {
1163  DCHECK(file_path);
1164  return url_index_->GetCacheFilePath(file_path);
1165}
1166
1167TEST_F(InMemoryURLIndexCacheTest, CacheFilePath) {
1168  base::FilePath expectedPath =
1169      temp_dir_.path().Append(FILE_PATH_LITERAL("History Provider Cache"));
1170  std::vector<base::FilePath::StringType> expected_parts;
1171  expectedPath.GetComponents(&expected_parts);
1172  base::FilePath full_file_path;
1173  ASSERT_TRUE(GetCacheFilePath(&full_file_path));
1174  std::vector<base::FilePath::StringType> actual_parts;
1175  full_file_path.GetComponents(&actual_parts);
1176  ASSERT_EQ(expected_parts.size(), actual_parts.size());
1177  size_t count = expected_parts.size();
1178  for (size_t i = 0; i < count; ++i)
1179    EXPECT_EQ(expected_parts[i], actual_parts[i]);
1180  // Must clear the history_dir_ to satisfy the dtor's DCHECK.
1181  set_history_dir(base::FilePath());
1182}
1183
1184}  // namespace history
1185