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