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