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