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