Home | History | Annotate | Download | only in history
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/basictypes.h"
      6 #include "base/callback.h"
      7 #include "base/file_path.h"
      8 #include "base/file_util.h"
      9 #include "base/memory/scoped_temp_dir.h"
     10 #include "base/path_service.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/browser/history/history.h"
     13 #include "testing/gtest/include/gtest/gtest.h"
     14 
     15 using base::Time;
     16 using base::TimeDelta;
     17 
     18 // Tests the history service for querying functionality.
     19 
     20 namespace history {
     21 
     22 namespace {
     23 
     24 struct 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.
     51 bool 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 
     70 class 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 
    148 TEST_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.
    172 TEST_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 
    187 TEST_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.
    215 TEST_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.
    245 TEST_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.
    261 TEST_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.
    276 TEST_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.
    302 TEST_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.
    338 TEST_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