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