history_unittest.cc revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
1// Copyright (c) 2012 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 "base/basictypes.h"
26#include "base/bind.h"
27#include "base/bind_helpers.h"
28#include "base/callback.h"
29#include "base/command_line.h"
30#include "base/compiler_specific.h"
31#include "base/file_util.h"
32#include "base/files/file_path.h"
33#include "base/files/scoped_temp_dir.h"
34#include "base/logging.h"
35#include "base/memory/scoped_ptr.h"
36#include "base/memory/scoped_vector.h"
37#include "base/message_loop.h"
38#include "base/path_service.h"
39#include "base/string_util.h"
40#include "base/stringprintf.h"
41#include "base/threading/platform_thread.h"
42#include "base/time.h"
43#include "base/utf_string_conversions.h"
44#include "chrome/browser/history/download_row.h"
45#include "chrome/browser/history/history_backend.h"
46#include "chrome/browser/history/history_database.h"
47#include "chrome/browser/history/history_db_task.h"
48#include "chrome/browser/history/history_notifications.h"
49#include "chrome/browser/history/history_service.h"
50#include "chrome/browser/history/history_unittest_base.h"
51#include "chrome/browser/history/in_memory_database.h"
52#include "chrome/browser/history/in_memory_history_backend.h"
53#include "chrome/browser/history/page_usage_data.h"
54#include "chrome/common/chrome_constants.h"
55#include "chrome/common/chrome_paths.h"
56#include "chrome/common/thumbnail_score.h"
57#include "chrome/tools/profiles/thumbnail-inl.h"
58#include "content/public/browser/download_item.h"
59#include "content/public/browser/notification_details.h"
60#include "content/public/browser/notification_source.h"
61#include "sql/connection.h"
62#include "sql/statement.h"
63#include "sync/api/sync_change.h"
64#include "sync/api/sync_change_processor.h"
65#include "sync/api/sync_error.h"
66#include "sync/api/sync_error_factory.h"
67#include "sync/api/sync_merge_result.h"
68#include "sync/protocol/history_delete_directive_specifics.pb.h"
69#include "sync/protocol/sync.pb.h"
70#include "testing/gtest/include/gtest/gtest.h"
71#include "third_party/skia/include/core/SkBitmap.h"
72#include "ui/gfx/codec/jpeg_codec.h"
73
74using base::Time;
75using base::TimeDelta;
76using content::DownloadItem;
77
78namespace history {
79class HistoryBackendDBTest;
80
81// Delegate class for when we create a backend without a HistoryService.
82//
83// This must be outside the anonymous namespace for the friend statement in
84// HistoryBackendDBTest to work.
85class BackendDelegate : public HistoryBackend::Delegate {
86 public:
87  explicit BackendDelegate(HistoryBackendDBTest* history_test)
88      : history_test_(history_test) {
89  }
90
91  virtual void NotifyProfileError(int backend_id,
92                                  sql::InitStatus init_status) OVERRIDE {}
93  virtual void SetInMemoryBackend(int backend_id,
94                                  InMemoryHistoryBackend* backend) OVERRIDE;
95  virtual void BroadcastNotifications(int type,
96                                      HistoryDetails* details) OVERRIDE;
97  virtual void DBLoaded(int backend_id) OVERRIDE {}
98  virtual void StartTopSitesMigration(int backend_id) OVERRIDE {}
99  virtual void NotifyVisitDBObserversOnAddVisit(
100      const BriefVisitInfo& info) OVERRIDE {}
101 private:
102  HistoryBackendDBTest* history_test_;
103};
104
105// This must be outside the anonymous namespace for the friend statement in
106// HistoryBackend to work.
107class HistoryBackendDBTest : public HistoryUnitTestBase {
108 public:
109  HistoryBackendDBTest() : db_(NULL) {
110  }
111
112  virtual ~HistoryBackendDBTest() {
113  }
114
115 protected:
116  friend class BackendDelegate;
117
118  // Creates the HistoryBackend and HistoryDatabase on the current thread,
119  // assigning the values to backend_ and db_.
120  void CreateBackendAndDatabase() {
121    backend_ = new HistoryBackend(history_dir_, 0, new BackendDelegate(this),
122                                  NULL);
123    backend_->Init(std::string(), false);
124    db_ = backend_->db_.get();
125    DCHECK(in_mem_backend_) << "Mem backend should have been set by "
126        "HistoryBackend::Init";
127  }
128
129  void CreateDBVersion(int version) {
130    base::FilePath data_path;
131    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
132    data_path = data_path.AppendASCII("History");
133    data_path =
134          data_path.AppendASCII(base::StringPrintf("history.%d.sql", version));
135    ASSERT_NO_FATAL_FAILURE(
136        ExecuteSQLScript(data_path, history_dir_.Append(
137            chrome::kHistoryFilename)));
138  }
139
140  // testing::Test
141  virtual void SetUp() {
142    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
143    history_dir_ = temp_dir_.path().AppendASCII("HistoryBackendDBTest");
144    ASSERT_TRUE(file_util::CreateDirectory(history_dir_));
145  }
146
147  void DeleteBackend() {
148    if (backend_) {
149      backend_->Closing();
150      backend_ = NULL;
151    }
152  }
153
154  virtual void TearDown() {
155    DeleteBackend();
156
157    // Make sure we don't have any event pending that could disrupt the next
158    // test.
159    MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
160    MessageLoop::current()->Run();
161  }
162
163  int64 AddDownload(DownloadItem::DownloadState state, const Time& time) {
164    std::vector<GURL> url_chain;
165    url_chain.push_back(GURL("foo-url"));
166
167    DownloadRow download(base::FilePath(FILE_PATH_LITERAL("foo-path")),
168                         base::FilePath(FILE_PATH_LITERAL("foo-path")),
169                         url_chain,
170                         GURL(std::string()),
171                         time,
172                         time,
173                         0,
174                         512,
175                         state,
176                         content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
177                         content::DOWNLOAD_INTERRUPT_REASON_NONE,
178                         0,
179                         0);
180    return db_->CreateDownload(download);
181  }
182
183  base::ScopedTempDir temp_dir_;
184
185  MessageLoopForUI message_loop_;
186
187  // names of the database files
188  base::FilePath history_dir_;
189
190  // Created via CreateBackendAndDatabase.
191  scoped_refptr<HistoryBackend> backend_;
192  scoped_ptr<InMemoryHistoryBackend> in_mem_backend_;
193  HistoryDatabase* db_;  // Cached reference to the backend's database.
194};
195
196void BackendDelegate::SetInMemoryBackend(int backend_id,
197                                         InMemoryHistoryBackend* backend) {
198  // Save the in-memory backend to the history test object, this happens
199  // synchronously, so we don't have to do anything fancy.
200  history_test_->in_mem_backend_.reset(backend);
201}
202
203void BackendDelegate::BroadcastNotifications(int type,
204                                             HistoryDetails* details) {
205  // Currently, just send the notifications directly to the in-memory database.
206  // We may want do do something more fancy in the future.
207  content::Details<HistoryDetails> det(details);
208  history_test_->in_mem_backend_->Observe(type,
209      content::Source<HistoryBackendDBTest>(NULL), det);
210
211  // The backend passes ownership of the details pointer to us.
212  delete details;
213}
214
215TEST_F(HistoryBackendDBTest, ClearBrowsingData_Downloads) {
216  CreateBackendAndDatabase();
217
218  // Initially there should be nothing in the downloads database.
219  std::vector<DownloadRow> downloads;
220  db_->QueryDownloads(&downloads);
221  EXPECT_EQ(0U, downloads.size());
222
223  // Add a download, test that it was added, remove it, test that it was
224  // removed.
225  DownloadID handle;
226  EXPECT_NE(0, handle = AddDownload(DownloadItem::COMPLETE, Time()));
227  db_->QueryDownloads(&downloads);
228  EXPECT_EQ(1U, downloads.size());
229  db_->RemoveDownload(handle);
230  db_->QueryDownloads(&downloads);
231  EXPECT_EQ(0U, downloads.size());
232}
233
234TEST_F(HistoryBackendDBTest, MigrateDownloadsState) {
235  // Create the db we want.
236  ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
237  {
238    // Open the db for manual manipulation.
239    sql::Connection db;
240    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
241
242    // Manually insert corrupted rows; there's infrastructure in place now to
243    // make this impossible, at least according to the test above.
244    for (int state = 0; state < 5; ++state) {
245      sql::Statement s(db.GetUniqueStatement(
246            "INSERT INTO downloads (id, full_path, url, start_time, "
247            "received_bytes, total_bytes, state, end_time, opened) VALUES "
248            "(?, ?, ?, ?, ?, ?, ?, ?, ?)"));
249      s.BindInt64(0, 1 + state);
250      s.BindString(1, "path");
251      s.BindString(2, "url");
252      s.BindInt64(3, base::Time::Now().ToTimeT());
253      s.BindInt64(4, 100);
254      s.BindInt64(5, 100);
255      s.BindInt(6, state);
256      s.BindInt64(7, base::Time::Now().ToTimeT());
257      s.BindInt(8, state % 2);
258      ASSERT_TRUE(s.Run());
259    }
260  }
261
262  // Re-open the db using the HistoryDatabase, which should migrate from version
263  // 22 to the current version, fixing just the row whose state was 3.
264  // Then close the db so that we can re-open it directly.
265  CreateBackendAndDatabase();
266  DeleteBackend();
267  {
268    // Re-open the db for manual manipulation.
269    sql::Connection db;
270    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
271    {
272      // The version should have been updated.
273      int cur_version = HistoryDatabase::GetCurrentVersion();
274      ASSERT_LT(22, cur_version);
275      sql::Statement s(db.GetUniqueStatement(
276          "SELECT value FROM meta WHERE key = 'version'"));
277      EXPECT_TRUE(s.Step());
278      EXPECT_EQ(cur_version, s.ColumnInt(0));
279    }
280    {
281      sql::Statement statement(db.GetUniqueStatement(
282          "SELECT id, state, opened "
283          "FROM downloads "
284          "ORDER BY id"));
285      int counter = 0;
286      while (statement.Step()) {
287        EXPECT_EQ(1 + counter, statement.ColumnInt64(0));
288        // The only thing that migration should have changed was state from 3 to
289        // 4.
290        EXPECT_EQ(((counter == 3) ? 4 : counter), statement.ColumnInt(1));
291        EXPECT_EQ(counter % 2, statement.ColumnInt(2));
292        ++counter;
293      }
294      EXPECT_EQ(5, counter);
295    }
296  }
297}
298
299TEST_F(HistoryBackendDBTest, MigrateDownloadsReasonPathsAndDangerType) {
300  Time now(base::Time::Now());
301
302  // Create the db we want.  The schema didn't change from 22->23, so just
303  // re-use the v22 file.
304  ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
305  {
306    // Re-open the db for manual manipulation.
307    sql::Connection db;
308    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
309
310    // Manually insert some rows.
311    sql::Statement s(db.GetUniqueStatement(
312        "INSERT INTO downloads (id, full_path, url, start_time, "
313        "received_bytes, total_bytes, state, end_time, opened) VALUES "
314        "(?, ?, ?, ?, ?, ?, ?, ?, ?)"));
315
316    int64 db_handle = 0;
317    // Null path.
318    s.BindInt64(0, ++db_handle);
319    s.BindString(1, std::string());
320    s.BindString(2, "http://whatever.com/index.html");
321    s.BindInt64(3, now.ToTimeT());
322    s.BindInt64(4, 100);
323    s.BindInt64(5, 100);
324    s.BindInt(6, 1);
325    s.BindInt64(7, now.ToTimeT());
326    s.BindInt(8, 1);
327    ASSERT_TRUE(s.Run());
328    s.Reset(true);
329
330    // Non-null path.
331    s.BindInt64(0, ++db_handle);
332    s.BindString(1, "/path/to/some/file");
333    s.BindString(2, "http://whatever.com/index1.html");
334    s.BindInt64(3, now.ToTimeT());
335    s.BindInt64(4, 100);
336    s.BindInt64(5, 100);
337    s.BindInt(6, 1);
338    s.BindInt64(7, now.ToTimeT());
339    s.BindInt(8, 1);
340    ASSERT_TRUE(s.Run());
341  }
342
343  // Re-open the db using the HistoryDatabase, which should migrate from version
344  // 23 to 24, creating the new tables and creating the new path, reason,
345  // and danger columns.
346  CreateBackendAndDatabase();
347  DeleteBackend();
348  {
349    // Re-open the db for manual manipulation.
350    sql::Connection db;
351    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
352    {
353      // The version should have been updated.
354      int cur_version = HistoryDatabase::GetCurrentVersion();
355      ASSERT_LT(23, cur_version);
356      sql::Statement s(db.GetUniqueStatement(
357          "SELECT value FROM meta WHERE key = 'version'"));
358      EXPECT_TRUE(s.Step());
359      EXPECT_EQ(cur_version, s.ColumnInt(0));
360    }
361    {
362      base::Time nowish(base::Time::FromTimeT(now.ToTimeT()));
363
364      // Confirm downloads table is valid.
365      sql::Statement statement(db.GetUniqueStatement(
366          "SELECT id, interrupt_reason, current_path, target_path, "
367          "       danger_type, start_time, end_time "
368          "FROM downloads ORDER BY id"));
369      EXPECT_TRUE(statement.Step());
370      EXPECT_EQ(1, statement.ColumnInt64(0));
371      EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE,
372                statement.ColumnInt(1));
373      EXPECT_EQ("", statement.ColumnString(2));
374      EXPECT_EQ("", statement.ColumnString(3));
375      // Implicit dependence on value of kDangerTypeNotDangerous from
376      // download_database.cc.
377      EXPECT_EQ(0, statement.ColumnInt(4));
378      EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5));
379      EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6));
380
381      EXPECT_TRUE(statement.Step());
382      EXPECT_EQ(2, statement.ColumnInt64(0));
383      EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE,
384                statement.ColumnInt(1));
385      EXPECT_EQ("/path/to/some/file", statement.ColumnString(2));
386      EXPECT_EQ("/path/to/some/file", statement.ColumnString(3));
387      EXPECT_EQ(0, statement.ColumnInt(4));
388      EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5));
389      EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6));
390
391      EXPECT_FALSE(statement.Step());
392    }
393    {
394      // Confirm downloads_url_chains table is valid.
395      sql::Statement statement(db.GetUniqueStatement(
396          "SELECT id, chain_index, url FROM downloads_url_chains "
397          " ORDER BY id, chain_index"));
398      EXPECT_TRUE(statement.Step());
399      EXPECT_EQ(1, statement.ColumnInt64(0));
400      EXPECT_EQ(0, statement.ColumnInt(1));
401      EXPECT_EQ("http://whatever.com/index.html", statement.ColumnString(2));
402
403      EXPECT_TRUE(statement.Step());
404      EXPECT_EQ(2, statement.ColumnInt64(0));
405      EXPECT_EQ(0, statement.ColumnInt(1));
406      EXPECT_EQ("http://whatever.com/index1.html", statement.ColumnString(2));
407
408      EXPECT_FALSE(statement.Step());
409    }
410  }
411}
412
413TEST_F(HistoryBackendDBTest, ConfirmDownloadRowCreateAndDelete) {
414  // Create the DB.
415  CreateBackendAndDatabase();
416
417  base::Time now(base::Time::Now());
418
419  // Add some downloads.
420  AddDownload(DownloadItem::COMPLETE, now);
421  int64 did2 = AddDownload(DownloadItem::COMPLETE, now +
422                           base::TimeDelta::FromDays(2));
423  int64 did3 = AddDownload(DownloadItem::COMPLETE, now -
424                           base::TimeDelta::FromDays(2));
425
426  // Confirm that resulted in the correct number of rows in the DB.
427  DeleteBackend();
428  {
429    sql::Connection db;
430    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
431    sql::Statement statement(db.GetUniqueStatement(
432        "Select Count(*) from downloads"));
433    EXPECT_TRUE(statement.Step());
434    EXPECT_EQ(3, statement.ColumnInt(0));
435
436    sql::Statement statement1(db.GetUniqueStatement(
437        "Select Count(*) from downloads_url_chains"));
438    EXPECT_TRUE(statement1.Step());
439    EXPECT_EQ(3, statement1.ColumnInt(0));
440  }
441
442  // Delete some rows and make sure the results are still correct.
443  CreateBackendAndDatabase();
444  db_->RemoveDownload(did2);
445  db_->RemoveDownload(did3);
446  DeleteBackend();
447  {
448    sql::Connection db;
449    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
450    sql::Statement statement(db.GetUniqueStatement(
451        "Select Count(*) from downloads"));
452    EXPECT_TRUE(statement.Step());
453    EXPECT_EQ(1, statement.ColumnInt(0));
454
455    sql::Statement statement1(db.GetUniqueStatement(
456        "Select Count(*) from downloads_url_chains"));
457    EXPECT_TRUE(statement1.Step());
458    EXPECT_EQ(1, statement1.ColumnInt(0));
459  }
460}
461
462TEST_F(HistoryBackendDBTest, DownloadNukeRecordsMissingURLs) {
463  CreateBackendAndDatabase();
464  base::Time now(base::Time::Now());
465  std::vector<GURL> url_chain;
466  DownloadRow download(base::FilePath(FILE_PATH_LITERAL("foo-path")),
467                       base::FilePath(FILE_PATH_LITERAL("foo-path")),
468                       url_chain,
469                       GURL(std::string()),
470                       now,
471                       now,
472                       0,
473                       512,
474                       DownloadItem::COMPLETE,
475                       content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
476                       content::DOWNLOAD_INTERRUPT_REASON_NONE,
477                       0,
478                       0);
479
480  // Creating records without any urls should fail.
481  EXPECT_EQ(DownloadDatabase::kUninitializedHandle,
482            db_->CreateDownload(download));
483
484  download.url_chain.push_back(GURL("foo-url"));
485  EXPECT_EQ(1, db_->CreateDownload(download));
486
487  // Pretend that the URLs were dropped.
488  DeleteBackend();
489  {
490    sql::Connection db;
491    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
492    sql::Statement statement(db.GetUniqueStatement(
493        "DELETE FROM downloads_url_chains WHERE id=1"));
494    ASSERT_TRUE(statement.Run());
495  }
496  CreateBackendAndDatabase();
497  std::vector<DownloadRow> downloads;
498  db_->QueryDownloads(&downloads);
499  EXPECT_EQ(0U, downloads.size());
500
501  // QueryDownloads should have nuked the corrupt record.
502  DeleteBackend();
503  {
504    sql::Connection db;
505    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
506    {
507      sql::Statement statement(db.GetUniqueStatement(
508            "SELECT count(*) from downloads"));
509      ASSERT_TRUE(statement.Step());
510      EXPECT_EQ(0, statement.ColumnInt(0));
511    }
512  }
513}
514
515TEST_F(HistoryBackendDBTest, ConfirmDownloadInProgressCleanup) {
516  // Create the DB.
517  CreateBackendAndDatabase();
518
519  base::Time now(base::Time::Now());
520
521  // Put an IN_PROGRESS download in the DB.
522  AddDownload(DownloadItem::IN_PROGRESS, now);
523
524  // Confirm that they made it into the DB unchanged.
525  DeleteBackend();
526  {
527    sql::Connection db;
528    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
529    sql::Statement statement(db.GetUniqueStatement(
530        "Select Count(*) from downloads"));
531    EXPECT_TRUE(statement.Step());
532    EXPECT_EQ(1, statement.ColumnInt(0));
533
534    sql::Statement statement1(db.GetUniqueStatement(
535        "Select state, interrupt_reason from downloads"));
536    EXPECT_TRUE(statement1.Step());
537    EXPECT_EQ(DownloadDatabase::kStateInProgress, statement1.ColumnInt(0));
538    EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, statement1.ColumnInt(1));
539    EXPECT_FALSE(statement1.Step());
540  }
541
542  // Read in the DB through query downloads, then test that the
543  // right transformation was returned.
544  CreateBackendAndDatabase();
545  std::vector<DownloadRow> results;
546  db_->QueryDownloads(&results);
547  ASSERT_EQ(1u, results.size());
548  EXPECT_EQ(content::DownloadItem::INTERRUPTED, results[0].state);
549  EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_CRASH,
550            results[0].interrupt_reason);
551
552  // Allow the update to propagate, shut down the DB, and confirm that
553  // the query updated the on disk database as well.
554  MessageLoop::current()->RunUntilIdle();
555  DeleteBackend();
556  {
557    sql::Connection db;
558    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
559    sql::Statement statement(db.GetUniqueStatement(
560        "Select Count(*) from downloads"));
561    EXPECT_TRUE(statement.Step());
562    EXPECT_EQ(1, statement.ColumnInt(0));
563
564    sql::Statement statement1(db.GetUniqueStatement(
565        "Select state, interrupt_reason from downloads"));
566    EXPECT_TRUE(statement1.Step());
567    EXPECT_EQ(DownloadDatabase::kStateInterrupted, statement1.ColumnInt(0));
568    EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_CRASH,
569              statement1.ColumnInt(1));
570    EXPECT_FALSE(statement1.Step());
571  }
572}
573
574struct InterruptReasonAssociation {
575  std::string name;
576  int value;
577};
578
579// Test is dependent on interrupt reasons being listed in header file
580// in order.
581const InterruptReasonAssociation current_reasons[] = {
582#define INTERRUPT_REASON(a, b) { #a, b },
583#include "content/public/browser/download_interrupt_reason_values.h"
584#undef INTERRUPT_REASON
585};
586
587// This represents a list of all reasons we've previously used;
588// Do Not Remove Any Entries From This List.
589const InterruptReasonAssociation historical_reasons[] = {
590  {"FILE_FAILED",  1},
591  {"FILE_ACCESS_DENIED",  2},
592  {"FILE_NO_SPACE",  3},
593  {"FILE_NAME_TOO_LONG",  5},
594  {"FILE_TOO_LARGE",  6},
595  {"FILE_VIRUS_INFECTED",  7},
596  {"FILE_TRANSIENT_ERROR",  10},
597  {"FILE_BLOCKED",  11},
598  {"FILE_SECURITY_CHECK_FAILED",  12},
599  {"FILE_TOO_SHORT", 13},
600  {"NETWORK_FAILED",  20},
601  {"NETWORK_TIMEOUT",  21},
602  {"NETWORK_DISCONNECTED",  22},
603  {"NETWORK_SERVER_DOWN",  23},
604  {"SERVER_FAILED",  30},
605  {"SERVER_NO_RANGE",  31},
606  {"SERVER_PRECONDITION",  32},
607  {"SERVER_BAD_CONTENT",  33},
608  {"USER_CANCELED",  40},
609  {"USER_SHUTDOWN",  41},
610  {"CRASH",  50},
611};
612
613// Make sure no one has changed a DownloadInterruptReason we've previously
614// persisted.
615TEST_F(HistoryBackendDBTest,
616       ConfirmDownloadInterruptReasonBackwardsCompatible) {
617  // Are there any cases in which a historical number has been repurposed
618  // for an error other than it's original?
619  for (size_t i = 0; i < arraysize(current_reasons); i++) {
620    const InterruptReasonAssociation& cur_reason(current_reasons[i]);
621    bool found = false;
622
623    for (size_t j = 0; j < arraysize(historical_reasons); ++j) {
624      const InterruptReasonAssociation& hist_reason(historical_reasons[j]);
625
626      if (hist_reason.value == cur_reason.value) {
627        EXPECT_EQ(cur_reason.name, hist_reason.name)
628            << "Same integer value used for old error \""
629            << hist_reason.name
630            << "\" as for new error \""
631            << cur_reason.name
632            << "\"." << std::endl
633            << "**This will cause database conflicts with persisted values**"
634            << std::endl
635            << "Please assign a new, non-conflicting value for the new error.";
636      }
637
638      if (hist_reason.name == cur_reason.name) {
639        EXPECT_EQ(cur_reason.value, hist_reason.value)
640            << "Same name (\"" << hist_reason.name
641            << "\") maps to a different value historically ("
642            << hist_reason.value << ") and currently ("
643            << cur_reason.value << ")" << std::endl
644            << "This may cause database conflicts with persisted values"
645            << std::endl
646            << "If this error is the same as the old one, you should"
647            << std::endl
648            << "use the old value, and if it is different, you should"
649            << std::endl
650            << "use a new name.";
651
652        found = true;
653      }
654    }
655
656    EXPECT_TRUE(found)
657        << "Error \"" << cur_reason.name << "\" not found in historical list."
658        << std::endl
659        << "Please add it.";
660  }
661}
662
663// The tracker uses RenderProcessHost pointers for scoping but never
664// dereferences them. We use ints because it's easier. This function converts
665// between the two.
666static void* MakeFakeHost(int id) {
667  void* host = 0;
668  memcpy(&host, &id, sizeof(id));
669  return host;
670}
671
672class HistoryTest : public testing::Test {
673 public:
674  HistoryTest()
675      : got_thumbnail_callback_(false),
676        redirect_query_success_(false),
677        query_url_success_(false) {
678  }
679
680  virtual ~HistoryTest() {
681  }
682
683  void OnSegmentUsageAvailable(CancelableRequestProvider::Handle handle,
684                               std::vector<PageUsageData*>* data) {
685    page_usage_data_.swap(*data);
686    MessageLoop::current()->Quit();
687  }
688
689  void OnDeleteURLsDone(CancelableRequestProvider::Handle handle) {
690    MessageLoop::current()->Quit();
691  }
692
693  void OnMostVisitedURLsAvailable(CancelableRequestProvider::Handle handle,
694                                  MostVisitedURLList url_list) {
695    most_visited_urls_.swap(url_list);
696    MessageLoop::current()->Quit();
697  }
698
699 protected:
700  friend class BackendDelegate;
701
702  // testing::Test
703  virtual void SetUp() {
704    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
705    history_dir_ = temp_dir_.path().AppendASCII("HistoryTest");
706    ASSERT_TRUE(file_util::CreateDirectory(history_dir_));
707    history_service_.reset(new HistoryService);
708    if (!history_service_->Init(history_dir_, NULL)) {
709      history_service_.reset();
710      ADD_FAILURE();
711    }
712  }
713
714  virtual void TearDown() {
715    if (history_service_)
716      CleanupHistoryService();
717
718    // Make sure we don't have any event pending that could disrupt the next
719    // test.
720    MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
721    MessageLoop::current()->Run();
722  }
723
724  void CleanupHistoryService() {
725    DCHECK(history_service_);
726
727    history_service_->NotifyRenderProcessHostDestruction(0);
728    history_service_->SetOnBackendDestroyTask(MessageLoop::QuitClosure());
729    history_service_->Cleanup();
730    history_service_.reset();
731
732    // Wait for the backend class to terminate before deleting the files and
733    // moving to the next test. Note: if this never terminates, somebody is
734    // probably leaking a reference to the history backend, so it never calls
735    // our destroy task.
736    MessageLoop::current()->Run();
737  }
738
739  // Fills the query_url_row_ and query_url_visits_ structures with the
740  // information about the given URL and returns true. If the URL was not
741  // found, this will return false and those structures will not be changed.
742  bool QueryURL(HistoryService* history, const GURL& url) {
743    history_service_->QueryURL(url, true, &consumer_,
744                               base::Bind(&HistoryTest::SaveURLAndQuit,
745                                          base::Unretained(this)));
746    MessageLoop::current()->Run();  // Will be exited in SaveURLAndQuit.
747    return query_url_success_;
748  }
749
750  // Callback for HistoryService::QueryURL.
751  void SaveURLAndQuit(HistoryService::Handle handle,
752                      bool success,
753                      const URLRow* url_row,
754                      VisitVector* visit_vector) {
755    query_url_success_ = success;
756    if (query_url_success_) {
757      query_url_row_ = *url_row;
758      query_url_visits_.swap(*visit_vector);
759    } else {
760      query_url_row_ = URLRow();
761      query_url_visits_.clear();
762    }
763    MessageLoop::current()->Quit();
764  }
765
766  // Fills in saved_redirects_ with the redirect information for the given URL,
767  // returning true on success. False means the URL was not found.
768  bool QueryRedirectsFrom(HistoryService* history, const GURL& url) {
769    history_service_->QueryRedirectsFrom(
770        url, &consumer_,
771        base::Bind(&HistoryTest::OnRedirectQueryComplete,
772                   base::Unretained(this)));
773    MessageLoop::current()->Run();  // Will be exited in *QueryComplete.
774    return redirect_query_success_;
775  }
776
777  // Callback for QueryRedirects.
778  void OnRedirectQueryComplete(HistoryService::Handle handle,
779                               GURL url,
780                               bool success,
781                               history::RedirectList* redirects) {
782    redirect_query_success_ = success;
783    if (redirect_query_success_)
784      saved_redirects_.swap(*redirects);
785    else
786      saved_redirects_.clear();
787    MessageLoop::current()->Quit();
788  }
789
790  base::ScopedTempDir temp_dir_;
791
792  MessageLoopForUI message_loop_;
793
794  // PageUsageData vector to test segments.
795  ScopedVector<PageUsageData> page_usage_data_;
796
797  MostVisitedURLList most_visited_urls_;
798
799  // When non-NULL, this will be deleted on tear down and we will block until
800  // the backend thread has completed. This allows tests for the history
801  // service to use this feature, but other tests to ignore this.
802  scoped_ptr<HistoryService> history_service_;
803
804  // names of the database files
805  base::FilePath history_dir_;
806
807  // Set by the thumbnail callback when we get data, you should be sure to
808  // clear this before issuing a thumbnail request.
809  bool got_thumbnail_callback_;
810  std::vector<unsigned char> thumbnail_data_;
811
812  // Set by the redirect callback when we get data. You should be sure to
813  // clear this before issuing a redirect request.
814  history::RedirectList saved_redirects_;
815  bool redirect_query_success_;
816
817  // For history requests.
818  CancelableRequestConsumer consumer_;
819
820  // For saving URL info after a call to QueryURL
821  bool query_url_success_;
822  URLRow query_url_row_;
823  VisitVector query_url_visits_;
824};
825
826TEST_F(HistoryTest, AddPage) {
827  ASSERT_TRUE(history_service_.get());
828  // Add the page once from a child frame.
829  const GURL test_url("http://www.google.com/");
830  history_service_->AddPage(test_url, base::Time::Now(), NULL, 0, GURL(),
831                            history::RedirectList(),
832                            content::PAGE_TRANSITION_MANUAL_SUBFRAME,
833                            history::SOURCE_BROWSED, false);
834  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
835  EXPECT_EQ(1, query_url_row_.visit_count());
836  EXPECT_EQ(0, query_url_row_.typed_count());
837  EXPECT_TRUE(query_url_row_.hidden());  // Hidden because of child frame.
838
839  // Add the page once from the main frame (should unhide it).
840  history_service_->AddPage(test_url, base::Time::Now(), NULL, 0, GURL(),
841                   history::RedirectList(), content::PAGE_TRANSITION_LINK,
842                   history::SOURCE_BROWSED, false);
843  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
844  EXPECT_EQ(2, query_url_row_.visit_count());  // Added twice.
845  EXPECT_EQ(0, query_url_row_.typed_count());  // Never typed.
846  EXPECT_FALSE(query_url_row_.hidden());  // Because loaded in main frame.
847}
848
849TEST_F(HistoryTest, AddRedirect) {
850  ASSERT_TRUE(history_service_.get());
851  const char* first_sequence[] = {
852    "http://first.page.com/",
853    "http://second.page.com/"};
854  int first_count = arraysize(first_sequence);
855  history::RedirectList first_redirects;
856  for (int i = 0; i < first_count; i++)
857    first_redirects.push_back(GURL(first_sequence[i]));
858
859  // Add the sequence of pages as a server with no referrer. Note that we need
860  // to have a non-NULL page ID scope.
861  history_service_->AddPage(
862      first_redirects.back(), base::Time::Now(), MakeFakeHost(1),
863      0, GURL(), first_redirects, content::PAGE_TRANSITION_LINK,
864      history::SOURCE_BROWSED, true);
865
866  // The first page should be added once with a link visit type (because we set
867  // LINK when we added the original URL, and a referrer of nowhere (0).
868  EXPECT_TRUE(QueryURL(history_service_.get(), first_redirects[0]));
869  EXPECT_EQ(1, query_url_row_.visit_count());
870  ASSERT_EQ(1U, query_url_visits_.size());
871  int64 first_visit = query_url_visits_[0].visit_id;
872  EXPECT_EQ(content::PAGE_TRANSITION_LINK |
873            content::PAGE_TRANSITION_CHAIN_START,
874            query_url_visits_[0].transition);
875  EXPECT_EQ(0, query_url_visits_[0].referring_visit);  // No referrer.
876
877  // The second page should be a server redirect type with a referrer of the
878  // first page.
879  EXPECT_TRUE(QueryURL(history_service_.get(), first_redirects[1]));
880  EXPECT_EQ(1, query_url_row_.visit_count());
881  ASSERT_EQ(1U, query_url_visits_.size());
882  int64 second_visit = query_url_visits_[0].visit_id;
883  EXPECT_EQ(content::PAGE_TRANSITION_SERVER_REDIRECT |
884            content::PAGE_TRANSITION_CHAIN_END,
885            query_url_visits_[0].transition);
886  EXPECT_EQ(first_visit, query_url_visits_[0].referring_visit);
887
888  // Check that the redirect finding function successfully reports it.
889  saved_redirects_.clear();
890  QueryRedirectsFrom(history_service_.get(), first_redirects[0]);
891  ASSERT_EQ(1U, saved_redirects_.size());
892  EXPECT_EQ(first_redirects[1], saved_redirects_[0]);
893
894  // Now add a client redirect from that second visit to a third, client
895  // redirects are tracked by the RenderView prior to updating history,
896  // so we pass in a CLIENT_REDIRECT qualifier to mock that behavior.
897  history::RedirectList second_redirects;
898  second_redirects.push_back(first_redirects[1]);
899  second_redirects.push_back(GURL("http://last.page.com/"));
900  history_service_->AddPage(second_redirects[1], base::Time::Now(),
901                   MakeFakeHost(1), 1, second_redirects[0], second_redirects,
902                   static_cast<content::PageTransition>(
903                       content::PAGE_TRANSITION_LINK |
904                       content::PAGE_TRANSITION_CLIENT_REDIRECT),
905                   history::SOURCE_BROWSED, true);
906
907  // The last page (source of the client redirect) should NOT have an
908  // additional visit added, because it was a client redirect (normally it
909  // would). We should only have 1 left over from the first sequence.
910  EXPECT_TRUE(QueryURL(history_service_.get(), second_redirects[0]));
911  EXPECT_EQ(1, query_url_row_.visit_count());
912
913  // The final page should be set as a client redirect from the previous visit.
914  EXPECT_TRUE(QueryURL(history_service_.get(), second_redirects[1]));
915  EXPECT_EQ(1, query_url_row_.visit_count());
916  ASSERT_EQ(1U, query_url_visits_.size());
917  EXPECT_EQ(content::PAGE_TRANSITION_CLIENT_REDIRECT |
918            content::PAGE_TRANSITION_CHAIN_END,
919            query_url_visits_[0].transition);
920  EXPECT_EQ(second_visit, query_url_visits_[0].referring_visit);
921}
922
923TEST_F(HistoryTest, MakeIntranetURLsTyped) {
924  ASSERT_TRUE(history_service_.get());
925
926  // Add a non-typed visit to an intranet URL on an unvisited host.  This should
927  // get promoted to a typed visit.
928  const GURL test_url("http://intranet_host/path");
929  history_service_->AddPage(
930      test_url, base::Time::Now(), NULL, 0, GURL(),
931      history::RedirectList(), content::PAGE_TRANSITION_LINK,
932      history::SOURCE_BROWSED, false);
933  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
934  EXPECT_EQ(1, query_url_row_.visit_count());
935  EXPECT_EQ(1, query_url_row_.typed_count());
936  ASSERT_EQ(1U, query_url_visits_.size());
937  EXPECT_EQ(content::PAGE_TRANSITION_TYPED,
938      content::PageTransitionStripQualifier(query_url_visits_[0].transition));
939
940  // Add more visits on the same host.  None of these should be promoted since
941  // there is already a typed visit.
942
943  // Different path.
944  const GURL test_url2("http://intranet_host/different_path");
945  history_service_->AddPage(
946      test_url2, base::Time::Now(), NULL, 0, GURL(),
947      history::RedirectList(), content::PAGE_TRANSITION_LINK,
948      history::SOURCE_BROWSED, false);
949  EXPECT_TRUE(QueryURL(history_service_.get(), test_url2));
950  EXPECT_EQ(1, query_url_row_.visit_count());
951  EXPECT_EQ(0, query_url_row_.typed_count());
952  ASSERT_EQ(1U, query_url_visits_.size());
953  EXPECT_EQ(content::PAGE_TRANSITION_LINK,
954      content::PageTransitionStripQualifier(query_url_visits_[0].transition));
955
956  // No path.
957  const GURL test_url3("http://intranet_host/");
958  history_service_->AddPage(
959      test_url3, base::Time::Now(), NULL, 0, GURL(),
960      history::RedirectList(), content::PAGE_TRANSITION_LINK,
961      history::SOURCE_BROWSED, false);
962  EXPECT_TRUE(QueryURL(history_service_.get(), test_url3));
963  EXPECT_EQ(1, query_url_row_.visit_count());
964  EXPECT_EQ(0, query_url_row_.typed_count());
965  ASSERT_EQ(1U, query_url_visits_.size());
966  EXPECT_EQ(content::PAGE_TRANSITION_LINK,
967      content::PageTransitionStripQualifier(query_url_visits_[0].transition));
968
969  // Different scheme.
970  const GURL test_url4("https://intranet_host/");
971  history_service_->AddPage(
972      test_url4, base::Time::Now(), NULL, 0, GURL(),
973      history::RedirectList(), content::PAGE_TRANSITION_LINK,
974      history::SOURCE_BROWSED, false);
975  EXPECT_TRUE(QueryURL(history_service_.get(), test_url4));
976  EXPECT_EQ(1, query_url_row_.visit_count());
977  EXPECT_EQ(0, query_url_row_.typed_count());
978  ASSERT_EQ(1U, query_url_visits_.size());
979  EXPECT_EQ(content::PAGE_TRANSITION_LINK,
980      content::PageTransitionStripQualifier(query_url_visits_[0].transition));
981
982  // Different transition.
983  const GURL test_url5("http://intranet_host/another_path");
984  history_service_->AddPage(
985      test_url5, base::Time::Now(), NULL, 0, GURL(),
986      history::RedirectList(),
987      content::PAGE_TRANSITION_AUTO_BOOKMARK,
988      history::SOURCE_BROWSED, false);
989  EXPECT_TRUE(QueryURL(history_service_.get(), test_url5));
990  EXPECT_EQ(1, query_url_row_.visit_count());
991  EXPECT_EQ(0, query_url_row_.typed_count());
992  ASSERT_EQ(1U, query_url_visits_.size());
993  EXPECT_EQ(content::PAGE_TRANSITION_AUTO_BOOKMARK,
994      content::PageTransitionStripQualifier(query_url_visits_[0].transition));
995
996  // Original URL.
997  history_service_->AddPage(
998      test_url, base::Time::Now(), NULL, 0, GURL(),
999      history::RedirectList(), content::PAGE_TRANSITION_LINK,
1000      history::SOURCE_BROWSED, false);
1001  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1002  EXPECT_EQ(2, query_url_row_.visit_count());
1003  EXPECT_EQ(1, query_url_row_.typed_count());
1004  ASSERT_EQ(2U, query_url_visits_.size());
1005  EXPECT_EQ(content::PAGE_TRANSITION_LINK,
1006      content::PageTransitionStripQualifier(query_url_visits_[1].transition));
1007}
1008
1009TEST_F(HistoryTest, Typed) {
1010  ASSERT_TRUE(history_service_.get());
1011
1012  // Add the page once as typed.
1013  const GURL test_url("http://www.google.com/");
1014  history_service_->AddPage(
1015      test_url, base::Time::Now(), NULL, 0, GURL(),
1016      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1017      history::SOURCE_BROWSED, false);
1018  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1019
1020  // We should have the same typed & visit count.
1021  EXPECT_EQ(1, query_url_row_.visit_count());
1022  EXPECT_EQ(1, query_url_row_.typed_count());
1023
1024  // Add the page again not typed.
1025  history_service_->AddPage(
1026      test_url, base::Time::Now(), NULL, 0, GURL(),
1027      history::RedirectList(), content::PAGE_TRANSITION_LINK,
1028      history::SOURCE_BROWSED, false);
1029  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1030
1031  // The second time should not have updated the typed count.
1032  EXPECT_EQ(2, query_url_row_.visit_count());
1033  EXPECT_EQ(1, query_url_row_.typed_count());
1034
1035  // Add the page again as a generated URL.
1036  history_service_->AddPage(
1037      test_url, base::Time::Now(), NULL, 0, GURL(),
1038      history::RedirectList(), content::PAGE_TRANSITION_GENERATED,
1039      history::SOURCE_BROWSED, false);
1040  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1041
1042  // This should have worked like a link click.
1043  EXPECT_EQ(3, query_url_row_.visit_count());
1044  EXPECT_EQ(1, query_url_row_.typed_count());
1045
1046  // Add the page again as a reload.
1047  history_service_->AddPage(
1048      test_url, base::Time::Now(), NULL, 0, GURL(),
1049      history::RedirectList(), content::PAGE_TRANSITION_RELOAD,
1050      history::SOURCE_BROWSED, false);
1051  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1052
1053  // This should not have incremented any visit counts.
1054  EXPECT_EQ(3, query_url_row_.visit_count());
1055  EXPECT_EQ(1, query_url_row_.typed_count());
1056}
1057
1058TEST_F(HistoryTest, SetTitle) {
1059  ASSERT_TRUE(history_service_.get());
1060
1061  // Add a URL.
1062  const GURL existing_url("http://www.google.com/");
1063  history_service_->AddPage(
1064      existing_url, base::Time::Now(), history::SOURCE_BROWSED);
1065
1066  // Set some title.
1067  const string16 existing_title = UTF8ToUTF16("Google");
1068  history_service_->SetPageTitle(existing_url, existing_title);
1069
1070  // Make sure the title got set.
1071  EXPECT_TRUE(QueryURL(history_service_.get(), existing_url));
1072  EXPECT_EQ(existing_title, query_url_row_.title());
1073
1074  // set a title on a nonexistent page
1075  const GURL nonexistent_url("http://news.google.com/");
1076  const string16 nonexistent_title = UTF8ToUTF16("Google News");
1077  history_service_->SetPageTitle(nonexistent_url, nonexistent_title);
1078
1079  // Make sure nothing got written.
1080  EXPECT_FALSE(QueryURL(history_service_.get(), nonexistent_url));
1081  EXPECT_EQ(string16(), query_url_row_.title());
1082
1083  // TODO(brettw) this should also test redirects, which get the title of the
1084  // destination page.
1085}
1086
1087// crbug.com/159387: This test fails when daylight savings time ends.
1088TEST_F(HistoryTest, DISABLED_Segments) {
1089  ASSERT_TRUE(history_service_.get());
1090
1091  static const void* scope = static_cast<void*>(this);
1092
1093  // Add a URL.
1094  const GURL existing_url("http://www.google.com/");
1095  history_service_->AddPage(
1096      existing_url, base::Time::Now(), scope, 0, GURL(),
1097      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1098      history::SOURCE_BROWSED, false);
1099
1100  // Make sure a segment was created.
1101  history_service_->QuerySegmentUsageSince(
1102      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
1103      base::Bind(&HistoryTest::OnSegmentUsageAvailable,
1104                 base::Unretained(this)));
1105
1106  // Wait for processing.
1107  MessageLoop::current()->Run();
1108
1109  ASSERT_EQ(1U, page_usage_data_.size());
1110  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
1111  EXPECT_DOUBLE_EQ(3.0, page_usage_data_[0]->GetScore());
1112
1113  // Add a URL which doesn't create a segment.
1114  const GURL link_url("http://yahoo.com/");
1115  history_service_->AddPage(
1116      link_url, base::Time::Now(), scope, 0, GURL(),
1117      history::RedirectList(), content::PAGE_TRANSITION_LINK,
1118      history::SOURCE_BROWSED, false);
1119
1120  // Query again
1121  history_service_->QuerySegmentUsageSince(
1122      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
1123      base::Bind(&HistoryTest::OnSegmentUsageAvailable,
1124                 base::Unretained(this)));
1125
1126  // Wait for processing.
1127  MessageLoop::current()->Run();
1128
1129  // Make sure we still have one segment.
1130  ASSERT_EQ(1U, page_usage_data_.size());
1131  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
1132
1133  // Add a page linked from existing_url.
1134  history_service_->AddPage(
1135      GURL("http://www.google.com/foo"), base::Time::Now(),
1136      scope, 3, existing_url, history::RedirectList(),
1137      content::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED,
1138      false);
1139
1140  // Query again
1141  history_service_->QuerySegmentUsageSince(
1142      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
1143      base::Bind(&HistoryTest::OnSegmentUsageAvailable,
1144                 base::Unretained(this)));
1145
1146  // Wait for processing.
1147  MessageLoop::current()->Run();
1148
1149  // Make sure we still have one segment.
1150  ASSERT_EQ(1U, page_usage_data_.size());
1151  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
1152
1153  // However, the score should have increased.
1154  EXPECT_GT(page_usage_data_[0]->GetScore(), 5.0);
1155}
1156
1157TEST_F(HistoryTest, MostVisitedURLs) {
1158  ASSERT_TRUE(history_service_.get());
1159
1160  const GURL url0("http://www.google.com/url0/");
1161  const GURL url1("http://www.google.com/url1/");
1162  const GURL url2("http://www.google.com/url2/");
1163  const GURL url3("http://www.google.com/url3/");
1164  const GURL url4("http://www.google.com/url4/");
1165
1166  static const void* scope = static_cast<void*>(this);
1167
1168  // Add two pages.
1169  history_service_->AddPage(
1170      url0, base::Time::Now(), scope, 0, GURL(),
1171      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1172      history::SOURCE_BROWSED, false);
1173  history_service_->AddPage(
1174      url1, base::Time::Now(), scope, 0, GURL(),
1175      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1176      history::SOURCE_BROWSED, false);
1177  history_service_->QueryMostVisitedURLs(
1178      20, 90, &consumer_,
1179      base::Bind(
1180          &HistoryTest::OnMostVisitedURLsAvailable,
1181          base::Unretained(this)));
1182  MessageLoop::current()->Run();
1183
1184  EXPECT_EQ(2U, most_visited_urls_.size());
1185  EXPECT_EQ(url0, most_visited_urls_[0].url);
1186  EXPECT_EQ(url1, most_visited_urls_[1].url);
1187
1188  // Add another page.
1189  history_service_->AddPage(
1190      url2, base::Time::Now(), scope, 0, GURL(),
1191      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1192      history::SOURCE_BROWSED, false);
1193  history_service_->QueryMostVisitedURLs(
1194      20, 90, &consumer_,
1195      base::Bind(
1196          &HistoryTest::OnMostVisitedURLsAvailable,
1197          base::Unretained(this)));
1198  MessageLoop::current()->Run();
1199
1200  EXPECT_EQ(3U, most_visited_urls_.size());
1201  EXPECT_EQ(url0, most_visited_urls_[0].url);
1202  EXPECT_EQ(url1, most_visited_urls_[1].url);
1203  EXPECT_EQ(url2, most_visited_urls_[2].url);
1204
1205  // Revisit url2, making it the top URL.
1206  history_service_->AddPage(
1207      url2, base::Time::Now(), scope, 0, GURL(),
1208      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1209      history::SOURCE_BROWSED, false);
1210  history_service_->QueryMostVisitedURLs(
1211      20, 90, &consumer_,
1212      base::Bind(
1213          &HistoryTest::OnMostVisitedURLsAvailable,
1214          base::Unretained(this)));
1215  MessageLoop::current()->Run();
1216
1217  EXPECT_EQ(3U, most_visited_urls_.size());
1218  EXPECT_EQ(url2, most_visited_urls_[0].url);
1219  EXPECT_EQ(url0, most_visited_urls_[1].url);
1220  EXPECT_EQ(url1, most_visited_urls_[2].url);
1221
1222  // Revisit url1, making it the top URL.
1223  history_service_->AddPage(
1224      url1, base::Time::Now(), scope, 0, GURL(),
1225      history::RedirectList(), content::PAGE_TRANSITION_TYPED,
1226      history::SOURCE_BROWSED, false);
1227  history_service_->QueryMostVisitedURLs(
1228      20, 90, &consumer_,
1229      base::Bind(
1230          &HistoryTest::OnMostVisitedURLsAvailable,
1231          base::Unretained(this)));
1232  MessageLoop::current()->Run();
1233
1234  EXPECT_EQ(3U, most_visited_urls_.size());
1235  EXPECT_EQ(url1, most_visited_urls_[0].url);
1236  EXPECT_EQ(url2, most_visited_urls_[1].url);
1237  EXPECT_EQ(url0, most_visited_urls_[2].url);
1238
1239  // Redirects
1240  history::RedirectList redirects;
1241  redirects.push_back(url3);
1242  redirects.push_back(url4);
1243
1244  // Visit url4 using redirects.
1245  history_service_->AddPage(
1246      url4, base::Time::Now(), scope, 0, GURL(),
1247      redirects, content::PAGE_TRANSITION_TYPED,
1248      history::SOURCE_BROWSED, false);
1249  history_service_->QueryMostVisitedURLs(
1250      20, 90, &consumer_,
1251      base::Bind(
1252          &HistoryTest::OnMostVisitedURLsAvailable,
1253          base::Unretained(this)));
1254  MessageLoop::current()->Run();
1255
1256  EXPECT_EQ(4U, most_visited_urls_.size());
1257  EXPECT_EQ(url1, most_visited_urls_[0].url);
1258  EXPECT_EQ(url2, most_visited_urls_[1].url);
1259  EXPECT_EQ(url0, most_visited_urls_[2].url);
1260  EXPECT_EQ(url3, most_visited_urls_[3].url);
1261  EXPECT_EQ(2U, most_visited_urls_[3].redirects.size());
1262}
1263
1264// The version of the history database should be current in the "typical
1265// history" example file or it will be imported on startup, throwing off timing
1266// measurements.
1267//
1268// See test/data/profiles/profile_with_default_theme/README.txt for
1269// instructions on how to up the version.
1270TEST(HistoryProfileTest, TypicalProfileVersion) {
1271  base::FilePath file;
1272  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &file));
1273  file = file.AppendASCII("profiles");
1274  file = file.AppendASCII("profile_with_default_theme");
1275  file = file.AppendASCII("Default");
1276  file = file.AppendASCII("History");
1277
1278  int cur_version = HistoryDatabase::GetCurrentVersion();
1279
1280  sql::Connection db;
1281  ASSERT_TRUE(db.Open(file));
1282
1283  {
1284    sql::Statement s(db.GetUniqueStatement(
1285        "SELECT value FROM meta WHERE key = 'version'"));
1286    EXPECT_TRUE(s.Step());
1287    int file_version = s.ColumnInt(0);
1288    EXPECT_EQ(cur_version, file_version);
1289  }
1290}
1291
1292namespace {
1293
1294// A HistoryDBTask implementation. Each time RunOnDBThread is invoked
1295// invoke_count is increment. When invoked kWantInvokeCount times, true is
1296// returned from RunOnDBThread which should stop RunOnDBThread from being
1297// invoked again. When DoneRunOnMainThread is invoked, done_invoked is set to
1298// true.
1299class HistoryDBTaskImpl : public HistoryDBTask {
1300 public:
1301  static const int kWantInvokeCount;
1302
1303  HistoryDBTaskImpl() : invoke_count(0), done_invoked(false) {}
1304
1305  virtual bool RunOnDBThread(HistoryBackend* backend,
1306                             HistoryDatabase* db) OVERRIDE {
1307    return (++invoke_count == kWantInvokeCount);
1308  }
1309
1310  virtual void DoneRunOnMainThread() OVERRIDE {
1311    done_invoked = true;
1312    MessageLoop::current()->Quit();
1313  }
1314
1315  int invoke_count;
1316  bool done_invoked;
1317
1318 private:
1319  virtual ~HistoryDBTaskImpl() {}
1320
1321  DISALLOW_COPY_AND_ASSIGN(HistoryDBTaskImpl);
1322};
1323
1324// static
1325const int HistoryDBTaskImpl::kWantInvokeCount = 2;
1326
1327}  // namespace
1328
1329TEST_F(HistoryTest, HistoryDBTask) {
1330  ASSERT_TRUE(history_service_.get());
1331  CancelableRequestConsumerT<int, 0> request_consumer;
1332  scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
1333  history_service_->ScheduleDBTask(task.get(), &request_consumer);
1334  // Run the message loop. When HistoryDBTaskImpl::DoneRunOnMainThread runs,
1335  // it will stop the message loop. If the test hangs here, it means
1336  // DoneRunOnMainThread isn't being invoked correctly.
1337  MessageLoop::current()->Run();
1338  CleanupHistoryService();
1339  // WARNING: history has now been deleted.
1340  history_service_.reset();
1341  ASSERT_EQ(HistoryDBTaskImpl::kWantInvokeCount, task->invoke_count);
1342  ASSERT_TRUE(task->done_invoked);
1343}
1344
1345TEST_F(HistoryTest, HistoryDBTaskCanceled) {
1346  ASSERT_TRUE(history_service_.get());
1347  CancelableRequestConsumerT<int, 0> request_consumer;
1348  scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
1349  history_service_->ScheduleDBTask(task.get(), &request_consumer);
1350  request_consumer.CancelAllRequests();
1351  CleanupHistoryService();
1352  // WARNING: history has now been deleted.
1353  history_service_.reset();
1354  ASSERT_FALSE(task->done_invoked);
1355}
1356
1357// Dummy SyncChangeProcessor used to help review what SyncChanges are pushed
1358// back up to Sync.
1359//
1360// TODO(akalin): Unify all the various test implementations of
1361// syncer::SyncChangeProcessor.
1362class TestChangeProcessor : public syncer::SyncChangeProcessor {
1363 public:
1364  TestChangeProcessor() {}
1365  virtual ~TestChangeProcessor() {}
1366
1367  virtual syncer::SyncError ProcessSyncChanges(
1368      const tracked_objects::Location& from_here,
1369      const syncer::SyncChangeList& change_list) OVERRIDE {
1370    changes_.insert(changes_.end(), change_list.begin(), change_list.end());
1371    return syncer::SyncError();
1372  }
1373
1374  const syncer::SyncChangeList& GetChanges() const {
1375    return changes_;
1376  }
1377
1378 private:
1379  syncer::SyncChangeList changes_;
1380
1381  DISALLOW_COPY_AND_ASSIGN(TestChangeProcessor);
1382};
1383
1384// SyncChangeProcessor implementation that delegates to another one.
1385// This is necessary since most things expect a
1386// scoped_ptr<SyncChangeProcessor>.
1387//
1388// TODO(akalin): Unify this too.
1389class SyncChangeProcessorDelegate : public syncer::SyncChangeProcessor {
1390 public:
1391  explicit SyncChangeProcessorDelegate(syncer::SyncChangeProcessor* recipient)
1392      : recipient_(recipient) {
1393    DCHECK(recipient_);
1394  }
1395
1396  virtual ~SyncChangeProcessorDelegate() {}
1397
1398  // syncer::SyncChangeProcessor implementation.
1399  virtual syncer::SyncError ProcessSyncChanges(
1400      const tracked_objects::Location& from_here,
1401      const syncer::SyncChangeList& change_list) OVERRIDE {
1402    return recipient_->ProcessSyncChanges(from_here, change_list);
1403  }
1404
1405 private:
1406  // The recipient of all sync changes.
1407  syncer::SyncChangeProcessor* const recipient_;
1408
1409  DISALLOW_COPY_AND_ASSIGN(SyncChangeProcessorDelegate);
1410};
1411
1412// Create a local delete directive and process it while sync is
1413// online, and then when offline. The delete directive should be sent to sync,
1414// no error should be returned for the first time, and an error should be
1415// returned for the second time.
1416TEST_F(HistoryTest, ProcessLocalDeleteDirectiveSyncOnline) {
1417  ASSERT_TRUE(history_service_.get());
1418
1419  const GURL test_url("http://www.google.com/");
1420  for (int64 i = 1; i <= 10; ++i) {
1421    base::Time t =
1422        base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
1423    history_service_->AddPage(test_url, t, NULL, 0, GURL(),
1424                              history::RedirectList(),
1425                              content::PAGE_TRANSITION_LINK,
1426                              history::SOURCE_BROWSED, false);
1427  }
1428
1429  sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
1430  sync_pb::GlobalIdDirective* global_id_directive =
1431      delete_directive.mutable_global_id_directive();
1432  global_id_directive->add_global_id(
1433      (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1))
1434      .ToInternalValue());
1435
1436  TestChangeProcessor change_processor;
1437
1438  EXPECT_FALSE(
1439      history_service_->MergeDataAndStartSyncing(
1440          syncer::HISTORY_DELETE_DIRECTIVES,
1441          syncer::SyncDataList(),
1442          scoped_ptr<syncer::SyncChangeProcessor>(
1443              new SyncChangeProcessorDelegate(&change_processor)),
1444          scoped_ptr<syncer::SyncErrorFactory>()).error().IsSet());
1445
1446  syncer::SyncError err =
1447      history_service_->ProcessLocalDeleteDirective(delete_directive);
1448  EXPECT_FALSE(err.IsSet());
1449  EXPECT_EQ(1u, change_processor.GetChanges().size());
1450
1451  history_service_->StopSyncing(syncer::HISTORY_DELETE_DIRECTIVES);
1452  err = history_service_->ProcessLocalDeleteDirective(delete_directive);
1453  EXPECT_TRUE(err.IsSet());
1454  EXPECT_EQ(1u, change_processor.GetChanges().size());
1455}
1456
1457// Closure function that runs periodically to check result of delete directive
1458// processing. Stop when timeout or processing ends indicated by the creation
1459// of sync changes.
1460void CheckDirectiveProcessingResult(
1461    Time timeout, const TestChangeProcessor* change_processor,
1462    uint32 num_changes) {
1463  if (base::Time::Now() > timeout ||
1464      change_processor->GetChanges().size() >= num_changes) {
1465    return;
1466  }
1467
1468  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
1469  MessageLoop::current()->PostTask(
1470      FROM_HERE,
1471      base::Bind(&CheckDirectiveProcessingResult, timeout,
1472                 change_processor, num_changes));
1473}
1474
1475// Create a delete directive for a few specific history entries,
1476// including ones that don't exist. The expected entries should be
1477// deleted.
1478TEST_F(HistoryTest, ProcessGlobalIdDeleteDirective) {
1479  ASSERT_TRUE(history_service_.get());
1480  const GURL test_url("http://www.google.com/");
1481  for (int64 i = 1; i <= 20; i++) {
1482    base::Time t =
1483        base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
1484    history_service_->AddPage(test_url, t, NULL, 0, GURL(),
1485                              history::RedirectList(),
1486                              content::PAGE_TRANSITION_LINK,
1487                              history::SOURCE_BROWSED, false);
1488  }
1489
1490  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1491  EXPECT_EQ(20, query_url_row_.visit_count());
1492
1493  syncer::SyncDataList directives;
1494  // 1st directive.
1495  sync_pb::EntitySpecifics entity_specs;
1496  sync_pb::GlobalIdDirective* global_id_directive =
1497      entity_specs.mutable_history_delete_directive()
1498          ->mutable_global_id_directive();
1499  global_id_directive->add_global_id(
1500      (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(6))
1501      .ToInternalValue());
1502  global_id_directive->set_start_time_usec(3);
1503  global_id_directive->set_end_time_usec(10);
1504  directives.push_back(
1505      syncer::SyncData::CreateRemoteData(1, entity_specs));
1506
1507  // 2nd directive.
1508  global_id_directive->Clear();
1509  global_id_directive->add_global_id(
1510      (base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(17))
1511      .ToInternalValue());
1512  global_id_directive->set_start_time_usec(13);
1513  global_id_directive->set_end_time_usec(19);
1514  directives.push_back(
1515      syncer::SyncData::CreateRemoteData(2, entity_specs));
1516
1517  TestChangeProcessor change_processor;
1518  EXPECT_FALSE(
1519      history_service_->MergeDataAndStartSyncing(
1520          syncer::HISTORY_DELETE_DIRECTIVES,
1521          directives,
1522          scoped_ptr<syncer::SyncChangeProcessor>(
1523              new SyncChangeProcessorDelegate(&change_processor)),
1524          scoped_ptr<syncer::SyncErrorFactory>()).error().IsSet());
1525
1526  // Inject a task to check status and keep message loop filled before directive
1527  // processing finishes.
1528  MessageLoop::current()->PostTask(
1529      FROM_HERE,
1530      base::Bind(&CheckDirectiveProcessingResult,
1531                 base::Time::Now() + base::TimeDelta::FromSeconds(10),
1532                 &change_processor, 2));
1533  MessageLoop::current()->RunUntilIdle();
1534  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1535  ASSERT_EQ(5, query_url_row_.visit_count());
1536  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1),
1537            query_url_visits_[0].visit_time);
1538  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(2),
1539            query_url_visits_[1].visit_time);
1540  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(11),
1541            query_url_visits_[2].visit_time);
1542  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(12),
1543            query_url_visits_[3].visit_time);
1544  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(20),
1545            query_url_visits_[4].visit_time);
1546
1547  // Expect two sync changes for deleting processed directives.
1548  const syncer::SyncChangeList& sync_changes = change_processor.GetChanges();
1549  ASSERT_EQ(2u, sync_changes.size());
1550  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
1551  EXPECT_EQ(1, sync_changes[0].sync_data().GetRemoteId());
1552  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
1553  EXPECT_EQ(2, sync_changes[1].sync_data().GetRemoteId());
1554}
1555
1556// Create delete directives for time ranges.  The expected entries should be
1557// deleted.
1558TEST_F(HistoryTest, ProcessTimeRangeDeleteDirective) {
1559  ASSERT_TRUE(history_service_.get());
1560  const GURL test_url("http://www.google.com/");
1561  for (int64 i = 1; i <= 10; ++i) {
1562    base::Time t =
1563        base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(i);
1564    history_service_->AddPage(test_url, t, NULL, 0, GURL(),
1565                              history::RedirectList(),
1566                              content::PAGE_TRANSITION_LINK,
1567                              history::SOURCE_BROWSED, false);
1568  }
1569
1570  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1571  EXPECT_EQ(10, query_url_row_.visit_count());
1572
1573  syncer::SyncDataList directives;
1574  // 1st directive.
1575  sync_pb::EntitySpecifics entity_specs;
1576  sync_pb::TimeRangeDirective* time_range_directive =
1577      entity_specs.mutable_history_delete_directive()
1578          ->mutable_time_range_directive();
1579  time_range_directive->set_start_time_usec(2);
1580  time_range_directive->set_end_time_usec(5);
1581  directives.push_back(syncer::SyncData::CreateRemoteData(1, entity_specs));
1582
1583  // 2nd directive.
1584  time_range_directive->Clear();
1585  time_range_directive->set_start_time_usec(8);
1586  time_range_directive->set_end_time_usec(10);
1587  directives.push_back(syncer::SyncData::CreateRemoteData(2, entity_specs));
1588
1589  TestChangeProcessor change_processor;
1590  EXPECT_FALSE(
1591      history_service_->MergeDataAndStartSyncing(
1592          syncer::HISTORY_DELETE_DIRECTIVES,
1593          directives,
1594          scoped_ptr<syncer::SyncChangeProcessor>(
1595              new SyncChangeProcessorDelegate(&change_processor)),
1596          scoped_ptr<syncer::SyncErrorFactory>()).error().IsSet());
1597
1598  // Inject a task to check status and keep message loop filled before
1599  // directive processing finishes.
1600  MessageLoop::current()->PostTask(
1601      FROM_HERE,
1602      base::Bind(&CheckDirectiveProcessingResult,
1603                 base::Time::Now() + base::TimeDelta::FromSeconds(10),
1604                 &change_processor, 2));
1605  MessageLoop::current()->RunUntilIdle();
1606  EXPECT_TRUE(QueryURL(history_service_.get(), test_url));
1607  ASSERT_EQ(3, query_url_row_.visit_count());
1608  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(1),
1609            query_url_visits_[0].visit_time);
1610  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(6),
1611            query_url_visits_[1].visit_time);
1612  EXPECT_EQ(base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(7),
1613            query_url_visits_[2].visit_time);
1614
1615  // Expect two sync changes for deleting processed directives.
1616  const syncer::SyncChangeList& sync_changes = change_processor.GetChanges();
1617  ASSERT_EQ(2u, sync_changes.size());
1618  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[0].change_type());
1619  EXPECT_EQ(1, sync_changes[0].sync_data().GetRemoteId());
1620  EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, sync_changes[1].change_type());
1621  EXPECT_EQ(2, sync_changes[1].sync_data().GetRemoteId());
1622}
1623
1624TEST_F(HistoryBackendDBTest, MigratePresentations) {
1625  // Create the db we want. Use 22 since segments didn't change in that time
1626  // frame.
1627  ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
1628
1629  const SegmentID segment_id = 2;
1630  const URLID url_id = 3;
1631  const GURL url("http://www.foo.com");
1632  const std::string url_name(VisitSegmentDatabase::ComputeSegmentName(url));
1633  const string16 title(ASCIIToUTF16("Title1"));
1634  const Time segment_time(Time::Now());
1635
1636  {
1637    // Re-open the db for manual manipulation.
1638    sql::Connection db;
1639    ASSERT_TRUE(db.Open(history_dir_.Append(chrome::kHistoryFilename)));
1640
1641    // Add an entry to urls.
1642    {
1643      sql::Statement s(db.GetUniqueStatement(
1644                           "INSERT INTO urls "
1645                           "(id, url, title, last_visit_time) VALUES "
1646                           "(?, ?, ?, ?)"));
1647      s.BindInt64(0, url_id);
1648      s.BindString(1, url.spec());
1649      s.BindString16(2, title);
1650      s.BindInt64(3, segment_time.ToInternalValue());
1651      ASSERT_TRUE(s.Run());
1652    }
1653
1654    // Add an entry to segments.
1655    {
1656      sql::Statement s(db.GetUniqueStatement(
1657                           "INSERT INTO segments "
1658                           "(id, name, url_id, pres_index) VALUES "
1659                           "(?, ?, ?, ?)"));
1660      s.BindInt64(0, segment_id);
1661      s.BindString(1, url_name);
1662      s.BindInt64(2, url_id);
1663      s.BindInt(3, 4);  // pres_index
1664      ASSERT_TRUE(s.Run());
1665    }
1666
1667    // And one to segment_usage.
1668    {
1669      sql::Statement s(db.GetUniqueStatement(
1670                           "INSERT INTO segment_usage "
1671                           "(id, segment_id, time_slot, visit_count) VALUES "
1672                           "(?, ?, ?, ?)"));
1673      s.BindInt64(0, 4);  // id.
1674      s.BindInt64(1, segment_id);
1675      s.BindInt64(2, segment_time.ToInternalValue());
1676      s.BindInt(3, 5);  // visit count.
1677      ASSERT_TRUE(s.Run());
1678    }
1679  }
1680
1681  // Re-open the db, triggering migration.
1682  CreateBackendAndDatabase();
1683
1684  std::vector<PageUsageData*> results;
1685  db_->QuerySegmentUsage(segment_time, 10, &results);
1686  ASSERT_EQ(1u, results.size());
1687  EXPECT_EQ(url, results[0]->GetURL());
1688  EXPECT_EQ(segment_id, results[0]->GetID());
1689  EXPECT_EQ(title, results[0]->GetTitle());
1690  STLDeleteElements(&results);
1691}
1692
1693}  // namespace history
1694