expire_history_backend_unittest.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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 "base/basictypes.h" 6#include "base/compiler_specific.h" 7#include "base/file_path.h" 8#include "base/file_util.h" 9#include "base/path_service.h" 10#include "base/scoped_ptr.h" 11#include "base/string16.h" 12#include "base/utf_string_conversions.h" 13#include "chrome/browser/bookmarks/bookmark_model.h" 14#include "chrome/browser/history/archived_database.h" 15#include "chrome/browser/history/expire_history_backend.h" 16#include "chrome/browser/history/history_database.h" 17#include "chrome/browser/history/history_notifications.h" 18#include "chrome/browser/history/text_database_manager.h" 19#include "chrome/browser/history/thumbnail_database.h" 20#include "chrome/browser/history/top_sites.h" 21#include "chrome/common/notification_service.h" 22#include "chrome/common/thumbnail_score.h" 23#include "chrome/test/testing_profile.h" 24#include "chrome/tools/profiles/thumbnail-inl.h" 25#include "gfx/codec/jpeg_codec.h" 26#include "testing/gtest/include/gtest/gtest.h" 27#include "third_party/skia/include/core/SkBitmap.h" 28 29using base::Time; 30using base::TimeDelta; 31using base::TimeTicks; 32 33// Filename constants. 34static const FilePath::CharType kTestDir[] = FILE_PATH_LITERAL("ExpireTest"); 35static const FilePath::CharType kHistoryFile[] = FILE_PATH_LITERAL("History"); 36static const FilePath::CharType kArchivedHistoryFile[] = 37 FILE_PATH_LITERAL("Archived History"); 38static const FilePath::CharType kThumbnailFile[] = 39 FILE_PATH_LITERAL("Thumbnails"); 40 41// The test must be in the history namespace for the gtest forward declarations 42// to work. It also eliminates a bunch of ugly "history::". 43namespace history { 44 45// ExpireHistoryTest ----------------------------------------------------------- 46 47class ExpireHistoryTest : public testing::Test, 48 public BroadcastNotificationDelegate { 49 public: 50 ExpireHistoryTest() 51 : bookmark_model_(NULL), 52 ALLOW_THIS_IN_INITIALIZER_LIST(expirer_(this, &bookmark_model_)), 53 now_(Time::Now()) { 54 } 55 56 protected: 57 // Called by individual tests when they want data populated. 58 void AddExampleData(URLID url_ids[3], Time visit_times[4]); 59 // Add visits with source information. 60 void AddExampleSourceData(const GURL& url, URLID* id); 61 62 // Returns true if the given favicon/thumanil has an entry in the DB. 63 bool HasFavIcon(FavIconID favicon_id); 64 bool HasThumbnail(URLID url_id); 65 66 // Returns the number of text matches for the given URL in the example data 67 // added by AddExampleData. 68 int CountTextMatchesForURL(const GURL& url); 69 70 // EXPECTs that each URL-specific history thing (basically, everything but 71 // favicons) is gone. 72 void EnsureURLInfoGone(const URLRow& row); 73 74 // Clears the list of notifications received. 75 void ClearLastNotifications() { 76 for (size_t i = 0; i < notifications_.size(); i++) 77 delete notifications_[i].second; 78 notifications_.clear(); 79 } 80 81 void StarURL(const GURL& url) { 82 bookmark_model_.AddURL( 83 bookmark_model_.GetBookmarkBarNode(), 0, string16(), url); 84 } 85 86 static bool IsStringInFile(const FilePath& filename, const char* str); 87 88 BookmarkModel bookmark_model_; 89 90 MessageLoop message_loop_; 91 92 ExpireHistoryBackend expirer_; 93 94 scoped_ptr<HistoryDatabase> main_db_; 95 scoped_ptr<ArchivedDatabase> archived_db_; 96 scoped_ptr<ThumbnailDatabase> thumb_db_; 97 scoped_ptr<TextDatabaseManager> text_db_; 98 TestingProfile profile_; 99 scoped_refptr<TopSites> top_sites_; 100 101 // Time at the beginning of the test, so everybody agrees what "now" is. 102 const Time now_; 103 104 // Notifications intended to be broadcast, we can check these values to make 105 // sure that the deletor is doing the correct broadcasts. We own the details 106 // pointers. 107 typedef std::vector< std::pair<NotificationType, HistoryDetails*> > 108 NotificationList; 109 NotificationList notifications_; 110 111 // Directory for the history files. 112 FilePath dir_; 113 114 private: 115 void SetUp() { 116 FilePath temp_dir; 117 PathService::Get(base::DIR_TEMP, &temp_dir); 118 dir_ = temp_dir.Append(kTestDir); 119 file_util::Delete(dir_, true); 120 file_util::CreateDirectory(dir_); 121 122 FilePath history_name = dir_.Append(kHistoryFile); 123 main_db_.reset(new HistoryDatabase); 124 if (main_db_->Init(history_name, FilePath()) != sql::INIT_OK) 125 main_db_.reset(); 126 127 FilePath archived_name = dir_.Append(kArchivedHistoryFile); 128 archived_db_.reset(new ArchivedDatabase); 129 if (!archived_db_->Init(archived_name)) 130 archived_db_.reset(); 131 132 FilePath thumb_name = dir_.Append(kThumbnailFile); 133 thumb_db_.reset(new ThumbnailDatabase); 134 if (thumb_db_->Init(thumb_name, NULL) != sql::INIT_OK) 135 thumb_db_.reset(); 136 137 text_db_.reset(new TextDatabaseManager(dir_, 138 main_db_.get(), main_db_.get())); 139 if (!text_db_->Init(NULL)) 140 text_db_.reset(); 141 142 expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get(), 143 text_db_.get()); 144 top_sites_ = profile_.GetTopSites(); 145 } 146 147 void TearDown() { 148 ClearLastNotifications(); 149 150 expirer_.SetDatabases(NULL, NULL, NULL, NULL); 151 152 main_db_.reset(); 153 archived_db_.reset(); 154 thumb_db_.reset(); 155 text_db_.reset(); 156 TopSites::DeleteTopSites(top_sites_); 157 file_util::Delete(dir_, true); 158 } 159 160 // BroadcastNotificationDelegate implementation. 161 void BroadcastNotifications(NotificationType type, 162 HistoryDetails* details_deleted) { 163 // This gets called when there are notifications to broadcast. Instead, we 164 // store them so we can tell that the correct notifications were sent. 165 notifications_.push_back(std::make_pair(type, details_deleted)); 166 } 167}; 168 169// The example data consists of 4 visits. The middle two visits are to the 170// same URL, while the first and last are for unique ones. This allows a test 171// for the oldest or newest to include both a URL that should get totally 172// deleted (the one on the end) with one that should only get a visit deleted 173// (with the one in the middle) when it picks the proper threshold time. 174// 175// Each visit has indexed data, each URL has thumbnail. The first two URLs will 176// share the same favicon, while the last one will have a unique favicon. The 177// second visit for the middle URL is typed. 178// 179// The IDs of the added URLs, and the times of the four added visits will be 180// added to the given arrays. 181void ExpireHistoryTest::AddExampleData(URLID url_ids[3], Time visit_times[4]) { 182 if (!main_db_.get() || !text_db_.get()) 183 return; 184 185 // Four times for each visit. 186 visit_times[3] = Time::Now(); 187 visit_times[2] = visit_times[3] - TimeDelta::FromDays(1); 188 visit_times[1] = visit_times[3] - TimeDelta::FromDays(2); 189 visit_times[0] = visit_times[3] - TimeDelta::FromDays(3); 190 191 // Two favicons. The first two URLs will share the same one, while the last 192 // one will have a unique favicon. 193 FavIconID favicon1 = thumb_db_->AddFavIcon(GURL("http://favicon/url1")); 194 FavIconID favicon2 = thumb_db_->AddFavIcon(GURL("http://favicon/url2")); 195 196 // Three URLs. 197 URLRow url_row1(GURL("http://www.google.com/1")); 198 url_row1.set_last_visit(visit_times[0]); 199 url_row1.set_favicon_id(favicon1); 200 url_row1.set_visit_count(1); 201 url_ids[0] = main_db_->AddURL(url_row1); 202 203 URLRow url_row2(GURL("http://www.google.com/2")); 204 url_row2.set_last_visit(visit_times[2]); 205 url_row2.set_favicon_id(favicon1); 206 url_row2.set_visit_count(2); 207 url_row2.set_typed_count(1); 208 url_ids[1] = main_db_->AddURL(url_row2); 209 210 URLRow url_row3(GURL("http://www.google.com/3")); 211 url_row3.set_last_visit(visit_times[3]); 212 url_row3.set_favicon_id(favicon2); 213 url_row3.set_visit_count(1); 214 url_ids[2] = main_db_->AddURL(url_row3); 215 216 // Thumbnails for each URL. 217 scoped_ptr<SkBitmap> thumbnail( 218 gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); 219 ThumbnailScore score(0.25, true, true, Time::Now()); 220 221 Time time; 222 GURL gurl; 223 top_sites_->SetPageThumbnail(url_row1.url(), *thumbnail, score); 224 top_sites_->SetPageThumbnail(url_row2.url(), *thumbnail, score); 225 top_sites_->SetPageThumbnail(url_row3.url(), *thumbnail, score); 226 227 // Four visits. 228 VisitRow visit_row1; 229 visit_row1.url_id = url_ids[0]; 230 visit_row1.visit_time = visit_times[0]; 231 visit_row1.is_indexed = true; 232 main_db_->AddVisit(&visit_row1, SOURCE_BROWSED); 233 234 VisitRow visit_row2; 235 visit_row2.url_id = url_ids[1]; 236 visit_row2.visit_time = visit_times[1]; 237 visit_row2.is_indexed = true; 238 main_db_->AddVisit(&visit_row2, SOURCE_BROWSED); 239 240 VisitRow visit_row3; 241 visit_row3.url_id = url_ids[1]; 242 visit_row3.visit_time = visit_times[2]; 243 visit_row3.is_indexed = true; 244 visit_row3.transition = PageTransition::TYPED; 245 main_db_->AddVisit(&visit_row3, SOURCE_BROWSED); 246 247 VisitRow visit_row4; 248 visit_row4.url_id = url_ids[2]; 249 visit_row4.visit_time = visit_times[3]; 250 visit_row4.is_indexed = true; 251 main_db_->AddVisit(&visit_row4, SOURCE_BROWSED); 252 253 // Full text index for each visit. 254 text_db_->AddPageData(url_row1.url(), visit_row1.url_id, visit_row1.visit_id, 255 visit_row1.visit_time, UTF8ToUTF16("title"), 256 UTF8ToUTF16("body")); 257 258 text_db_->AddPageData(url_row2.url(), visit_row2.url_id, visit_row2.visit_id, 259 visit_row2.visit_time, UTF8ToUTF16("title"), 260 UTF8ToUTF16("body")); 261 text_db_->AddPageData(url_row2.url(), visit_row3.url_id, visit_row3.visit_id, 262 visit_row3.visit_time, UTF8ToUTF16("title"), 263 UTF8ToUTF16("body")); 264 265 // Note the special text in this URL. We'll search the file for this string 266 // to make sure it doesn't hang around after the delete. 267 text_db_->AddPageData(url_row3.url(), visit_row4.url_id, visit_row4.visit_id, 268 visit_row4.visit_time, UTF8ToUTF16("title"), 269 UTF8ToUTF16("goats body")); 270} 271 272void ExpireHistoryTest::AddExampleSourceData(const GURL& url, URLID* id) { 273 if (!main_db_.get()) 274 return; 275 276 Time last_visit_time = Time::Now(); 277 // Add one URL. 278 URLRow url_row1(url); 279 url_row1.set_last_visit(last_visit_time); 280 url_row1.set_visit_count(4); 281 URLID url_id = main_db_->AddURL(url_row1); 282 *id = url_id; 283 284 // Four times for each visit. 285 VisitRow visit_row1(url_id, last_visit_time - TimeDelta::FromDays(4), 0, 286 PageTransition::TYPED, 0); 287 main_db_->AddVisit(&visit_row1, SOURCE_SYNCED); 288 289 VisitRow visit_row2(url_id, last_visit_time - TimeDelta::FromDays(3), 0, 290 PageTransition::TYPED, 0); 291 main_db_->AddVisit(&visit_row2, SOURCE_BROWSED); 292 293 VisitRow visit_row3(url_id, last_visit_time - TimeDelta::FromDays(2), 0, 294 PageTransition::TYPED, 0); 295 main_db_->AddVisit(&visit_row3, SOURCE_EXTENSION); 296 297 VisitRow visit_row4(url_id, last_visit_time, 0, PageTransition::TYPED, 0); 298 main_db_->AddVisit(&visit_row4, SOURCE_FIREFOX_IMPORTED); 299} 300 301bool ExpireHistoryTest::HasFavIcon(FavIconID favicon_id) { 302 if (!thumb_db_.get()) 303 return false; 304 Time last_updated; 305 std::vector<unsigned char> icon_data_unused; 306 GURL icon_url; 307 return thumb_db_->GetFavIcon(favicon_id, &last_updated, &icon_data_unused, 308 &icon_url); 309} 310 311bool ExpireHistoryTest::HasThumbnail(URLID url_id) { 312 URLRow info; 313 if (!main_db_->GetURLRow(url_id, &info)) 314 return false; 315 GURL url = info.url(); 316 RefCountedBytes *data; 317 return top_sites_->GetPageThumbnail(url, &data); 318} 319 320int ExpireHistoryTest::CountTextMatchesForURL(const GURL& url) { 321 if (!text_db_.get()) 322 return 0; 323 324 // "body" should match all pages in the example data. 325 std::vector<TextDatabase::Match> results; 326 QueryOptions options; 327 Time first_time; 328 text_db_->GetTextMatches(UTF8ToUTF16("body"), options, 329 &results, &first_time); 330 331 int count = 0; 332 for (size_t i = 0; i < results.size(); i++) { 333 if (results[i].url == url) 334 count++; 335 } 336 return count; 337} 338 339void ExpireHistoryTest::EnsureURLInfoGone(const URLRow& row) { 340 // Verify the URL no longer exists. 341 URLRow temp_row; 342 EXPECT_FALSE(main_db_->GetURLRow(row.id(), &temp_row)); 343 344 // The indexed data should be gone. 345 EXPECT_EQ(0, CountTextMatchesForURL(row.url())); 346 347 // There should be no visits. 348 VisitVector visits; 349 main_db_->GetVisitsForURL(row.id(), &visits); 350 EXPECT_EQ(0U, visits.size()); 351 352 // Thumbnail should be gone. 353 EXPECT_FALSE(HasThumbnail(row.id())); 354 355 // Check the notifications. There should be a delete notification with this 356 // URL in it. There should also be a "typed URL changed" notification if the 357 // row is marked typed. 358 bool found_delete_notification = false; 359 bool found_typed_changed_notification = false; 360 for (size_t i = 0; i < notifications_.size(); i++) { 361 if (notifications_[i].first == NotificationType::HISTORY_URLS_DELETED) { 362 const URLsDeletedDetails* deleted_details = 363 reinterpret_cast<URLsDeletedDetails*>(notifications_[i].second); 364 if (deleted_details->urls.find(row.url()) != 365 deleted_details->urls.end()) { 366 found_delete_notification = true; 367 } 368 } else if (notifications_[i].first == 369 NotificationType::HISTORY_TYPED_URLS_MODIFIED) { 370 // See if we got a typed URL changed notification. 371 const URLsModifiedDetails* modified_details = 372 reinterpret_cast<URLsModifiedDetails*>(notifications_[i].second); 373 for (size_t cur_url = 0; cur_url < modified_details->changed_urls.size(); 374 cur_url++) { 375 if (modified_details->changed_urls[cur_url].url() == row.url()) 376 found_typed_changed_notification = true; 377 } 378 } else if (notifications_[i].first == 379 NotificationType::HISTORY_URL_VISITED) { 380 // See if we got a visited URL notification. 381 const URLVisitedDetails* visited_details = 382 reinterpret_cast<URLVisitedDetails*>(notifications_[i].second); 383 if (visited_details->row.url() == row.url()) 384 found_typed_changed_notification = true; 385 } 386 } 387 EXPECT_TRUE(found_delete_notification); 388 EXPECT_EQ(row.typed_count() > 0, found_typed_changed_notification); 389} 390 391TEST_F(ExpireHistoryTest, DeleteFaviconsIfPossible) { 392 // Add a favicon record. 393 const GURL favicon_url("http://www.google.com/favicon.ico"); 394 FavIconID icon_id = thumb_db_->AddFavIcon(favicon_url); 395 EXPECT_TRUE(icon_id); 396 EXPECT_TRUE(HasFavIcon(icon_id)); 397 398 // The favicon should be deletable with no users. 399 std::set<FavIconID> favicon_set; 400 favicon_set.insert(icon_id); 401 expirer_.DeleteFaviconsIfPossible(favicon_set); 402 EXPECT_FALSE(HasFavIcon(icon_id)); 403 404 // Add back the favicon. 405 icon_id = thumb_db_->AddFavIcon(favicon_url); 406 EXPECT_TRUE(icon_id); 407 EXPECT_TRUE(HasFavIcon(icon_id)); 408 409 // Add a page that references the favicon. 410 URLRow row(GURL("http://www.google.com/2")); 411 row.set_visit_count(1); 412 row.set_favicon_id(icon_id); 413 EXPECT_TRUE(main_db_->AddURL(row)); 414 415 // Favicon should not be deletable. 416 favicon_set.clear(); 417 favicon_set.insert(icon_id); 418 expirer_.DeleteFaviconsIfPossible(favicon_set); 419 EXPECT_TRUE(HasFavIcon(icon_id)); 420} 421 422// static 423bool ExpireHistoryTest::IsStringInFile(const FilePath& filename, 424 const char* str) { 425 std::string contents; 426 EXPECT_TRUE(file_util::ReadFileToString(filename, &contents)); 427 return contents.find(str) != std::string::npos; 428} 429 430// Deletes a URL with a favicon that it is the last referencer of, so that it 431// should also get deleted. 432// Fails near end of month. http://crbug.com/43586 433TEST_F(ExpireHistoryTest, FLAKY_DeleteURLAndFavicon) { 434 URLID url_ids[3]; 435 Time visit_times[4]; 436 AddExampleData(url_ids, visit_times); 437 438 // Verify things are the way we expect with a URL row, favicon, thumbnail. 439 URLRow last_row; 440 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &last_row)); 441 EXPECT_TRUE(HasFavIcon(last_row.favicon_id())); 442 EXPECT_TRUE(HasThumbnail(url_ids[2])); 443 444 VisitVector visits; 445 main_db_->GetVisitsForURL(url_ids[2], &visits); 446 ASSERT_EQ(1U, visits.size()); 447 EXPECT_EQ(1, CountTextMatchesForURL(last_row.url())); 448 449 // In this test we also make sure that any pending entries in the text 450 // database manager are removed. 451 text_db_->AddPageURL(last_row.url(), last_row.id(), visits[0].visit_id, 452 visits[0].visit_time); 453 454 // Compute the text DB filename. 455 FilePath fts_filename = dir_.Append( 456 TextDatabase::IDToFileName(text_db_->TimeToID(visit_times[3]))); 457 458 // When checking the file, the database must be closed. We then re-initialize 459 // it just like the test set-up did. 460 text_db_.reset(); 461 EXPECT_TRUE(IsStringInFile(fts_filename, "goats")); 462 text_db_.reset(new TextDatabaseManager(dir_, 463 main_db_.get(), main_db_.get())); 464 ASSERT_TRUE(text_db_->Init(NULL)); 465 expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get(), 466 text_db_.get()); 467 468 // Delete the URL and its dependencies. 469 expirer_.DeleteURL(last_row.url()); 470 471 // The string should be removed from the file. FTS can mark it as gone but 472 // doesn't remove it from the file, we want to be sure we're doing the latter. 473 text_db_.reset(); 474 EXPECT_FALSE(IsStringInFile(fts_filename, "goats")); 475 text_db_.reset(new TextDatabaseManager(dir_, 476 main_db_.get(), main_db_.get())); 477 ASSERT_TRUE(text_db_->Init(NULL)); 478 expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get(), 479 text_db_.get()); 480 481 // Run the text database expirer. This will flush any pending entries so we 482 // can check that nothing was committed. We use a time far in the future so 483 // that anything added recently will get flushed. 484 TimeTicks expiration_time = TimeTicks::Now() + TimeDelta::FromDays(1); 485 text_db_->FlushOldChangesForTime(expiration_time); 486 487 // All the normal data + the favicon should be gone. 488 EnsureURLInfoGone(last_row); 489 EXPECT_FALSE(HasFavIcon(last_row.favicon_id())); 490} 491 492// Deletes a URL with a favicon that other URLs reference, so that the favicon 493// should not get deleted. This also tests deleting more than one visit. 494TEST_F(ExpireHistoryTest, DeleteURLWithoutFavicon) { 495 URLID url_ids[3]; 496 Time visit_times[4]; 497 AddExampleData(url_ids, visit_times); 498 499 // Verify things are the way we expect with a URL row, favicon, thumbnail. 500 URLRow last_row; 501 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &last_row)); 502 EXPECT_TRUE(HasFavIcon(last_row.favicon_id())); 503 EXPECT_TRUE(HasThumbnail(url_ids[1])); 504 505 VisitVector visits; 506 main_db_->GetVisitsForURL(url_ids[1], &visits); 507 EXPECT_EQ(2U, visits.size()); 508 EXPECT_EQ(1, CountTextMatchesForURL(last_row.url())); 509 510 // Delete the URL and its dependencies. 511 expirer_.DeleteURL(last_row.url()); 512 513 // All the normal data + the favicon should be gone. 514 EnsureURLInfoGone(last_row); 515 EXPECT_TRUE(HasFavIcon(last_row.favicon_id())); 516} 517 518// DeleteURL should not delete starred urls. 519TEST_F(ExpireHistoryTest, DontDeleteStarredURL) { 520 URLID url_ids[3]; 521 Time visit_times[4]; 522 AddExampleData(url_ids, visit_times); 523 524 URLRow url_row; 525 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row)); 526 527 // Star the last URL. 528 StarURL(url_row.url()); 529 530 // Attempt to delete the url. 531 expirer_.DeleteURL(url_row.url()); 532 533 // Because the url is starred, it shouldn't be deleted. 534 GURL url = url_row.url(); 535 ASSERT_TRUE(main_db_->GetRowForURL(url, &url_row)); 536 537 // And the favicon should exist. 538 EXPECT_TRUE(HasFavIcon(url_row.favicon_id())); 539 540 // But there should be no fts. 541 ASSERT_EQ(0, CountTextMatchesForURL(url_row.url())); 542 543 // And no visits. 544 VisitVector visits; 545 main_db_->GetVisitsForURL(url_row.id(), &visits); 546 ASSERT_EQ(0U, visits.size()); 547 548 // Should still have the thumbnail. 549 ASSERT_TRUE(HasThumbnail(url_row.id())); 550 551 // Unstar the URL and delete again. 552 bookmark_model_.SetURLStarred(url, string16(), false); 553 expirer_.DeleteURL(url); 554 555 // Now it should be completely deleted. 556 EnsureURLInfoGone(url_row); 557} 558 559// Expires all URLs more recent than a given time, with no starred items. 560// Our time threshold is such that one URL should be updated (we delete one of 561// the two visits) and one is deleted. 562TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { 563 URLID url_ids[3]; 564 Time visit_times[4]; 565 AddExampleData(url_ids, visit_times); 566 567 URLRow url_row1, url_row2; 568 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1)); 569 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2)); 570 571 // In this test we also make sure that any pending entries in the text 572 // database manager are removed. 573 VisitVector visits; 574 main_db_->GetVisitsForURL(url_ids[2], &visits); 575 ASSERT_EQ(1U, visits.size()); 576 text_db_->AddPageURL(url_row2.url(), url_row2.id(), visits[0].visit_id, 577 visits[0].visit_time); 578 579 // This should delete the last two visits. 580 std::set<GURL> restrict_urls; 581 expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); 582 583 // Run the text database expirer. This will flush any pending entries so we 584 // can check that nothing was committed. We use a time far in the future so 585 // that anything added recently will get flushed. 586 TimeTicks expiration_time = TimeTicks::Now() + TimeDelta::FromDays(1); 587 text_db_->FlushOldChangesForTime(expiration_time); 588 589 // Verify that the middle URL had its last visit deleted only. 590 visits.clear(); 591 main_db_->GetVisitsForURL(url_ids[1], &visits); 592 EXPECT_EQ(1U, visits.size()); 593 EXPECT_EQ(0, CountTextMatchesForURL(url_row1.url())); 594 595 // Verify that the middle URL visit time and visit counts were updated. 596 URLRow temp_row; 597 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row)); 598 EXPECT_TRUE(visit_times[2] == url_row1.last_visit()); // Previous value. 599 EXPECT_TRUE(visit_times[1] == temp_row.last_visit()); // New value. 600 EXPECT_EQ(2, url_row1.visit_count()); 601 EXPECT_EQ(1, temp_row.visit_count()); 602 EXPECT_EQ(1, url_row1.typed_count()); 603 EXPECT_EQ(0, temp_row.typed_count()); 604 605 // Verify that the middle URL's favicon and thumbnail is still there. 606 EXPECT_TRUE(HasFavIcon(url_row1.favicon_id())); 607 EXPECT_TRUE(HasThumbnail(url_row1.id())); 608 609 // Verify that the last URL was deleted. 610 EnsureURLInfoGone(url_row2); 611 EXPECT_FALSE(HasFavIcon(url_row2.favicon_id())); 612} 613 614// Expires only a specific URLs more recent than a given time, with no starred 615// items. Our time threshold is such that the URL should be updated (we delete 616// one of the two visits). 617TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) { 618 URLID url_ids[3]; 619 Time visit_times[4]; 620 AddExampleData(url_ids, visit_times); 621 622 URLRow url_row1, url_row2; 623 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1)); 624 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2)); 625 626 // In this test we also make sure that any pending entries in the text 627 // database manager are removed. 628 VisitVector visits; 629 main_db_->GetVisitsForURL(url_ids[2], &visits); 630 ASSERT_EQ(1U, visits.size()); 631 text_db_->AddPageURL(url_row2.url(), url_row2.id(), visits[0].visit_id, 632 visits[0].visit_time); 633 634 // This should delete the last two visits. 635 std::set<GURL> restrict_urls; 636 restrict_urls.insert(url_row1.url()); 637 expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); 638 639 // Run the text database expirer. This will flush any pending entries so we 640 // can check that nothing was committed. We use a time far in the future so 641 // that anything added recently will get flushed. 642 TimeTicks expiration_time = TimeTicks::Now() + TimeDelta::FromDays(1); 643 text_db_->FlushOldChangesForTime(expiration_time); 644 645 // Verify that the middle URL had its last visit deleted only. 646 visits.clear(); 647 main_db_->GetVisitsForURL(url_ids[1], &visits); 648 EXPECT_EQ(1U, visits.size()); 649 EXPECT_EQ(0, CountTextMatchesForURL(url_row1.url())); 650 651 // Verify that the middle URL visit time and visit counts were updated. 652 URLRow temp_row; 653 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row)); 654 EXPECT_TRUE(visit_times[2] == url_row1.last_visit()); // Previous value. 655 EXPECT_TRUE(visit_times[1] == temp_row.last_visit()); // New value. 656 EXPECT_EQ(2, url_row1.visit_count()); 657 EXPECT_EQ(1, temp_row.visit_count()); 658 EXPECT_EQ(1, url_row1.typed_count()); 659 EXPECT_EQ(0, temp_row.typed_count()); 660 661 // Verify that the middle URL's favicon and thumbnail is still there. 662 EXPECT_TRUE(HasFavIcon(url_row1.favicon_id())); 663 EXPECT_TRUE(HasThumbnail(url_row1.id())); 664 665 // Verify that the last URL was not touched. 666 EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row)); 667 EXPECT_TRUE(HasFavIcon(url_row2.favicon_id())); 668 EXPECT_TRUE(HasThumbnail(url_row2.id())); 669} 670 671// Expire a starred URL, it shouldn't get deleted 672TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { 673 URLID url_ids[3]; 674 Time visit_times[4]; 675 AddExampleData(url_ids, visit_times); 676 677 URLRow url_row1, url_row2; 678 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1)); 679 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2)); 680 681 // Star the last two URLs. 682 StarURL(url_row1.url()); 683 StarURL(url_row2.url()); 684 685 // This should delete the last two visits. 686 std::set<GURL> restrict_urls; 687 expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); 688 689 // The URL rows should still exist. 690 URLRow new_url_row1, new_url_row2; 691 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &new_url_row1)); 692 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &new_url_row2)); 693 694 // The visit times should be updated. 695 EXPECT_TRUE(new_url_row1.last_visit() == visit_times[1]); 696 EXPECT_TRUE(new_url_row2.last_visit().is_null()); // No last visit time. 697 698 // Visit/typed count should not be updated for bookmarks. 699 EXPECT_EQ(0, new_url_row1.typed_count()); 700 EXPECT_EQ(1, new_url_row1.visit_count()); 701 EXPECT_EQ(0, new_url_row2.typed_count()); 702 EXPECT_EQ(0, new_url_row2.visit_count()); 703 704 // Thumbnails and favicons should still exist. Note that we keep thumbnails 705 // that may have been updated since the time threshold. Since the URL still 706 // exists in history, this should not be a privacy problem, we only update 707 // the visit counts in this case for consistency anyway. 708 EXPECT_TRUE(HasFavIcon(new_url_row1.favicon_id())); 709 EXPECT_TRUE(HasThumbnail(new_url_row1.id())); 710 EXPECT_TRUE(HasFavIcon(new_url_row2.favicon_id())); 711 EXPECT_TRUE(HasThumbnail(new_url_row2.id())); 712} 713 714TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeUnstarred) { 715 URLID url_ids[3]; 716 Time visit_times[4]; 717 AddExampleData(url_ids, visit_times); 718 719 URLRow url_row1, url_row2; 720 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1)); 721 ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2)); 722 723 // Archive the oldest two visits. This will actually result in deleting them 724 // since their transition types are empty (not important). 725 expirer_.ArchiveHistoryBefore(visit_times[1]); 726 727 // The first URL should be deleted, the second should not be affected. 728 URLRow temp_row; 729 EXPECT_FALSE(main_db_->GetURLRow(url_ids[0], &temp_row)); 730 EXPECT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row)); 731 EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row)); 732 733 // Make sure the archived database has nothing in it. 734 EXPECT_FALSE(archived_db_->GetRowForURL(url_row1.url(), NULL)); 735 EXPECT_FALSE(archived_db_->GetRowForURL(url_row2.url(), NULL)); 736 737 // Now archive one more visit so that the middle URL should be removed. This 738 // one will actually be archived instead of deleted. 739 expirer_.ArchiveHistoryBefore(visit_times[2]); 740 EXPECT_FALSE(main_db_->GetURLRow(url_ids[1], &temp_row)); 741 EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row)); 742 743 // Make sure the archived database has an entry for the second URL. 744 URLRow archived_row; 745 // Note that the ID is different in the archived DB, so look up by URL. 746 EXPECT_TRUE(archived_db_->GetRowForURL(url_row1.url(), &archived_row)); 747 VisitVector archived_visits; 748 archived_db_->GetVisitsForURL(archived_row.id(), &archived_visits); 749 EXPECT_EQ(1U, archived_visits.size()); 750} 751 752TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeStarred) { 753 URLID url_ids[3]; 754 Time visit_times[4]; 755 AddExampleData(url_ids, visit_times); 756 757 URLRow url_row0, url_row1; 758 ASSERT_TRUE(main_db_->GetURLRow(url_ids[0], &url_row0)); 759 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1)); 760 761 // Star the URLs. 762 StarURL(url_row0.url()); 763 StarURL(url_row1.url()); 764 765 // Now archive the first three visits (first two URLs). The first two visits 766 // should be, the third deleted, but the URL records should not. 767 expirer_.ArchiveHistoryBefore(visit_times[2]); 768 769 // The first URL should have its visit deleted, but it should still be present 770 // in the main DB and not in the archived one since it is starred. 771 URLRow temp_row; 772 ASSERT_TRUE(main_db_->GetURLRow(url_ids[0], &temp_row)); 773 // Note that the ID is different in the archived DB, so look up by URL. 774 EXPECT_FALSE(archived_db_->GetRowForURL(temp_row.url(), NULL)); 775 VisitVector visits; 776 main_db_->GetVisitsForURL(temp_row.id(), &visits); 777 EXPECT_EQ(0U, visits.size()); 778 779 // The second URL should have its first visit deleted and its second visit 780 // archived. It should be present in both the main DB (because it's starred) 781 // and the archived DB (for the archived visit). 782 ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row)); 783 main_db_->GetVisitsForURL(temp_row.id(), &visits); 784 EXPECT_EQ(0U, visits.size()); 785 786 // Note that the ID is different in the archived DB, so look up by URL. 787 ASSERT_TRUE(archived_db_->GetRowForURL(temp_row.url(), &temp_row)); 788 archived_db_->GetVisitsForURL(temp_row.id(), &visits); 789 ASSERT_EQ(1U, visits.size()); 790 EXPECT_TRUE(visit_times[2] == visits[0].visit_time); 791 792 // The third URL should be unchanged. 793 EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row)); 794 EXPECT_FALSE(archived_db_->GetRowForURL(temp_row.url(), NULL)); 795} 796 797// Tests the return values from ArchiveSomeOldHistory. The rest of the 798// functionality of this function is tested by the ArchiveHistoryBefore* 799// tests which use this function internally. 800TEST_F(ExpireHistoryTest, ArchiveSomeOldHistory) { 801 URLID url_ids[3]; 802 Time visit_times[4]; 803 AddExampleData(url_ids, visit_times); 804 const ExpiringVisitsReader* reader = expirer_.GetAllVisitsReader(); 805 806 // Deleting a time range with no URLs should return false (nothing found). 807 EXPECT_FALSE(expirer_.ArchiveSomeOldHistory( 808 visit_times[0] - TimeDelta::FromDays(100), reader, 1)); 809 810 // Deleting a time range with not up the the max results should also return 811 // false (there will only be one visit deleted in this range). 812 EXPECT_FALSE(expirer_.ArchiveSomeOldHistory(visit_times[0], reader, 2)); 813 814 // Deleting a time range with the max number of results should return true 815 // (max deleted). 816 EXPECT_TRUE(expirer_.ArchiveSomeOldHistory(visit_times[2], reader, 1)); 817} 818 819TEST_F(ExpireHistoryTest, ExpiringVisitsReader) { 820 URLID url_ids[3]; 821 Time visit_times[4]; 822 AddExampleData(url_ids, visit_times); 823 824 const ExpiringVisitsReader* all = expirer_.GetAllVisitsReader(); 825 const ExpiringVisitsReader* auto_subframes = 826 expirer_.GetAutoSubframeVisitsReader(); 827 828 VisitVector visits; 829 Time now = Time::Now(); 830 831 // Verify that the early expiration threshold, stored in the meta table is 832 // initialized. 833 EXPECT_TRUE(main_db_->GetEarlyExpirationThreshold() == 834 Time::FromInternalValue(1L)); 835 836 // First, attempt reading AUTO_SUBFRAME visits. We should get none. 837 EXPECT_FALSE(auto_subframes->Read(now, main_db_.get(), &visits, 1)); 838 EXPECT_EQ(0U, visits.size()); 839 840 // Verify that the early expiration threshold was updated, since there are no 841 // AUTO_SUBFRAME visits in the given time range. 842 EXPECT_TRUE(now <= main_db_->GetEarlyExpirationThreshold()); 843 844 // Now, read all visits and verify that there's at least one. 845 EXPECT_TRUE(all->Read(now, main_db_.get(), &visits, 1)); 846 EXPECT_EQ(1U, visits.size()); 847} 848 849// Tests how ArchiveSomeOldHistory treats source information. 850TEST_F(ExpireHistoryTest, ArchiveSomeOldHistoryWithSource) { 851 const GURL url("www.testsource.com"); 852 URLID url_id; 853 AddExampleSourceData(url, &url_id); 854 const ExpiringVisitsReader* reader = expirer_.GetAllVisitsReader(); 855 856 // Archiving all the visits we added. 857 ASSERT_FALSE(expirer_.ArchiveSomeOldHistory(Time::Now(), reader, 10)); 858 859 URLRow archived_row; 860 ASSERT_TRUE(archived_db_->GetRowForURL(url, &archived_row)); 861 VisitVector archived_visits; 862 archived_db_->GetVisitsForURL(archived_row.id(), &archived_visits); 863 ASSERT_EQ(4U, archived_visits.size()); 864 VisitSourceMap sources; 865 archived_db_->GetVisitsSource(archived_visits, &sources); 866 ASSERT_EQ(3U, sources.size()); 867 int result = 0; 868 VisitSourceMap::iterator iter; 869 for (int i = 0; i < 4; i++) { 870 iter = sources.find(archived_visits[i].visit_id); 871 if (iter == sources.end()) 872 continue; 873 switch (iter->second) { 874 case history::SOURCE_EXTENSION: 875 result |= 0x1; 876 break; 877 case history::SOURCE_FIREFOX_IMPORTED: 878 result |= 0x2; 879 break; 880 case history::SOURCE_SYNCED: 881 result |= 0x4; 882 default: 883 break; 884 } 885 } 886 EXPECT_EQ(0x7, result); 887 main_db_->GetVisitsSource(archived_visits, &sources); 888 EXPECT_EQ(0U, sources.size()); 889 main_db_->GetVisitsForURL(url_id, &archived_visits); 890 EXPECT_EQ(0U, archived_visits.size()); 891} 892 893// TODO(brettw) add some visits with no URL to make sure everything is updated 894// properly. Have the visits also refer to nonexistent FTS rows. 895// 896// Maybe also refer to invalid favicons. 897 898} // namespace history 899