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