history_querying_unittest.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/basictypes.h"
6#include "base/callback.h"
7#include "base/file_path.h"
8#include "base/file_util.h"
9#include "base/path_service.h"
10#include "base/scoped_temp_dir.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/history/history.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15using base::Time;
16using base::TimeDelta;
17
18// Tests the history service for querying functionality.
19
20namespace history {
21
22namespace {
23
24struct TestEntry {
25  const char* url;
26  const char* title;
27  const int days_ago;
28  const char* body;
29  Time time;  // Filled by SetUp.
30} test_entries[] = {
31  // This one is visited super long ago so it will be in a different database
32  // from the next appearance of it at the end.
33  {"http://example.com/", "Other", 180, "Other"},
34
35  // These are deliberately added out of chronological order. The history
36  // service should sort them by visit time when returning query results.
37  // The correct index sort order is 4 2 3 1 0.
38  {"http://www.google.com/1", "Title 1", 10,
39   "PAGEONE FOO some body text"},
40  {"http://www.google.com/3", "Title 3", 8,
41   "PAGETHREE BAR some hello world for you"},
42  {"http://www.google.com/2", "Title 2", 9,
43   "PAGETWO FOO some more blah blah blah"},
44
45  // A more recent visit of the first one.
46  {"http://example.com/", "Other", 6, "Other"},
47};
48
49// Returns true if the nth result in the given results set matches. It will
50// return false on a non-match or if there aren't enough results.
51bool NthResultIs(const QueryResults& results,
52                 int n,  // Result index to check.
53                 int test_entry_index) {  // Index of test_entries to compare.
54  if (static_cast<int>(results.size()) <= n)
55    return false;
56
57  const URLResult& result = results[n];
58
59  // Check the visit time.
60  if (result.visit_time() != test_entries[test_entry_index].time)
61    return false;
62
63  // Now check the URL & title.
64  return result.url() == GURL(test_entries[test_entry_index].url) &&
65         result.title() == UTF8ToUTF16(test_entries[test_entry_index].title);
66}
67
68}  // namespace
69
70class HistoryQueryTest : public testing::Test {
71 public:
72  HistoryQueryTest() {
73  }
74
75  // Acts like a synchronous call to history's QueryHistory.
76  void QueryHistory(const std::string& text_query,
77                    const QueryOptions& options,
78                    QueryResults* results) {
79    history_->QueryHistory(UTF8ToUTF16(text_query), options, &consumer_,
80        NewCallback(this, &HistoryQueryTest::QueryHistoryComplete));
81    MessageLoop::current()->Run();  // Will go until ...Complete calls Quit.
82    results->Swap(&last_query_results_);
83  }
84
85 protected:
86  scoped_refptr<HistoryService> history_;
87
88 private:
89  virtual void SetUp() {
90    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
91    history_dir_ = temp_dir_.path().AppendASCII("HistoryTest");
92    ASSERT_TRUE(file_util::CreateDirectory(history_dir_));
93
94    history_ = new HistoryService;
95    if (!history_->Init(history_dir_, NULL)) {
96      history_ = NULL;  // Tests should notice this NULL ptr & fail.
97      return;
98    }
99
100    // Fill the test data.
101    Time now = Time::Now().LocalMidnight();
102    for (size_t i = 0; i < arraysize(test_entries); i++) {
103      test_entries[i].time =
104          now - (test_entries[i].days_ago * TimeDelta::FromDays(1));
105
106      // We need the ID scope and page ID so that the visit tracker can find it.
107      const void* id_scope = reinterpret_cast<void*>(1);
108      int32 page_id = i;
109      GURL url(test_entries[i].url);
110
111      history_->AddPage(url, test_entries[i].time, id_scope, page_id, GURL(),
112                        PageTransition::LINK, history::RedirectList(),
113                        history::SOURCE_BROWSED, false);
114      history_->SetPageTitle(url, UTF8ToUTF16(test_entries[i].title));
115      history_->SetPageContents(url, UTF8ToUTF16(test_entries[i].body));
116    }
117  }
118
119  virtual void TearDown() {
120    if (history_.get()) {
121      history_->SetOnBackendDestroyTask(new MessageLoop::QuitTask);
122      history_->Cleanup();
123      history_ = NULL;
124      MessageLoop::current()->Run();  // Wait for the other thread.
125    }
126  }
127
128  void QueryHistoryComplete(HistoryService::Handle, QueryResults* results) {
129    results->Swap(&last_query_results_);
130    MessageLoop::current()->Quit();  // Will return out to QueryHistory.
131  }
132
133  ScopedTempDir temp_dir_;
134
135  MessageLoop message_loop_;
136
137  FilePath history_dir_;
138
139  CancelableRequestConsumer consumer_;
140
141  // The QueryHistoryComplete callback will put the results here so QueryHistory
142  // can return them.
143  QueryResults last_query_results_;
144
145  DISALLOW_COPY_AND_ASSIGN(HistoryQueryTest);
146};
147
148TEST_F(HistoryQueryTest, Basic) {
149  ASSERT_TRUE(history_.get());
150
151  QueryOptions options;
152  QueryResults results;
153
154  // Test duplicate collapsing.
155  QueryHistory(std::string(), options, &results);
156  EXPECT_EQ(4U, results.size());
157  EXPECT_TRUE(NthResultIs(results, 0, 4));
158  EXPECT_TRUE(NthResultIs(results, 1, 2));
159  EXPECT_TRUE(NthResultIs(results, 2, 3));
160  EXPECT_TRUE(NthResultIs(results, 3, 1));
161
162  // Next query a time range. The beginning should be inclusive, the ending
163  // should be exclusive.
164  options.begin_time = test_entries[3].time;
165  options.end_time = test_entries[2].time;
166  QueryHistory(std::string(), options, &results);
167  EXPECT_EQ(1U, results.size());
168  EXPECT_TRUE(NthResultIs(results, 0, 3));
169}
170
171// Tests max_count feature for basic (non-Full Text Search) queries.
172TEST_F(HistoryQueryTest, BasicCount) {
173  ASSERT_TRUE(history_.get());
174
175  QueryOptions options;
176  QueryResults results;
177
178  // Query all time but with a limit on the number of entries. We should
179  // get the N most recent entries.
180  options.max_count = 2;
181  QueryHistory(std::string(), options, &results);
182  EXPECT_EQ(2U, results.size());
183  EXPECT_TRUE(NthResultIs(results, 0, 4));
184  EXPECT_TRUE(NthResultIs(results, 1, 2));
185}
186
187TEST_F(HistoryQueryTest, ReachedBeginning) {
188  ASSERT_TRUE(history_.get());
189
190  QueryOptions options;
191  QueryResults results;
192
193  QueryHistory(std::string(), options, &results);
194  EXPECT_TRUE(results.reached_beginning());
195
196  options.begin_time = test_entries[1].time;
197  QueryHistory(std::string(), options, &results);
198  EXPECT_FALSE(results.reached_beginning());
199
200  options.begin_time = test_entries[0].time + TimeDelta::FromMicroseconds(1);
201  QueryHistory(std::string(), options, &results);
202  EXPECT_FALSE(results.reached_beginning());
203
204  options.begin_time = test_entries[0].time;
205  QueryHistory(std::string(), options, &results);
206  EXPECT_TRUE(results.reached_beginning());
207
208  options.begin_time = test_entries[0].time - TimeDelta::FromMicroseconds(1);
209  QueryHistory(std::string(), options, &results);
210  EXPECT_TRUE(results.reached_beginning());
211}
212
213// This does most of the same tests above, but searches for a FTS string that
214// will match the pages in question. This will trigger a different code path.
215TEST_F(HistoryQueryTest, FTS) {
216  ASSERT_TRUE(history_.get());
217
218  QueryOptions options;
219  QueryResults results;
220
221  // Query all of them to make sure they are there and in order. Note that
222  // this query will return the starred item twice since we requested all
223  // starred entries and no de-duping.
224  QueryHistory("some", options, &results);
225  EXPECT_EQ(3U, results.size());
226  EXPECT_TRUE(NthResultIs(results, 0, 2));
227  EXPECT_TRUE(NthResultIs(results, 1, 3));
228  EXPECT_TRUE(NthResultIs(results, 2, 1));
229
230  // Do a query that should only match one of them.
231  QueryHistory("PAGETWO", options, &results);
232  EXPECT_EQ(1U, results.size());
233  EXPECT_TRUE(NthResultIs(results, 0, 3));
234
235  // Next query a time range. The beginning should be inclusive, the ending
236  // should be exclusive.
237  options.begin_time = test_entries[1].time;
238  options.end_time = test_entries[3].time;
239  QueryHistory("some", options, &results);
240  EXPECT_EQ(1U, results.size());
241  EXPECT_TRUE(NthResultIs(results, 0, 1));
242}
243
244// Searches titles.
245TEST_F(HistoryQueryTest, FTSTitle) {
246  ASSERT_TRUE(history_.get());
247
248  QueryOptions options;
249  QueryResults results;
250
251  // Query all time but with a limit on the number of entries. We should
252  // get the N most recent entries.
253  QueryHistory("title", options, &results);
254  EXPECT_EQ(3U, results.size());
255  EXPECT_TRUE(NthResultIs(results, 0, 2));
256  EXPECT_TRUE(NthResultIs(results, 1, 3));
257  EXPECT_TRUE(NthResultIs(results, 2, 1));
258}
259
260// Tests prefix searching for Full Text Search queries.
261TEST_F(HistoryQueryTest, FTSPrefix) {
262  ASSERT_TRUE(history_.get());
263
264  QueryOptions options;
265  QueryResults results;
266
267  // Query with a prefix search.  Should return matches for "PAGETWO" and
268  // "PAGETHREE".
269  QueryHistory("PAGET", options, &results);
270  EXPECT_EQ(2U, results.size());
271  EXPECT_TRUE(NthResultIs(results, 0, 2));
272  EXPECT_TRUE(NthResultIs(results, 1, 3));
273}
274
275// Tests max_count feature for Full Text Search queries.
276TEST_F(HistoryQueryTest, FTSCount) {
277  ASSERT_TRUE(history_.get());
278
279  QueryOptions options;
280  QueryResults results;
281
282  // Query all time but with a limit on the number of entries. We should
283  // get the N most recent entries.
284  options.max_count = 2;
285  QueryHistory("some", options, &results);
286  EXPECT_EQ(2U, results.size());
287  EXPECT_TRUE(NthResultIs(results, 0, 2));
288  EXPECT_TRUE(NthResultIs(results, 1, 3));
289
290  // Now query a subset of the pages and limit by N items. "FOO" should match
291  // the 2nd & 3rd pages, but we should only get the 3rd one because of the one
292  // page max restriction.
293  options.max_count = 1;
294  QueryHistory("FOO", options, &results);
295  EXPECT_EQ(1U, results.size());
296  EXPECT_TRUE(NthResultIs(results, 0, 3));
297}
298
299// Tests that FTS queries can find URLs when they exist only in the archived
300// database. This also tests that imported URLs can be found, since we use
301// AddPageWithDetails just like the importer.
302TEST_F(HistoryQueryTest, FTSArchived) {
303  ASSERT_TRUE(history_.get());
304
305  std::vector<URLRow> urls_to_add;
306
307  URLRow row1(GURL("http://foo.bar/"));
308  row1.set_title(UTF8ToUTF16("archived title"));
309  row1.set_last_visit(Time::Now() - TimeDelta::FromDays(365));
310  urls_to_add.push_back(row1);
311
312  URLRow row2(GURL("http://foo.bar/"));
313  row2.set_title(UTF8ToUTF16("nonarchived title"));
314  row2.set_last_visit(Time::Now());
315  urls_to_add.push_back(row2);
316
317  history_->AddPagesWithDetails(urls_to_add, history::SOURCE_BROWSED);
318
319  QueryOptions options;
320  QueryResults results;
321
322  // Query all time. The title we get should be the one in the full text
323  // database and not the most current title (since otherwise highlighting in
324  // the title might be wrong).
325  QueryHistory("archived", options, &results);
326  ASSERT_EQ(1U, results.size());
327  EXPECT_TRUE(row1.url() == results[0].url());
328  EXPECT_TRUE(row1.title() == results[0].title());
329}
330
331/* TODO(brettw) re-enable this. It is commented out because the current history
332   code prohibits adding more than one indexed page with the same URL. When we
333   have tiered history, there could be a dupe in the archived history which
334   won't get picked up by the deletor and it can happen again. When this is the
335   case, we should fix this test to duplicate that situation.
336
337// Tests duplicate collapsing and not in Full Text Search situations.
338TEST_F(HistoryQueryTest, FTSDupes) {
339  ASSERT_TRUE(history_.get());
340
341  QueryOptions options;
342  QueryResults results;
343
344  QueryHistory("Other", options, &results);
345  EXPECT_EQ(1, results.urls().size());
346  EXPECT_TRUE(NthResultIs(results, 0, 4));
347}
348*/
349
350}  // namespace history
351