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.
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"
37 using content::BrowserThread;
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.
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.
52 // -----------------------------------------------------------------------------
54 // Observer class so the unit tests can wait while the cache is being saved.
55 class CacheFileSaverObserver : public InMemoryURLIndex::SaveCacheObserver {
57 explicit CacheFileSaverObserver(base::MessageLoop* loop);
58 virtual void OnCacheSaveFinished(bool succeeded) OVERRIDE;
60 base::MessageLoop* loop_;
62 DISALLOW_COPY_AND_ASSIGN(CacheFileSaverObserver);
65 CacheFileSaverObserver::CacheFileSaverObserver(base::MessageLoop* loop)
71 void CacheFileSaverObserver::OnCacheSaveFinished(bool succeeded) {
72 succeeded_ = succeeded;
76 // -----------------------------------------------------------------------------
78 class InMemoryURLIndexTest : public testing::Test {
80 InMemoryURLIndexTest();
86 // Allows the database containing the test data to be customized by
88 virtual base::FilePath::StringType TestDBName() const;
90 // Validates that the given |term| is contained in |cache| and that it is
92 void CheckTerm(const URLIndexPrivateData::SearchTermCacheMap& cache,
95 // Pass-through function to simplify our friendship with HistoryService.
96 sql::Connection& GetDB();
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();
111 // Pass-through functions to simplify our friendship with URLIndexPrivateData.
112 bool UpdateURL(const URLRow& row);
113 bool DeleteURL(const GURL& url);
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);
121 base::MessageLoopForUI message_loop_;
122 content::TestBrowserThread ui_thread_;
123 content::TestBrowserThread file_thread_;
124 TestingProfile profile_;
125 HistoryService* history_service_;
127 scoped_ptr<InMemoryURLIndex> url_index_;
128 HistoryDatabase* history_database_;
131 InMemoryURLIndexTest::InMemoryURLIndexTest()
132 : ui_thread_(content::BrowserThread::UI, &message_loop_),
133 file_thread_(content::BrowserThread::FILE, &message_loop_) {
136 sql::Connection& InMemoryURLIndexTest::GetDB() {
137 return history_database_->GetDB();
140 URLIndexPrivateData* InMemoryURLIndexTest::GetPrivateData() const {
141 DCHECK(url_index_->private_data());
142 return url_index_->private_data();
145 void InMemoryURLIndexTest::ClearPrivateData() {
146 return url_index_->ClearPrivateData();
149 void InMemoryURLIndexTest::set_history_dir(const base::FilePath& dir_path) {
150 return url_index_->set_history_dir(dir_path);
153 bool InMemoryURLIndexTest::GetCacheFilePath(base::FilePath* file_path) const {
155 return url_index_->GetCacheFilePath(file_path);
158 void InMemoryURLIndexTest::PostRestoreFromCacheFileTask() {
159 url_index_->PostRestoreFromCacheFileTask();
162 void InMemoryURLIndexTest::PostSaveToCacheFileTask() {
163 url_index_->PostSaveToCacheFileTask();
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);
173 const std::set<std::string>& InMemoryURLIndexTest::scheme_whitelist() {
174 return url_index_->scheme_whitelist();
177 bool InMemoryURLIndexTest::UpdateURL(const URLRow& row) {
178 return GetPrivateData()->UpdateURL(
179 history_service_, row, url_index_->languages_,
180 url_index_->scheme_whitelist_);
183 bool InMemoryURLIndexTest::DeleteURL(const GURL& url) {
184 return GetPrivateData()->DeleteURL(url);
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();
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));
208 std::ifstream proto_file(history_proto_path.value().c_str());
209 static const size_t kCommandBufferMaxSize = 2048;
210 char sql_cmd_line[kCommandBufferMaxSize];
212 sql::Connection& db(GetDB());
213 ASSERT_TRUE(db.is_open());
215 sql::Transaction transaction(&db);
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());
229 transaction.Commit();
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);
240 sql::Transaction transaction(&db);
242 while (statement.Step()) {
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);
251 transaction.Commit();
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());
260 sql::Transaction transaction(&db);
262 while (statement.Step()) {
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);
271 transaction.Commit();
275 new InMemoryURLIndex(&profile_, base::FilePath(), "en,ja,hi,zh"));
277 url_index_->RebuildFromHistory(history_database_);
280 base::FilePath::StringType InMemoryURLIndexTest::TestDBName() const {
281 return FILE_PATH_LITERAL("url_history_provider_test.db.txt");
284 void InMemoryURLIndexTest::CheckTerm(
285 const URLIndexPrivateData::SearchTermCacheMap& cache,
286 string16 term) const {
287 URLIndexPrivateData::SearchTermCacheMap::const_iterator cache_iter(
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.";
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());
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());
320 // Helper function which compares two maps for equivalence. The maps' values
321 // are associative containers and their contents are compared as well.
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));
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]);
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_);
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());
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);
388 for (WordStartsMap::const_iterator expected_starts =
389 expected.word_starts_map_.begin();
390 expected_starts != expected.word_starts_map_.end();
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()));
413 //------------------------------------------------------------------------------
415 class LimitedInMemoryURLIndexTest : public InMemoryURLIndexTest {
417 virtual base::FilePath::StringType TestDBName() const OVERRIDE;
420 base::FilePath::StringType LimitedInMemoryURLIndexTest::TestDBName() const {
421 return FILE_PATH_LITERAL("url_history_provider_test_limited.db.txt");
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);
433 new InMemoryURLIndex(&profile_, base::FilePath(), "en,ja,hi,zh"));
435 url_index_->RebuildFromHistory(history_database_);
436 URLIndexPrivateData& private_data(*GetPrivateData());
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());
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());
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);
456 // Make sure a trailing space prevents inline-ability but still results
457 // in the expected result.
458 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("DrudgeReport "),
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);
466 // Search which should result in multiple results.
467 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("drudge"),
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);
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);
486 // Search which should result in very poor result.
487 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("qui c"),
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);
499 // Search which will match at the end of an URL with encoded characters.
500 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("Mice"),
502 ASSERT_EQ(1U, matches.size());
503 EXPECT_EQ(30, matches[0].url_info.id());
504 EXPECT_FALSE(matches[0].can_inline);
506 // Check that URLs are not escaped an escape time.
507 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("1% wikipedia"),
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());
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"),
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);
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());
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());
536 // If the cursor is between Drud and Report, we should find the desired
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());
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());
549 // Ditto with cursor at end.
550 matches = url_index_->HistoryItemsForTerms(
551 ASCIIToUTF16("MORTGAGERATE DROPS"), 18u);
552 ASSERT_EQ(0U, matches.size());
554 // If the cursor is between MORTAGE And RATE, we should find the
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());
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);
573 // "drudgere" - found, can inline
574 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("drudgere"),
576 ASSERT_EQ(1U, matches.size());
577 EXPECT_TRUE(matches[0].can_inline);
579 // "www.atdmt" - not found
580 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("www.atdmt"),
582 EXPECT_EQ(0U, matches.size());
584 // "atdmt" - found, cannot inline
585 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("atdmt"),
587 ASSERT_EQ(1U, matches.size());
588 EXPECT_FALSE(matches[0].can_inline);
590 // "view.atdmt" - found, can inline
591 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
593 ASSERT_EQ(1U, matches.size());
594 EXPECT_TRUE(matches[0].can_inline);
596 // "view.atdmt" - found, can inline
597 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
599 ASSERT_EQ(1U, matches.size());
600 EXPECT_TRUE(matches[0].can_inline);
602 // "cnn.com" - found, can inline
603 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("cnn.com"),
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);
609 // "www.cnn.com" - found, can inline
610 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("www.cnn.com"),
612 ASSERT_EQ(1U, matches.size());
613 EXPECT_TRUE(matches[0].can_inline);
615 // "ww.cnn.com" - found because we allow mid-term matches in hostnames
616 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ww.cnn.com"),
618 ASSERT_EQ(1U, matches.size());
620 // "www.cnn.com" - found, can inline
622 url_index_->HistoryItemsForTerms(ASCIIToUTF16("www.cnn.com"),
624 ASSERT_EQ(1U, matches.size());
625 EXPECT_TRUE(matches[0].can_inline);
627 // "tp://www.cnn.com" - not found because we don't allow tp as a mid-term
630 url_index_->HistoryItemsForTerms(ASCIIToUTF16("tp://www.cnn.com"),
632 ASSERT_EQ(0U, matches.size());
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"),
645 ASSERT_EQ(0U, matches.size());
646 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
648 ASSERT_EQ(1U, matches.size());
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));
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_);
670 TEST_F(InMemoryURLIndexTest, TitleSearch) {
671 // Signal if someone has changed the test DB.
672 EXPECT_EQ(29U, GetPrivateData()->history_info_map_.size());
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());
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());
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());
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);
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());
712 old_row.set_title(ASCIIToUTF16("Does eat oats and little lambs eat ivy"));
713 EXPECT_TRUE(UpdateURL(old_row));
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());
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());
733 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACAD"),
735 ASSERT_EQ(1U, matches.size());
736 EXPECT_EQ(28, matches[0].url_info.id());
738 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACADABRA"),
740 ASSERT_EQ(1U, matches.size());
741 EXPECT_EQ(28, matches[0].url_info.id());
743 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACADABR"),
745 ASSERT_EQ(1U, matches.size());
746 EXPECT_EQ(28, matches[0].url_info.id());
748 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACA"),
750 ASSERT_EQ(1U, matches.size());
751 EXPECT_EQ(28, matches[0].url_info.id());
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
758 typedef URLIndexPrivateData::SearchTermCacheMap::iterator CacheIter;
759 typedef URLIndexPrivateData::SearchTermCacheItem CacheItem;
761 URLIndexPrivateData::SearchTermCacheMap& cache(
762 GetPrivateData()->search_term_cache_);
764 // The cache should be empty at this point.
765 EXPECT_EQ(0U, cache.size());
767 // Now simulate typing search terms into the omnibox and check the state of
768 // the cache as each item is 'typed'.
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());
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
778 url_index_->HistoryItemsForTerms(ASCIIToUTF16("r re"), string16::npos);
779 ASSERT_EQ(1U, cache.size());
780 CheckTerm(cache, ASCIIToUTF16("re"));
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
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"));
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"));
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"));
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"));
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());
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));
823 // Verify that we can retrieve it.
824 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(
825 ASCIIToUTF16("brokeandalone"), string16::npos).size());
827 // Add it again just to be sure that is harmless and that it does not update
829 EXPECT_FALSE(UpdateURL(new_row));
830 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(
831 ASCIIToUTF16("brokeandalone"), string16::npos).size());
833 // Make up an URL that does not qualify and try to add it.
834 URLRow unqualified_row(GURL("http://www.brokeandaloneinmanitoba.com/"),
836 EXPECT_FALSE(UpdateURL(new_row));
839 TEST_F(InMemoryURLIndexTest, DeleteRows) {
840 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
841 ASCIIToUTF16("DrudgeReport"), string16::npos);
842 ASSERT_EQ(1U, matches.size());
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());
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));
854 TEST_F(InMemoryURLIndexTest, ExpireRow) {
855 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
856 ASCIIToUTF16("DrudgeReport"), string16::npos);
857 ASSERT_EQ(1U, matches.size());
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());
871 TEST_F(InMemoryURLIndexTest, WhitelistedURLs) {
873 const std::string url_spec;
874 const bool expected_is_whitelisted;
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 },
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));
954 TEST_F(InMemoryURLIndexTest, ReadVisitsFromHistory) {
955 const HistoryInfoMap& history_info_map = GetPrivateData()->history_info_map_;
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
964 HistoryInfoMap::const_iterator entry = history_info_map.find(1);
965 ASSERT_TRUE(entry != history_info_map.end());
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);
974 // Ditto but for URL with id 35.
975 entry = history_info_map.find(35);
976 ASSERT_TRUE(entry != history_info_map.end());
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);
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());
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);
996 TEST_F(InMemoryURLIndexTest, CacheSaveRestore) {
997 base::ScopedTempDir temp_directory;
998 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
999 set_history_dir(temp_directory.path());
1001 URLIndexPrivateData& private_data(*GetPrivateData());
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());
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_);
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_;
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_);
1030 // Clear and then prove it's clear before restoring.
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());
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());
1048 URLIndexPrivateData& new_data(*GetPrivateData());
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_);
1056 // Compare the captured and restored for equality.
1057 ExpectPrivateDataEqual(*old_data.get(), new_data);
1060 TEST_F(InMemoryURLIndexTest, RebuildFromHistoryIfCacheOld) {
1061 base::ScopedTempDir temp_directory;
1062 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
1063 set_history_dir(temp_directory.path());
1065 URLIndexPrivateData& private_data(*GetPrivateData());
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());
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_);
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;
1089 // Capture the current private data for later comparison to restored data.
1090 scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
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_);
1099 // Clear and then prove it's clear before restoring.
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());
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());
1117 URLIndexPrivateData& new_data(*GetPrivateData());
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_);
1125 // Compare the captured and restored for equality.
1126 ExpectPrivateDataEqual(*old_data.get(), new_data);
1129 class InMemoryURLIndexCacheTest : public testing::Test {
1131 InMemoryURLIndexCacheTest() {}
1134 virtual void SetUp() OVERRIDE;
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;
1140 base::ScopedTempDir temp_dir_;
1141 scoped_ptr<InMemoryURLIndex> url_index_;
1144 void InMemoryURLIndexCacheTest::SetUp() {
1145 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
1146 base::FilePath path(temp_dir_.path());
1148 new InMemoryURLIndex(NULL, path, "en,ja,hi,zh"));
1151 void InMemoryURLIndexCacheTest::set_history_dir(
1152 const base::FilePath& dir_path) {
1153 return url_index_->set_history_dir(dir_path);
1156 bool InMemoryURLIndexCacheTest::GetCacheFilePath(
1157 base::FilePath* file_path) const {
1159 return url_index_->GetCacheFilePath(file_path);
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());
1179 } // namespace history