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