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