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