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