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