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// History unit tests come in two flavors:
6//
7// 1. The more complicated style is that the unit test creates a full history
8//    service. This spawns a background thread for the history backend, and
9//    all communication is asynchronous. This is useful for testing more
10//    complicated things or end-to-end behavior.
11//
12// 2. The simpler style is to create a history backend on this thread and
13//    access it directly without a HistoryService object. This is much simpler
14//    because communication is synchronous. Generally, sets should go through
15//    the history backend (since there is a lot of logic) but gets can come
16//    directly from the HistoryDatabase. This is because the backend generally
17//    has no logic in the getter except threading stuff, which we don't want
18//    to run.
19
20#include <time.h>
21
22#include <algorithm>
23#include <string>
24
25#include "app/sql/connection.h"
26#include "app/sql/statement.h"
27#include "base/basictypes.h"
28#include "base/callback.h"
29#include "base/command_line.h"
30#include "base/file_path.h"
31#include "base/file_util.h"
32#include "base/memory/scoped_temp_dir.h"
33#include "base/memory/scoped_vector.h"
34#include "base/message_loop.h"
35#include "base/path_service.h"
36#include "base/string_util.h"
37#include "base/task.h"
38#include "base/utf_string_conversions.h"
39#include "chrome/browser/download/download_item.h"
40#include "chrome/browser/history/download_create_info.h"
41#include "chrome/browser/history/history.h"
42#include "chrome/browser/history/history_backend.h"
43#include "chrome/browser/history/history_database.h"
44#include "chrome/browser/history/history_notifications.h"
45#include "chrome/browser/history/in_memory_database.h"
46#include "chrome/browser/history/in_memory_history_backend.h"
47#include "chrome/browser/history/page_usage_data.h"
48#include "chrome/common/chrome_paths.h"
49#include "chrome/common/thumbnail_score.h"
50#include "chrome/tools/profiles/thumbnail-inl.h"
51#include "content/common/notification_details.h"
52#include "content/common/notification_source.h"
53#include "testing/gtest/include/gtest/gtest.h"
54#include "third_party/skia/include/core/SkBitmap.h"
55#include "ui/gfx/codec/jpeg_codec.h"
56
57using base::Time;
58using base::TimeDelta;
59
60namespace history {
61class HistoryTest;
62}
63
64// Specialize RunnableMethodTraits for HistoryTest so we can create callbacks.
65// None of these callbacks can outlast the test, so there is not need to retain
66// the HistoryTest object.
67DISABLE_RUNNABLE_METHOD_REFCOUNT(history::HistoryTest);
68
69namespace history {
70
71namespace {
72
73// The tracker uses RenderProcessHost pointers for scoping but never
74// dereferences them. We use ints because it's easier. This function converts
75// between the two.
76static void* MakeFakeHost(int id) {
77  void* host = 0;
78  memcpy(&host, &id, sizeof(id));
79  return host;
80}
81
82}  // namespace
83
84// Delegate class for when we create a backend without a HistoryService.
85class BackendDelegate : public HistoryBackend::Delegate {
86 public:
87  explicit BackendDelegate(HistoryTest* history_test)
88      : history_test_(history_test) {
89  }
90
91  virtual void NotifyProfileError(sql::InitStatus init_status) OVERRIDE {}
92  virtual void SetInMemoryBackend(InMemoryHistoryBackend* backend) OVERRIDE;
93  virtual void BroadcastNotifications(NotificationType type,
94                                      HistoryDetails* details) OVERRIDE;
95  virtual void DBLoaded() OVERRIDE {}
96  virtual void StartTopSitesMigration() OVERRIDE {}
97 private:
98  HistoryTest* history_test_;
99};
100
101// This must be outside the anonymous namespace for the friend statement in
102// HistoryBackend to work.
103class HistoryTest : public testing::Test {
104 public:
105  HistoryTest()
106      : history_service_(NULL),
107        got_thumbnail_callback_(false),
108        redirect_query_success_(false),
109        query_url_success_(false),
110        db_(NULL) {
111  }
112  ~HistoryTest() {
113  }
114
115  // Creates the HistoryBackend and HistoryDatabase on the current thread,
116  // assigning the values to backend_ and db_.
117  void CreateBackendAndDatabase() {
118    backend_ =
119        new HistoryBackend(history_dir_, new BackendDelegate(this), NULL);
120    backend_->Init(std::string(), false);
121    db_ = backend_->db_.get();
122    DCHECK(in_mem_backend_.get()) << "Mem backend should have been set by "
123        "HistoryBackend::Init";
124  }
125
126  void OnSegmentUsageAvailable(CancelableRequestProvider::Handle handle,
127                               std::vector<PageUsageData*>* data) {
128    page_usage_data_->swap(*data);
129    MessageLoop::current()->Quit();
130  }
131
132  void OnDeleteURLsDone(CancelableRequestProvider::Handle handle) {
133    MessageLoop::current()->Quit();
134  }
135
136  void OnMostVisitedURLsAvailable(CancelableRequestProvider::Handle handle,
137                                  MostVisitedURLList url_list) {
138    most_visited_urls_.swap(url_list);
139    MessageLoop::current()->Quit();
140  }
141
142 protected:
143  friend class BackendDelegate;
144
145  // testing::Test
146  virtual void SetUp() {
147    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
148    history_dir_ = temp_dir_.path().AppendASCII("HistoryTest");
149    ASSERT_TRUE(file_util::CreateDirectory(history_dir_));
150  }
151
152  void DeleteBackend() {
153    if (backend_) {
154      backend_->Closing();
155      backend_ = NULL;
156    }
157  }
158
159  virtual void TearDown() {
160    DeleteBackend();
161
162    if (history_service_)
163      CleanupHistoryService();
164
165    // Make sure we don't have any event pending that could disrupt the next
166    // test.
167    MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
168    MessageLoop::current()->Run();
169  }
170
171  void CleanupHistoryService() {
172    DCHECK(history_service_.get());
173
174    history_service_->NotifyRenderProcessHostDestruction(0);
175    history_service_->SetOnBackendDestroyTask(new MessageLoop::QuitTask);
176    history_service_->Cleanup();
177    history_service_ = NULL;
178
179    // Wait for the backend class to terminate before deleting the files and
180    // moving to the next test. Note: if this never terminates, somebody is
181    // probably leaking a reference to the history backend, so it never calls
182    // our destroy task.
183    MessageLoop::current()->Run();
184  }
185
186  int64 AddDownload(int32 state, const Time& time) {
187    DownloadCreateInfo download(FilePath(FILE_PATH_LITERAL("foo-path")),
188                                GURL("foo-url"), time, 0, 512, state, 0, false);
189    return db_->CreateDownload(download);
190  }
191
192  // Fills the query_url_row_ and query_url_visits_ structures with the
193  // information about the given URL and returns true. If the URL was not
194  // found, this will return false and those structures will not be changed.
195  bool QueryURL(HistoryService* history, const GURL& url) {
196    history->QueryURL(url, true, &consumer_,
197                      NewCallback(this, &HistoryTest::SaveURLAndQuit));
198    MessageLoop::current()->Run();  // Will be exited in SaveURLAndQuit.
199    return query_url_success_;
200  }
201
202  // Callback for HistoryService::QueryURL.
203  void SaveURLAndQuit(HistoryService::Handle handle,
204                      bool success,
205                      const URLRow* url_row,
206                      VisitVector* visit_vector) {
207    query_url_success_ = success;
208    if (query_url_success_) {
209      query_url_row_ = *url_row;
210      query_url_visits_.swap(*visit_vector);
211    } else {
212      query_url_row_ = URLRow();
213      query_url_visits_.clear();
214    }
215    MessageLoop::current()->Quit();
216  }
217
218  // Fills in saved_redirects_ with the redirect information for the given URL,
219  // returning true on success. False means the URL was not found.
220  bool QueryRedirectsFrom(HistoryService* history, const GURL& url) {
221    history->QueryRedirectsFrom(url, &consumer_,
222        NewCallback(this, &HistoryTest::OnRedirectQueryComplete));
223    MessageLoop::current()->Run();  // Will be exited in *QueryComplete.
224    return redirect_query_success_;
225  }
226
227  // Callback for QueryRedirects.
228  void OnRedirectQueryComplete(HistoryService::Handle handle,
229                               GURL url,
230                               bool success,
231                               history::RedirectList* redirects) {
232    redirect_query_success_ = success;
233    if (redirect_query_success_)
234      saved_redirects_.swap(*redirects);
235    else
236      saved_redirects_.clear();
237    MessageLoop::current()->Quit();
238  }
239
240  ScopedTempDir temp_dir_;
241
242  MessageLoopForUI message_loop_;
243
244  // PageUsageData vector to test segments.
245  ScopedVector<PageUsageData> page_usage_data_;
246
247  MostVisitedURLList most_visited_urls_;
248
249  // When non-NULL, this will be deleted on tear down and we will block until
250  // the backend thread has completed. This allows tests for the history
251  // service to use this feature, but other tests to ignore this.
252  scoped_refptr<HistoryService> history_service_;
253
254  // names of the database files
255  FilePath history_dir_;
256
257  // Set by the thumbnail callback when we get data, you should be sure to
258  // clear this before issuing a thumbnail request.
259  bool got_thumbnail_callback_;
260  std::vector<unsigned char> thumbnail_data_;
261
262  // Set by the redirect callback when we get data. You should be sure to
263  // clear this before issuing a redirect request.
264  history::RedirectList saved_redirects_;
265  bool redirect_query_success_;
266
267  // For history requests.
268  CancelableRequestConsumer consumer_;
269
270  // For saving URL info after a call to QueryURL
271  bool query_url_success_;
272  URLRow query_url_row_;
273  VisitVector query_url_visits_;
274
275  // Created via CreateBackendAndDatabase.
276  scoped_refptr<HistoryBackend> backend_;
277  scoped_ptr<InMemoryHistoryBackend> in_mem_backend_;
278  HistoryDatabase* db_;  // Cached reference to the backend's database.
279};
280
281void BackendDelegate::SetInMemoryBackend(InMemoryHistoryBackend* backend) {
282  // Save the in-memory backend to the history test object, this happens
283  // synchronously, so we don't have to do anything fancy.
284  history_test_->in_mem_backend_.reset(backend);
285}
286
287void BackendDelegate::BroadcastNotifications(NotificationType type,
288                                             HistoryDetails* details) {
289  // Currently, just send the notifications directly to the in-memory database.
290  // We may want do do something more fancy in the future.
291  Details<HistoryDetails> det(details);
292  history_test_->in_mem_backend_->Observe(type,
293      Source<HistoryTest>(NULL), det);
294
295  // The backend passes ownership of the details pointer to us.
296  delete details;
297}
298
299TEST_F(HistoryTest, ClearBrowsingData_Downloads) {
300  CreateBackendAndDatabase();
301
302  Time now = Time::Now();
303  TimeDelta one_day = TimeDelta::FromDays(1);
304  Time month_ago = now - TimeDelta::FromDays(30);
305
306  // Initially there should be nothing in the downloads database.
307  std::vector<DownloadCreateInfo> downloads;
308  db_->QueryDownloads(&downloads);
309  EXPECT_EQ(0U, downloads.size());
310
311  // Keep track of these as we need to update them later during the test.
312  DownloadID in_progress, removing;
313
314  // Create one with a 0 time.
315  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, Time()));
316  // Create one for now and +/- 1 day.
317  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now - one_day));
318  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now));
319  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now + one_day));
320  // Try the other four states.
321  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, month_ago));
322  EXPECT_NE(0, in_progress = AddDownload(DownloadItem::IN_PROGRESS, month_ago));
323  EXPECT_NE(0, AddDownload(DownloadItem::CANCELLED, month_ago));
324  EXPECT_NE(0, AddDownload(DownloadItem::INTERRUPTED, month_ago));
325  EXPECT_NE(0, removing = AddDownload(DownloadItem::REMOVING, month_ago));
326
327  // Test to see if inserts worked.
328  db_->QueryDownloads(&downloads);
329  EXPECT_EQ(9U, downloads.size());
330
331  // Try removing from current timestamp. This should delete the one in the
332  // future and one very recent one.
333  db_->RemoveDownloadsBetween(now, Time());
334  db_->QueryDownloads(&downloads);
335  EXPECT_EQ(7U, downloads.size());
336
337  // Try removing from two months ago. This should not delete items that are
338  // 'in progress' or in 'removing' state.
339  db_->RemoveDownloadsBetween(now - TimeDelta::FromDays(60), Time());
340  db_->QueryDownloads(&downloads);
341  EXPECT_EQ(3U, downloads.size());
342
343  // Download manager converts to TimeT, which is lossy, so we do the same
344  // for comparison.
345  Time month_ago_lossy = Time::FromTimeT(month_ago.ToTimeT());
346
347  // Make sure the right values remain.
348  EXPECT_EQ(DownloadItem::COMPLETE, downloads[0].state);
349  EXPECT_EQ(0, downloads[0].start_time.ToInternalValue());
350  EXPECT_EQ(DownloadItem::IN_PROGRESS, downloads[1].state);
351  EXPECT_EQ(month_ago_lossy.ToInternalValue(),
352            downloads[1].start_time.ToInternalValue());
353  EXPECT_EQ(DownloadItem::REMOVING, downloads[2].state);
354  EXPECT_EQ(month_ago_lossy.ToInternalValue(),
355            downloads[2].start_time.ToInternalValue());
356
357  // Change state so we can delete the downloads.
358  EXPECT_TRUE(db_->UpdateDownload(512, DownloadItem::COMPLETE, in_progress));
359  EXPECT_TRUE(db_->UpdateDownload(512, DownloadItem::CANCELLED, removing));
360
361  // Try removing from Time=0. This should delete all.
362  db_->RemoveDownloadsBetween(Time(), Time());
363  db_->QueryDownloads(&downloads);
364  EXPECT_EQ(0U, downloads.size());
365
366  // Check removal of downloads stuck in IN_PROGRESS state.
367  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE,    month_ago));
368  EXPECT_NE(0, AddDownload(DownloadItem::IN_PROGRESS, month_ago));
369  db_->QueryDownloads(&downloads);
370  EXPECT_EQ(2U, downloads.size());
371  db_->RemoveDownloadsBetween(Time(), Time());
372  db_->QueryDownloads(&downloads);
373  // IN_PROGRESS download should remain. It it indicated as "Canceled"
374  EXPECT_EQ(1U, downloads.size());
375  db_->CleanUpInProgressEntries();
376  db_->QueryDownloads(&downloads);
377  EXPECT_EQ(1U, downloads.size());
378  db_->RemoveDownloadsBetween(Time(), Time());
379  db_->QueryDownloads(&downloads);
380  EXPECT_EQ(0U, downloads.size());
381}
382
383TEST_F(HistoryTest, AddPage) {
384  scoped_refptr<HistoryService> history(new HistoryService);
385  history_service_ = history;
386  ASSERT_TRUE(history->Init(history_dir_, NULL));
387
388  // Add the page once from a child frame.
389  const GURL test_url("http://www.google.com/");
390  history->AddPage(test_url, NULL, 0, GURL(),
391                   PageTransition::MANUAL_SUBFRAME,
392                   history::RedirectList(),
393                   history::SOURCE_BROWSED, false);
394  EXPECT_TRUE(QueryURL(history, test_url));
395  EXPECT_EQ(1, query_url_row_.visit_count());
396  EXPECT_EQ(0, query_url_row_.typed_count());
397  EXPECT_TRUE(query_url_row_.hidden());  // Hidden because of child frame.
398
399  // Add the page once from the main frame (should unhide it).
400  history->AddPage(test_url, NULL, 0, GURL(), PageTransition::LINK,
401                   history::RedirectList(),
402                   history::SOURCE_BROWSED, false);
403  EXPECT_TRUE(QueryURL(history, test_url));
404  EXPECT_EQ(2, query_url_row_.visit_count());  // Added twice.
405  EXPECT_EQ(0, query_url_row_.typed_count());  // Never typed.
406  EXPECT_FALSE(query_url_row_.hidden());  // Because loaded in main frame.
407}
408
409TEST_F(HistoryTest, AddPageSameTimes) {
410  scoped_refptr<HistoryService> history(new HistoryService);
411  history_service_ = history;
412  ASSERT_TRUE(history->Init(history_dir_, NULL));
413
414  Time now = Time::Now();
415  const GURL test_urls[] = {
416    GURL("http://timer.first.page/"),
417    GURL("http://timer.second.page/"),
418    GURL("http://timer.third.page/"),
419  };
420
421  // Make sure that two pages added at the same time with no intervening
422  // additions have different timestamps.
423  history->AddPage(test_urls[0], now, NULL, 0, GURL(),
424                   PageTransition::LINK,
425                   history::RedirectList(),
426                   history::SOURCE_BROWSED, false);
427  EXPECT_TRUE(QueryURL(history, test_urls[0]));
428  EXPECT_EQ(1, query_url_row_.visit_count());
429  EXPECT_TRUE(now == query_url_row_.last_visit());  // gtest doesn't like Time
430
431  history->AddPage(test_urls[1], now, NULL, 0, GURL(),
432                   PageTransition::LINK,
433                   history::RedirectList(),
434                   history::SOURCE_BROWSED, false);
435  EXPECT_TRUE(QueryURL(history, test_urls[1]));
436  EXPECT_EQ(1, query_url_row_.visit_count());
437  EXPECT_TRUE(now + TimeDelta::FromMicroseconds(1) ==
438      query_url_row_.last_visit());
439
440  // Make sure the next page, at a different time, is also correct.
441  history->AddPage(test_urls[2], now + TimeDelta::FromMinutes(1),
442                   NULL, 0, GURL(),
443                   PageTransition::LINK,
444                   history::RedirectList(),
445                   history::SOURCE_BROWSED, false);
446  EXPECT_TRUE(QueryURL(history, test_urls[2]));
447  EXPECT_EQ(1, query_url_row_.visit_count());
448  EXPECT_TRUE(now + TimeDelta::FromMinutes(1) ==
449      query_url_row_.last_visit());
450}
451
452TEST_F(HistoryTest, AddRedirect) {
453  scoped_refptr<HistoryService> history(new HistoryService);
454  history_service_ = history;
455  ASSERT_TRUE(history->Init(history_dir_, NULL));
456
457  const char* first_sequence[] = {
458    "http://first.page/",
459    "http://second.page/"};
460  int first_count = arraysize(first_sequence);
461  history::RedirectList first_redirects;
462  for (int i = 0; i < first_count; i++)
463    first_redirects.push_back(GURL(first_sequence[i]));
464
465  // Add the sequence of pages as a server with no referrer. Note that we need
466  // to have a non-NULL page ID scope.
467  history->AddPage(first_redirects.back(), MakeFakeHost(1), 0, GURL(),
468                   PageTransition::LINK, first_redirects,
469                   history::SOURCE_BROWSED,  true);
470
471  // The first page should be added once with a link visit type (because we set
472  // LINK when we added the original URL, and a referrer of nowhere (0).
473  EXPECT_TRUE(QueryURL(history, first_redirects[0]));
474  EXPECT_EQ(1, query_url_row_.visit_count());
475  ASSERT_EQ(1U, query_url_visits_.size());
476  int64 first_visit = query_url_visits_[0].visit_id;
477  EXPECT_EQ(PageTransition::LINK |
478            PageTransition::CHAIN_START, query_url_visits_[0].transition);
479  EXPECT_EQ(0, query_url_visits_[0].referring_visit);  // No referrer.
480
481  // The second page should be a server redirect type with a referrer of the
482  // first page.
483  EXPECT_TRUE(QueryURL(history, first_redirects[1]));
484  EXPECT_EQ(1, query_url_row_.visit_count());
485  ASSERT_EQ(1U, query_url_visits_.size());
486  int64 second_visit = query_url_visits_[0].visit_id;
487  EXPECT_EQ(PageTransition::SERVER_REDIRECT |
488            PageTransition::CHAIN_END, query_url_visits_[0].transition);
489  EXPECT_EQ(first_visit, query_url_visits_[0].referring_visit);
490
491  // Check that the redirect finding function successfully reports it.
492  saved_redirects_.clear();
493  QueryRedirectsFrom(history, first_redirects[0]);
494  ASSERT_EQ(1U, saved_redirects_.size());
495  EXPECT_EQ(first_redirects[1], saved_redirects_[0]);
496
497  // Now add a client redirect from that second visit to a third, client
498  // redirects are tracked by the RenderView prior to updating history,
499  // so we pass in a CLIENT_REDIRECT qualifier to mock that behavior.
500  history::RedirectList second_redirects;
501  second_redirects.push_back(first_redirects[1]);
502  second_redirects.push_back(GURL("http://last.page/"));
503  history->AddPage(second_redirects[1], MakeFakeHost(1), 1,
504                   second_redirects[0],
505                   static_cast<PageTransition::Type>(PageTransition::LINK |
506                       PageTransition::CLIENT_REDIRECT),
507                   second_redirects, history::SOURCE_BROWSED, true);
508
509  // The last page (source of the client redirect) should NOT have an
510  // additional visit added, because it was a client redirect (normally it
511  // would). We should only have 1 left over from the first sequence.
512  EXPECT_TRUE(QueryURL(history, second_redirects[0]));
513  EXPECT_EQ(1, query_url_row_.visit_count());
514
515  // The final page should be set as a client redirect from the previous visit.
516  EXPECT_TRUE(QueryURL(history, second_redirects[1]));
517  EXPECT_EQ(1, query_url_row_.visit_count());
518  ASSERT_EQ(1U, query_url_visits_.size());
519  EXPECT_EQ(PageTransition::CLIENT_REDIRECT |
520            PageTransition::CHAIN_END, query_url_visits_[0].transition);
521  EXPECT_EQ(second_visit, query_url_visits_[0].referring_visit);
522}
523
524TEST_F(HistoryTest, Typed) {
525  scoped_refptr<HistoryService> history(new HistoryService);
526  history_service_ = history;
527  ASSERT_TRUE(history->Init(history_dir_, NULL));
528
529  // Add the page once as typed.
530  const GURL test_url("http://www.google.com/");
531  history->AddPage(test_url, NULL, 0, GURL(), PageTransition::TYPED,
532                   history::RedirectList(),
533                   history::SOURCE_BROWSED, false);
534  EXPECT_TRUE(QueryURL(history, test_url));
535
536  // We should have the same typed & visit count.
537  EXPECT_EQ(1, query_url_row_.visit_count());
538  EXPECT_EQ(1, query_url_row_.typed_count());
539
540  // Add the page again not typed.
541  history->AddPage(test_url, NULL, 0, GURL(), PageTransition::LINK,
542                   history::RedirectList(),
543                   history::SOURCE_BROWSED, false);
544  EXPECT_TRUE(QueryURL(history, test_url));
545
546  // The second time should not have updated the typed count.
547  EXPECT_EQ(2, query_url_row_.visit_count());
548  EXPECT_EQ(1, query_url_row_.typed_count());
549
550  // Add the page again as a generated URL.
551  history->AddPage(test_url, NULL, 0, GURL(),
552                   PageTransition::GENERATED, history::RedirectList(),
553                   history::SOURCE_BROWSED, false);
554  EXPECT_TRUE(QueryURL(history, test_url));
555
556  // This should have worked like a link click.
557  EXPECT_EQ(3, query_url_row_.visit_count());
558  EXPECT_EQ(1, query_url_row_.typed_count());
559
560  // Add the page again as a reload.
561  history->AddPage(test_url, NULL, 0, GURL(),
562                   PageTransition::RELOAD, history::RedirectList(),
563                   history::SOURCE_BROWSED, false);
564  EXPECT_TRUE(QueryURL(history, test_url));
565
566  // This should not have incremented any visit counts.
567  EXPECT_EQ(3, query_url_row_.visit_count());
568  EXPECT_EQ(1, query_url_row_.typed_count());
569}
570
571TEST_F(HistoryTest, SetTitle) {
572  scoped_refptr<HistoryService> history(new HistoryService);
573  history_service_ = history;
574  ASSERT_TRUE(history->Init(history_dir_, NULL));
575
576  // Add a URL.
577  const GURL existing_url("http://www.google.com/");
578  history->AddPage(existing_url, history::SOURCE_BROWSED);
579
580  // Set some title.
581  const string16 existing_title = UTF8ToUTF16("Google");
582  history->SetPageTitle(existing_url, existing_title);
583
584  // Make sure the title got set.
585  EXPECT_TRUE(QueryURL(history, existing_url));
586  EXPECT_EQ(existing_title, query_url_row_.title());
587
588  // set a title on a nonexistent page
589  const GURL nonexistent_url("http://news.google.com/");
590  const string16 nonexistent_title = UTF8ToUTF16("Google News");
591  history->SetPageTitle(nonexistent_url, nonexistent_title);
592
593  // Make sure nothing got written.
594  EXPECT_FALSE(QueryURL(history, nonexistent_url));
595  EXPECT_EQ(string16(), query_url_row_.title());
596
597  // TODO(brettw) this should also test redirects, which get the title of the
598  // destination page.
599}
600
601TEST_F(HistoryTest, Segments) {
602  scoped_refptr<HistoryService> history(new HistoryService);
603  history_service_ = history;
604
605  ASSERT_TRUE(history->Init(history_dir_, NULL));
606
607  static const void* scope = static_cast<void*>(this);
608
609  // Add a URL.
610  const GURL existing_url("http://www.google.com/");
611  history->AddPage(existing_url, scope, 0, GURL(),
612                   PageTransition::TYPED, history::RedirectList(),
613                   history::SOURCE_BROWSED, false);
614
615  // Make sure a segment was created.
616  history->QuerySegmentUsageSince(
617      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
618      NewCallback(static_cast<HistoryTest*>(this),
619                  &HistoryTest::OnSegmentUsageAvailable));
620
621  // Wait for processing.
622  MessageLoop::current()->Run();
623
624  ASSERT_EQ(1U, page_usage_data_->size());
625  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
626  EXPECT_DOUBLE_EQ(3.0, page_usage_data_[0]->GetScore());
627
628  // Add a URL which doesn't create a segment.
629  const GURL link_url("http://yahoo.com/");
630  history->AddPage(link_url, scope, 0, GURL(),
631                   PageTransition::LINK, history::RedirectList(),
632                   history::SOURCE_BROWSED, false);
633
634  // Query again
635  history->QuerySegmentUsageSince(
636      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
637      NewCallback(static_cast<HistoryTest*>(this),
638                  &HistoryTest::OnSegmentUsageAvailable));
639
640  // Wait for processing.
641  MessageLoop::current()->Run();
642
643  // Make sure we still have one segment.
644  ASSERT_EQ(1U, page_usage_data_->size());
645  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
646
647  // Add a page linked from existing_url.
648  history->AddPage(GURL("http://www.google.com/foo"), scope, 3, existing_url,
649                   PageTransition::LINK, history::RedirectList(),
650                   history::SOURCE_BROWSED, false);
651
652  // Query again
653  history->QuerySegmentUsageSince(
654      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
655      NewCallback(static_cast<HistoryTest*>(this),
656                  &HistoryTest::OnSegmentUsageAvailable));
657
658  // Wait for processing.
659  MessageLoop::current()->Run();
660
661  // Make sure we still have one segment.
662  ASSERT_EQ(1U, page_usage_data_->size());
663  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
664
665  // However, the score should have increased.
666  EXPECT_GT(page_usage_data_[0]->GetScore(), 5.0);
667}
668
669TEST_F(HistoryTest, MostVisitedURLs) {
670  scoped_refptr<HistoryService> history(new HistoryService);
671  history_service_ = history;
672  ASSERT_TRUE(history->Init(history_dir_, NULL));
673
674  const GURL url0("http://www.google.com/url0/");
675  const GURL url1("http://www.google.com/url1/");
676  const GURL url2("http://www.google.com/url2/");
677  const GURL url3("http://www.google.com/url3/");
678  const GURL url4("http://www.google.com/url4/");
679
680  static const void* scope = static_cast<void*>(this);
681
682  // Add two pages.
683  history->AddPage(url0, scope, 0, GURL(),
684                   PageTransition::TYPED, history::RedirectList(),
685                   history::SOURCE_BROWSED, false);
686  history->AddPage(url1, scope, 0, GURL(),
687                   PageTransition::TYPED, history::RedirectList(),
688                   history::SOURCE_BROWSED, false);
689  history->QueryMostVisitedURLs(20, 90, &consumer_,
690                                NewCallback(static_cast<HistoryTest*>(this),
691                                    &HistoryTest::OnMostVisitedURLsAvailable));
692  MessageLoop::current()->Run();
693
694  EXPECT_EQ(2U, most_visited_urls_.size());
695  EXPECT_EQ(url0, most_visited_urls_[0].url);
696  EXPECT_EQ(url1, most_visited_urls_[1].url);
697
698  // Add another page.
699  history->AddPage(url2, scope, 0, GURL(),
700                   PageTransition::TYPED, history::RedirectList(),
701                   history::SOURCE_BROWSED, false);
702  history->QueryMostVisitedURLs(20, 90, &consumer_,
703                                NewCallback(static_cast<HistoryTest*>(this),
704                                    &HistoryTest::OnMostVisitedURLsAvailable));
705  MessageLoop::current()->Run();
706
707  EXPECT_EQ(3U, most_visited_urls_.size());
708  EXPECT_EQ(url0, most_visited_urls_[0].url);
709  EXPECT_EQ(url1, most_visited_urls_[1].url);
710  EXPECT_EQ(url2, most_visited_urls_[2].url);
711
712  // Revisit url2, making it the top URL.
713  history->AddPage(url2, scope, 0, GURL(),
714                   PageTransition::TYPED, history::RedirectList(),
715                   history::SOURCE_BROWSED, false);
716  history->QueryMostVisitedURLs(20, 90, &consumer_,
717                                NewCallback(static_cast<HistoryTest*>(this),
718                                    &HistoryTest::OnMostVisitedURLsAvailable));
719  MessageLoop::current()->Run();
720
721  EXPECT_EQ(3U, most_visited_urls_.size());
722  EXPECT_EQ(url2, most_visited_urls_[0].url);
723  EXPECT_EQ(url0, most_visited_urls_[1].url);
724  EXPECT_EQ(url1, most_visited_urls_[2].url);
725
726  // Revisit url1, making it the top URL.
727  history->AddPage(url1, scope, 0, GURL(),
728                   PageTransition::TYPED, history::RedirectList(),
729                   history::SOURCE_BROWSED, false);
730  history->QueryMostVisitedURLs(20, 90, &consumer_,
731                                NewCallback(static_cast<HistoryTest*>(this),
732                                    &HistoryTest::OnMostVisitedURLsAvailable));
733  MessageLoop::current()->Run();
734
735  EXPECT_EQ(3U, most_visited_urls_.size());
736  EXPECT_EQ(url1, most_visited_urls_[0].url);
737  EXPECT_EQ(url2, most_visited_urls_[1].url);
738  EXPECT_EQ(url0, most_visited_urls_[2].url);
739
740  // Redirects
741  history::RedirectList redirects;
742  redirects.push_back(url3);
743  redirects.push_back(url4);
744
745  // Visit url4 using redirects.
746  history->AddPage(url4, scope, 0, GURL(),
747                   PageTransition::TYPED, redirects,
748                   history::SOURCE_BROWSED, false);
749  history->QueryMostVisitedURLs(20, 90, &consumer_,
750                                NewCallback(static_cast<HistoryTest*>(this),
751                                    &HistoryTest::OnMostVisitedURLsAvailable));
752  MessageLoop::current()->Run();
753
754  EXPECT_EQ(4U, most_visited_urls_.size());
755  EXPECT_EQ(url1, most_visited_urls_[0].url);
756  EXPECT_EQ(url2, most_visited_urls_[1].url);
757  EXPECT_EQ(url0, most_visited_urls_[2].url);
758  EXPECT_EQ(url3, most_visited_urls_[3].url);
759  EXPECT_EQ(2U, most_visited_urls_[3].redirects.size());
760}
761
762// The version of the history database should be current in the "typical
763// history" example file or it will be imported on startup, throwing off timing
764// measurements.
765//
766// See test/data/profiles/typical_history/README.txt for instructions on
767// how to up the version.
768TEST(HistoryProfileTest, TypicalProfileVersion) {
769  FilePath file;
770  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &file));
771  file = file.AppendASCII("profiles");
772  file = file.AppendASCII("typical_history");
773  file = file.AppendASCII("Default");
774  file = file.AppendASCII("History");
775
776  int cur_version = HistoryDatabase::GetCurrentVersion();
777
778  sql::Connection db;
779  ASSERT_TRUE(db.Open(file));
780
781  {
782    sql::Statement s(db.GetUniqueStatement(
783        "SELECT value FROM meta WHERE key = 'version'"));
784    EXPECT_TRUE(s.Step());
785    int file_version = s.ColumnInt(0);
786    EXPECT_EQ(cur_version, file_version);
787  }
788}
789
790namespace {
791
792// A HistoryDBTask implementation. Each time RunOnDBThread is invoked
793// invoke_count is increment. When invoked kWantInvokeCount times, true is
794// returned from RunOnDBThread which should stop RunOnDBThread from being
795// invoked again. When DoneRunOnMainThread is invoked, done_invoked is set to
796// true.
797class HistoryDBTaskImpl : public HistoryDBTask {
798 public:
799  static const int kWantInvokeCount;
800
801  HistoryDBTaskImpl() : invoke_count(0), done_invoked(false) {}
802
803  virtual bool RunOnDBThread(HistoryBackend* backend, HistoryDatabase* db) {
804    return (++invoke_count == kWantInvokeCount);
805  }
806
807  virtual void DoneRunOnMainThread() {
808    done_invoked = true;
809    MessageLoop::current()->Quit();
810  }
811
812  int invoke_count;
813  bool done_invoked;
814
815 private:
816  virtual ~HistoryDBTaskImpl() {}
817
818  DISALLOW_COPY_AND_ASSIGN(HistoryDBTaskImpl);
819};
820
821// static
822const int HistoryDBTaskImpl::kWantInvokeCount = 2;
823
824}  // namespace
825
826TEST_F(HistoryTest, HistoryDBTask) {
827  CancelableRequestConsumerT<int, 0> request_consumer;
828  HistoryService* history = new HistoryService();
829  ASSERT_TRUE(history->Init(history_dir_, NULL));
830  scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
831  history_service_ = history;
832  history->ScheduleDBTask(task.get(), &request_consumer);
833  // Run the message loop. When HistoryDBTaskImpl::DoneRunOnMainThread runs,
834  // it will stop the message loop. If the test hangs here, it means
835  // DoneRunOnMainThread isn't being invoked correctly.
836  MessageLoop::current()->Run();
837  CleanupHistoryService();
838  // WARNING: history has now been deleted.
839  history = NULL;
840  ASSERT_EQ(HistoryDBTaskImpl::kWantInvokeCount, task->invoke_count);
841  ASSERT_TRUE(task->done_invoked);
842}
843
844TEST_F(HistoryTest, HistoryDBTaskCanceled) {
845  CancelableRequestConsumerT<int, 0> request_consumer;
846  HistoryService* history = new HistoryService();
847  ASSERT_TRUE(history->Init(history_dir_, NULL));
848  scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
849  history_service_ = history;
850  history->ScheduleDBTask(task.get(), &request_consumer);
851  request_consumer.CancelAllRequests();
852  CleanupHistoryService();
853  // WARNING: history has now been deleted.
854  history = NULL;
855  ASSERT_FALSE(task->done_invoked);
856}
857
858}  // namespace history
859