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/autocomplete/autocomplete_result.h" 6 7 #include "base/memory/scoped_ptr.h" 8 #include "base/metrics/field_trial.h" 9 #include "base/strings/string_number_conversions.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "chrome/browser/autocomplete/autocomplete_input.h" 13 #include "chrome/browser/autocomplete/autocomplete_match.h" 14 #include "chrome/browser/autocomplete/autocomplete_provider.h" 15 #include "chrome/browser/omnibox/omnibox_field_trial.h" 16 #include "chrome/browser/search_engines/template_url_prepopulate_data.h" 17 #include "chrome/browser/search_engines/template_url_service.h" 18 #include "chrome/browser/search_engines/template_url_service_test_util.h" 19 #include "chrome/common/autocomplete_match_type.h" 20 #include "chrome/common/metrics/entropy_provider.h" 21 #include "chrome/common/metrics/variations/variations_util.h" 22 #include "chrome/test/base/testing_profile.h" 23 #include "testing/gtest/include/gtest/gtest.h" 24 25 class AutocompleteResultTest : public testing::Test { 26 public: 27 struct TestData { 28 // Used to build a url for the AutocompleteMatch. The URL becomes 29 // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://b"). 30 int url_id; 31 32 // ID of the provider. 33 int provider_id; 34 35 // Relevance score. 36 int relevance; 37 }; 38 39 AutocompleteResultTest() { 40 // Destroy the existing FieldTrialList before creating a new one to avoid 41 // a DCHECK. 42 field_trial_list_.reset(); 43 field_trial_list_.reset(new base::FieldTrialList( 44 new metrics::SHA1EntropyProvider("foo"))); 45 chrome_variations::testing::ClearAllVariationParams(); 46 } 47 48 virtual void SetUp() OVERRIDE { 49 #if defined(OS_ANDROID) 50 TemplateURLPrepopulateData::InitCountryCode( 51 std::string() /* unknown country code */); 52 #endif 53 test_util_.SetUp(); 54 test_util_.VerifyLoad(); 55 } 56 57 virtual void TearDown() OVERRIDE { 58 test_util_.TearDown(); 59 } 60 61 // Configures |match| from |data|. 62 static void PopulateAutocompleteMatch(const TestData& data, 63 AutocompleteMatch* match); 64 65 // Adds |count| AutocompleteMatches to |matches|. 66 static void PopulateAutocompleteMatches(const TestData* data, 67 size_t count, 68 ACMatches* matches); 69 70 // Asserts that |result| has |expected_count| matches matching |expected|. 71 void AssertResultMatches(const AutocompleteResult& result, 72 const TestData* expected, 73 size_t expected_count); 74 75 // Creates an AutocompleteResult from |last| and |current|. The two are 76 // merged by |CopyOldMatches| and compared by |AssertResultMatches|. 77 void RunCopyOldMatchesTest(const TestData* last, size_t last_size, 78 const TestData* current, size_t current_size, 79 const TestData* expected, size_t expected_size); 80 81 protected: 82 TemplateURLServiceTestUtil test_util_; 83 84 private: 85 scoped_ptr<base::FieldTrialList> field_trial_list_; 86 87 DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest); 88 }; 89 90 // static 91 void AutocompleteResultTest::PopulateAutocompleteMatch( 92 const TestData& data, 93 AutocompleteMatch* match) { 94 match->provider = reinterpret_cast<AutocompleteProvider*>(data.provider_id); 95 match->fill_into_edit = base::IntToString16(data.url_id); 96 std::string url_id(1, data.url_id + 'a'); 97 match->destination_url = GURL("http://" + url_id); 98 match->relevance = data.relevance; 99 match->allowed_to_be_default_match = true; 100 } 101 102 // static 103 void AutocompleteResultTest::PopulateAutocompleteMatches( 104 const TestData* data, 105 size_t count, 106 ACMatches* matches) { 107 for (size_t i = 0; i < count; ++i) { 108 AutocompleteMatch match; 109 PopulateAutocompleteMatch(data[i], &match); 110 matches->push_back(match); 111 } 112 } 113 114 void AutocompleteResultTest::AssertResultMatches( 115 const AutocompleteResult& result, 116 const TestData* expected, 117 size_t expected_count) { 118 ASSERT_EQ(expected_count, result.size()); 119 for (size_t i = 0; i < expected_count; ++i) { 120 AutocompleteMatch expected_match; 121 PopulateAutocompleteMatch(expected[i], &expected_match); 122 const AutocompleteMatch& match = *(result.begin() + i); 123 EXPECT_EQ(expected_match.provider, match.provider) << i; 124 EXPECT_EQ(expected_match.relevance, match.relevance) << i; 125 EXPECT_EQ(expected_match.destination_url.spec(), 126 match.destination_url.spec()) << i; 127 } 128 } 129 130 void AutocompleteResultTest::RunCopyOldMatchesTest( 131 const TestData* last, size_t last_size, 132 const TestData* current, size_t current_size, 133 const TestData* expected, size_t expected_size) { 134 AutocompleteInput input(ASCIIToUTF16("a"), string16::npos, string16(), GURL(), 135 AutocompleteInput::INVALID_SPEC, false, false, false, 136 AutocompleteInput::ALL_MATCHES); 137 138 ACMatches last_matches; 139 PopulateAutocompleteMatches(last, last_size, &last_matches); 140 AutocompleteResult last_result; 141 last_result.AppendMatches(last_matches); 142 last_result.SortAndCull(input, test_util_.profile()); 143 144 ACMatches current_matches; 145 PopulateAutocompleteMatches(current, current_size, ¤t_matches); 146 AutocompleteResult current_result; 147 current_result.AppendMatches(current_matches); 148 current_result.SortAndCull(input, test_util_.profile()); 149 current_result.CopyOldMatches(input, last_result, test_util_.profile()); 150 151 AssertResultMatches(current_result, expected, expected_size); 152 } 153 154 // Assertion testing for AutocompleteResult::Swap. 155 TEST_F(AutocompleteResultTest, Swap) { 156 AutocompleteResult r1; 157 AutocompleteResult r2; 158 159 // Swap with empty shouldn't do anything interesting. 160 r1.Swap(&r2); 161 EXPECT_EQ(r1.end(), r1.default_match()); 162 EXPECT_EQ(r2.end(), r2.default_match()); 163 164 // Swap with a single match. 165 ACMatches matches; 166 AutocompleteMatch match; 167 match.relevance = 1; 168 match.allowed_to_be_default_match = true; 169 AutocompleteInput input(ASCIIToUTF16("a"), string16::npos, string16(), GURL(), 170 AutocompleteInput::INVALID_SPEC, false, false, false, 171 AutocompleteInput::ALL_MATCHES); 172 matches.push_back(match); 173 r1.AppendMatches(matches); 174 r1.SortAndCull(input, test_util_.profile()); 175 EXPECT_EQ(r1.begin(), r1.default_match()); 176 EXPECT_EQ("http://a/", r1.alternate_nav_url().spec()); 177 r1.Swap(&r2); 178 EXPECT_TRUE(r1.empty()); 179 EXPECT_EQ(r1.end(), r1.default_match()); 180 EXPECT_TRUE(r1.alternate_nav_url().is_empty()); 181 ASSERT_FALSE(r2.empty()); 182 EXPECT_EQ(r2.begin(), r2.default_match()); 183 EXPECT_EQ("http://a/", r2.alternate_nav_url().spec()); 184 } 185 186 // Tests that if the new results have a lower max relevance score than last, 187 // any copied results have their relevance shifted down. 188 TEST_F(AutocompleteResultTest, CopyOldMatches) { 189 TestData last[] = { 190 { 0, 0, 1000 }, 191 { 1, 0, 500 }, 192 }; 193 TestData current[] = { 194 { 2, 0, 400 }, 195 }; 196 TestData result[] = { 197 { 2, 0, 400 }, 198 { 1, 0, 399 }, 199 }; 200 201 ASSERT_NO_FATAL_FAILURE( 202 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last), 203 current, ARRAYSIZE_UNSAFE(current), 204 result, ARRAYSIZE_UNSAFE(result))); 205 } 206 207 // Tests that matches are copied correctly from two distinct providers. 208 TEST_F(AutocompleteResultTest, CopyOldMatches2) { 209 TestData last[] = { 210 { 0, 0, 1000 }, 211 { 1, 1, 500 }, 212 { 2, 0, 400 }, 213 { 3, 1, 300 }, 214 }; 215 TestData current[] = { 216 { 4, 0, 1100 }, 217 { 5, 1, 550 }, 218 }; 219 TestData result[] = { 220 { 4, 0, 1100 }, 221 { 5, 1, 550 }, 222 { 2, 0, 400 }, 223 { 3, 1, 300 }, 224 }; 225 226 ASSERT_NO_FATAL_FAILURE( 227 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last), 228 current, ARRAYSIZE_UNSAFE(current), 229 result, ARRAYSIZE_UNSAFE(result))); 230 } 231 232 // Tests that matches with empty destination URLs aren't treated as duplicates 233 // and culled. 234 TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) { 235 TestData data[] = { 236 { 1, 0, 500 }, 237 { 0, 0, 1100 }, 238 { 1, 0, 1000 }, 239 { 0, 0, 1300 }, 240 { 0, 0, 1200 }, 241 }; 242 243 ACMatches matches; 244 PopulateAutocompleteMatches(data, arraysize(data), &matches); 245 matches[1].destination_url = GURL(); 246 matches[3].destination_url = GURL(); 247 matches[4].destination_url = GURL(); 248 249 AutocompleteResult result; 250 result.AppendMatches(matches); 251 AutocompleteInput input(string16(), string16::npos, string16(), GURL(), 252 AutocompleteInput::INVALID_SPEC, false, false, false, 253 AutocompleteInput::ALL_MATCHES); 254 result.SortAndCull(input, test_util_.profile()); 255 256 // Of the two results with the same non-empty destination URL, the 257 // lower-relevance one should be dropped. All of the results with empty URLs 258 // should be kept. 259 ASSERT_EQ(4U, result.size()); 260 EXPECT_TRUE(result.match_at(0)->destination_url.is_empty()); 261 EXPECT_EQ(1300, result.match_at(0)->relevance); 262 EXPECT_TRUE(result.match_at(1)->destination_url.is_empty()); 263 EXPECT_EQ(1200, result.match_at(1)->relevance); 264 EXPECT_TRUE(result.match_at(2)->destination_url.is_empty()); 265 EXPECT_EQ(1100, result.match_at(2)->relevance); 266 EXPECT_EQ("http://b/", result.match_at(3)->destination_url.spec()); 267 EXPECT_EQ(1000, result.match_at(3)->relevance); 268 } 269 270 TEST_F(AutocompleteResultTest, SortAndCullDuplicateSearchURLs) { 271 // Register a template URL that corresponds to 'foo' search engine. 272 TemplateURLData url_data; 273 url_data.short_name = ASCIIToUTF16("unittest"); 274 url_data.SetKeyword(ASCIIToUTF16("foo")); 275 url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); 276 test_util_.model()->Add(new TemplateURL(test_util_.profile(), url_data)); 277 278 TestData data[] = { 279 { 0, 0, 1300 }, 280 { 1, 0, 1200 }, 281 { 2, 0, 1100 }, 282 { 3, 0, 1000 }, 283 { 4, 1, 900 }, 284 }; 285 286 ACMatches matches; 287 PopulateAutocompleteMatches(data, arraysize(data), &matches); 288 matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); 289 matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); 290 matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); 291 matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); 292 matches[4].destination_url = GURL("http://www.foo.com/"); 293 294 AutocompleteResult result; 295 result.AppendMatches(matches); 296 AutocompleteInput input(string16(), string16::npos, string16(), GURL(), 297 AutocompleteInput::INVALID_SPEC, false, false, false, 298 AutocompleteInput::ALL_MATCHES); 299 result.SortAndCull(input, test_util_.profile()); 300 301 // We expect the 3rd and 4th results to be removed. 302 ASSERT_EQ(3U, result.size()); 303 EXPECT_EQ("http://www.foo.com/s?q=foo", 304 result.match_at(0)->destination_url.spec()); 305 EXPECT_EQ(1300, result.match_at(0)->relevance); 306 EXPECT_EQ("http://www.foo.com/s?q=foo2", 307 result.match_at(1)->destination_url.spec()); 308 EXPECT_EQ(1200, result.match_at(1)->relevance); 309 EXPECT_EQ("http://www.foo.com/", 310 result.match_at(2)->destination_url.spec()); 311 EXPECT_EQ(900, result.match_at(2)->relevance); 312 } 313 314 TEST_F(AutocompleteResultTest, SortAndCullWithDemotionsByType) { 315 // Add some matches. 316 ACMatches matches; 317 { 318 AutocompleteMatch match; 319 match.destination_url = GURL("http://history-url/"); 320 match.relevance = 1400; 321 match.allowed_to_be_default_match = true; 322 match.type = AutocompleteMatchType::HISTORY_URL; 323 matches.push_back(match); 324 } 325 { 326 AutocompleteMatch match; 327 match.destination_url = GURL("http://search-what-you-typed/"); 328 match.relevance = 1300; 329 match.allowed_to_be_default_match = true; 330 match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED; 331 matches.push_back(match); 332 } 333 { 334 AutocompleteMatch match; 335 match.destination_url = GURL("http://history-title/"); 336 match.relevance = 1200; 337 match.allowed_to_be_default_match = true; 338 match.type = AutocompleteMatchType::HISTORY_TITLE; 339 matches.push_back(match); 340 } 341 { 342 AutocompleteMatch match; 343 match.destination_url = GURL("http://search-history/"); 344 match.relevance = 500; 345 match.allowed_to_be_default_match = true; 346 match.type = AutocompleteMatchType::SEARCH_HISTORY; 347 matches.push_back(match); 348 } 349 350 // Add a rule demoting history-url and killing history-title. 351 { 352 std::map<std::string, std::string> params; 353 params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":3:*"] = 354 "1:50,7:100,2:0"; // 3 == HOMEPAGE 355 ASSERT_TRUE(chrome_variations::AssociateVariationParams( 356 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params)); 357 } 358 base::FieldTrialList::CreateFieldTrial( 359 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A"); 360 361 AutocompleteResult result; 362 result.AppendMatches(matches); 363 AutocompleteInput input(string16(), string16::npos, string16(), GURL(), 364 AutocompleteInput::HOMEPAGE, false, false, false, 365 AutocompleteInput::ALL_MATCHES); 366 result.SortAndCull(input, test_util_.profile()); 367 368 // Check the new ordering. The history-title results should be omitted. 369 // We cannot check relevance scores because the matches are sorted by 370 // demoted relevance but the actual relevance scores are not modified. 371 ASSERT_EQ(3u, result.size()); 372 EXPECT_EQ("http://search-what-you-typed/", 373 result.match_at(0)->destination_url.spec()); 374 EXPECT_EQ("http://history-url/", 375 result.match_at(1)->destination_url.spec()); 376 EXPECT_EQ("http://search-history/", 377 result.match_at(2)->destination_url.spec()); 378 } 379 380 TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) { 381 TestData data[] = { 382 { 0, 0, 1300 }, 383 { 1, 0, 1200 }, 384 { 2, 0, 1100 }, 385 { 3, 0, 1000 } 386 }; 387 388 std::map<std::string, std::string> params; 389 // Enable reorder for omnibox inputs on the user's homepage. 390 params[std::string(OmniboxFieldTrial::kReorderForLegalDefaultMatchRule) + 391 ":3:*"] = OmniboxFieldTrial::kReorderForLegalDefaultMatchRuleEnabled; 392 ASSERT_TRUE(chrome_variations::AssociateVariationParams( 393 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params)); 394 base::FieldTrialList::CreateFieldTrial( 395 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A"); 396 397 { 398 // Check that reorder doesn't do anything if the top result 399 // is already a legal default match (which is the default from 400 // PopulateAutocompleteMatches()). 401 ACMatches matches; 402 PopulateAutocompleteMatches(data, arraysize(data), &matches); 403 AutocompleteResult result; 404 result.AppendMatches(matches); 405 AutocompleteInput input(string16(), string16::npos, string16(), GURL(), 406 AutocompleteInput::HOMEPAGE, false, false, false, 407 AutocompleteInput::ALL_MATCHES); 408 result.SortAndCull(input, test_util_.profile()); 409 AssertResultMatches(result, data, 4); 410 } 411 412 { 413 // Check that reorder swaps up a result appropriately. 414 ACMatches matches; 415 PopulateAutocompleteMatches(data, arraysize(data), &matches); 416 matches[0].allowed_to_be_default_match = false; 417 matches[1].allowed_to_be_default_match = false; 418 AutocompleteResult result; 419 result.AppendMatches(matches); 420 AutocompleteInput input(string16(), string16::npos, string16(), GURL(), 421 AutocompleteInput::HOMEPAGE, false, false, false, 422 AutocompleteInput::ALL_MATCHES); 423 result.SortAndCull(input, test_util_.profile()); 424 ASSERT_EQ(4U, result.size()); 425 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); 426 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); 427 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); 428 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); 429 } 430 } 431