1 // Copyright (c) 2011 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 <stdio.h> 6 7 #include <fstream> 8 #include <string> 9 #include <vector> 10 11 #include "app/sql/connection.h" 12 #include "app/sql/statement.h" 13 #include "app/sql/transaction.h" 14 #include "base/file_path.h" 15 #include "base/file_util.h" 16 #include "base/memory/scoped_ptr.h" 17 #include "base/path_service.h" 18 #include "base/string_util.h" 19 #include "base/time.h" 20 #include "base/utf_string_conversions.h" 21 #include "chrome/browser/history/in_memory_url_index.h" 22 #include "chrome/browser/history/in_memory_database.h" 23 #include "chrome/common/chrome_paths.h" 24 #include "testing/gtest/include/gtest/gtest.h" 25 26 // The test version of the history url database table ('url') is contained in 27 // a database file created from a text file('url_history_provider_test.db.txt'). 28 // The only difference between this table and a live 'urls' table from a 29 // profile is that the last_visit_time column in the test table contains a 30 // number specifying the number of days relative to 'today' to which the 31 // absolute time should be set during the test setup stage. 32 // 33 // The format of the test database text file is of a SQLite .dump file. 34 // Note that only lines whose first character is an upper-case letter are 35 // processed when creating the test database. 36 37 namespace history { 38 39 class InMemoryURLIndexTest : public testing::Test, 40 public InMemoryDatabase { 41 public: 42 InMemoryURLIndexTest() { InitFromScratch(); } 43 44 protected: 45 // Test setup. 46 virtual void SetUp() { 47 // Create and populate a working copy of the URL history database. 48 FilePath history_proto_path; 49 PathService::Get(chrome::DIR_TEST_DATA, &history_proto_path); 50 history_proto_path = history_proto_path.Append( 51 FILE_PATH_LITERAL("History")); 52 history_proto_path = history_proto_path.Append(TestDBName()); 53 EXPECT_TRUE(file_util::PathExists(history_proto_path)); 54 55 std::ifstream proto_file(history_proto_path.value().c_str()); 56 static const size_t kCommandBufferMaxSize = 2048; 57 char sql_cmd_line[kCommandBufferMaxSize]; 58 59 sql::Connection& db(GetDB()); 60 { 61 sql::Transaction transaction(&db); 62 transaction.Begin(); 63 while (!proto_file.eof()) { 64 proto_file.getline(sql_cmd_line, kCommandBufferMaxSize); 65 if (!proto_file.eof()) { 66 // We only process lines which begin with a upper-case letter. 67 // TODO(mrossetti): Can iswupper() be used here? 68 if (sql_cmd_line[0] >= 'A' && sql_cmd_line[0] <= 'Z') { 69 std::string sql_cmd(sql_cmd_line); 70 sql::Statement sql_stmt(db.GetUniqueStatement(sql_cmd_line)); 71 EXPECT_TRUE(sql_stmt.Run()); 72 } 73 } 74 } 75 transaction.Commit(); 76 } 77 proto_file.close(); 78 79 // Update the last_visit_time table column 80 // such that it represents a time relative to 'now'. 81 sql::Statement statement(db.GetUniqueStatement( 82 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls;")); 83 EXPECT_TRUE(statement); 84 base::Time time_right_now = base::Time::NowFromSystemTime(); 85 base::TimeDelta day_delta = base::TimeDelta::FromDays(1); 86 { 87 sql::Transaction transaction(&db); 88 transaction.Begin(); 89 while (statement.Step()) { 90 URLRow row; 91 FillURLRow(statement, &row); 92 base::Time last_visit = time_right_now; 93 for (int64 i = row.last_visit().ToInternalValue(); i > 0; --i) 94 last_visit -= day_delta; 95 row.set_last_visit(last_visit); 96 UpdateURLRow(row.id(), row); 97 } 98 transaction.Commit(); 99 } 100 } 101 102 virtual FilePath::StringType TestDBName() const { 103 return FILE_PATH_LITERAL("url_history_provider_test.db.txt"); 104 } 105 106 URLRow MakeURLRow(const char* url, 107 const char* title, 108 int visit_count, 109 int last_visit_ago, 110 int typed_count) { 111 URLRow row(GURL(url), 0); 112 row.set_title(UTF8ToUTF16(title)); 113 row.set_visit_count(visit_count); 114 row.set_typed_count(typed_count); 115 row.set_last_visit(base::Time::NowFromSystemTime() - 116 base::TimeDelta::FromDays(last_visit_ago)); 117 return row; 118 } 119 120 InMemoryURLIndex::String16Vector Make1Term(const char* term) { 121 InMemoryURLIndex::String16Vector terms; 122 terms.push_back(UTF8ToUTF16(term)); 123 return terms; 124 } 125 126 InMemoryURLIndex::String16Vector Make2Terms(const char* term_1, 127 const char* term_2) { 128 InMemoryURLIndex::String16Vector terms; 129 terms.push_back(UTF8ToUTF16(term_1)); 130 terms.push_back(UTF8ToUTF16(term_2)); 131 return terms; 132 } 133 134 scoped_ptr<InMemoryURLIndex> url_index_; 135 }; 136 137 class LimitedInMemoryURLIndexTest : public InMemoryURLIndexTest { 138 protected: 139 FilePath::StringType TestDBName() const { 140 return FILE_PATH_LITERAL("url_history_provider_test_limited.db.txt"); 141 } 142 }; 143 144 class ExpandedInMemoryURLIndexTest : public InMemoryURLIndexTest { 145 protected: 146 virtual void SetUp() { 147 InMemoryURLIndexTest::SetUp(); 148 // Add 600 more history items. 149 // NOTE: Keep the string length constant at least the length of the format 150 // string plus 5 to account for a 3 digit number and terminator. 151 char url_format[] = "http://www.google.com/%d"; 152 const size_t kMaxLen = arraysize(url_format) + 5; 153 char url_string[kMaxLen + 1]; 154 for (int i = 0; i < 600; ++i) { 155 base::snprintf(url_string, kMaxLen, url_format, i); 156 URLRow row(MakeURLRow(url_string, "Google Search", 20, 0, 20)); 157 AddURL(row); 158 } 159 } 160 }; 161 162 TEST_F(InMemoryURLIndexTest, Construction) { 163 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 164 EXPECT_TRUE(url_index_.get()); 165 } 166 167 TEST_F(LimitedInMemoryURLIndexTest, Initialization) { 168 // Verify that the database contains the expected number of items, which 169 // is the pre-filtered count, i.e. all of the items. 170 sql::Statement statement(GetDB().GetUniqueStatement("SELECT * FROM urls;")); 171 EXPECT_TRUE(statement); 172 uint64 row_count = 0; 173 while (statement.Step()) ++row_count; 174 EXPECT_EQ(1U, row_count); 175 url_index_.reset(new InMemoryURLIndex); 176 url_index_->Init(this, "en,ja,hi,zh"); 177 EXPECT_EQ(1, url_index_->history_item_count_); 178 179 // history_info_map_ should have the same number of items as were filtered. 180 EXPECT_EQ(1U, url_index_->history_info_map_.size()); 181 EXPECT_EQ(36U, url_index_->char_word_map_.size()); 182 EXPECT_EQ(17U, url_index_->word_map_.size()); 183 } 184 185 TEST_F(InMemoryURLIndexTest, Retrieval) { 186 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 187 url_index_->Init(this, "en,ja,hi,zh"); 188 InMemoryURLIndex::String16Vector terms; 189 // The term will be lowercased by the search. 190 191 // See if a very specific term gives a single result. 192 terms.push_back(ASCIIToUTF16("DrudgeReport")); 193 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms); 194 EXPECT_EQ(1U, matches.size()); 195 196 // Verify that we got back the result we expected. 197 EXPECT_EQ(5, matches[0].url_info.id()); 198 EXPECT_EQ("http://drudgereport.com/", matches[0].url_info.url().spec()); 199 EXPECT_EQ(ASCIIToUTF16("DRUDGE REPORT 2010"), matches[0].url_info.title()); 200 201 // Search which should result in multiple results. 202 terms.clear(); 203 terms.push_back(ASCIIToUTF16("drudge")); 204 matches = url_index_->HistoryItemsForTerms(terms); 205 ASSERT_EQ(2U, matches.size()); 206 // The results should be in descending score order. 207 EXPECT_GE(matches[0].raw_score, matches[1].raw_score); 208 209 // Search which should result in nearly perfect result. 210 terms.clear(); 211 terms.push_back(ASCIIToUTF16("https")); 212 terms.push_back(ASCIIToUTF16("NearlyPerfectResult")); 213 matches = url_index_->HistoryItemsForTerms(terms); 214 ASSERT_EQ(1U, matches.size()); 215 // The results should have a very high score. 216 EXPECT_GT(matches[0].raw_score, 900); 217 EXPECT_EQ(32, matches[0].url_info.id()); 218 EXPECT_EQ("https://nearlyperfectresult.com/", 219 matches[0].url_info.url().spec()); // Note: URL gets lowercased. 220 EXPECT_EQ(ASCIIToUTF16("Practically Perfect Search Result"), 221 matches[0].url_info.title()); 222 223 // Search which should result in very poor result. 224 terms.clear(); 225 terms.push_back(ASCIIToUTF16("z")); 226 terms.push_back(ASCIIToUTF16("y")); 227 terms.push_back(ASCIIToUTF16("x")); 228 matches = url_index_->HistoryItemsForTerms(terms); 229 ASSERT_EQ(1U, matches.size()); 230 // The results should have a poor score. 231 EXPECT_LT(matches[0].raw_score, 500); 232 EXPECT_EQ(33, matches[0].url_info.id()); 233 EXPECT_EQ("http://quiteuselesssearchresultxyz.com/", 234 matches[0].url_info.url().spec()); // Note: URL gets lowercased. 235 EXPECT_EQ(ASCIIToUTF16("Practically Useless Search Result"), 236 matches[0].url_info.title()); 237 238 // Search which will match at the end of an URL with encoded characters. 239 terms.clear(); 240 terms.push_back(ASCIIToUTF16("ice")); 241 matches = url_index_->HistoryItemsForTerms(terms); 242 ASSERT_EQ(1U, matches.size()); 243 } 244 245 TEST_F(ExpandedInMemoryURLIndexTest, ShortCircuit) { 246 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 247 url_index_->Init(this, "en,ja,hi,zh"); 248 InMemoryURLIndex::String16Vector terms; 249 250 // A search for 'w' should short-circuit and not return any matches. 251 terms.push_back(ASCIIToUTF16("w")); 252 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms); 253 EXPECT_TRUE(matches.empty()); 254 255 // A search for 'working' should not short-circuit. 256 terms.clear(); 257 terms.push_back(ASCIIToUTF16("working")); 258 matches = url_index_->HistoryItemsForTerms(terms); 259 EXPECT_EQ(1U, matches.size()); 260 } 261 262 TEST_F(InMemoryURLIndexTest, TitleSearch) { 263 url_index_.reset(new InMemoryURLIndex()); 264 url_index_->Init(this, "en,ja,hi,zh"); 265 // Signal if someone has changed the test DB. 266 EXPECT_EQ(25U, url_index_->history_info_map_.size()); 267 InMemoryURLIndex::String16Vector terms; 268 269 // Ensure title is being searched. 270 terms.push_back(ASCIIToUTF16("MORTGAGE")); 271 terms.push_back(ASCIIToUTF16("RATE")); 272 terms.push_back(ASCIIToUTF16("DROPS")); 273 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms); 274 EXPECT_EQ(1U, matches.size()); 275 276 // Verify that we got back the result we expected. 277 EXPECT_EQ(1, matches[0].url_info.id()); 278 EXPECT_EQ("http://www.reuters.com/article/idUSN0839880620100708", 279 matches[0].url_info.url().spec()); 280 EXPECT_EQ(ASCIIToUTF16( 281 "UPDATE 1-US 30-yr mortgage rate drops to new record low | Reuters"), 282 matches[0].url_info.title()); 283 } 284 285 TEST_F(InMemoryURLIndexTest, Char16Utilities) { 286 string16 term = ASCIIToUTF16("drudgereport"); 287 string16 expected = ASCIIToUTF16("drugepot"); 288 EXPECT_EQ(expected.size(), 289 InMemoryURLIndex::Char16SetFromString16(term).size()); 290 InMemoryURLIndex::Char16Vector c_vector = 291 InMemoryURLIndex::Char16VectorFromString16(term); 292 ASSERT_EQ(expected.size(), c_vector.size()); 293 294 InMemoryURLIndex::Char16Vector::const_iterator c_iter = c_vector.begin(); 295 for (string16::const_iterator s_iter = expected.begin(); 296 s_iter != expected.end(); ++s_iter, ++c_iter) 297 EXPECT_EQ(*s_iter, *c_iter); 298 } 299 300 TEST_F(InMemoryURLIndexTest, StaticFunctions) { 301 // Test WordVectorFromString16 302 string16 string_a(ASCIIToUTF16("http://www.google.com/ frammy the brammy")); 303 InMemoryURLIndex::String16Vector string_vec = 304 InMemoryURLIndex::WordVectorFromString16(string_a, false); 305 ASSERT_EQ(7U, string_vec.size()); 306 // See if we got the words we expected. 307 EXPECT_EQ(UTF8ToUTF16("http"), string_vec[0]); 308 EXPECT_EQ(UTF8ToUTF16("www"), string_vec[1]); 309 EXPECT_EQ(UTF8ToUTF16("google"), string_vec[2]); 310 EXPECT_EQ(UTF8ToUTF16("com"), string_vec[3]); 311 EXPECT_EQ(UTF8ToUTF16("frammy"), string_vec[4]); 312 EXPECT_EQ(UTF8ToUTF16("the"), string_vec[5]); 313 EXPECT_EQ(UTF8ToUTF16("brammy"), string_vec[6]); 314 315 string_vec = InMemoryURLIndex::WordVectorFromString16(string_a, true); 316 ASSERT_EQ(5U, string_vec.size()); 317 EXPECT_EQ(UTF8ToUTF16("http://"), string_vec[0]); 318 EXPECT_EQ(UTF8ToUTF16("www.google.com/"), string_vec[1]); 319 EXPECT_EQ(UTF8ToUTF16("frammy"), string_vec[2]); 320 EXPECT_EQ(UTF8ToUTF16("the"), string_vec[3]); 321 EXPECT_EQ(UTF8ToUTF16("brammy"), string_vec[4]); 322 323 // Test WordSetFromString16 324 string16 string_b(ASCIIToUTF16( 325 "http://web.google.com/search Google Web Search")); 326 InMemoryURLIndex::String16Set string_set = 327 InMemoryURLIndex::WordSetFromString16(string_b); 328 EXPECT_EQ(5U, string_set.size()); 329 // See if we got the words we expected. 330 EXPECT_TRUE(string_set.find(UTF8ToUTF16("com")) != string_set.end()); 331 EXPECT_TRUE(string_set.find(UTF8ToUTF16("google")) != string_set.end()); 332 EXPECT_TRUE(string_set.find(UTF8ToUTF16("http")) != string_set.end()); 333 EXPECT_TRUE(string_set.find(UTF8ToUTF16("search")) != string_set.end()); 334 EXPECT_TRUE(string_set.find(UTF8ToUTF16("web")) != string_set.end()); 335 336 // Test SortAndDeoverlap 337 TermMatches matches_a; 338 matches_a.push_back(TermMatch(1, 13, 10)); 339 matches_a.push_back(TermMatch(2, 23, 10)); 340 matches_a.push_back(TermMatch(3, 3, 10)); 341 matches_a.push_back(TermMatch(4, 40, 5)); 342 TermMatches matches_b = InMemoryURLIndex::SortAndDeoverlap(matches_a); 343 // Nothing should have been eliminated. 344 EXPECT_EQ(matches_a.size(), matches_b.size()); 345 // The order should now be 3, 1, 2, 4. 346 EXPECT_EQ(3, matches_b[0].term_num); 347 EXPECT_EQ(1, matches_b[1].term_num); 348 EXPECT_EQ(2, matches_b[2].term_num); 349 EXPECT_EQ(4, matches_b[3].term_num); 350 matches_a.push_back(TermMatch(5, 18, 10)); 351 matches_a.push_back(TermMatch(6, 38, 5)); 352 matches_b = InMemoryURLIndex::SortAndDeoverlap(matches_a); 353 // Two matches should have been eliminated. 354 EXPECT_EQ(matches_a.size() - 2, matches_b.size()); 355 // The order should now be 3, 1, 2, 6. 356 EXPECT_EQ(3, matches_b[0].term_num); 357 EXPECT_EQ(1, matches_b[1].term_num); 358 EXPECT_EQ(2, matches_b[2].term_num); 359 EXPECT_EQ(6, matches_b[3].term_num); 360 361 // Test MatchTermInString 362 TermMatches matches_c = InMemoryURLIndex::MatchTermInString( 363 UTF8ToUTF16("x"), UTF8ToUTF16("axbxcxdxex fxgx/hxixjx.kx"), 123); 364 ASSERT_EQ(11U, matches_c.size()); 365 const size_t expected_offsets[] = { 1, 3, 5, 7, 9, 12, 14, 17, 19, 21, 24 }; 366 for (int i = 0; i < 11; ++i) 367 EXPECT_EQ(expected_offsets[i], matches_c[i].offset); 368 } 369 370 TEST_F(InMemoryURLIndexTest, OffsetsAndTermMatches) { 371 // Test OffsetsFromTermMatches 372 history::TermMatches matches_a; 373 matches_a.push_back(history::TermMatch(1, 1, 2)); 374 matches_a.push_back(history::TermMatch(2, 4, 3)); 375 matches_a.push_back(history::TermMatch(3, 9, 1)); 376 matches_a.push_back(history::TermMatch(3, 10, 1)); 377 matches_a.push_back(history::TermMatch(4, 14, 5)); 378 std::vector<size_t> offsets = 379 InMemoryURLIndex::OffsetsFromTermMatches(matches_a); 380 const size_t expected_offsets_a[] = {1, 4, 9, 10, 14}; 381 ASSERT_EQ(offsets.size(), arraysize(expected_offsets_a)); 382 for (size_t i = 0; i < offsets.size(); ++i) 383 EXPECT_EQ(expected_offsets_a[i], offsets[i]); 384 385 // Test ReplaceOffsetsInTermMatches 386 offsets[2] = string16::npos; 387 history::TermMatches matches_b = 388 InMemoryURLIndex::ReplaceOffsetsInTermMatches(matches_a, offsets); 389 const size_t expected_offsets_b[] = {1, 4, 10, 14}; 390 ASSERT_EQ(arraysize(expected_offsets_b), matches_b.size()); 391 for (size_t i = 0; i < matches_b.size(); ++i) 392 EXPECT_EQ(expected_offsets_b[i], matches_b[i].offset); 393 } 394 395 TEST_F(InMemoryURLIndexTest, TypedCharacterCaching) { 396 // Verify that match results for previously typed characters are retained 397 // (in the term_char_word_set_cache_) and reused, if possible, in future 398 // autocompletes. 399 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 400 url_index_->Init(this, "en,ja,hi,zh"); 401 402 // Verify that we can find something that already exists. 403 InMemoryURLIndex::String16Vector terms; 404 string16 term = ASCIIToUTF16("drudgerepo"); 405 terms.push_back(term); 406 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(terms).size()); 407 408 { 409 // Exercise the term matching cache with the same term. 410 InMemoryURLIndex::Char16Vector uni_chars = 411 InMemoryURLIndex::Char16VectorFromString16(term); 412 EXPECT_EQ(uni_chars.size(), 7U); // Equivalent to 'degopru' 413 EXPECT_EQ(6U, url_index_->CachedResultsIndexForTerm(uni_chars)); 414 } 415 416 { 417 // Back off a character. 418 InMemoryURLIndex::Char16Vector uni_chars = 419 InMemoryURLIndex::Char16VectorFromString16(ASCIIToUTF16("drudgerep")); 420 EXPECT_EQ(6U, uni_chars.size()); // Equivalent to 'degpru' 421 EXPECT_EQ(5U, url_index_->CachedResultsIndexForTerm(uni_chars)); 422 } 423 424 { 425 // Add a couple of characters. 426 InMemoryURLIndex::Char16Vector uni_chars = 427 InMemoryURLIndex::Char16VectorFromString16( 428 ASCIIToUTF16("drudgereporta")); 429 EXPECT_EQ(9U, uni_chars.size()); // Equivalent to 'adegoprtu' 430 EXPECT_EQ(6U, url_index_->CachedResultsIndexForTerm(uni_chars)); 431 } 432 433 { 434 // Use different string. 435 InMemoryURLIndex::Char16Vector uni_chars = 436 InMemoryURLIndex::Char16VectorFromString16(ASCIIToUTF16("abcde")); 437 EXPECT_EQ(5U, uni_chars.size()); 438 EXPECT_EQ(static_cast<size_t>(-1), 439 url_index_->CachedResultsIndexForTerm(uni_chars)); 440 } 441 } 442 443 TEST_F(InMemoryURLIndexTest, Scoring) { 444 URLRow row_a(MakeURLRow("http://abcdef", "fedcba", 3, 30, 1)); 445 // Test scores based on position. 446 ScoredHistoryMatch scored_a( 447 InMemoryURLIndex::ScoredMatchForURL(row_a, Make1Term("abc"))); 448 ScoredHistoryMatch scored_b( 449 InMemoryURLIndex::ScoredMatchForURL(row_a, Make1Term("bcd"))); 450 EXPECT_GT(scored_a.raw_score, scored_b.raw_score); 451 // Test scores based on length. 452 ScoredHistoryMatch scored_c( 453 InMemoryURLIndex::ScoredMatchForURL(row_a, Make1Term("abcd"))); 454 EXPECT_LT(scored_a.raw_score, scored_c.raw_score); 455 // Test scores based on order. 456 ScoredHistoryMatch scored_d( 457 InMemoryURLIndex::ScoredMatchForURL(row_a, Make2Terms("abc", "def"))); 458 ScoredHistoryMatch scored_e( 459 InMemoryURLIndex::ScoredMatchForURL(row_a, Make2Terms("def", "abc"))); 460 EXPECT_GT(scored_d.raw_score, scored_e.raw_score); 461 // Test scores based on visit_count. 462 URLRow row_b(MakeURLRow("http://abcdef", "fedcba", 10, 30, 1)); 463 ScoredHistoryMatch scored_f( 464 InMemoryURLIndex::ScoredMatchForURL(row_b, Make1Term("abc"))); 465 EXPECT_GT(scored_f.raw_score, scored_a.raw_score); 466 // Test scores based on last_visit. 467 URLRow row_c(MakeURLRow("http://abcdef", "fedcba", 3, 10, 1)); 468 ScoredHistoryMatch scored_g( 469 InMemoryURLIndex::ScoredMatchForURL(row_c, Make1Term("abc"))); 470 EXPECT_GT(scored_g.raw_score, scored_a.raw_score); 471 // Test scores based on typed_count. 472 URLRow row_d(MakeURLRow("http://abcdef", "fedcba", 3, 30, 10)); 473 ScoredHistoryMatch scored_h( 474 InMemoryURLIndex::ScoredMatchForURL(row_d, Make1Term("abc"))); 475 EXPECT_GT(scored_h.raw_score, scored_a.raw_score); 476 } 477 478 TEST_F(InMemoryURLIndexTest, AddNewRows) { 479 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 480 url_index_->Init(this, "en,ja,hi,zh"); 481 InMemoryURLIndex::String16Vector terms; 482 483 // Verify that the row we're going to add does not already exist. 484 URLID new_row_id = 87654321; 485 // Newly created URLRows get a last_visit time of 'right now' so it should 486 // qualify as a quick result candidate. 487 terms.push_back(ASCIIToUTF16("brokeandalone")); 488 EXPECT_TRUE(url_index_->HistoryItemsForTerms(terms).empty()); 489 490 // Add a new row. 491 URLRow new_row(GURL("http://www.brokeandaloneinmanitoba.com/"), new_row_id); 492 new_row.set_last_visit(base::Time::Now()); 493 url_index_->UpdateURL(new_row_id, new_row); 494 495 // Verify that we can retrieve it. 496 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(terms).size()); 497 498 // Add it again just to be sure that is harmless. 499 url_index_->UpdateURL(new_row_id, new_row); 500 EXPECT_EQ(1U, url_index_->HistoryItemsForTerms(terms).size()); 501 } 502 503 TEST_F(InMemoryURLIndexTest, DeleteRows) { 504 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 505 url_index_->Init(this, "en,ja,hi,zh"); 506 InMemoryURLIndex::String16Vector terms; 507 508 // Make sure we actually get an existing result. 509 terms.push_back(ASCIIToUTF16("DrudgeReport")); 510 ScoredHistoryMatches matches = url_index_->HistoryItemsForTerms(terms); 511 ASSERT_EQ(1U, matches.size()); 512 513 // Determine the row id for that result, delete that id, then search again. 514 url_index_->DeleteURL(matches[0].url_info.id()); 515 EXPECT_TRUE(url_index_->HistoryItemsForTerms(terms).empty()); 516 } 517 518 TEST_F(InMemoryURLIndexTest, CacheFilePath) { 519 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL( 520 "/flammmy/frammy/")))); 521 FilePath full_file_path; 522 url_index_->GetCacheFilePath(&full_file_path); 523 std::vector<FilePath::StringType> expected_parts; 524 FilePath(FILE_PATH_LITERAL("/flammmy/frammy/History Provider Cache")). 525 GetComponents(&expected_parts); 526 std::vector<FilePath::StringType> actual_parts; 527 full_file_path.GetComponents(&actual_parts); 528 ASSERT_EQ(expected_parts.size(), actual_parts.size()); 529 size_t count = expected_parts.size(); 530 for (size_t i = 0; i < count; ++i) 531 EXPECT_EQ(expected_parts[i], actual_parts[i]); 532 } 533 534 TEST_F(InMemoryURLIndexTest, CacheSaveRestore) { 535 // Save the cache to a protobuf, restore it, and compare the results. 536 url_index_.reset(new InMemoryURLIndex(FilePath(FILE_PATH_LITERAL("/dummy")))); 537 InMemoryURLIndex& url_index(*(url_index_.get())); 538 url_index.Init(this, "en,ja,hi,zh"); 539 in_memory_url_index::InMemoryURLIndexCacheItem index_cache; 540 url_index.SavePrivateData(&index_cache); 541 542 // Capture our private data so we can later compare for equality. 543 int history_item_count(url_index.history_item_count_); 544 InMemoryURLIndex::String16Vector word_list(url_index.word_list_); 545 InMemoryURLIndex::WordMap word_map(url_index.word_map_); 546 InMemoryURLIndex::CharWordIDMap char_word_map(url_index.char_word_map_); 547 InMemoryURLIndex::WordIDHistoryMap word_id_history_map( 548 url_index.word_id_history_map_); 549 InMemoryURLIndex::HistoryInfoMap history_info_map( 550 url_index.history_info_map_); 551 552 // Prove that there is really something there. 553 EXPECT_GT(url_index.history_item_count_, 0); 554 EXPECT_FALSE(url_index.word_list_.empty()); 555 EXPECT_FALSE(url_index.word_map_.empty()); 556 EXPECT_FALSE(url_index.char_word_map_.empty()); 557 EXPECT_FALSE(url_index.word_id_history_map_.empty()); 558 EXPECT_FALSE(url_index.history_info_map_.empty()); 559 560 // Clear and then prove it's clear. 561 url_index.ClearPrivateData(); 562 EXPECT_EQ(0, url_index.history_item_count_); 563 EXPECT_TRUE(url_index.word_list_.empty()); 564 EXPECT_TRUE(url_index.word_map_.empty()); 565 EXPECT_TRUE(url_index.char_word_map_.empty()); 566 EXPECT_TRUE(url_index.word_id_history_map_.empty()); 567 EXPECT_TRUE(url_index.history_info_map_.empty()); 568 569 // Restore the cache. 570 EXPECT_TRUE(url_index.RestorePrivateData(index_cache)); 571 572 // Compare the restored and captured for equality. 573 EXPECT_EQ(history_item_count, url_index.history_item_count_); 574 EXPECT_EQ(word_list.size(), url_index.word_list_.size()); 575 EXPECT_EQ(word_map.size(), url_index.word_map_.size()); 576 EXPECT_EQ(char_word_map.size(), url_index.char_word_map_.size()); 577 EXPECT_EQ(word_id_history_map.size(), url_index.word_id_history_map_.size()); 578 EXPECT_EQ(history_info_map.size(), url_index.history_info_map_.size()); 579 // WordList must be index-by-index equal. 580 size_t count = word_list.size(); 581 for (size_t i = 0; i < count; ++i) 582 EXPECT_EQ(word_list[i], url_index.word_list_[i]); 583 for (InMemoryURLIndex::CharWordIDMap::const_iterator expected = 584 char_word_map.begin(); expected != char_word_map.end(); ++expected) { 585 InMemoryURLIndex::CharWordIDMap::const_iterator actual = 586 url_index.char_word_map_.find(expected->first); 587 ASSERT_TRUE(url_index.char_word_map_.end() != actual); 588 const InMemoryURLIndex::WordIDSet& expected_set(expected->second); 589 const InMemoryURLIndex::WordIDSet& actual_set(actual->second); 590 ASSERT_EQ(expected_set.size(), actual_set.size()); 591 for (InMemoryURLIndex::WordIDSet::const_iterator set_iter = 592 expected_set.begin(); set_iter != expected_set.end(); ++set_iter) 593 EXPECT_GT(actual_set.count(*set_iter), 0U); 594 } 595 for (InMemoryURLIndex::WordIDHistoryMap::const_iterator expected = 596 word_id_history_map.begin(); expected != word_id_history_map.end(); 597 ++expected) { 598 InMemoryURLIndex::WordIDHistoryMap::const_iterator actual = 599 url_index.word_id_history_map_.find(expected->first); 600 ASSERT_TRUE(url_index.word_id_history_map_.end() != actual); 601 const InMemoryURLIndex::HistoryIDSet& expected_set(expected->second); 602 const InMemoryURLIndex::HistoryIDSet& actual_set(actual->second); 603 ASSERT_EQ(expected_set.size(), actual_set.size()); 604 for (InMemoryURLIndex::HistoryIDSet::const_iterator set_iter = 605 expected_set.begin(); set_iter != expected_set.end(); ++set_iter) 606 EXPECT_GT(actual_set.count(*set_iter), 0U); 607 } 608 for (InMemoryURLIndex::HistoryInfoMap::const_iterator expected = 609 history_info_map.begin(); expected != history_info_map.end(); 610 ++expected) { 611 InMemoryURLIndex::HistoryInfoMap::const_iterator actual = 612 url_index.history_info_map_.find(expected->first); 613 ASSERT_FALSE(url_index.history_info_map_.end() == actual); 614 const URLRow& expected_row(expected->second); 615 const URLRow& actual_row(actual->second); 616 EXPECT_EQ(expected_row.visit_count(), actual_row.visit_count()); 617 EXPECT_EQ(expected_row.typed_count(), actual_row.typed_count()); 618 EXPECT_EQ(expected_row.last_visit(), actual_row.last_visit()); 619 EXPECT_EQ(expected_row.url(), actual_row.url()); 620 } 621 } 622 623 } // namespace history 624