Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / autocomplete / in_memory_url_index_unittest.cc
bloba660481d3f4634e61f1eb481af02c60fe870eaf5
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.
5 #include <algorithm>
6 #include <fstream>
8 #include "base/auto_reset.h"
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/i18n/case_conversion.h"
13 #include "base/path_service.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string16.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
21 #include "chrome/browser/history/history_service_factory.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "chrome/test/base/history_index_restore_observer.h"
24 #include "chrome/test/base/testing_profile.h"
25 #include "components/bookmarks/test/bookmark_test_helpers.h"
26 #include "components/history/core/browser/history_backend.h"
27 #include "components/history/core/browser/history_database.h"
28 #include "components/history/core/browser/history_service.h"
29 #include "components/omnibox/browser/in_memory_url_index.h"
30 #include "components/omnibox/browser/in_memory_url_index_types.h"
31 #include "components/omnibox/browser/url_index_private_data.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/test/test_browser_thread_bundle.h"
34 #include "sql/transaction.h"
35 #include "testing/gtest/include/gtest/gtest.h"
37 using base::ASCIIToUTF16;
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.
50 namespace {
51 const size_t kInvalid = base::string16::npos;
52 const size_t kMaxMatches = 3;
53 const char kTestLanguages[] = "en,ja,hi,zh";
54 const char kClientWhitelistedScheme[] = "xyz";
56 // Helper function to set lower case |lower_string| and |lower_terms| (words
57 // list) based on supplied |search_string| and |cursor_position|. If
58 // |cursor_position| is set and useful (not at either end of the string), allow
59 // the |search_string| to be broken at |cursor_position|. We do this by
60 // pretending there's a space where the cursor is. |lower_terms| are obtained by
61 // splitting the |lower_string| on whitespace into tokens.
62 void StringToTerms(const char* search_string,
63 size_t cursor_position,
64 base::string16* lower_string,
65 String16Vector* lower_terms) {
66 *lower_string = base::i18n::ToLower(ASCIIToUTF16(search_string));
67 if ((cursor_position != kInvalid) &&
68 (cursor_position < lower_string->length()) && (cursor_position > 0)) {
69 lower_string->insert(cursor_position, base::ASCIIToUTF16(" "));
72 *lower_terms = base::SplitString(*lower_string, base::kWhitespaceUTF16,
73 base::KEEP_WHITESPACE,
74 base::SPLIT_WANT_NONEMPTY);
77 } // namespace
79 // -----------------------------------------------------------------------------
81 // Observer class so the unit tests can wait while the cache is being saved.
82 class CacheFileSaverObserver : public InMemoryURLIndex::SaveCacheObserver {
83 public:
84 explicit CacheFileSaverObserver(const base::Closure& task);
86 bool succeeded() { return succeeded_; }
88 private:
89 // SaveCacheObserver implementation.
90 void OnCacheSaveFinished(bool succeeded) override;
92 base::Closure task_;
93 bool succeeded_;
95 DISALLOW_COPY_AND_ASSIGN(CacheFileSaverObserver);
98 CacheFileSaverObserver::CacheFileSaverObserver(const base::Closure& task)
99 : task_(task),
100 succeeded_(false) {
103 void CacheFileSaverObserver::OnCacheSaveFinished(bool succeeded) {
104 succeeded_ = succeeded;
105 task_.Run();
108 // -----------------------------------------------------------------------------
110 class InMemoryURLIndexTest : public testing::Test {
111 public:
112 InMemoryURLIndexTest();
114 protected:
115 // Test setup.
116 void SetUp() override;
117 void TearDown() override;
119 // Allows the database containing the test data to be customized by
120 // subclasses.
121 virtual base::FilePath::StringType TestDBName() const;
123 // Allows the test to control when the InMemoryURLIndex is initialized.
124 virtual bool InitializeInMemoryURLIndexInSetUp() const;
126 // Initialize the InMemoryURLIndex for the tests.
127 void InitializeInMemoryURLIndex();
129 // Validates that the given |term| is contained in |cache| and that it is
130 // marked as in-use.
131 void CheckTerm(const URLIndexPrivateData::SearchTermCacheMap& cache,
132 base::string16 term) const;
134 // Pass-through function to simplify our friendship with HistoryService.
135 sql::Connection& GetDB();
137 // Pass-through functions to simplify our friendship with InMemoryURLIndex.
138 URLIndexPrivateData* GetPrivateData() const;
139 base::CancelableTaskTracker* GetPrivateDataTracker() const;
140 void ClearPrivateData();
141 void set_history_dir(const base::FilePath& dir_path);
142 bool GetCacheFilePath(base::FilePath* file_path) const;
143 void PostRestoreFromCacheFileTask();
144 void PostSaveToCacheFileTask();
145 const SchemeSet& scheme_whitelist();
148 // Pass-through functions to simplify our friendship with URLIndexPrivateData.
149 bool UpdateURL(const history::URLRow& row);
150 bool DeleteURL(const GURL& url);
152 // Data verification helper functions.
153 void ExpectPrivateDataNotEmpty(const URLIndexPrivateData& data);
154 void ExpectPrivateDataEmpty(const URLIndexPrivateData& data);
155 void ExpectPrivateDataEqual(const URLIndexPrivateData& expected,
156 const URLIndexPrivateData& actual);
158 content::TestBrowserThreadBundle thread_bundle_;
159 scoped_ptr<InMemoryURLIndex> url_index_;
160 TestingProfile profile_;
161 history::HistoryService* history_service_;
162 history::HistoryDatabase* history_database_;
165 InMemoryURLIndexTest::InMemoryURLIndexTest()
166 : history_service_(nullptr), history_database_(nullptr) {
169 sql::Connection& InMemoryURLIndexTest::GetDB() {
170 return history_database_->GetDB();
173 URLIndexPrivateData* InMemoryURLIndexTest::GetPrivateData() const {
174 DCHECK(url_index_->private_data());
175 return url_index_->private_data();
178 base::CancelableTaskTracker* InMemoryURLIndexTest::GetPrivateDataTracker()
179 const {
180 DCHECK(url_index_->private_data_tracker());
181 return url_index_->private_data_tracker();
184 void InMemoryURLIndexTest::ClearPrivateData() {
185 return url_index_->ClearPrivateData();
188 void InMemoryURLIndexTest::set_history_dir(const base::FilePath& dir_path) {
189 return url_index_->set_history_dir(dir_path);
192 bool InMemoryURLIndexTest::GetCacheFilePath(base::FilePath* file_path) const {
193 DCHECK(file_path);
194 return url_index_->GetCacheFilePath(file_path);
197 void InMemoryURLIndexTest::PostRestoreFromCacheFileTask() {
198 url_index_->PostRestoreFromCacheFileTask();
201 void InMemoryURLIndexTest::PostSaveToCacheFileTask() {
202 url_index_->PostSaveToCacheFileTask();
205 const SchemeSet& InMemoryURLIndexTest::scheme_whitelist() {
206 return url_index_->scheme_whitelist();
209 bool InMemoryURLIndexTest::UpdateURL(const history::URLRow& row) {
210 return GetPrivateData()->UpdateURL(history_service_,
211 row,
212 url_index_->languages_,
213 url_index_->scheme_whitelist_,
214 GetPrivateDataTracker());
217 bool InMemoryURLIndexTest::DeleteURL(const GURL& url) {
218 return GetPrivateData()->DeleteURL(url);
221 void InMemoryURLIndexTest::SetUp() {
222 // We cannot access the database until the backend has been loaded.
223 ASSERT_TRUE(profile_.CreateHistoryService(true, false));
224 profile_.CreateBookmarkModel(true);
225 bookmarks::test::WaitForBookmarkModelToLoad(
226 BookmarkModelFactory::GetForProfile(&profile_));
227 profile_.BlockUntilHistoryProcessesPendingRequests();
228 profile_.BlockUntilHistoryIndexIsRefreshed();
229 history_service_ = HistoryServiceFactory::GetForProfile(
230 &profile_, ServiceAccessType::EXPLICIT_ACCESS);
231 ASSERT_TRUE(history_service_);
232 history::HistoryBackend* backend = history_service_->history_backend_.get();
233 history_database_ = backend->db();
235 // Create and populate a working copy of the URL history database.
236 base::FilePath history_proto_path;
237 PathService::Get(chrome::DIR_TEST_DATA, &history_proto_path);
238 history_proto_path = history_proto_path.Append(
239 FILE_PATH_LITERAL("History"));
240 history_proto_path = history_proto_path.Append(TestDBName());
241 EXPECT_TRUE(base::PathExists(history_proto_path));
243 std::ifstream proto_file(history_proto_path.value().c_str());
244 static const size_t kCommandBufferMaxSize = 2048;
245 char sql_cmd_line[kCommandBufferMaxSize];
247 sql::Connection& db(GetDB());
248 ASSERT_TRUE(db.is_open());
250 sql::Transaction transaction(&db);
251 transaction.Begin();
252 while (!proto_file.eof()) {
253 proto_file.getline(sql_cmd_line, kCommandBufferMaxSize);
254 if (!proto_file.eof()) {
255 // We only process lines which begin with a upper-case letter.
256 // TODO(mrossetti): Can iswupper() be used here?
257 if (sql_cmd_line[0] >= 'A' && sql_cmd_line[0] <= 'Z') {
258 std::string sql_cmd(sql_cmd_line);
259 sql::Statement sql_stmt(db.GetUniqueStatement(sql_cmd_line));
260 EXPECT_TRUE(sql_stmt.Run());
264 transaction.Commit();
267 // Update the last_visit_time table column in the "urls" table
268 // such that it represents a time relative to 'now'.
269 sql::Statement statement(db.GetUniqueStatement(
270 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls;"));
271 ASSERT_TRUE(statement.is_valid());
272 base::Time time_right_now = base::Time::NowFromSystemTime();
273 base::TimeDelta day_delta = base::TimeDelta::FromDays(1);
275 sql::Transaction transaction(&db);
276 transaction.Begin();
277 while (statement.Step()) {
278 history::URLRow row;
279 history_database_->FillURLRow(statement, &row);
280 base::Time last_visit = time_right_now;
281 for (int64 i = row.last_visit().ToInternalValue(); i > 0; --i)
282 last_visit -= day_delta;
283 row.set_last_visit(last_visit);
284 history_database_->UpdateURLRow(row.id(), row);
286 transaction.Commit();
289 // Update the visit_time table column in the "visits" table
290 // such that it represents a time relative to 'now'.
291 statement.Assign(db.GetUniqueStatement(
292 "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits;"));
293 ASSERT_TRUE(statement.is_valid());
295 sql::Transaction transaction(&db);
296 transaction.Begin();
297 while (statement.Step()) {
298 history::VisitRow row;
299 history_database_->FillVisitRow(statement, &row);
300 base::Time last_visit = time_right_now;
301 for (int64 i = row.visit_time.ToInternalValue(); i > 0; --i)
302 last_visit -= day_delta;
303 row.visit_time = last_visit;
304 history_database_->UpdateVisitRow(row);
306 transaction.Commit();
309 if (InitializeInMemoryURLIndexInSetUp())
310 InitializeInMemoryURLIndex();
313 void InMemoryURLIndexTest::TearDown() {
314 // Ensure that the InMemoryURLIndex no longer observes HistoryService before
315 // it is destroyed in order to prevent HistoryService calling dead observer.
316 if (url_index_)
317 url_index_->Shutdown();
320 base::FilePath::StringType InMemoryURLIndexTest::TestDBName() const {
321 return FILE_PATH_LITERAL("url_history_provider_test.db.txt");
324 bool InMemoryURLIndexTest::InitializeInMemoryURLIndexInSetUp() const {
325 return true;
328 void InMemoryURLIndexTest::InitializeInMemoryURLIndex() {
329 DCHECK(!url_index_);
331 SchemeSet client_schemes_to_whitelist;
332 client_schemes_to_whitelist.insert(kClientWhitelistedScheme);
333 url_index_.reset(new InMemoryURLIndex(
334 nullptr, history_service_, content::BrowserThread::GetBlockingPool(),
335 base::FilePath(), kTestLanguages, client_schemes_to_whitelist));
336 url_index_->Init();
337 url_index_->RebuildFromHistory(history_database_);
340 void InMemoryURLIndexTest::CheckTerm(
341 const URLIndexPrivateData::SearchTermCacheMap& cache,
342 base::string16 term) const {
343 URLIndexPrivateData::SearchTermCacheMap::const_iterator cache_iter(
344 cache.find(term));
345 ASSERT_TRUE(cache.end() != cache_iter)
346 << "Cache does not contain '" << term << "' but should.";
347 URLIndexPrivateData::SearchTermCacheItem cache_item = cache_iter->second;
348 EXPECT_TRUE(cache_item.used_)
349 << "Cache item '" << term << "' should be marked as being in use.";
352 void InMemoryURLIndexTest::ExpectPrivateDataNotEmpty(
353 const URLIndexPrivateData& data) {
354 EXPECT_FALSE(data.word_list_.empty());
355 // available_words_ will be empty since we have freshly built the
356 // data set for these tests.
357 EXPECT_TRUE(data.available_words_.empty());
358 EXPECT_FALSE(data.word_map_.empty());
359 EXPECT_FALSE(data.char_word_map_.empty());
360 EXPECT_FALSE(data.word_id_history_map_.empty());
361 EXPECT_FALSE(data.history_id_word_map_.empty());
362 EXPECT_FALSE(data.history_info_map_.empty());
365 void InMemoryURLIndexTest::ExpectPrivateDataEmpty(
366 const URLIndexPrivateData& data) {
367 EXPECT_TRUE(data.word_list_.empty());
368 EXPECT_TRUE(data.available_words_.empty());
369 EXPECT_TRUE(data.word_map_.empty());
370 EXPECT_TRUE(data.char_word_map_.empty());
371 EXPECT_TRUE(data.word_id_history_map_.empty());
372 EXPECT_TRUE(data.history_id_word_map_.empty());
373 EXPECT_TRUE(data.history_info_map_.empty());
376 // Helper function which compares two maps for equivalence. The maps' values
377 // are associative containers and their contents are compared as well.
378 template<typename T>
379 void ExpectMapOfContainersIdentical(const T& expected, const T& actual) {
380 ASSERT_EQ(expected.size(), actual.size());
381 for (typename T::const_iterator expected_iter = expected.begin();
382 expected_iter != expected.end(); ++expected_iter) {
383 typename T::const_iterator actual_iter = actual.find(expected_iter->first);
384 ASSERT_TRUE(actual.end() != actual_iter);
385 typename T::mapped_type const& expected_values(expected_iter->second);
386 typename T::mapped_type const& actual_values(actual_iter->second);
387 ASSERT_EQ(expected_values.size(), actual_values.size());
388 for (typename T::mapped_type::const_iterator set_iter =
389 expected_values.begin(); set_iter != expected_values.end(); ++set_iter)
390 EXPECT_EQ(actual_values.count(*set_iter),
391 expected_values.count(*set_iter));
395 void InMemoryURLIndexTest::ExpectPrivateDataEqual(
396 const URLIndexPrivateData& expected,
397 const URLIndexPrivateData& actual) {
398 EXPECT_EQ(expected.word_list_.size(), actual.word_list_.size());
399 EXPECT_EQ(expected.word_map_.size(), actual.word_map_.size());
400 EXPECT_EQ(expected.char_word_map_.size(), actual.char_word_map_.size());
401 EXPECT_EQ(expected.word_id_history_map_.size(),
402 actual.word_id_history_map_.size());
403 EXPECT_EQ(expected.history_id_word_map_.size(),
404 actual.history_id_word_map_.size());
405 EXPECT_EQ(expected.history_info_map_.size(), actual.history_info_map_.size());
406 EXPECT_EQ(expected.word_starts_map_.size(), actual.word_starts_map_.size());
407 // WordList must be index-by-index equal.
408 size_t count = expected.word_list_.size();
409 for (size_t i = 0; i < count; ++i)
410 EXPECT_EQ(expected.word_list_[i], actual.word_list_[i]);
412 ExpectMapOfContainersIdentical(expected.char_word_map_,
413 actual.char_word_map_);
414 ExpectMapOfContainersIdentical(expected.word_id_history_map_,
415 actual.word_id_history_map_);
416 ExpectMapOfContainersIdentical(expected.history_id_word_map_,
417 actual.history_id_word_map_);
419 for (HistoryInfoMap::const_iterator expected_info =
420 expected.history_info_map_.begin();
421 expected_info != expected.history_info_map_.end(); ++expected_info) {
422 HistoryInfoMap::const_iterator actual_info =
423 actual.history_info_map_.find(expected_info->first);
424 // NOTE(yfriedman): ASSERT_NE can't be used due to incompatibility between
425 // gtest and STLPort in the Android build. See
426 // http://code.google.com/p/googletest/issues/detail?id=359
427 ASSERT_TRUE(actual_info != actual.history_info_map_.end());
428 const history::URLRow& expected_row(expected_info->second.url_row);
429 const history::URLRow& actual_row(actual_info->second.url_row);
430 EXPECT_EQ(expected_row.visit_count(), actual_row.visit_count());
431 EXPECT_EQ(expected_row.typed_count(), actual_row.typed_count());
432 EXPECT_EQ(expected_row.last_visit(), actual_row.last_visit());
433 EXPECT_EQ(expected_row.url(), actual_row.url());
434 const VisitInfoVector& expected_visits(expected_info->second.visits);
435 const VisitInfoVector& actual_visits(actual_info->second.visits);
436 EXPECT_EQ(expected_visits.size(), actual_visits.size());
437 for (size_t i = 0;
438 i < std::min(expected_visits.size(), actual_visits.size()); ++i) {
439 EXPECT_EQ(expected_visits[i].first, actual_visits[i].first);
440 EXPECT_EQ(expected_visits[i].second, actual_visits[i].second);
444 for (WordStartsMap::const_iterator expected_starts =
445 expected.word_starts_map_.begin();
446 expected_starts != expected.word_starts_map_.end();
447 ++expected_starts) {
448 WordStartsMap::const_iterator actual_starts =
449 actual.word_starts_map_.find(expected_starts->first);
450 // NOTE(yfriedman): ASSERT_NE can't be used due to incompatibility between
451 // gtest and STLPort in the Android build. See
452 // http://code.google.com/p/googletest/issues/detail?id=359
453 ASSERT_TRUE(actual_starts != actual.word_starts_map_.end());
454 const RowWordStarts& expected_word_starts(expected_starts->second);
455 const RowWordStarts& actual_word_starts(actual_starts->second);
456 EXPECT_EQ(expected_word_starts.url_word_starts_.size(),
457 actual_word_starts.url_word_starts_.size());
458 EXPECT_TRUE(std::equal(expected_word_starts.url_word_starts_.begin(),
459 expected_word_starts.url_word_starts_.end(),
460 actual_word_starts.url_word_starts_.begin()));
461 EXPECT_EQ(expected_word_starts.title_word_starts_.size(),
462 actual_word_starts.title_word_starts_.size());
463 EXPECT_TRUE(std::equal(expected_word_starts.title_word_starts_.begin(),
464 expected_word_starts.title_word_starts_.end(),
465 actual_word_starts.title_word_starts_.begin()));
469 //------------------------------------------------------------------------------
471 class LimitedInMemoryURLIndexTest : public InMemoryURLIndexTest {
472 protected:
473 base::FilePath::StringType TestDBName() const override;
474 bool InitializeInMemoryURLIndexInSetUp() const override;
477 base::FilePath::StringType LimitedInMemoryURLIndexTest::TestDBName() const {
478 return FILE_PATH_LITERAL("url_history_provider_test_limited.db.txt");
481 bool LimitedInMemoryURLIndexTest::InitializeInMemoryURLIndexInSetUp() const {
482 return false;
485 TEST_F(LimitedInMemoryURLIndexTest, Initialization) {
486 // Verify that the database contains the expected number of items, which
487 // is the pre-filtered count, i.e. all of the items.
488 sql::Statement statement(GetDB().GetUniqueStatement("SELECT * FROM urls;"));
489 ASSERT_TRUE(statement.is_valid());
490 uint64 row_count = 0;
491 while (statement.Step()) ++row_count;
492 EXPECT_EQ(1U, row_count);
494 InitializeInMemoryURLIndex();
495 URLIndexPrivateData& private_data(*GetPrivateData());
497 // history_info_map_ should have the same number of items as were filtered.
498 EXPECT_EQ(1U, private_data.history_info_map_.size());
499 EXPECT_EQ(35U, private_data.char_word_map_.size());
500 EXPECT_EQ(17U, private_data.word_map_.size());
503 #if defined(OS_WIN)
504 // Flaky on windows trybots: http://crbug.com/351500
505 #define MAYBE_Retrieval DISABLED_Retrieval
506 #else
507 #define MAYBE_Retrieval Retrieval
508 #endif
509 TEST_F(InMemoryURLIndexTest, MAYBE_Retrieval) {
510 // See if a very specific term gives a single result.
511 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
512 ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches);
513 ASSERT_EQ(1U, matches.size());
515 // Verify that we got back the result we expected.
516 EXPECT_EQ(5, matches[0].url_info.id());
517 EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
518 EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
519 EXPECT_TRUE(matches[0].can_inline);
521 // Make sure a trailing space prevents inline-ability but still results
522 // in the expected result.
523 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("DrudgeReport "),
524 base::string16::npos, kMaxMatches);
525 ASSERT_EQ(1U, matches.size());
526 EXPECT_EQ(5, matches[0].url_info.id());
527 EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
528 EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
529 EXPECT_FALSE(matches[0].can_inline);
531 // Search which should result in multiple results.
532 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("drudge"),
533 base::string16::npos, kMaxMatches);
534 ASSERT_EQ(2U, matches.size());
535 // The results should be in descending score order.
536 EXPECT_GE(matches[0].raw_score, matches[1].raw_score);
538 // Search which should result in nearly perfect result.
539 matches = url_index_->HistoryItemsForTerms(
540 ASCIIToUTF16("Nearly Perfect Result"), base::string16::npos, kMaxMatches);
541 ASSERT_EQ(1U, matches.size());
542 // The results should have a very high score.
543 EXPECT_GT(matches[0].raw_score, 900);
544 EXPECT_EQ(32, matches[0].url_info.id());
545 EXPECT_EQ("https://nearlyperfectresult.com/",
546 matches[0].url_info.url().spec()); // Note: URL gets lowercased.
547 EXPECT_EQ(ASCIIToUTF16("Practically Perfect Search Result"),
548 matches[0].url_info.title());
549 EXPECT_FALSE(matches[0].can_inline);
551 // Search which should result in very poor result.
552 // No results since it will be suppressed by default scoring.
553 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("qui c"),
554 base::string16::npos, kMaxMatches);
555 ASSERT_EQ(0U, matches.size());
557 // Search which will match at the end of an URL with encoded characters.
558 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("Mice"),
559 base::string16::npos, kMaxMatches);
560 ASSERT_EQ(1U, matches.size());
561 EXPECT_EQ(30, matches[0].url_info.id());
562 EXPECT_FALSE(matches[0].can_inline);
564 // Check that URLs are not escaped an escape time.
565 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("1% wikipedia"),
566 base::string16::npos, kMaxMatches);
567 ASSERT_EQ(1U, matches.size());
568 EXPECT_EQ(35, matches[0].url_info.id());
569 EXPECT_EQ("http://en.wikipedia.org/wiki/1%25_rule_(Internet_culture)",
570 matches[0].url_info.url().spec());
572 // Verify that a single term can appear multiple times in the URL and as long
573 // as one starts the URL it is still inlined.
574 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("fubar"),
575 base::string16::npos, kMaxMatches);
576 ASSERT_EQ(1U, matches.size());
577 EXPECT_EQ(34, matches[0].url_info.id());
578 EXPECT_EQ("http://fubarfubarandfubar.com/", matches[0].url_info.url().spec());
579 EXPECT_EQ(ASCIIToUTF16("Situation Normal -- FUBARED"),
580 matches[0].url_info.title());
581 EXPECT_TRUE(matches[0].can_inline);
584 TEST_F(InMemoryURLIndexTest, CursorPositionRetrieval) {
585 // See if a very specific term with no cursor gives an empty result.
586 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
587 ASCIIToUTF16("DrudReport"), base::string16::npos, kMaxMatches);
588 ASSERT_EQ(0U, matches.size());
590 // The same test with the cursor at the end should give an empty result.
591 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("DrudReport"), 10u,
592 kMaxMatches);
593 ASSERT_EQ(0U, matches.size());
595 // If the cursor is between Drud and Report, we should find the desired
596 // result.
597 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("DrudReport"), 4u,
598 kMaxMatches);
599 ASSERT_EQ(1U, matches.size());
600 EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec());
601 EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title());
603 // Now check multi-word inputs. No cursor should fail to find a
604 // result on this input.
605 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("MORTGAGERATE DROPS"),
606 base::string16::npos, kMaxMatches);
607 ASSERT_EQ(0U, matches.size());
609 // Ditto with cursor at end.
610 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("MORTGAGERATE DROPS"),
611 18u, kMaxMatches);
612 ASSERT_EQ(0U, matches.size());
614 // If the cursor is between MORTAGE And RATE, we should find the
615 // desired result.
616 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("MORTGAGERATE DROPS"),
617 8u, kMaxMatches);
618 ASSERT_EQ(1U, matches.size());
619 EXPECT_EQ("http://www.reuters.com/article/idUSN0839880620100708",
620 matches[0].url_info.url().spec());
621 EXPECT_EQ(ASCIIToUTF16(
622 "UPDATE 1-US 30-yr mortgage rate drops to new record low | Reuters"),
623 matches[0].url_info.title());
626 TEST_F(InMemoryURLIndexTest, URLPrefixMatching) {
627 // "drudgere" - found, can inline
628 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
629 ASCIIToUTF16("drudgere"), base::string16::npos, kMaxMatches);
630 ASSERT_EQ(1U, matches.size());
631 EXPECT_TRUE(matches[0].can_inline);
633 // "drudgere" - found, can inline
634 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("drudgere"),
635 base::string16::npos, kMaxMatches);
636 ASSERT_EQ(1U, matches.size());
637 EXPECT_TRUE(matches[0].can_inline);
639 // "www.atdmt" - not found
640 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("www.atdmt"),
641 base::string16::npos, kMaxMatches);
642 EXPECT_EQ(0U, matches.size());
644 // "atdmt" - found, cannot inline
645 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("atdmt"),
646 base::string16::npos, kMaxMatches);
647 ASSERT_EQ(1U, matches.size());
648 EXPECT_FALSE(matches[0].can_inline);
650 // "view.atdmt" - found, can inline
651 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
652 base::string16::npos, kMaxMatches);
653 ASSERT_EQ(1U, matches.size());
654 EXPECT_TRUE(matches[0].can_inline);
656 // "view.atdmt" - found, can inline
657 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
658 base::string16::npos, kMaxMatches);
659 ASSERT_EQ(1U, matches.size());
660 EXPECT_TRUE(matches[0].can_inline);
662 // "cnn.com" - found, can inline
663 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("cnn.com"),
664 base::string16::npos, kMaxMatches);
665 ASSERT_EQ(2U, matches.size());
666 // One match should be inline-able, the other not.
667 EXPECT_TRUE(matches[0].can_inline != matches[1].can_inline);
669 // "www.cnn.com" - found, can inline
670 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("www.cnn.com"),
671 base::string16::npos, kMaxMatches);
672 ASSERT_EQ(1U, matches.size());
673 EXPECT_TRUE(matches[0].can_inline);
675 // "ww.cnn.com" - found because we suppress mid-term matches.
676 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ww.cnn.com"),
677 base::string16::npos, kMaxMatches);
678 ASSERT_EQ(0U, matches.size());
680 // "www.cnn.com" - found, can inline
681 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("www.cnn.com"),
682 base::string16::npos, kMaxMatches);
683 ASSERT_EQ(1U, matches.size());
684 EXPECT_TRUE(matches[0].can_inline);
686 // "tp://www.cnn.com" - not found because we don't allow tp as a mid-term
687 // match
688 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("tp://www.cnn.com"),
689 base::string16::npos, kMaxMatches);
690 ASSERT_EQ(0U, matches.size());
693 TEST_F(InMemoryURLIndexTest, ProperStringMatching) {
694 // Search for the following with the expected results:
695 // "atdmt view" - found
696 // "atdmt.view" - not found
697 // "view.atdmt" - found
698 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
699 ASCIIToUTF16("atdmt view"), base::string16::npos, kMaxMatches);
700 ASSERT_EQ(1U, matches.size());
701 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("atdmt.view"),
702 base::string16::npos, kMaxMatches);
703 ASSERT_EQ(0U, matches.size());
704 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("view.atdmt"),
705 base::string16::npos, kMaxMatches);
706 ASSERT_EQ(1U, matches.size());
709 TEST_F(InMemoryURLIndexTest, HugeResultSet) {
710 // Create a huge set of qualifying history items.
711 for (history::URLID row_id = 5000; row_id < 6000; ++row_id) {
712 history::URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"),
713 row_id);
714 new_row.set_last_visit(base::Time::Now());
715 EXPECT_TRUE(UpdateURL(new_row));
718 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
719 ASCIIToUTF16("b"), base::string16::npos, kMaxMatches);
720 URLIndexPrivateData& private_data(*GetPrivateData());
721 ASSERT_EQ(kMaxMatches, matches.size());
722 // There are 7 matches already in the database.
723 ASSERT_EQ(1008U, private_data.pre_filter_item_count_);
724 ASSERT_EQ(500U, private_data.post_filter_item_count_);
725 ASSERT_EQ(kMaxMatches, private_data.post_scoring_item_count_);
728 #if defined(OS_WIN)
729 // Flaky on windows trybots: http://crbug.com/351500
730 #define MAYBE_TitleSearch DISABLED_TitleSearch
731 #else
732 #define MAYBE_TitleSearch TitleSearch
733 #endif
734 TEST_F(InMemoryURLIndexTest, MAYBE_TitleSearch) {
735 // Signal if someone has changed the test DB.
736 EXPECT_EQ(29U, GetPrivateData()->history_info_map_.size());
738 // Ensure title is being searched.
739 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
740 ASCIIToUTF16("MORTGAGE RATE DROPS"), base::string16::npos, kMaxMatches);
741 ASSERT_EQ(1U, matches.size());
743 // Verify that we got back the result we expected.
744 EXPECT_EQ(1, matches[0].url_info.id());
745 EXPECT_EQ("http://www.reuters.com/article/idUSN0839880620100708",
746 matches[0].url_info.url().spec());
747 EXPECT_EQ(ASCIIToUTF16(
748 "UPDATE 1-US 30-yr mortgage rate drops to new record low | Reuters"),
749 matches[0].url_info.title());
752 TEST_F(InMemoryURLIndexTest, TitleChange) {
753 // Verify current title terms retrieves desired item.
754 base::string16 original_terms =
755 ASCIIToUTF16("lebronomics could high taxes influence");
756 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
757 original_terms, base::string16::npos, kMaxMatches);
758 ASSERT_EQ(1U, matches.size());
760 // Verify that we got back the result we expected.
761 const history::URLID expected_id = 3;
762 EXPECT_EQ(expected_id, matches[0].url_info.id());
763 EXPECT_EQ("http://www.businessandmedia.org/articles/2010/20100708120415.aspx",
764 matches[0].url_info.url().spec());
765 EXPECT_EQ(ASCIIToUTF16(
766 "LeBronomics: Could High Taxes Influence James' Team Decision?"),
767 matches[0].url_info.title());
768 history::URLRow old_row(matches[0].url_info);
770 // Verify new title terms retrieves nothing.
771 base::string16 new_terms = ASCIIToUTF16("does eat oats little lambs ivy");
772 matches = url_index_->HistoryItemsForTerms(new_terms, base::string16::npos,
773 kMaxMatches);
774 ASSERT_EQ(0U, matches.size());
776 // Update the row.
777 old_row.set_title(ASCIIToUTF16("Does eat oats and little lambs eat ivy"));
778 EXPECT_TRUE(UpdateURL(old_row));
780 // Verify we get the row using the new terms but not the original terms.
781 matches = url_index_->HistoryItemsForTerms(new_terms, base::string16::npos,
782 kMaxMatches);
783 ASSERT_EQ(1U, matches.size());
784 EXPECT_EQ(expected_id, matches[0].url_info.id());
785 matches = url_index_->HistoryItemsForTerms(original_terms,
786 base::string16::npos, kMaxMatches);
787 ASSERT_EQ(0U, matches.size());
790 TEST_F(InMemoryURLIndexTest, NonUniqueTermCharacterSets) {
791 // The presence of duplicate characters should succeed. Exercise by cycling
792 // through a string with several duplicate characters.
793 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
794 ASCIIToUTF16("ABRA"), base::string16::npos, kMaxMatches);
795 ASSERT_EQ(1U, matches.size());
796 EXPECT_EQ(28, matches[0].url_info.id());
797 EXPECT_EQ("http://www.ddj.com/windows/184416623",
798 matches[0].url_info.url().spec());
800 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACAD"),
801 base::string16::npos, kMaxMatches);
802 ASSERT_EQ(1U, matches.size());
803 EXPECT_EQ(28, matches[0].url_info.id());
805 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACADABRA"),
806 base::string16::npos, kMaxMatches);
807 ASSERT_EQ(1U, matches.size());
808 EXPECT_EQ(28, matches[0].url_info.id());
810 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACADABR"),
811 base::string16::npos, kMaxMatches);
812 ASSERT_EQ(1U, matches.size());
813 EXPECT_EQ(28, matches[0].url_info.id());
815 matches = url_index_->HistoryItemsForTerms(ASCIIToUTF16("ABRACA"),
816 base::string16::npos, kMaxMatches);
817 ASSERT_EQ(1U, matches.size());
818 EXPECT_EQ(28, matches[0].url_info.id());
821 TEST_F(InMemoryURLIndexTest, TypedCharacterCaching) {
822 // Verify that match results for previously typed characters are retained
823 // (in the term_char_word_set_cache_) and reused, if possible, in future
824 // autocompletes.
826 URLIndexPrivateData::SearchTermCacheMap& cache(
827 GetPrivateData()->search_term_cache_);
829 // The cache should be empty at this point.
830 EXPECT_EQ(0U, cache.size());
832 // Now simulate typing search terms into the omnibox and check the state of
833 // the cache as each item is 'typed'.
835 // Simulate typing "r" giving "r" in the simulated omnibox. The results for
836 // 'r' will be not cached because it is only 1 character long.
837 url_index_->HistoryItemsForTerms(ASCIIToUTF16("r"), base::string16::npos,
838 kMaxMatches);
839 EXPECT_EQ(0U, cache.size());
841 // Simulate typing "re" giving "r re" in the simulated omnibox.
842 // 're' should be cached at this point but not 'r' as it is a single
843 // character.
844 url_index_->HistoryItemsForTerms(ASCIIToUTF16("r re"), base::string16::npos,
845 kMaxMatches);
846 ASSERT_EQ(1U, cache.size());
847 CheckTerm(cache, ASCIIToUTF16("re"));
849 // Simulate typing "reco" giving "r re reco" in the simulated omnibox.
850 // 're' and 'reco' should be cached at this point but not 'r' as it is a
851 // single character.
852 url_index_->HistoryItemsForTerms(ASCIIToUTF16("r re reco"),
853 base::string16::npos, kMaxMatches);
854 ASSERT_EQ(2U, cache.size());
855 CheckTerm(cache, ASCIIToUTF16("re"));
856 CheckTerm(cache, ASCIIToUTF16("reco"));
858 // Simulate typing "mort".
859 // Since we now have only one search term, the cached results for 're' and
860 // 'reco' should be purged, giving us only 1 item in the cache (for 'mort').
861 url_index_->HistoryItemsForTerms(ASCIIToUTF16("mort"), base::string16::npos,
862 kMaxMatches);
863 ASSERT_EQ(1U, cache.size());
864 CheckTerm(cache, ASCIIToUTF16("mort"));
866 // Simulate typing "reco" giving "mort reco" in the simulated omnibox.
867 url_index_->HistoryItemsForTerms(ASCIIToUTF16("mort reco"),
868 base::string16::npos, kMaxMatches);
869 ASSERT_EQ(2U, cache.size());
870 CheckTerm(cache, ASCIIToUTF16("mort"));
871 CheckTerm(cache, ASCIIToUTF16("reco"));
873 // Simulate a <DELETE> by removing the 'reco' and adding back the 'rec'.
874 url_index_->HistoryItemsForTerms(ASCIIToUTF16("mort rec"),
875 base::string16::npos, kMaxMatches);
876 ASSERT_EQ(2U, cache.size());
877 CheckTerm(cache, ASCIIToUTF16("mort"));
878 CheckTerm(cache, ASCIIToUTF16("rec"));
881 TEST_F(InMemoryURLIndexTest, AddNewRows) {
882 // Verify that the row we're going to add does not already exist.
883 history::URLID new_row_id = 87654321;
884 // Newly created history::URLRows get a last_visit time of 'right now' so it
885 // should
886 // qualify as a quick result candidate.
887 EXPECT_TRUE(url_index_->HistoryItemsForTerms(ASCIIToUTF16("brokeandalone"),
888 base::string16::npos,
889 kMaxMatches).empty());
891 // Add a new row.
892 history::URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"),
893 new_row_id++);
894 new_row.set_last_visit(base::Time::Now());
895 EXPECT_TRUE(UpdateURL(new_row));
897 // Verify that we can retrieve it.
898 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(ASCIIToUTF16("brokeandalone"),
899 base::string16::npos,
900 kMaxMatches).size());
902 // Add it again just to be sure that is harmless and that it does not update
903 // the index.
904 EXPECT_FALSE(UpdateURL(new_row));
905 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(ASCIIToUTF16("brokeandalone"),
906 base::string16::npos,
907 kMaxMatches).size());
909 // Make up an URL that does not qualify and try to add it.
910 history::URLRow unqualified_row(
911 GURL("http://www.brokeandaloneinmanitoba.com/"), new_row_id++);
912 EXPECT_FALSE(UpdateURL(new_row));
915 TEST_F(InMemoryURLIndexTest, DeleteRows) {
916 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
917 ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches);
918 ASSERT_EQ(1U, matches.size());
920 // Delete the URL then search again.
921 EXPECT_TRUE(DeleteURL(matches[0].url_info.url()));
922 EXPECT_TRUE(url_index_->HistoryItemsForTerms(ASCIIToUTF16("DrudgeReport"),
923 base::string16::npos,
924 kMaxMatches).empty());
926 // Make up an URL that does not exist in the database and delete it.
927 GURL url("http://www.hokeypokey.com/putyourrightfootin.html");
928 EXPECT_FALSE(DeleteURL(url));
931 TEST_F(InMemoryURLIndexTest, ExpireRow) {
932 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(
933 ASCIIToUTF16("DrudgeReport"), base::string16::npos, kMaxMatches);
934 ASSERT_EQ(1U, matches.size());
936 // Determine the row id for the result, remember that id, broadcast a
937 // delete notification, then ensure that the row has been deleted.
938 history::URLRows deleted_rows;
939 deleted_rows.push_back(matches[0].url_info);
940 url_index_->OnURLsDeleted(nullptr, false, false, deleted_rows,
941 std::set<GURL>());
942 EXPECT_TRUE(url_index_->HistoryItemsForTerms(ASCIIToUTF16("DrudgeReport"),
943 base::string16::npos,
944 kMaxMatches).empty());
947 TEST_F(InMemoryURLIndexTest, WhitelistedURLs) {
948 std::string client_whitelisted_url =
949 base::StringPrintf("%s://foo", kClientWhitelistedScheme);
950 struct TestData {
951 const std::string url_spec;
952 const bool expected_is_whitelisted;
953 } data[] = {
954 // URLs with whitelisted schemes.
955 { "about:histograms", true },
956 { "file://localhost/Users/joeschmoe/sekrets", true },
957 { "ftp://public.mycompany.com/myfile.txt", true },
958 { "http://www.google.com/translate", true },
959 { "https://www.gmail.com/", true },
960 { "mailto:support@google.com", true },
961 { client_whitelisted_url, true },
962 // URLs with unacceptable schemes.
963 { "aaa://www.dummyhost.com;frammy", false },
964 { "aaas://www.dummyhost.com;frammy", false },
965 { "acap://suzie@somebody.com", false },
966 { "cap://cal.example.com/Company/Holidays", false },
967 { "cid:foo4*foo1@bar.net", false },
968 { "crid://example.com/foobar", false },
969 { "data:image/png;base64,iVBORw0KGgoAAAANSUhE=", false },
970 { "dict://dict.org/d:shortcake:", false },
971 { "dns://192.168.1.1/ftp.example.org?type=A", false },
972 { "fax:+358.555.1234567", false },
973 { "geo:13.4125,103.8667", false },
974 { "go:Mercedes%20Benz", false },
975 { "gopher://farnsworth.ca:666/gopher", false },
976 { "h323:farmer-john;sixpence", false },
977 { "iax:johnQ@example.com/12022561414", false },
978 { "icap://icap.net/service?mode=translate&lang=french", false },
979 { "im:fred@example.com", false },
980 { "imap://michael@minbari.org/users.*", false },
981 { "info:ddc/22/eng//004.678", false },
982 { "ipp://example.com/printer/fox", false },
983 { "iris:dreg1//example.com/local/myhosts", false },
984 { "iris.beep:dreg1//example.com/local/myhosts", false },
985 { "iris.lws:dreg1//example.com/local/myhosts", false },
986 { "iris.xpc:dreg1//example.com/local/myhosts", false },
987 { "iris.xpcs:dreg1//example.com/local/myhosts", false },
988 { "ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US", false },
989 { "mid:foo4%25foo1@bar.net", false },
990 { "modem:+3585551234567;type=v32b?7e1;type=v110", false },
991 { "msrp://atlanta.example.com:7654/jshA7weztas;tcp", false },
992 { "msrps://atlanta.example.com:7654/jshA7weztas;tcp", false },
993 { "news:colorectal.info.banned", false },
994 { "nfs://server/d/e/f", false },
995 { "nntp://www.example.com:6543/info.comp.lies/1234", false },
996 { "pop://rg;AUTH=+APOP@mail.mycompany.com:8110", false },
997 { "pres:fred@example.com", false },
998 { "prospero://host.dom//pros/name", false },
999 { "rsync://syler@lost.com/Source", false },
1000 { "rtsp://media.example.com:554/twister/audiotrack", false },
1001 { "service:acap://some.where.net;authentication=KERBEROSV4", false },
1002 { "shttp://www.terces.com/secret", false },
1003 { "sieve://example.com//script", false },
1004 { "sip:+1-212-555-1212:1234@gateway.com;user=phone", false },
1005 { "sips:+1-212-555-1212:1234@gateway.com;user=phone", false },
1006 { "sms:+15105551212?body=hello%20there", false },
1007 { "snmp://tester5@example.com:8161/bridge1;800002b804616263", false },
1008 { "soap.beep://stockquoteserver.example.com/StockQuote", false },
1009 { "soap.beeps://stockquoteserver.example.com/StockQuote", false },
1010 { "tag:blogger.com,1999:blog-555", false },
1011 { "tel:+358-555-1234567;postd=pp22", false },
1012 { "telnet://mayor_margie:one2rule4All@www.mycity.com:6789/", false },
1013 { "tftp://example.com/mystartupfile", false },
1014 { "tip://123.123.123.123/?urn:xopen:xid", false },
1015 { "tv:nbc.com", false },
1016 { "urn:foo:A123,456", false },
1017 { "vemmi://zeus.mctel.fr/demo", false },
1018 { "wais://www.mydomain.net:8765/mydatabase", false },
1019 { "xmpp:node@example.com", false },
1020 { "xmpp://guest@example.com", false },
1023 const SchemeSet& whitelist(scheme_whitelist());
1024 for (size_t i = 0; i < arraysize(data); ++i) {
1025 GURL url(data[i].url_spec);
1026 EXPECT_EQ(data[i].expected_is_whitelisted,
1027 URLIndexPrivateData::URLSchemeIsWhitelisted(url, whitelist));
1031 TEST_F(InMemoryURLIndexTest, ReadVisitsFromHistory) {
1032 const HistoryInfoMap& history_info_map = GetPrivateData()->history_info_map_;
1034 // Check (for URL with id 1) that the number of visits and their
1035 // transition types are what we expect. We don't bother checking
1036 // the timestamps because it's too much trouble. (The timestamps go
1037 // through a transformation in InMemoryURLIndexTest::SetUp(). We
1038 // assume that if the count and transitions show up with the right
1039 // information, we're getting the right information from the history
1040 // database file.)
1041 HistoryInfoMap::const_iterator entry = history_info_map.find(1);
1042 ASSERT_TRUE(entry != history_info_map.end());
1044 const VisitInfoVector& visits = entry->second.visits;
1045 EXPECT_EQ(3u, visits.size());
1046 EXPECT_EQ(0u, visits[0].second);
1047 EXPECT_EQ(1u, visits[1].second);
1048 EXPECT_EQ(0u, visits[2].second);
1051 // Ditto but for URL with id 35.
1052 entry = history_info_map.find(35);
1053 ASSERT_TRUE(entry != history_info_map.end());
1055 const VisitInfoVector& visits = entry->second.visits;
1056 EXPECT_EQ(2u, visits.size());
1057 EXPECT_EQ(1u, visits[0].second);
1058 EXPECT_EQ(1u, visits[1].second);
1061 // The URL with id 32 has many visits listed in the database, but we
1062 // should only read the most recent 10 (which are all transition type 0).
1063 entry = history_info_map.find(32);
1064 ASSERT_TRUE(entry != history_info_map.end());
1066 const VisitInfoVector& visits = entry->second.visits;
1067 EXPECT_EQ(10u, visits.size());
1068 for (size_t i = 0; i < visits.size(); ++i)
1069 EXPECT_EQ(0u, visits[i].second);
1073 TEST_F(InMemoryURLIndexTest, CacheSaveRestore) {
1074 base::ScopedTempDir temp_directory;
1075 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
1076 set_history_dir(temp_directory.path());
1078 URLIndexPrivateData& private_data(*GetPrivateData());
1080 // Ensure that there is really something there to be saved.
1081 EXPECT_FALSE(private_data.word_list_.empty());
1082 // available_words_ will already be empty since we have freshly built the
1083 // data set for this test.
1084 EXPECT_TRUE(private_data.available_words_.empty());
1085 EXPECT_FALSE(private_data.word_map_.empty());
1086 EXPECT_FALSE(private_data.char_word_map_.empty());
1087 EXPECT_FALSE(private_data.word_id_history_map_.empty());
1088 EXPECT_FALSE(private_data.history_id_word_map_.empty());
1089 EXPECT_FALSE(private_data.history_info_map_.empty());
1090 EXPECT_FALSE(private_data.word_starts_map_.empty());
1092 // Make sure the data we have was built from history. (Version 0
1093 // means rebuilt from history.)
1094 EXPECT_EQ(0, private_data.restored_cache_version_);
1096 // Capture the current private data for later comparison to restored data.
1097 scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
1098 const base::Time rebuild_time = private_data.last_time_rebuilt_from_history_;
1101 // Save then restore our private data.
1102 base::RunLoop run_loop;
1103 CacheFileSaverObserver save_observer(run_loop.QuitClosure());
1104 url_index_->set_save_cache_observer(&save_observer);
1105 PostSaveToCacheFileTask();
1106 run_loop.Run();
1107 EXPECT_TRUE(save_observer.succeeded());
1110 // Clear and then prove it's clear before restoring.
1111 ClearPrivateData();
1112 EXPECT_TRUE(private_data.word_list_.empty());
1113 EXPECT_TRUE(private_data.available_words_.empty());
1114 EXPECT_TRUE(private_data.word_map_.empty());
1115 EXPECT_TRUE(private_data.char_word_map_.empty());
1116 EXPECT_TRUE(private_data.word_id_history_map_.empty());
1117 EXPECT_TRUE(private_data.history_id_word_map_.empty());
1118 EXPECT_TRUE(private_data.history_info_map_.empty());
1119 EXPECT_TRUE(private_data.word_starts_map_.empty());
1122 base::RunLoop run_loop;
1123 HistoryIndexRestoreObserver restore_observer(run_loop.QuitClosure());
1124 url_index_->set_restore_cache_observer(&restore_observer);
1125 PostRestoreFromCacheFileTask();
1126 run_loop.Run();
1127 EXPECT_TRUE(restore_observer.succeeded());
1130 URLIndexPrivateData& new_data(*GetPrivateData());
1132 // Make sure the data we have was reloaded from cache. (Version 0
1133 // means rebuilt from history; anything else means restored from
1134 // a cache version.) Also, the rebuild time should not have changed.
1135 EXPECT_GT(new_data.restored_cache_version_, 0);
1136 EXPECT_EQ(rebuild_time, new_data.last_time_rebuilt_from_history_);
1138 // Compare the captured and restored for equality.
1139 ExpectPrivateDataEqual(*old_data.get(), new_data);
1142 #if defined(OS_WIN)
1143 // http://crbug.com/351500
1144 #define MAYBE_RebuildFromHistoryIfCacheOld DISABLED_RebuildFromHistoryIfCacheOld
1145 #else
1146 #define MAYBE_RebuildFromHistoryIfCacheOld RebuildFromHistoryIfCacheOld
1147 #endif
1148 TEST_F(InMemoryURLIndexTest, MAYBE_RebuildFromHistoryIfCacheOld) {
1149 base::ScopedTempDir temp_directory;
1150 ASSERT_TRUE(temp_directory.CreateUniqueTempDir());
1151 set_history_dir(temp_directory.path());
1153 URLIndexPrivateData& private_data(*GetPrivateData());
1155 // Ensure that there is really something there to be saved.
1156 EXPECT_FALSE(private_data.word_list_.empty());
1157 // available_words_ will already be empty since we have freshly built the
1158 // data set for this test.
1159 EXPECT_TRUE(private_data.available_words_.empty());
1160 EXPECT_FALSE(private_data.word_map_.empty());
1161 EXPECT_FALSE(private_data.char_word_map_.empty());
1162 EXPECT_FALSE(private_data.word_id_history_map_.empty());
1163 EXPECT_FALSE(private_data.history_id_word_map_.empty());
1164 EXPECT_FALSE(private_data.history_info_map_.empty());
1165 EXPECT_FALSE(private_data.word_starts_map_.empty());
1167 // Make sure the data we have was built from history. (Version 0
1168 // means rebuilt from history.)
1169 EXPECT_EQ(0, private_data.restored_cache_version_);
1171 // Overwrite the build time so that we'll think the data is too old
1172 // and rebuild the cache from history.
1173 const base::Time fake_rebuild_time =
1174 private_data.last_time_rebuilt_from_history_ -
1175 base::TimeDelta::FromDays(30);
1176 private_data.last_time_rebuilt_from_history_ = fake_rebuild_time;
1178 // Capture the current private data for later comparison to restored data.
1179 scoped_refptr<URLIndexPrivateData> old_data(private_data.Duplicate());
1182 // Save then restore our private data.
1183 base::RunLoop run_loop;
1184 CacheFileSaverObserver save_observer(run_loop.QuitClosure());
1185 url_index_->set_save_cache_observer(&save_observer);
1186 PostSaveToCacheFileTask();
1187 run_loop.Run();
1188 EXPECT_TRUE(save_observer.succeeded());
1191 // Clear and then prove it's clear before restoring.
1192 ClearPrivateData();
1193 EXPECT_TRUE(private_data.word_list_.empty());
1194 EXPECT_TRUE(private_data.available_words_.empty());
1195 EXPECT_TRUE(private_data.word_map_.empty());
1196 EXPECT_TRUE(private_data.char_word_map_.empty());
1197 EXPECT_TRUE(private_data.word_id_history_map_.empty());
1198 EXPECT_TRUE(private_data.history_id_word_map_.empty());
1199 EXPECT_TRUE(private_data.history_info_map_.empty());
1200 EXPECT_TRUE(private_data.word_starts_map_.empty());
1203 base::RunLoop run_loop;
1204 HistoryIndexRestoreObserver restore_observer(run_loop.QuitClosure());
1205 url_index_->set_restore_cache_observer(&restore_observer);
1206 PostRestoreFromCacheFileTask();
1207 run_loop.Run();
1208 EXPECT_TRUE(restore_observer.succeeded());
1211 URLIndexPrivateData& new_data(*GetPrivateData());
1213 // Make sure the data we have was rebuilt from history. (Version 0
1214 // means rebuilt from history; anything else means restored from
1215 // a cache version.)
1216 EXPECT_EQ(0, new_data.restored_cache_version_);
1217 EXPECT_NE(fake_rebuild_time, new_data.last_time_rebuilt_from_history_);
1219 // Compare the captured and restored for equality.
1220 ExpectPrivateDataEqual(*old_data.get(), new_data);
1223 TEST_F(InMemoryURLIndexTest, AddHistoryMatch) {
1224 const struct {
1225 const char* search_string;
1226 size_t cursor_position;
1227 const size_t expected_word_starts_offsets_size;
1228 const size_t expected_word_starts_offsets[3];
1229 } test_cases[] = {
1230 /* No punctuations, only cursor position change. */
1231 { "ABCD", kInvalid, 1, {0, kInvalid, kInvalid} },
1232 { "abcd", 0, 1, {0, kInvalid, kInvalid} },
1233 { "AbcD", 1, 2, {0, 0, kInvalid} },
1234 { "abcd", 4, 1, {0, kInvalid, kInvalid} },
1236 /* Starting with punctuation. */
1237 { ".abcd", kInvalid, 1, {1, kInvalid, kInvalid} },
1238 { ".abcd", 0, 1, {1, kInvalid, kInvalid} },
1239 { "!abcd", 1, 2, {1, 0, kInvalid} },
1240 { "::abcd", 1, 2, {1, 1, kInvalid} },
1241 { ":abcd", 5, 1, {1, kInvalid, kInvalid} },
1243 /* Ending with punctuation. */
1244 { "abcd://", kInvalid, 1, {0, kInvalid, kInvalid} },
1245 { "ABCD://", 0, 1, {0, kInvalid, kInvalid} },
1246 { "abcd://", 1, 2, {0, 0, kInvalid} },
1247 { "abcd://", 4, 2, {0, 3, kInvalid} },
1248 { "abcd://", 7, 1, {0, kInvalid, kInvalid} },
1250 /* Punctuation in the middle. */
1251 { "ab.cd", kInvalid, 1, {0, kInvalid, kInvalid} },
1252 { "ab.cd", 0, 1, {0, kInvalid, kInvalid} },
1253 { "ab!cd", 1, 2, {0, 0, kInvalid} },
1254 { "AB.cd", 2, 2, {0, 1, kInvalid} },
1255 { "AB.cd", 3, 2, {0, 0, kInvalid} },
1256 { "ab:cd", 5, 1, {0, kInvalid, kInvalid} },
1258 /* Hyphenation */
1259 { "Ab-cd", kInvalid, 1, {0, kInvalid, kInvalid} },
1260 { "ab-cd", 0, 1, {0, kInvalid, kInvalid} },
1261 { "-abcd", 0, 1, {1, kInvalid, kInvalid} },
1262 { "-abcd", 1, 2, {1, 0, kInvalid} },
1263 { "abcd-", 2, 2, {0, 0, kInvalid} },
1264 { "abcd-", 4, 2, {0, 1, kInvalid} },
1265 { "ab-cd", 5, 1, {0, kInvalid, kInvalid} },
1267 /* Whitespace */
1268 { "Ab cd", kInvalid, 2, {0, 0, kInvalid} },
1269 { "ab cd", 0, 2, {0, 0, kInvalid} },
1270 { " abcd", 0, 1, {0, kInvalid, kInvalid} },
1271 { " abcd", 1, 1, {0, kInvalid, kInvalid} },
1272 { "abcd ", 2, 2, {0, 0, kInvalid} },
1273 { "abcd :", 4, 2, {0, 1, kInvalid} },
1274 { "abcd :", 5, 2, {0, 1, kInvalid} },
1275 { "abcd :", 2, 3, {0, 0, 1} }
1278 for (size_t i = 0; i < arraysize(test_cases); ++i) {
1279 SCOPED_TRACE(testing::Message()
1280 << "search_string = " << test_cases[i].search_string
1281 << ", cursor_position = " << test_cases[i].cursor_position);
1283 base::string16 lower_string;
1284 String16Vector lower_terms;
1285 StringToTerms(test_cases[i].search_string, test_cases[i].cursor_position,
1286 &lower_string, &lower_terms);
1287 URLIndexPrivateData::AddHistoryMatch match(nullptr, *GetPrivateData(),
1288 kTestLanguages, lower_string,
1289 lower_terms, base::Time::Now());
1291 // Verify against expectations.
1292 EXPECT_EQ(test_cases[i].expected_word_starts_offsets_size,
1293 match.lower_terms_to_word_starts_offsets_.size());
1294 for (size_t j = 0; j < test_cases[i].expected_word_starts_offsets_size;
1295 ++j) {
1296 EXPECT_EQ(test_cases[i].expected_word_starts_offsets[j],
1297 match.lower_terms_to_word_starts_offsets_[j]);
1302 class InMemoryURLIndexCacheTest : public testing::Test {
1303 public:
1304 InMemoryURLIndexCacheTest() {}
1306 protected:
1307 void SetUp() override;
1308 void TearDown() override;
1310 // Pass-through functions to simplify our friendship with InMemoryURLIndex.
1311 void set_history_dir(const base::FilePath& dir_path);
1312 bool GetCacheFilePath(base::FilePath* file_path) const;
1314 content::TestBrowserThreadBundle thread_bundle_;
1315 base::ScopedTempDir temp_dir_;
1316 scoped_ptr<InMemoryURLIndex> url_index_;
1319 void InMemoryURLIndexCacheTest::SetUp() {
1320 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
1321 base::FilePath path(temp_dir_.path());
1322 url_index_.reset(new InMemoryURLIndex(
1323 nullptr, nullptr, content::BrowserThread::GetBlockingPool(), path,
1324 kTestLanguages, SchemeSet()));
1327 void InMemoryURLIndexCacheTest::TearDown() {
1328 if (url_index_)
1329 url_index_->Shutdown();
1332 void InMemoryURLIndexCacheTest::set_history_dir(
1333 const base::FilePath& dir_path) {
1334 return url_index_->set_history_dir(dir_path);
1337 bool InMemoryURLIndexCacheTest::GetCacheFilePath(
1338 base::FilePath* file_path) const {
1339 DCHECK(file_path);
1340 return url_index_->GetCacheFilePath(file_path);
1343 TEST_F(InMemoryURLIndexCacheTest, CacheFilePath) {
1344 base::FilePath expectedPath =
1345 temp_dir_.path().Append(FILE_PATH_LITERAL("History Provider Cache"));
1346 std::vector<base::FilePath::StringType> expected_parts;
1347 expectedPath.GetComponents(&expected_parts);
1348 base::FilePath full_file_path;
1349 ASSERT_TRUE(GetCacheFilePath(&full_file_path));
1350 std::vector<base::FilePath::StringType> actual_parts;
1351 full_file_path.GetComponents(&actual_parts);
1352 ASSERT_EQ(expected_parts.size(), actual_parts.size());
1353 size_t count = expected_parts.size();
1354 for (size_t i = 0; i < count; ++i)
1355 EXPECT_EQ(expected_parts[i], actual_parts[i]);
1356 // Must clear the history_dir_ to satisfy the dtor's DCHECK.
1357 set_history_dir(base::FilePath());