• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
ExpireHistoryTest()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.
ClearLastNotifications()82   void ClearLastNotifications() {
83     for (size_t i = 0; i < notifications_.size(); i++)
84       delete notifications_[i].second;
85     notifications_.clear();
86   }
87 
StarURL(const GURL & url)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.
path() const96   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:
SetUp()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 
TearDown()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.
BroadcastNotifications(NotificationType type,HistoryDetails * details_deleted)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.
AddExampleData(URLID url_ids[3],Time visit_times[4])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 
AddExampleSourceData(const GURL & url,URLID * id)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 
HasFavicon(FaviconID favicon_id)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 
GetFavicon(const GURL & page_url,IconType icon_type)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 
HasThumbnail(URLID url_id)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 
CountTextMatchesForURL(const GURL & url)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 
EnsureURLInfoGone(const URLRow & row)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 
TEST_F(ExpireHistoryTest,DeleteFaviconsIfPossible)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
IsStringInFile(const FilePath & filename,const char * str)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
TEST_F(ExpireHistoryTest,FLAKY_DeleteURLAndFavicon)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.
TEST_F(ExpireHistoryTest,DeleteURLWithoutFavicon)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.
TEST_F(ExpireHistoryTest,DontDeleteStarredURL)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.
TEST_F(ExpireHistoryTest,FlushRecentURLsUnstarred)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).
TEST_F(ExpireHistoryTest,FlushRecentURLsUnstarredRestricted)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
TEST_F(ExpireHistoryTest,FlushRecentURLsStarred)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 
TEST_F(ExpireHistoryTest,ArchiveHistoryBeforeUnstarred)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 
TEST_F(ExpireHistoryTest,ArchiveHistoryBeforeStarred)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.
TEST_F(ExpireHistoryTest,ArchiveSomeOldHistory)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 
TEST_F(ExpireHistoryTest,ExpiringVisitsReader)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.
TEST_F(ExpireHistoryTest,ArchiveSomeOldHistoryWithSource)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