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 "chrome/browser/predictors/autocomplete_action_predictor.h" 6 7 #include "base/auto_reset.h" 8 #include "base/command_line.h" 9 #include "base/guid.h" 10 #include "base/memory/ref_counted.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/time/time.h" 15 #include "chrome/browser/autocomplete/autocomplete_match.h" 16 #include "chrome/browser/history/history_service.h" 17 #include "chrome/browser/history/history_service_factory.h" 18 #include "chrome/browser/history/in_memory_database.h" 19 #include "chrome/browser/history/url_database.h" 20 #include "chrome/browser/prerender/prerender_field_trial.h" 21 #include "chrome/common/chrome_switches.h" 22 #include "chrome/test/base/testing_profile.h" 23 #include "content/public/test/test_browser_thread.h" 24 #include "testing/gtest/include/gtest/gtest.h" 25 26 using base::ASCIIToUTF16; 27 using content::BrowserThread; 28 using predictors::AutocompleteActionPredictor; 29 30 namespace { 31 32 struct TestUrlInfo { 33 GURL url; 34 base::string16 title; 35 int days_from_now; 36 base::string16 user_text; 37 int number_of_hits; 38 int number_of_misses; 39 AutocompleteActionPredictor::Action expected_action; 40 } test_url_db[] = { 41 { GURL("http://www.testsite.com/a.html"), 42 ASCIIToUTF16("Test - site - just a test"), 1, 43 ASCIIToUTF16("j"), 5, 0, 44 AutocompleteActionPredictor::ACTION_PRERENDER }, 45 { GURL("http://www.testsite.com/b.html"), 46 ASCIIToUTF16("Test - site - just a test"), 1, 47 ASCIIToUTF16("ju"), 3, 0, 48 AutocompleteActionPredictor::ACTION_PRERENDER }, 49 { GURL("http://www.testsite.com/c.html"), 50 ASCIIToUTF16("Test - site - just a test"), 5, 51 ASCIIToUTF16("just"), 3, 1, 52 AutocompleteActionPredictor::ACTION_PRECONNECT }, 53 { GURL("http://www.testsite.com/d.html"), 54 ASCIIToUTF16("Test - site - just a test"), 5, 55 ASCIIToUTF16("just"), 3, 0, 56 AutocompleteActionPredictor::ACTION_PRERENDER }, 57 { GURL("http://www.testsite.com/e.html"), 58 ASCIIToUTF16("Test - site - just a test"), 8, 59 ASCIIToUTF16("just"), 3, 1, 60 AutocompleteActionPredictor::ACTION_PRECONNECT }, 61 { GURL("http://www.testsite.com/f.html"), 62 ASCIIToUTF16("Test - site - just a test"), 8, 63 ASCIIToUTF16("just"), 3, 0, 64 AutocompleteActionPredictor::ACTION_PRERENDER }, 65 { GURL("http://www.testsite.com/g.html"), 66 ASCIIToUTF16("Test - site - just a test"), 12, 67 base::string16(), 5, 0, 68 AutocompleteActionPredictor::ACTION_NONE }, 69 { GURL("http://www.testsite.com/h.html"), 70 ASCIIToUTF16("Test - site - just a test"), 21, 71 ASCIIToUTF16("just a test"), 2, 0, 72 AutocompleteActionPredictor::ACTION_NONE }, 73 { GURL("http://www.testsite.com/i.html"), 74 ASCIIToUTF16("Test - site - just a test"), 28, 75 ASCIIToUTF16("just a test"), 2, 0, 76 AutocompleteActionPredictor::ACTION_NONE } 77 }; 78 79 } // end namespace 80 81 namespace predictors { 82 83 class AutocompleteActionPredictorTest : public testing::Test { 84 public: 85 AutocompleteActionPredictorTest() 86 : ui_thread_(BrowserThread::UI, &loop_), 87 db_thread_(BrowserThread::DB, &loop_), 88 file_thread_(BrowserThread::FILE, &loop_), 89 profile_(new TestingProfile()), 90 predictor_(new AutocompleteActionPredictor(profile_.get())) { 91 } 92 93 virtual ~AutocompleteActionPredictorTest() { 94 predictor_.reset(NULL); 95 profile_.reset(NULL); 96 loop_.RunUntilIdle(); 97 } 98 99 virtual void SetUp() { 100 CommandLine::ForCurrentProcess()->AppendSwitchASCII( 101 switches::kPrerenderFromOmnibox, 102 switches::kPrerenderFromOmniboxSwitchValueEnabled); 103 104 predictor_->CreateLocalCachesFromDatabase(); 105 ASSERT_TRUE(profile_->CreateHistoryService(true, false)); 106 profile_->BlockUntilHistoryProcessesPendingRequests(); 107 108 ASSERT_TRUE(predictor_->initialized_); 109 ASSERT_TRUE(db_cache()->empty()); 110 ASSERT_TRUE(db_id_cache()->empty()); 111 } 112 113 virtual void TearDown() { 114 profile_->DestroyHistoryService(); 115 predictor_->Shutdown(); 116 } 117 118 protected: 119 typedef AutocompleteActionPredictor::DBCacheKey DBCacheKey; 120 typedef AutocompleteActionPredictor::DBCacheValue DBCacheValue; 121 typedef AutocompleteActionPredictor::DBCacheMap DBCacheMap; 122 typedef AutocompleteActionPredictor::DBIdCacheMap DBIdCacheMap; 123 124 void AddAllRowsToHistory() { 125 for (size_t i = 0; i < arraysize(test_url_db); ++i) 126 ASSERT_TRUE(AddRowToHistory(test_url_db[i])); 127 } 128 129 history::URLID AddRowToHistory(const TestUrlInfo& test_row) { 130 HistoryService* history = 131 HistoryServiceFactory::GetForProfile(profile_.get(), 132 Profile::EXPLICIT_ACCESS); 133 CHECK(history); 134 history::URLDatabase* url_db = history->InMemoryDatabase(); 135 CHECK(url_db); 136 137 const base::Time visit_time = 138 base::Time::Now() - base::TimeDelta::FromDays( 139 test_row.days_from_now); 140 141 history::URLRow row(test_row.url); 142 row.set_title(test_row.title); 143 row.set_last_visit(visit_time); 144 145 return url_db->AddURL(row); 146 } 147 148 AutocompleteActionPredictorTable::Row CreateRowFromTestUrlInfo( 149 const TestUrlInfo& test_row) const { 150 AutocompleteActionPredictorTable::Row row; 151 row.id = base::GenerateGUID(); 152 row.user_text = test_row.user_text; 153 row.url = test_row.url; 154 row.number_of_hits = test_row.number_of_hits; 155 row.number_of_misses = test_row.number_of_misses; 156 return row; 157 } 158 159 void AddAllRows() { 160 for (size_t i = 0; i < arraysize(test_url_db); ++i) 161 AddRow(test_url_db[i]); 162 } 163 164 std::string AddRow(const TestUrlInfo& test_row) { 165 AutocompleteActionPredictorTable::Row row = 166 CreateRowFromTestUrlInfo(test_row); 167 predictor_->AddAndUpdateRows( 168 AutocompleteActionPredictorTable::Rows(1, row), 169 AutocompleteActionPredictorTable::Rows()); 170 171 return row.id; 172 } 173 174 void UpdateRow(const AutocompleteActionPredictorTable::Row& row) { 175 AutocompleteActionPredictor::DBCacheKey key = { row.user_text, row.url }; 176 ASSERT_TRUE(db_cache()->find(key) != db_cache()->end()); 177 predictor_->AddAndUpdateRows( 178 AutocompleteActionPredictorTable::Rows(), 179 AutocompleteActionPredictorTable::Rows(1, row)); 180 } 181 182 void DeleteAllRows() { 183 predictor_->DeleteAllRows(); 184 } 185 186 void DeleteRowsWithURLs(const history::URLRows& rows) { 187 predictor_->DeleteRowsWithURLs(rows); 188 } 189 190 void DeleteOldIdsFromCaches( 191 std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) { 192 HistoryService* history_service = 193 HistoryServiceFactory::GetForProfile(profile_.get(), 194 Profile::EXPLICIT_ACCESS); 195 ASSERT_TRUE(history_service); 196 197 history::URLDatabase* url_db = history_service->InMemoryDatabase(); 198 ASSERT_TRUE(url_db); 199 200 // Reset the predictor's |initialized_| flag for the life of this call, 201 // since outside of testing this function is only supposed to be reached 202 // before initialization is completed. 203 base::AutoReset<bool> initialized_reset(&predictor_->initialized_, false); 204 predictor_->DeleteOldIdsFromCaches(url_db, id_list); 205 } 206 207 AutocompleteActionPredictor* predictor() { return predictor_.get(); } 208 209 DBCacheMap* db_cache() { return &predictor_->db_cache_; } 210 DBIdCacheMap* db_id_cache() { return &predictor_->db_id_cache_; } 211 212 static int maximum_days_to_keep_entry() { 213 return AutocompleteActionPredictor::kMaximumDaysToKeepEntry; 214 } 215 216 private: 217 base::MessageLoop loop_; 218 content::TestBrowserThread ui_thread_; 219 content::TestBrowserThread db_thread_; 220 content::TestBrowserThread file_thread_; 221 scoped_ptr<TestingProfile> profile_; 222 scoped_ptr<AutocompleteActionPredictor> predictor_; 223 }; 224 225 226 TEST_F(AutocompleteActionPredictorTest, AddRow) { 227 // Add a test entry to the predictor. 228 std::string guid = AddRow(test_url_db[0]); 229 230 // Get the data back out of the cache. 231 const DBCacheKey key = { test_url_db[0].user_text, test_url_db[0].url }; 232 DBCacheMap::const_iterator it = db_cache()->find(key); 233 EXPECT_TRUE(it != db_cache()->end()); 234 235 const DBCacheValue value = { test_url_db[0].number_of_hits, 236 test_url_db[0].number_of_misses }; 237 EXPECT_EQ(value.number_of_hits, it->second.number_of_hits); 238 EXPECT_EQ(value.number_of_misses, it->second.number_of_misses); 239 240 DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key); 241 EXPECT_TRUE(id_it != db_id_cache()->end()); 242 EXPECT_EQ(guid, id_it->second); 243 } 244 245 TEST_F(AutocompleteActionPredictorTest, UpdateRow) { 246 ASSERT_NO_FATAL_FAILURE(AddAllRows()); 247 248 EXPECT_EQ(arraysize(test_url_db), db_cache()->size()); 249 EXPECT_EQ(arraysize(test_url_db), db_id_cache()->size()); 250 251 // Get the data back out of the cache. 252 const DBCacheKey key = { test_url_db[0].user_text, test_url_db[0].url }; 253 DBCacheMap::const_iterator it = db_cache()->find(key); 254 EXPECT_TRUE(it != db_cache()->end()); 255 256 DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key); 257 EXPECT_TRUE(id_it != db_id_cache()->end()); 258 259 AutocompleteActionPredictorTable::Row update_row; 260 update_row.id = id_it->second; 261 update_row.user_text = key.user_text; 262 update_row.url = key.url; 263 update_row.number_of_hits = it->second.number_of_hits + 1; 264 update_row.number_of_misses = it->second.number_of_misses + 2; 265 266 UpdateRow(update_row); 267 268 // Get the updated version. 269 DBCacheMap::const_iterator update_it = db_cache()->find(key); 270 EXPECT_TRUE(update_it != db_cache()->end()); 271 272 EXPECT_EQ(update_row.number_of_hits, update_it->second.number_of_hits); 273 EXPECT_EQ(update_row.number_of_misses, update_it->second.number_of_misses); 274 275 DBIdCacheMap::const_iterator update_id_it = db_id_cache()->find(key); 276 EXPECT_TRUE(update_id_it != db_id_cache()->end()); 277 278 EXPECT_EQ(id_it->second, update_id_it->second); 279 } 280 281 TEST_F(AutocompleteActionPredictorTest, DeleteAllRows) { 282 ASSERT_NO_FATAL_FAILURE(AddAllRows()); 283 284 EXPECT_EQ(arraysize(test_url_db), db_cache()->size()); 285 EXPECT_EQ(arraysize(test_url_db), db_id_cache()->size()); 286 287 DeleteAllRows(); 288 289 EXPECT_TRUE(db_cache()->empty()); 290 EXPECT_TRUE(db_id_cache()->empty()); 291 } 292 293 TEST_F(AutocompleteActionPredictorTest, DeleteRowsWithURLs) { 294 ASSERT_NO_FATAL_FAILURE(AddAllRows()); 295 296 EXPECT_EQ(arraysize(test_url_db), db_cache()->size()); 297 EXPECT_EQ(arraysize(test_url_db), db_id_cache()->size()); 298 299 history::URLRows rows; 300 for (size_t i = 0; i < 2; ++i) 301 rows.push_back(history::URLRow(test_url_db[i].url)); 302 303 DeleteRowsWithURLs(rows); 304 305 EXPECT_EQ(arraysize(test_url_db) - 2, db_cache()->size()); 306 EXPECT_EQ(arraysize(test_url_db) - 2, db_id_cache()->size()); 307 308 for (size_t i = 0; i < arraysize(test_url_db); ++i) { 309 DBCacheKey key = { test_url_db[i].user_text, test_url_db[i].url }; 310 311 bool deleted = (i < 2); 312 EXPECT_EQ(deleted, db_cache()->find(key) == db_cache()->end()); 313 EXPECT_EQ(deleted, db_id_cache()->find(key) == db_id_cache()->end()); 314 } 315 } 316 317 TEST_F(AutocompleteActionPredictorTest, DeleteOldIdsFromCaches) { 318 std::vector<AutocompleteActionPredictorTable::Row::Id> expected; 319 std::vector<AutocompleteActionPredictorTable::Row::Id> all_ids; 320 321 for (size_t i = 0; i < arraysize(test_url_db); ++i) { 322 std::string row_id = AddRow(test_url_db[i]); 323 all_ids.push_back(row_id); 324 325 bool exclude_url = StartsWithASCII(test_url_db[i].url.path(), "/d", true) || 326 (test_url_db[i].days_from_now > maximum_days_to_keep_entry()); 327 328 if (exclude_url) 329 expected.push_back(row_id); 330 else 331 ASSERT_TRUE(AddRowToHistory(test_url_db[i])); 332 } 333 334 std::vector<AutocompleteActionPredictorTable::Row::Id> id_list; 335 DeleteOldIdsFromCaches(&id_list); 336 EXPECT_EQ(expected.size(), id_list.size()); 337 EXPECT_EQ(all_ids.size() - expected.size(), db_cache()->size()); 338 EXPECT_EQ(all_ids.size() - expected.size(), db_id_cache()->size()); 339 340 for (std::vector<AutocompleteActionPredictorTable::Row::Id>::iterator it = 341 all_ids.begin(); 342 it != all_ids.end(); ++it) { 343 bool in_expected = 344 (std::find(expected.begin(), expected.end(), *it) != expected.end()); 345 bool in_list = 346 (std::find(id_list.begin(), id_list.end(), *it) != id_list.end()); 347 EXPECT_EQ(in_expected, in_list); 348 } 349 } 350 351 TEST_F(AutocompleteActionPredictorTest, RecommendActionURL) { 352 ASSERT_NO_FATAL_FAILURE(AddAllRows()); 353 354 AutocompleteMatch match; 355 match.type = AutocompleteMatchType::HISTORY_URL; 356 357 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_url_db); ++i) { 358 match.destination_url = GURL(test_url_db[i].url); 359 EXPECT_EQ(test_url_db[i].expected_action, 360 predictor()->RecommendAction(test_url_db[i].user_text, match)) 361 << "Unexpected action for " << match.destination_url; 362 } 363 } 364 365 TEST_F(AutocompleteActionPredictorTest, RecommendActionSearch) { 366 ASSERT_NO_FATAL_FAILURE(AddAllRows()); 367 368 AutocompleteMatch match; 369 match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED; 370 371 for (size_t i = 0; i < arraysize(test_url_db); ++i) { 372 match.destination_url = GURL(test_url_db[i].url); 373 AutocompleteActionPredictor::Action expected_action = 374 (test_url_db[i].expected_action == 375 AutocompleteActionPredictor::ACTION_PRERENDER) ? 376 AutocompleteActionPredictor::ACTION_PRECONNECT : 377 test_url_db[i].expected_action; 378 EXPECT_EQ(expected_action, 379 predictor()->RecommendAction(test_url_db[i].user_text, match)) 380 << "Unexpected action for " << match.destination_url; 381 } 382 } 383 384 } // namespace predictors 385