• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "base/basictypes.h"
6 #include "base/bind.h"
7 #include "base/bind_helpers.h"
8 #include "base/file_util.h"
9 #include "base/files/file_path.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/path_service.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/browser/history/history_service.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 
16 using base::Time;
17 using base::TimeDelta;
18 
19 // Tests the history service for querying functionality.
20 
21 namespace history {
22 
23 namespace {
24 
25 struct TestEntry {
26   const char* url;
27   const char* title;
28   const int days_ago;
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},
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 7 6 5 0.
38   {"http://www.google.com/1", "Title PAGEONE FOO some text", 10},
39   {"http://www.google.com/3", "Title PAGETHREE BAR some hello world", 8},
40   {"http://www.google.com/2", "Title PAGETWO FOO some more blah blah blah", 9},
41 
42   // A more recent visit of the first one.
43   {"http://example.com/", "Other", 6},
44 
45   {"http://www.google.com/6", "Title I'm the second oldest", 13},
46   {"http://www.google.com/4", "Title four", 12},
47   {"http://www.google.com/5", "Title five", 11},
48 };
49 
50 // Returns true if the nth result in the given results set matches. It will
51 // return false on a non-match or if there aren't enough results.
NthResultIs(const QueryResults & results,int n,int test_entry_index)52 bool NthResultIs(const QueryResults& results,
53                  int n,  // Result index to check.
54                  int test_entry_index) {  // Index of test_entries to compare.
55   if (static_cast<int>(results.size()) <= n)
56     return false;
57 
58   const URLResult& result = results[n];
59 
60   // Check the visit time.
61   if (result.visit_time() != test_entries[test_entry_index].time)
62     return false;
63 
64   // Now check the URL & title.
65   return result.url() == GURL(test_entries[test_entry_index].url) &&
66          result.title() ==
67              base::UTF8ToUTF16(test_entries[test_entry_index].title);
68 }
69 
70 }  // namespace
71 
72 class HistoryQueryTest : public testing::Test {
73  public:
HistoryQueryTest()74   HistoryQueryTest() : page_id_(0) {
75   }
76 
77   // Acts like a synchronous call to history's QueryHistory.
QueryHistory(const std::string & text_query,const QueryOptions & options,QueryResults * results)78   void QueryHistory(const std::string& text_query,
79                     const QueryOptions& options,
80                     QueryResults* results) {
81     history_->QueryHistory(
82         base::UTF8ToUTF16(text_query), options, &consumer_,
83         base::Bind(&HistoryQueryTest::QueryHistoryComplete,
84                    base::Unretained(this)));
85     // Will go until ...Complete calls Quit.
86     base::MessageLoop::current()->Run();
87     results->Swap(&last_query_results_);
88   }
89 
90   // Test paging through results, with a fixed number of results per page.
91   // Defined here so code can be shared for the text search and the non-text
92   // seach versions.
TestPaging(const std::string & query_text,const int * expected_results,int results_length)93   void TestPaging(const std::string& query_text,
94                   const int* expected_results,
95                   int results_length) {
96     ASSERT_TRUE(history_.get());
97 
98     QueryOptions options;
99     QueryResults results;
100 
101     options.max_count = 1;
102     for (int i = 0; i < results_length; i++) {
103       SCOPED_TRACE(testing::Message() << "i = " << i);
104       QueryHistory(query_text, options, &results);
105       ASSERT_EQ(1U, results.size());
106       EXPECT_TRUE(NthResultIs(results, 0, expected_results[i]));
107       options.end_time = results.back().visit_time();
108     }
109     QueryHistory(query_text, options, &results);
110     EXPECT_EQ(0U, results.size());
111 
112     // Try with a max_count > 1.
113     options.max_count = 2;
114     options.end_time = base::Time();
115     for (int i = 0; i < results_length / 2; i++) {
116       SCOPED_TRACE(testing::Message() << "i = " << i);
117       QueryHistory(query_text, options, &results);
118       ASSERT_EQ(2U, results.size());
119       EXPECT_TRUE(NthResultIs(results, 0, expected_results[i * 2]));
120       EXPECT_TRUE(NthResultIs(results, 1, expected_results[i * 2 + 1]));
121       options.end_time = results.back().visit_time();
122     }
123 
124     // Add a couple of entries with duplicate timestamps. Use |query_text| as
125     // the title of both entries so that they match a text query.
126     TestEntry duplicates[] = {
127       { "http://www.google.com/x",  query_text.c_str(), 1, },
128       { "http://www.google.com/y",  query_text.c_str(), 1, }
129     };
130     AddEntryToHistory(duplicates[0]);
131     AddEntryToHistory(duplicates[1]);
132 
133     // Make sure that paging proceeds even if there are duplicate timestamps.
134     options.end_time = base::Time();
135     do {
136       QueryHistory(query_text, options, &results);
137       ASSERT_NE(options.end_time, results.back().visit_time());
138       options.end_time = results.back().visit_time();
139     } while (!results.reached_beginning());
140   }
141 
142  protected:
143   scoped_ptr<HistoryService> history_;
144 
145   // Counter used to generate a unique ID for each page added to the history.
146   int32 page_id_;
147 
AddEntryToHistory(const TestEntry & entry)148   void AddEntryToHistory(const TestEntry& entry) {
149     // We need the ID scope and page ID so that the visit tracker can find it.
150     ContextID context_id = reinterpret_cast<ContextID>(1);
151     GURL url(entry.url);
152 
153     history_->AddPage(url, entry.time, context_id, page_id_++, GURL(),
154                       history::RedirectList(), content::PAGE_TRANSITION_LINK,
155                       history::SOURCE_BROWSED, false);
156     history_->SetPageTitle(url, base::UTF8ToUTF16(entry.title));
157   }
158 
159  private:
SetUp()160   virtual void SetUp() {
161     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
162     history_dir_ = temp_dir_.path().AppendASCII("HistoryTest");
163     ASSERT_TRUE(base::CreateDirectory(history_dir_));
164 
165     history_.reset(new HistoryService);
166     if (!history_->Init(history_dir_)) {
167       history_.reset();  // Tests should notice this NULL ptr & fail.
168       return;
169     }
170 
171     // Fill the test data.
172     Time now = Time::Now().LocalMidnight();
173     for (size_t i = 0; i < arraysize(test_entries); i++) {
174       test_entries[i].time =
175           now - (test_entries[i].days_ago * TimeDelta::FromDays(1));
176       AddEntryToHistory(test_entries[i]);
177     }
178   }
179 
TearDown()180   virtual void TearDown() {
181     if (history_) {
182       history_->SetOnBackendDestroyTask(base::MessageLoop::QuitClosure());
183       history_->Cleanup();
184       history_.reset();
185       base::MessageLoop::current()->Run();  // Wait for the other thread.
186     }
187   }
188 
QueryHistoryComplete(HistoryService::Handle,QueryResults * results)189   void QueryHistoryComplete(HistoryService::Handle, QueryResults* results) {
190     results->Swap(&last_query_results_);
191     base::MessageLoop::current()->Quit();  // Will return out to QueryHistory.
192   }
193 
194   base::ScopedTempDir temp_dir_;
195 
196   base::MessageLoop message_loop_;
197 
198   base::FilePath history_dir_;
199 
200   CancelableRequestConsumer consumer_;
201 
202   // The QueryHistoryComplete callback will put the results here so QueryHistory
203   // can return them.
204   QueryResults last_query_results_;
205 
206   DISALLOW_COPY_AND_ASSIGN(HistoryQueryTest);
207 };
208 
TEST_F(HistoryQueryTest,Basic)209 TEST_F(HistoryQueryTest, Basic) {
210   ASSERT_TRUE(history_.get());
211 
212   QueryOptions options;
213   QueryResults results;
214 
215   // Test duplicate collapsing. 0 is an older duplicate of 4, and should not
216   // appear in the result set.
217   QueryHistory(std::string(), options, &results);
218   EXPECT_EQ(7U, results.size());
219 
220   EXPECT_TRUE(NthResultIs(results, 0, 4));
221   EXPECT_TRUE(NthResultIs(results, 1, 2));
222   EXPECT_TRUE(NthResultIs(results, 2, 3));
223   EXPECT_TRUE(NthResultIs(results, 3, 1));
224   EXPECT_TRUE(NthResultIs(results, 4, 7));
225   EXPECT_TRUE(NthResultIs(results, 5, 6));
226   EXPECT_TRUE(NthResultIs(results, 6, 5));
227 
228   // Next query a time range. The beginning should be inclusive, the ending
229   // should be exclusive.
230   options.begin_time = test_entries[3].time;
231   options.end_time = test_entries[2].time;
232   QueryHistory(std::string(), options, &results);
233   EXPECT_EQ(1U, results.size());
234   EXPECT_TRUE(NthResultIs(results, 0, 3));
235 }
236 
237 // Tests max_count feature for basic (non-Full Text Search) queries.
TEST_F(HistoryQueryTest,BasicCount)238 TEST_F(HistoryQueryTest, BasicCount) {
239   ASSERT_TRUE(history_.get());
240 
241   QueryOptions options;
242   QueryResults results;
243 
244   // Query all time but with a limit on the number of entries. We should
245   // get the N most recent entries.
246   options.max_count = 2;
247   QueryHistory(std::string(), options, &results);
248   EXPECT_EQ(2U, results.size());
249   EXPECT_TRUE(NthResultIs(results, 0, 4));
250   EXPECT_TRUE(NthResultIs(results, 1, 2));
251 }
252 
TEST_F(HistoryQueryTest,ReachedBeginning)253 TEST_F(HistoryQueryTest, ReachedBeginning) {
254   ASSERT_TRUE(history_.get());
255 
256   QueryOptions options;
257   QueryResults results;
258 
259   QueryHistory(std::string(), options, &results);
260   EXPECT_TRUE(results.reached_beginning());
261   QueryHistory("some", options, &results);
262   EXPECT_TRUE(results.reached_beginning());
263 
264   options.begin_time = test_entries[1].time;
265   QueryHistory(std::string(), options, &results);
266   EXPECT_FALSE(results.reached_beginning());
267   QueryHistory("some", options, &results);
268   EXPECT_FALSE(results.reached_beginning());
269 
270   // Try |begin_time| just later than the oldest visit.
271   options.begin_time = test_entries[0].time + TimeDelta::FromMicroseconds(1);
272   QueryHistory(std::string(), options, &results);
273   EXPECT_FALSE(results.reached_beginning());
274   QueryHistory("some", options, &results);
275   EXPECT_FALSE(results.reached_beginning());
276 
277   // Try |begin_time| equal to the oldest visit.
278   options.begin_time = test_entries[0].time;
279   QueryHistory(std::string(), options, &results);
280   EXPECT_TRUE(results.reached_beginning());
281   QueryHistory("some", options, &results);
282   EXPECT_TRUE(results.reached_beginning());
283 
284   // Try |begin_time| just earlier than the oldest visit.
285   options.begin_time = test_entries[0].time - TimeDelta::FromMicroseconds(1);
286   QueryHistory(std::string(), options, &results);
287   EXPECT_TRUE(results.reached_beginning());
288   QueryHistory("some", options, &results);
289   EXPECT_TRUE(results.reached_beginning());
290 
291   // Test with |max_count| specified.
292   options.max_count = 1;
293   QueryHistory(std::string(), options, &results);
294   EXPECT_FALSE(results.reached_beginning());
295   QueryHistory("some", options, &results);
296   EXPECT_FALSE(results.reached_beginning());
297 
298   // Test with |max_count| greater than the number of results,
299   // and exactly equal to the number of results.
300   options.max_count = 100;
301   QueryHistory(std::string(), options, &results);
302   EXPECT_TRUE(results.reached_beginning());
303   options.max_count = results.size();
304   QueryHistory(std::string(), options, &results);
305   EXPECT_TRUE(results.reached_beginning());
306 
307   options.max_count = 100;
308   QueryHistory("some", options, &results);
309   EXPECT_TRUE(results.reached_beginning());
310   options.max_count = results.size();
311   QueryHistory("some", options, &results);
312   EXPECT_TRUE(results.reached_beginning());
313 }
314 
315 // This does most of the same tests above, but performs a text searches for a
316 // string that will match the pages in question. This will trigger a
317 // different code path.
TEST_F(HistoryQueryTest,TextSearch)318 TEST_F(HistoryQueryTest, TextSearch) {
319   ASSERT_TRUE(history_.get());
320 
321   QueryOptions options;
322   QueryResults results;
323 
324   // Query all of them to make sure they are there and in order. Note that
325   // this query will return the starred item twice since we requested all
326   // starred entries and no de-duping.
327   QueryHistory("some", options, &results);
328   EXPECT_EQ(3U, results.size());
329   EXPECT_TRUE(NthResultIs(results, 0, 2));
330   EXPECT_TRUE(NthResultIs(results, 1, 3));
331   EXPECT_TRUE(NthResultIs(results, 2, 1));
332 
333   // Do a query that should only match one of them.
334   QueryHistory("PAGETWO", options, &results);
335   EXPECT_EQ(1U, results.size());
336   EXPECT_TRUE(NthResultIs(results, 0, 3));
337 
338   // Next query a time range. The beginning should be inclusive, the ending
339   // should be exclusive.
340   options.begin_time = test_entries[1].time;
341   options.end_time = test_entries[3].time;
342   QueryHistory("some", options, &results);
343   EXPECT_EQ(1U, results.size());
344   EXPECT_TRUE(NthResultIs(results, 0, 1));
345 }
346 
347 // Tests prefix searching for text search queries.
TEST_F(HistoryQueryTest,TextSearchPrefix)348 TEST_F(HistoryQueryTest, TextSearchPrefix) {
349   ASSERT_TRUE(history_.get());
350 
351   QueryOptions options;
352   QueryResults results;
353 
354   // Query with a prefix search.  Should return matches for "PAGETWO" and
355   // "PAGETHREE".
356   QueryHistory("PAGET", options, &results);
357   EXPECT_EQ(2U, results.size());
358   EXPECT_TRUE(NthResultIs(results, 0, 2));
359   EXPECT_TRUE(NthResultIs(results, 1, 3));
360 }
361 
362 // Tests max_count feature for text search queries.
TEST_F(HistoryQueryTest,TextSearchCount)363 TEST_F(HistoryQueryTest, TextSearchCount) {
364   ASSERT_TRUE(history_.get());
365 
366   QueryOptions options;
367   QueryResults results;
368 
369   // Query all time but with a limit on the number of entries. We should
370   // get the N most recent entries.
371   options.max_count = 2;
372   QueryHistory("some", options, &results);
373   EXPECT_EQ(2U, results.size());
374   EXPECT_TRUE(NthResultIs(results, 0, 2));
375   EXPECT_TRUE(NthResultIs(results, 1, 3));
376 
377   // Now query a subset of the pages and limit by N items. "FOO" should match
378   // the 2nd & 3rd pages, but we should only get the 3rd one because of the one
379   // page max restriction.
380   options.max_count = 1;
381   QueryHistory("FOO", options, &results);
382   EXPECT_EQ(1U, results.size());
383   EXPECT_TRUE(NthResultIs(results, 0, 3));
384 }
385 
386 // Tests IDN text search by both ASCII and UTF.
TEST_F(HistoryQueryTest,TextSearchIDN)387 TEST_F(HistoryQueryTest, TextSearchIDN) {
388   ASSERT_TRUE(history_.get());
389 
390   QueryOptions options;
391   QueryResults results;
392 
393   TestEntry entry = { "http://xn--d1abbgf6aiiy.xn--p1ai/",  "Nothing", 0, };
394   AddEntryToHistory(entry);
395 
396   struct QueryEntry {
397     std::string query;
398     size_t results_size;
399   } queries[] = {
400     { "bad query", 0 },
401     { std::string("xn--d1abbgf6aiiy.xn--p1ai"), 1 },
402     { base::WideToUTF8(std::wstring(L"\u043f\u0440\u0435\u0437") +
403                        L"\u0438\u0434\u0435\u043d\u0442.\u0440\u0444"), 1, },
404   };
405 
406   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(queries); ++i) {
407     QueryHistory(queries[i].query, options, &results);
408     EXPECT_EQ(queries[i].results_size, results.size());
409   }
410 }
411 
412 // Test iterating over pages of results.
TEST_F(HistoryQueryTest,Paging)413 TEST_F(HistoryQueryTest, Paging) {
414   // Since results are fetched 1 and 2 at a time, entry #0 and #6 will not
415   // be de-duplicated.
416   int expected_results[] = { 4, 2, 3, 1, 7, 6, 5, 0 };
417   TestPaging(std::string(), expected_results, arraysize(expected_results));
418 }
419 
TEST_F(HistoryQueryTest,TextSearchPaging)420 TEST_F(HistoryQueryTest, TextSearchPaging) {
421   // Since results are fetched 1 and 2 at a time, entry #0 and #6 will not
422   // be de-duplicated. Entry #4 does not contain the text "title", so it
423   // shouldn't appear.
424   int expected_results[] = { 2, 3, 1, 7, 6, 5 };
425   TestPaging("title", expected_results, arraysize(expected_results));
426 }
427 
428 }  // namespace history
429