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.
5 #include "base/basictypes.h"
7 #include "base/bind_helpers.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/path_service.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/task/cancelable_task_tracker.h"
15 #include "chrome/browser/history/history_service.h"
16 #include "testing/gtest/include/gtest/gtest.h"
19 using base::TimeDelta;
21 // Tests the history service for querying functionality.
31 Time time; // Filled by SetUp.
33 // This one is visited super long ago so it will be in a different database
34 // from the next appearance of it at the end.
35 {"http://example.com/", "Other", 180},
37 // These are deliberately added out of chronological order. The history
38 // service should sort them by visit time when returning query results.
39 // The correct index sort order is 4 2 3 1 7 6 5 0.
40 {"http://www.google.com/1", "Title PAGEONE FOO some text", 10},
41 {"http://www.google.com/3", "Title PAGETHREE BAR some hello world", 8},
42 {"http://www.google.com/2", "Title PAGETWO FOO some more blah blah blah", 9},
44 // A more recent visit of the first one.
45 {"http://example.com/", "Other", 6},
47 {"http://www.google.com/6", "Title I'm the second oldest", 13},
48 {"http://www.google.com/4", "Title four", 12},
49 {"http://www.google.com/5", "Title five", 11},
52 // Returns true if the nth result in the given results set matches. It will
53 // return false on a non-match or if there aren't enough results.
54 bool NthResultIs(const QueryResults& results,
55 int n, // Result index to check.
56 int test_entry_index) { // Index of test_entries to compare.
57 if (static_cast<int>(results.size()) <= n)
60 const URLResult& result = results[n];
62 // Check the visit time.
63 if (result.visit_time() != test_entries[test_entry_index].time)
66 // Now check the URL & title.
67 return result.url() == GURL(test_entries[test_entry_index].url) &&
69 base::UTF8ToUTF16(test_entries[test_entry_index].title);
74 class HistoryQueryTest : public testing::Test {
76 HistoryQueryTest() : page_id_(0) {
79 // Acts like a synchronous call to history's QueryHistory.
80 void QueryHistory(const std::string& text_query,
81 const QueryOptions& options,
82 QueryResults* results) {
83 history_->QueryHistory(base::UTF8ToUTF16(text_query),
85 base::Bind(&HistoryQueryTest::QueryHistoryComplete,
86 base::Unretained(this)),
88 // Will go until ...Complete calls Quit.
89 base::MessageLoop::current()->Run();
90 results->Swap(&last_query_results_);
93 // Test paging through results, with a fixed number of results per page.
94 // Defined here so code can be shared for the text search and the non-text
96 void TestPaging(const std::string& query_text,
97 const int* expected_results,
99 ASSERT_TRUE(history_.get());
101 QueryOptions options;
102 QueryResults results;
104 options.max_count = 1;
105 for (int i = 0; i < results_length; i++) {
106 SCOPED_TRACE(testing::Message() << "i = " << i);
107 QueryHistory(query_text, options, &results);
108 ASSERT_EQ(1U, results.size());
109 EXPECT_TRUE(NthResultIs(results, 0, expected_results[i]));
110 options.end_time = results.back().visit_time();
112 QueryHistory(query_text, options, &results);
113 EXPECT_EQ(0U, results.size());
115 // Try with a max_count > 1.
116 options.max_count = 2;
117 options.end_time = base::Time();
118 for (int i = 0; i < results_length / 2; i++) {
119 SCOPED_TRACE(testing::Message() << "i = " << i);
120 QueryHistory(query_text, options, &results);
121 ASSERT_EQ(2U, results.size());
122 EXPECT_TRUE(NthResultIs(results, 0, expected_results[i * 2]));
123 EXPECT_TRUE(NthResultIs(results, 1, expected_results[i * 2 + 1]));
124 options.end_time = results.back().visit_time();
127 // Add a couple of entries with duplicate timestamps. Use |query_text| as
128 // the title of both entries so that they match a text query.
129 TestEntry duplicates[] = {
130 { "http://www.google.com/x", query_text.c_str(), 1, },
131 { "http://www.google.com/y", query_text.c_str(), 1, }
133 AddEntryToHistory(duplicates[0]);
134 AddEntryToHistory(duplicates[1]);
136 // Make sure that paging proceeds even if there are duplicate timestamps.
137 options.end_time = base::Time();
139 QueryHistory(query_text, options, &results);
140 ASSERT_NE(options.end_time, results.back().visit_time());
141 options.end_time = results.back().visit_time();
142 } while (!results.reached_beginning());
146 scoped_ptr<HistoryService> history_;
148 // Counter used to generate a unique ID for each page added to the history.
151 void AddEntryToHistory(const TestEntry& entry) {
152 // We need the ID scope and page ID so that the visit tracker can find it.
153 ContextID context_id = reinterpret_cast<ContextID>(1);
156 history_->AddPage(url, entry.time, context_id, page_id_++, GURL(),
157 history::RedirectList(), ui::PAGE_TRANSITION_LINK,
158 history::SOURCE_BROWSED, false);
159 history_->SetPageTitle(url, base::UTF8ToUTF16(entry.title));
163 virtual void SetUp() {
164 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
165 history_dir_ = temp_dir_.path().AppendASCII("HistoryTest");
166 ASSERT_TRUE(base::CreateDirectory(history_dir_));
168 history_.reset(new HistoryService);
169 if (!history_->Init(history_dir_)) {
170 history_.reset(); // Tests should notice this NULL ptr & fail.
174 // Fill the test data.
175 Time now = Time::Now().LocalMidnight();
176 for (size_t i = 0; i < arraysize(test_entries); i++) {
177 test_entries[i].time =
178 now - (test_entries[i].days_ago * TimeDelta::FromDays(1));
179 AddEntryToHistory(test_entries[i]);
183 virtual void TearDown() {
185 history_->SetOnBackendDestroyTask(base::MessageLoop::QuitClosure());
188 base::MessageLoop::current()->Run(); // Wait for the other thread.
192 void QueryHistoryComplete(QueryResults* results) {
193 results->Swap(&last_query_results_);
194 base::MessageLoop::current()->Quit(); // Will return out to QueryHistory.
197 base::ScopedTempDir temp_dir_;
199 base::MessageLoop message_loop_;
201 base::FilePath history_dir_;
203 base::CancelableTaskTracker tracker_;
205 // The QueryHistoryComplete callback will put the results here so QueryHistory
207 QueryResults last_query_results_;
209 DISALLOW_COPY_AND_ASSIGN(HistoryQueryTest);
212 TEST_F(HistoryQueryTest, Basic) {
213 ASSERT_TRUE(history_.get());
215 QueryOptions options;
216 QueryResults results;
218 // Test duplicate collapsing. 0 is an older duplicate of 4, and should not
219 // appear in the result set.
220 QueryHistory(std::string(), options, &results);
221 EXPECT_EQ(7U, results.size());
223 EXPECT_TRUE(NthResultIs(results, 0, 4));
224 EXPECT_TRUE(NthResultIs(results, 1, 2));
225 EXPECT_TRUE(NthResultIs(results, 2, 3));
226 EXPECT_TRUE(NthResultIs(results, 3, 1));
227 EXPECT_TRUE(NthResultIs(results, 4, 7));
228 EXPECT_TRUE(NthResultIs(results, 5, 6));
229 EXPECT_TRUE(NthResultIs(results, 6, 5));
231 // Next query a time range. The beginning should be inclusive, the ending
232 // should be exclusive.
233 options.begin_time = test_entries[3].time;
234 options.end_time = test_entries[2].time;
235 QueryHistory(std::string(), options, &results);
236 EXPECT_EQ(1U, results.size());
237 EXPECT_TRUE(NthResultIs(results, 0, 3));
240 // Tests max_count feature for basic (non-Full Text Search) queries.
241 TEST_F(HistoryQueryTest, BasicCount) {
242 ASSERT_TRUE(history_.get());
244 QueryOptions options;
245 QueryResults results;
247 // Query all time but with a limit on the number of entries. We should
248 // get the N most recent entries.
249 options.max_count = 2;
250 QueryHistory(std::string(), options, &results);
251 EXPECT_EQ(2U, results.size());
252 EXPECT_TRUE(NthResultIs(results, 0, 4));
253 EXPECT_TRUE(NthResultIs(results, 1, 2));
256 TEST_F(HistoryQueryTest, ReachedBeginning) {
257 ASSERT_TRUE(history_.get());
259 QueryOptions options;
260 QueryResults results;
262 QueryHistory(std::string(), options, &results);
263 EXPECT_TRUE(results.reached_beginning());
264 QueryHistory("some", options, &results);
265 EXPECT_TRUE(results.reached_beginning());
267 options.begin_time = test_entries[1].time;
268 QueryHistory(std::string(), options, &results);
269 EXPECT_FALSE(results.reached_beginning());
270 QueryHistory("some", options, &results);
271 EXPECT_FALSE(results.reached_beginning());
273 // Try |begin_time| just later than the oldest visit.
274 options.begin_time = test_entries[0].time + TimeDelta::FromMicroseconds(1);
275 QueryHistory(std::string(), options, &results);
276 EXPECT_FALSE(results.reached_beginning());
277 QueryHistory("some", options, &results);
278 EXPECT_FALSE(results.reached_beginning());
280 // Try |begin_time| equal to the oldest visit.
281 options.begin_time = test_entries[0].time;
282 QueryHistory(std::string(), options, &results);
283 EXPECT_TRUE(results.reached_beginning());
284 QueryHistory("some", options, &results);
285 EXPECT_TRUE(results.reached_beginning());
287 // Try |begin_time| just earlier than the oldest visit.
288 options.begin_time = test_entries[0].time - TimeDelta::FromMicroseconds(1);
289 QueryHistory(std::string(), options, &results);
290 EXPECT_TRUE(results.reached_beginning());
291 QueryHistory("some", options, &results);
292 EXPECT_TRUE(results.reached_beginning());
294 // Test with |max_count| specified.
295 options.max_count = 1;
296 QueryHistory(std::string(), options, &results);
297 EXPECT_FALSE(results.reached_beginning());
298 QueryHistory("some", options, &results);
299 EXPECT_FALSE(results.reached_beginning());
301 // Test with |max_count| greater than the number of results,
302 // and exactly equal to the number of results.
303 options.max_count = 100;
304 QueryHistory(std::string(), options, &results);
305 EXPECT_TRUE(results.reached_beginning());
306 options.max_count = results.size();
307 QueryHistory(std::string(), options, &results);
308 EXPECT_TRUE(results.reached_beginning());
310 options.max_count = 100;
311 QueryHistory("some", options, &results);
312 EXPECT_TRUE(results.reached_beginning());
313 options.max_count = results.size();
314 QueryHistory("some", options, &results);
315 EXPECT_TRUE(results.reached_beginning());
318 // This does most of the same tests above, but performs a text searches for a
319 // string that will match the pages in question. This will trigger a
320 // different code path.
321 TEST_F(HistoryQueryTest, TextSearch) {
322 ASSERT_TRUE(history_.get());
324 QueryOptions options;
325 QueryResults results;
327 // Query all of them to make sure they are there and in order. Note that
328 // this query will return the starred item twice since we requested all
329 // starred entries and no de-duping.
330 QueryHistory("some", options, &results);
331 EXPECT_EQ(3U, results.size());
332 EXPECT_TRUE(NthResultIs(results, 0, 2));
333 EXPECT_TRUE(NthResultIs(results, 1, 3));
334 EXPECT_TRUE(NthResultIs(results, 2, 1));
336 // Do a query that should only match one of them.
337 QueryHistory("PAGETWO", options, &results);
338 EXPECT_EQ(1U, results.size());
339 EXPECT_TRUE(NthResultIs(results, 0, 3));
341 // Next query a time range. The beginning should be inclusive, the ending
342 // should be exclusive.
343 options.begin_time = test_entries[1].time;
344 options.end_time = test_entries[3].time;
345 QueryHistory("some", options, &results);
346 EXPECT_EQ(1U, results.size());
347 EXPECT_TRUE(NthResultIs(results, 0, 1));
350 // Tests prefix searching for text search queries.
351 TEST_F(HistoryQueryTest, TextSearchPrefix) {
352 ASSERT_TRUE(history_.get());
354 QueryOptions options;
355 QueryResults results;
357 // Query with a prefix search. Should return matches for "PAGETWO" and
359 QueryHistory("PAGET", options, &results);
360 EXPECT_EQ(2U, results.size());
361 EXPECT_TRUE(NthResultIs(results, 0, 2));
362 EXPECT_TRUE(NthResultIs(results, 1, 3));
365 // Tests max_count feature for text search queries.
366 TEST_F(HistoryQueryTest, TextSearchCount) {
367 ASSERT_TRUE(history_.get());
369 QueryOptions options;
370 QueryResults results;
372 // Query all time but with a limit on the number of entries. We should
373 // get the N most recent entries.
374 options.max_count = 2;
375 QueryHistory("some", options, &results);
376 EXPECT_EQ(2U, results.size());
377 EXPECT_TRUE(NthResultIs(results, 0, 2));
378 EXPECT_TRUE(NthResultIs(results, 1, 3));
380 // Now query a subset of the pages and limit by N items. "FOO" should match
381 // the 2nd & 3rd pages, but we should only get the 3rd one because of the one
382 // page max restriction.
383 options.max_count = 1;
384 QueryHistory("FOO", options, &results);
385 EXPECT_EQ(1U, results.size());
386 EXPECT_TRUE(NthResultIs(results, 0, 3));
389 // Tests IDN text search by both ASCII and UTF.
390 TEST_F(HistoryQueryTest, TextSearchIDN) {
391 ASSERT_TRUE(history_.get());
393 QueryOptions options;
394 QueryResults results;
396 TestEntry entry = { "http://xn--d1abbgf6aiiy.xn--p1ai/", "Nothing", 0, };
397 AddEntryToHistory(entry);
404 { std::string("xn--d1abbgf6aiiy.xn--p1ai"), 1 },
405 { base::WideToUTF8(std::wstring(L"\u043f\u0440\u0435\u0437") +
406 L"\u0438\u0434\u0435\u043d\u0442.\u0440\u0444"), 1, },
409 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(queries); ++i) {
410 QueryHistory(queries[i].query, options, &results);
411 EXPECT_EQ(queries[i].results_size, results.size());
415 // Test iterating over pages of results.
416 TEST_F(HistoryQueryTest, Paging) {
417 // Since results are fetched 1 and 2 at a time, entry #0 and #6 will not
419 int expected_results[] = { 4, 2, 3, 1, 7, 6, 5, 0 };
420 TestPaging(std::string(), expected_results, arraysize(expected_results));
423 TEST_F(HistoryQueryTest, TextSearchPaging) {
424 // Since results are fetched 1 and 2 at a time, entry #0 and #6 will not
425 // be de-duplicated. Entry #4 does not contain the text "title", so it
427 int expected_results[] = { 2, 3, 1, 7, 6, 5 };
428 TestPaging("title", expected_results, arraysize(expected_results));
431 } // namespace history