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