Home | History | Annotate | Download | only in history
      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