| OLD | NEW |
| (Empty) |
| 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 <vector> | |
| 8 | |
| 9 #include "base/memory/scoped_ptr.h" | |
| 10 #include "base/metrics/field_trial.h" | |
| 11 #include "base/strings/string_number_conversions.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "base/strings/utf_string_conversions.h" | |
| 14 #include "components/metrics/proto/omnibox_event.pb.h" | |
| 15 #include "components/omnibox/autocomplete_input.h" | |
| 16 #include "components/omnibox/autocomplete_match.h" | |
| 17 #include "components/omnibox/autocomplete_match_type.h" | |
| 18 #include "components/omnibox/autocomplete_provider.h" | |
| 19 #include "components/omnibox/omnibox_field_trial.h" | |
| 20 #include "components/omnibox/test_scheme_classifier.h" | |
| 21 #include "components/search_engines/template_url_prepopulate_data.h" | |
| 22 #include "components/search_engines/template_url_service.h" | |
| 23 #include "components/variations/entropy_provider.h" | |
| 24 #include "components/variations/variations_associated_data.h" | |
| 25 #include "testing/gtest/include/gtest/gtest.h" | |
| 26 | |
| 27 using metrics::OmniboxEventProto; | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 struct AutocompleteMatchTestData { | |
| 32 std::string destination_url; | |
| 33 AutocompleteMatch::Type type; | |
| 34 }; | |
| 35 | |
| 36 const AutocompleteMatchTestData kVerbatimMatches[] = { | |
| 37 { "http://search-what-you-typed/", | |
| 38 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, | |
| 39 { "http://url-what-you-typed/", AutocompleteMatchType::URL_WHAT_YOU_TYPED }, | |
| 40 }; | |
| 41 | |
| 42 const AutocompleteMatchTestData kNonVerbatimMatches[] = { | |
| 43 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY }, | |
| 44 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE }, | |
| 45 }; | |
| 46 | |
| 47 // Adds |count| AutocompleteMatches to |matches|. | |
| 48 void PopulateAutocompleteMatchesFromTestData( | |
| 49 const AutocompleteMatchTestData* data, | |
| 50 size_t count, | |
| 51 ACMatches* matches) { | |
| 52 ASSERT_TRUE(matches != NULL); | |
| 53 for (size_t i = 0; i < count; ++i) { | |
| 54 AutocompleteMatch match; | |
| 55 match.destination_url = GURL(data[i].destination_url); | |
| 56 match.relevance = | |
| 57 matches->empty() ? 1300 : (matches->back().relevance - 100); | |
| 58 match.allowed_to_be_default_match = true; | |
| 59 match.type = data[i].type; | |
| 60 matches->push_back(match); | |
| 61 } | |
| 62 } | |
| 63 | |
| 64 } // namespace | |
| 65 | |
| 66 class AutocompleteResultTest : public testing::Test { | |
| 67 public: | |
| 68 struct TestData { | |
| 69 // Used to build a url for the AutocompleteMatch. The URL becomes | |
| 70 // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://b"). | |
| 71 int url_id; | |
| 72 | |
| 73 // ID of the provider. | |
| 74 int provider_id; | |
| 75 | |
| 76 // Relevance score. | |
| 77 int relevance; | |
| 78 | |
| 79 // Duplicate matches. | |
| 80 std::vector<AutocompleteMatch> duplicate_matches; | |
| 81 }; | |
| 82 | |
| 83 AutocompleteResultTest() { | |
| 84 // Destroy the existing FieldTrialList before creating a new one to avoid | |
| 85 // a DCHECK. | |
| 86 field_trial_list_.reset(); | |
| 87 field_trial_list_.reset(new base::FieldTrialList( | |
| 88 new metrics::SHA1EntropyProvider("foo"))); | |
| 89 variations::testing::ClearAllVariationParams(); | |
| 90 } | |
| 91 | |
| 92 virtual void SetUp() OVERRIDE { | |
| 93 #if defined(OS_ANDROID) | |
| 94 TemplateURLPrepopulateData::InitCountryCode( | |
| 95 std::string() /* unknown country code */); | |
| 96 #endif | |
| 97 template_url_service_.reset(new TemplateURLService(NULL, 0)); | |
| 98 template_url_service_->Load(); | |
| 99 } | |
| 100 | |
| 101 // Configures |match| from |data|. | |
| 102 static void PopulateAutocompleteMatch(const TestData& data, | |
| 103 AutocompleteMatch* match); | |
| 104 | |
| 105 // Adds |count| AutocompleteMatches to |matches|. | |
| 106 static void PopulateAutocompleteMatches(const TestData* data, | |
| 107 size_t count, | |
| 108 ACMatches* matches); | |
| 109 | |
| 110 // Asserts that |result| has |expected_count| matches matching |expected|. | |
| 111 void AssertResultMatches(const AutocompleteResult& result, | |
| 112 const TestData* expected, | |
| 113 size_t expected_count); | |
| 114 | |
| 115 // Creates an AutocompleteResult from |last| and |current|. The two are | |
| 116 // merged by |CopyOldMatches| and compared by |AssertResultMatches|. | |
| 117 void RunCopyOldMatchesTest(const TestData* last, size_t last_size, | |
| 118 const TestData* current, size_t current_size, | |
| 119 const TestData* expected, size_t expected_size); | |
| 120 | |
| 121 protected: | |
| 122 scoped_ptr<TemplateURLService> template_url_service_; | |
| 123 | |
| 124 private: | |
| 125 scoped_ptr<base::FieldTrialList> field_trial_list_; | |
| 126 | |
| 127 DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest); | |
| 128 }; | |
| 129 | |
| 130 // static | |
| 131 void AutocompleteResultTest::PopulateAutocompleteMatch( | |
| 132 const TestData& data, | |
| 133 AutocompleteMatch* match) { | |
| 134 match->provider = reinterpret_cast<AutocompleteProvider*>(data.provider_id); | |
| 135 match->fill_into_edit = base::IntToString16(data.url_id); | |
| 136 std::string url_id(1, data.url_id + 'a'); | |
| 137 match->destination_url = GURL("http://" + url_id); | |
| 138 match->relevance = data.relevance; | |
| 139 match->allowed_to_be_default_match = true; | |
| 140 match->duplicate_matches = data.duplicate_matches; | |
| 141 } | |
| 142 | |
| 143 // static | |
| 144 void AutocompleteResultTest::PopulateAutocompleteMatches( | |
| 145 const TestData* data, | |
| 146 size_t count, | |
| 147 ACMatches* matches) { | |
| 148 for (size_t i = 0; i < count; ++i) { | |
| 149 AutocompleteMatch match; | |
| 150 PopulateAutocompleteMatch(data[i], &match); | |
| 151 matches->push_back(match); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 void AutocompleteResultTest::AssertResultMatches( | |
| 156 const AutocompleteResult& result, | |
| 157 const TestData* expected, | |
| 158 size_t expected_count) { | |
| 159 ASSERT_EQ(expected_count, result.size()); | |
| 160 for (size_t i = 0; i < expected_count; ++i) { | |
| 161 AutocompleteMatch expected_match; | |
| 162 PopulateAutocompleteMatch(expected[i], &expected_match); | |
| 163 const AutocompleteMatch& match = *(result.begin() + i); | |
| 164 EXPECT_EQ(expected_match.provider, match.provider) << i; | |
| 165 EXPECT_EQ(expected_match.relevance, match.relevance) << i; | |
| 166 EXPECT_EQ(expected_match.destination_url.spec(), | |
| 167 match.destination_url.spec()) << i; | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 void AutocompleteResultTest::RunCopyOldMatchesTest( | |
| 172 const TestData* last, size_t last_size, | |
| 173 const TestData* current, size_t current_size, | |
| 174 const TestData* expected, size_t expected_size) { | |
| 175 AutocompleteInput input(base::ASCIIToUTF16("a"), base::string16::npos, | |
| 176 base::string16(), GURL(), | |
| 177 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
| 178 true, | |
| 179 TestSchemeClassifier()); | |
| 180 | |
| 181 ACMatches last_matches; | |
| 182 PopulateAutocompleteMatches(last, last_size, &last_matches); | |
| 183 AutocompleteResult last_result; | |
| 184 last_result.AppendMatches(last_matches); | |
| 185 last_result.SortAndCull(input, template_url_service_.get()); | |
| 186 | |
| 187 ACMatches current_matches; | |
| 188 PopulateAutocompleteMatches(current, current_size, ¤t_matches); | |
| 189 AutocompleteResult current_result; | |
| 190 current_result.AppendMatches(current_matches); | |
| 191 current_result.SortAndCull(input, template_url_service_.get()); | |
| 192 current_result.CopyOldMatches( | |
| 193 input, last_result, template_url_service_.get()); | |
| 194 | |
| 195 AssertResultMatches(current_result, expected, expected_size); | |
| 196 } | |
| 197 | |
| 198 // Assertion testing for AutocompleteResult::Swap. | |
| 199 TEST_F(AutocompleteResultTest, Swap) { | |
| 200 AutocompleteResult r1; | |
| 201 AutocompleteResult r2; | |
| 202 | |
| 203 // Swap with empty shouldn't do anything interesting. | |
| 204 r1.Swap(&r2); | |
| 205 EXPECT_EQ(r1.end(), r1.default_match()); | |
| 206 EXPECT_EQ(r2.end(), r2.default_match()); | |
| 207 | |
| 208 // Swap with a single match. | |
| 209 ACMatches matches; | |
| 210 AutocompleteMatch match; | |
| 211 match.relevance = 1; | |
| 212 match.allowed_to_be_default_match = true; | |
| 213 AutocompleteInput input(base::ASCIIToUTF16("a"), base::string16::npos, | |
| 214 base::string16(), GURL(), | |
| 215 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
| 216 true, TestSchemeClassifier()); | |
| 217 matches.push_back(match); | |
| 218 r1.AppendMatches(matches); | |
| 219 r1.SortAndCull(input, template_url_service_.get()); | |
| 220 EXPECT_EQ(r1.begin(), r1.default_match()); | |
| 221 EXPECT_EQ("http://a/", r1.alternate_nav_url().spec()); | |
| 222 r1.Swap(&r2); | |
| 223 EXPECT_TRUE(r1.empty()); | |
| 224 EXPECT_EQ(r1.end(), r1.default_match()); | |
| 225 EXPECT_TRUE(r1.alternate_nav_url().is_empty()); | |
| 226 ASSERT_FALSE(r2.empty()); | |
| 227 EXPECT_EQ(r2.begin(), r2.default_match()); | |
| 228 EXPECT_EQ("http://a/", r2.alternate_nav_url().spec()); | |
| 229 } | |
| 230 | |
| 231 // Tests that if the new results have a lower max relevance score than last, | |
| 232 // any copied results have their relevance shifted down. | |
| 233 TEST_F(AutocompleteResultTest, CopyOldMatches) { | |
| 234 TestData last[] = { | |
| 235 { 0, 0, 1000 }, | |
| 236 { 1, 0, 500 }, | |
| 237 }; | |
| 238 TestData current[] = { | |
| 239 { 2, 0, 400 }, | |
| 240 }; | |
| 241 TestData result[] = { | |
| 242 { 2, 0, 400 }, | |
| 243 { 1, 0, 399 }, | |
| 244 }; | |
| 245 | |
| 246 ASSERT_NO_FATAL_FAILURE( | |
| 247 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last), | |
| 248 current, ARRAYSIZE_UNSAFE(current), | |
| 249 result, ARRAYSIZE_UNSAFE(result))); | |
| 250 } | |
| 251 | |
| 252 // Tests that matches are copied correctly from two distinct providers. | |
| 253 TEST_F(AutocompleteResultTest, CopyOldMatches2) { | |
| 254 TestData last[] = { | |
| 255 { 0, 0, 1000 }, | |
| 256 { 1, 1, 500 }, | |
| 257 { 2, 0, 400 }, | |
| 258 { 3, 1, 300 }, | |
| 259 }; | |
| 260 TestData current[] = { | |
| 261 { 4, 0, 1100 }, | |
| 262 { 5, 1, 550 }, | |
| 263 }; | |
| 264 TestData result[] = { | |
| 265 { 4, 0, 1100 }, | |
| 266 { 5, 1, 550 }, | |
| 267 { 2, 0, 400 }, | |
| 268 { 3, 1, 300 }, | |
| 269 }; | |
| 270 | |
| 271 ASSERT_NO_FATAL_FAILURE( | |
| 272 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last), | |
| 273 current, ARRAYSIZE_UNSAFE(current), | |
| 274 result, ARRAYSIZE_UNSAFE(result))); | |
| 275 } | |
| 276 | |
| 277 // Tests that matches with empty destination URLs aren't treated as duplicates | |
| 278 // and culled. | |
| 279 TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) { | |
| 280 TestData data[] = { | |
| 281 { 1, 0, 500 }, | |
| 282 { 0, 0, 1100 }, | |
| 283 { 1, 0, 1000 }, | |
| 284 { 0, 0, 1300 }, | |
| 285 { 0, 0, 1200 }, | |
| 286 }; | |
| 287 | |
| 288 ACMatches matches; | |
| 289 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 290 matches[1].destination_url = GURL(); | |
| 291 matches[3].destination_url = GURL(); | |
| 292 matches[4].destination_url = GURL(); | |
| 293 | |
| 294 AutocompleteResult result; | |
| 295 result.AppendMatches(matches); | |
| 296 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 297 base::string16(), GURL(), | |
| 298 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
| 299 true, | |
| 300 TestSchemeClassifier()); | |
| 301 result.SortAndCull(input, template_url_service_.get()); | |
| 302 | |
| 303 // Of the two results with the same non-empty destination URL, the | |
| 304 // lower-relevance one should be dropped. All of the results with empty URLs | |
| 305 // should be kept. | |
| 306 ASSERT_EQ(4U, result.size()); | |
| 307 EXPECT_TRUE(result.match_at(0)->destination_url.is_empty()); | |
| 308 EXPECT_EQ(1300, result.match_at(0)->relevance); | |
| 309 EXPECT_TRUE(result.match_at(1)->destination_url.is_empty()); | |
| 310 EXPECT_EQ(1200, result.match_at(1)->relevance); | |
| 311 EXPECT_TRUE(result.match_at(2)->destination_url.is_empty()); | |
| 312 EXPECT_EQ(1100, result.match_at(2)->relevance); | |
| 313 EXPECT_EQ("http://b/", result.match_at(3)->destination_url.spec()); | |
| 314 EXPECT_EQ(1000, result.match_at(3)->relevance); | |
| 315 } | |
| 316 | |
| 317 TEST_F(AutocompleteResultTest, SortAndCullDuplicateSearchURLs) { | |
| 318 // Register a template URL that corresponds to 'foo' search engine. | |
| 319 TemplateURLData url_data; | |
| 320 url_data.short_name = base::ASCIIToUTF16("unittest"); | |
| 321 url_data.SetKeyword(base::ASCIIToUTF16("foo")); | |
| 322 url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | |
| 323 template_url_service_.get()->Add(new TemplateURL(url_data)); | |
| 324 | |
| 325 TestData data[] = { | |
| 326 { 0, 0, 1300 }, | |
| 327 { 1, 0, 1200 }, | |
| 328 { 2, 0, 1100 }, | |
| 329 { 3, 0, 1000 }, | |
| 330 { 4, 1, 900 }, | |
| 331 }; | |
| 332 | |
| 333 ACMatches matches; | |
| 334 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 335 matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | |
| 336 matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | |
| 337 matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | |
| 338 matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | |
| 339 matches[4].destination_url = GURL("http://www.foo.com/"); | |
| 340 | |
| 341 AutocompleteResult result; | |
| 342 result.AppendMatches(matches); | |
| 343 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 344 base::string16(), GURL(), | |
| 345 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
| 346 true, | |
| 347 TestSchemeClassifier()); | |
| 348 result.SortAndCull(input, template_url_service_.get()); | |
| 349 | |
| 350 // We expect the 3rd and 4th results to be removed. | |
| 351 ASSERT_EQ(3U, result.size()); | |
| 352 EXPECT_EQ("http://www.foo.com/s?q=foo", | |
| 353 result.match_at(0)->destination_url.spec()); | |
| 354 EXPECT_EQ(1300, result.match_at(0)->relevance); | |
| 355 EXPECT_EQ("http://www.foo.com/s?q=foo2", | |
| 356 result.match_at(1)->destination_url.spec()); | |
| 357 EXPECT_EQ(1200, result.match_at(1)->relevance); | |
| 358 EXPECT_EQ("http://www.foo.com/", | |
| 359 result.match_at(2)->destination_url.spec()); | |
| 360 EXPECT_EQ(900, result.match_at(2)->relevance); | |
| 361 } | |
| 362 | |
| 363 TEST_F(AutocompleteResultTest, SortAndCullWithMatchDups) { | |
| 364 // Register a template URL that corresponds to 'foo' search engine. | |
| 365 TemplateURLData url_data; | |
| 366 url_data.short_name = base::ASCIIToUTF16("unittest"); | |
| 367 url_data.SetKeyword(base::ASCIIToUTF16("foo")); | |
| 368 url_data.SetURL("http://www.foo.com/s?q={searchTerms}"); | |
| 369 template_url_service_.get()->Add(new TemplateURL(url_data)); | |
| 370 | |
| 371 AutocompleteMatch dup_match; | |
| 372 dup_match.destination_url = GURL("http://www.foo.com/s?q=foo&oq=dup"); | |
| 373 std::vector<AutocompleteMatch> dups; | |
| 374 dups.push_back(dup_match); | |
| 375 | |
| 376 TestData data[] = { | |
| 377 { 0, 0, 1300, dups }, | |
| 378 { 1, 0, 1200 }, | |
| 379 { 2, 0, 1100 }, | |
| 380 { 3, 0, 1000, dups }, | |
| 381 { 4, 1, 900 }, | |
| 382 { 5, 0, 800 }, | |
| 383 }; | |
| 384 | |
| 385 ACMatches matches; | |
| 386 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 387 matches[0].destination_url = GURL("http://www.foo.com/s?q=foo"); | |
| 388 matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2"); | |
| 389 matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f"); | |
| 390 matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0"); | |
| 391 matches[4].destination_url = GURL("http://www.foo.com/"); | |
| 392 matches[5].destination_url = GURL("http://www.foo.com/s?q=foo2&oq=f"); | |
| 393 | |
| 394 AutocompleteResult result; | |
| 395 result.AppendMatches(matches); | |
| 396 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 397 base::string16(), GURL(), | |
| 398 OmniboxEventProto::INVALID_SPEC, false, false, false, | |
| 399 true, | |
| 400 TestSchemeClassifier()); | |
| 401 result.SortAndCull(input, template_url_service_.get()); | |
| 402 | |
| 403 // Expect 3 unique results after SortAndCull(). | |
| 404 ASSERT_EQ(3U, result.size()); | |
| 405 | |
| 406 // Check that 3rd and 4th result got added to the first result as dups | |
| 407 // and also duplicates of the 4th match got copied. | |
| 408 ASSERT_EQ(4U, result.match_at(0)->duplicate_matches.size()); | |
| 409 const AutocompleteMatch* first_match = result.match_at(0); | |
| 410 EXPECT_EQ(matches[2].destination_url, | |
| 411 first_match->duplicate_matches.at(1).destination_url); | |
| 412 EXPECT_EQ(dup_match.destination_url, | |
| 413 first_match->duplicate_matches.at(2).destination_url); | |
| 414 EXPECT_EQ(matches[3].destination_url, | |
| 415 first_match->duplicate_matches.at(3).destination_url); | |
| 416 | |
| 417 // Check that 6th result started a new list of dups for the second result. | |
| 418 ASSERT_EQ(1U, result.match_at(1)->duplicate_matches.size()); | |
| 419 EXPECT_EQ(matches[5].destination_url, | |
| 420 result.match_at(1)->duplicate_matches.at(0).destination_url); | |
| 421 } | |
| 422 | |
| 423 TEST_F(AutocompleteResultTest, SortAndCullWithDemotionsByType) { | |
| 424 // Add some matches. | |
| 425 ACMatches matches; | |
| 426 const AutocompleteMatchTestData data[] = { | |
| 427 { "http://history-url/", AutocompleteMatchType::HISTORY_URL }, | |
| 428 { "http://search-what-you-typed/", | |
| 429 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, | |
| 430 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE }, | |
| 431 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY }, | |
| 432 }; | |
| 433 PopulateAutocompleteMatchesFromTestData(data, arraysize(data), &matches); | |
| 434 | |
| 435 // Demote the search history match relevance score. | |
| 436 matches.back().relevance = 500; | |
| 437 | |
| 438 // Add a rule demoting history-url and killing history-title. | |
| 439 { | |
| 440 std::map<std::string, std::string> params; | |
| 441 params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":3:*"] = | |
| 442 "1:50,7:100,2:0"; // 3 == HOME_PAGE | |
| 443 ASSERT_TRUE(variations::AssociateVariationParams( | |
| 444 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params)); | |
| 445 } | |
| 446 base::FieldTrialList::CreateFieldTrial( | |
| 447 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A"); | |
| 448 | |
| 449 AutocompleteResult result; | |
| 450 result.AppendMatches(matches); | |
| 451 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 452 base::string16(), GURL(), | |
| 453 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 454 true, | |
| 455 TestSchemeClassifier()); | |
| 456 result.SortAndCull(input, template_url_service_.get()); | |
| 457 | |
| 458 // Check the new ordering. The history-title results should be omitted. | |
| 459 // We cannot check relevance scores because the matches are sorted by | |
| 460 // demoted relevance but the actual relevance scores are not modified. | |
| 461 ASSERT_EQ(3u, result.size()); | |
| 462 EXPECT_EQ("http://search-what-you-typed/", | |
| 463 result.match_at(0)->destination_url.spec()); | |
| 464 EXPECT_EQ("http://history-url/", | |
| 465 result.match_at(1)->destination_url.spec()); | |
| 466 EXPECT_EQ("http://search-history/", | |
| 467 result.match_at(2)->destination_url.spec()); | |
| 468 } | |
| 469 | |
| 470 TEST_F(AutocompleteResultTest, SortAndCullWithMatchDupsAndDemotionsByType) { | |
| 471 // Add some matches. | |
| 472 ACMatches matches; | |
| 473 const AutocompleteMatchTestData data[] = { | |
| 474 { "http://search-what-you-typed/", | |
| 475 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, | |
| 476 { "http://dup-url/", AutocompleteMatchType::HISTORY_URL }, | |
| 477 { "http://dup-url/", AutocompleteMatchType::NAVSUGGEST }, | |
| 478 { "http://search-url/", AutocompleteMatchType::SEARCH_SUGGEST }, | |
| 479 { "http://history-url/", AutocompleteMatchType::HISTORY_URL }, | |
| 480 }; | |
| 481 PopulateAutocompleteMatchesFromTestData(data, arraysize(data), &matches); | |
| 482 | |
| 483 // Add a rule demoting HISTORY_URL. | |
| 484 { | |
| 485 std::map<std::string, std::string> params; | |
| 486 params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":8:*"] = | |
| 487 "1:50"; // 8 == INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS | |
| 488 ASSERT_TRUE(variations::AssociateVariationParams( | |
| 489 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "C", params)); | |
| 490 } | |
| 491 base::FieldTrialList::CreateFieldTrial( | |
| 492 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "C"); | |
| 493 | |
| 494 { | |
| 495 AutocompleteResult result; | |
| 496 result.AppendMatches(matches); | |
| 497 AutocompleteInput input( | |
| 498 base::string16(), base::string16::npos, base::string16(), GURL(), | |
| 499 OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS, false, | |
| 500 false, false, true, | |
| 501 TestSchemeClassifier()); | |
| 502 result.SortAndCull(input, template_url_service_.get()); | |
| 503 | |
| 504 // The NAVSUGGEST dup-url stay above search-url since the navsuggest | |
| 505 // variant should not be demoted. | |
| 506 ASSERT_EQ(4u, result.size()); | |
| 507 EXPECT_EQ("http://search-what-you-typed/", | |
| 508 result.match_at(0)->destination_url.spec()); | |
| 509 EXPECT_EQ("http://dup-url/", | |
| 510 result.match_at(1)->destination_url.spec()); | |
| 511 EXPECT_EQ(AutocompleteMatchType::NAVSUGGEST, | |
| 512 result.match_at(1)->type); | |
| 513 EXPECT_EQ("http://search-url/", | |
| 514 result.match_at(2)->destination_url.spec()); | |
| 515 EXPECT_EQ("http://history-url/", | |
| 516 result.match_at(3)->destination_url.spec()); | |
| 517 } | |
| 518 } | |
| 519 | |
| 520 TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) { | |
| 521 TestData data[] = { | |
| 522 { 0, 0, 1300 }, | |
| 523 { 1, 0, 1200 }, | |
| 524 { 2, 0, 1100 }, | |
| 525 { 3, 0, 1000 } | |
| 526 }; | |
| 527 | |
| 528 { | |
| 529 // Check that reorder doesn't do anything if the top result | |
| 530 // is already a legal default match (which is the default from | |
| 531 // PopulateAutocompleteMatches()). | |
| 532 ACMatches matches; | |
| 533 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 534 AutocompleteResult result; | |
| 535 result.AppendMatches(matches); | |
| 536 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 537 base::string16(), GURL(), | |
| 538 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 539 true, | |
| 540 TestSchemeClassifier()); | |
| 541 result.SortAndCull(input, template_url_service_.get()); | |
| 542 AssertResultMatches(result, data, 4); | |
| 543 } | |
| 544 | |
| 545 { | |
| 546 // Check that reorder swaps up a result appropriately. | |
| 547 ACMatches matches; | |
| 548 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 549 matches[0].allowed_to_be_default_match = false; | |
| 550 matches[1].allowed_to_be_default_match = false; | |
| 551 AutocompleteResult result; | |
| 552 result.AppendMatches(matches); | |
| 553 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 554 base::string16(), GURL(), | |
| 555 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 556 true, | |
| 557 TestSchemeClassifier()); | |
| 558 result.SortAndCull(input, template_url_service_.get()); | |
| 559 ASSERT_EQ(4U, result.size()); | |
| 560 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | |
| 561 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
| 562 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | |
| 563 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
| 564 } | |
| 565 } | |
| 566 | |
| 567 | |
| 568 | |
| 569 TEST_F(AutocompleteResultTest, SortAndCullWithDisableInlining) { | |
| 570 TestData data[] = { | |
| 571 { 0, 0, 1300 }, | |
| 572 { 1, 0, 1200 }, | |
| 573 { 2, 0, 1100 }, | |
| 574 { 3, 0, 1000 } | |
| 575 }; | |
| 576 | |
| 577 { | |
| 578 // Check that with the field trial disabled, we keep keep the first match | |
| 579 // first even if it has an inline autocompletion. | |
| 580 ACMatches matches; | |
| 581 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 582 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 583 AutocompleteResult result; | |
| 584 result.AppendMatches(matches); | |
| 585 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 586 base::string16(), GURL(), | |
| 587 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 588 true, | |
| 589 TestSchemeClassifier()); | |
| 590 result.SortAndCull(input, template_url_service_.get()); | |
| 591 AssertResultMatches(result, data, 4); | |
| 592 } | |
| 593 | |
| 594 // Enable the field trial to disable inlining. | |
| 595 { | |
| 596 std::map<std::string, std::string> params; | |
| 597 params[OmniboxFieldTrial::kDisableInliningRule] = "true"; | |
| 598 ASSERT_TRUE(variations::AssociateVariationParams( | |
| 599 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "D", params)); | |
| 600 } | |
| 601 base::FieldTrialList::CreateFieldTrial( | |
| 602 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "D"); | |
| 603 | |
| 604 { | |
| 605 // Now the first match should be demoted past the second. | |
| 606 ACMatches matches; | |
| 607 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 608 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 609 AutocompleteResult result; | |
| 610 result.AppendMatches(matches); | |
| 611 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 612 base::string16(), GURL(), | |
| 613 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 614 true, | |
| 615 TestSchemeClassifier()); | |
| 616 result.SortAndCull(input, template_url_service_.get()); | |
| 617 ASSERT_EQ(4U, result.size()); | |
| 618 EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec()); | |
| 619 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
| 620 EXPECT_EQ("http://c/", result.match_at(2)->destination_url.spec()); | |
| 621 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
| 622 } | |
| 623 | |
| 624 { | |
| 625 // But if there was no inline autocompletion on the first match, then | |
| 626 // the order should stay the same. This is true even if there are | |
| 627 // inline autocompletions elsewhere. | |
| 628 ACMatches matches; | |
| 629 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 630 matches[2].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 631 AutocompleteResult result; | |
| 632 result.AppendMatches(matches); | |
| 633 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 634 base::string16(), GURL(), | |
| 635 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 636 true, | |
| 637 TestSchemeClassifier()); | |
| 638 result.SortAndCull(input, template_url_service_.get()); | |
| 639 AssertResultMatches(result, data, 4); | |
| 640 } | |
| 641 | |
| 642 { | |
| 643 // Try a more complicated situation. | |
| 644 ACMatches matches; | |
| 645 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 646 matches[0].allowed_to_be_default_match = false; | |
| 647 matches[1].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 648 AutocompleteResult result; | |
| 649 result.AppendMatches(matches); | |
| 650 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 651 base::string16(), GURL(), | |
| 652 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 653 true, | |
| 654 TestSchemeClassifier()); | |
| 655 result.SortAndCull(input, template_url_service_.get()); | |
| 656 ASSERT_EQ(4U, result.size()); | |
| 657 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | |
| 658 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
| 659 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | |
| 660 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
| 661 } | |
| 662 | |
| 663 { | |
| 664 // Try another complicated situation. | |
| 665 ACMatches matches; | |
| 666 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 667 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 668 matches[1].allowed_to_be_default_match = false; | |
| 669 AutocompleteResult result; | |
| 670 result.AppendMatches(matches); | |
| 671 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 672 base::string16(), GURL(), | |
| 673 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 674 true, | |
| 675 TestSchemeClassifier()); | |
| 676 result.SortAndCull(input, template_url_service_.get()); | |
| 677 ASSERT_EQ(4U, result.size()); | |
| 678 EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec()); | |
| 679 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
| 680 EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec()); | |
| 681 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
| 682 } | |
| 683 | |
| 684 { | |
| 685 // Check that disaster doesn't strike if we can't demote the top inline | |
| 686 // autocompletion because every match either has a completion or isn't | |
| 687 // allowed to be the default match. In this case, we should leave | |
| 688 // everything untouched. | |
| 689 ACMatches matches; | |
| 690 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 691 matches[0].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 692 matches[1].allowed_to_be_default_match = false; | |
| 693 matches[2].allowed_to_be_default_match = false; | |
| 694 matches[3].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 695 AutocompleteResult result; | |
| 696 result.AppendMatches(matches); | |
| 697 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 698 base::string16(), GURL(), | |
| 699 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 700 true, | |
| 701 TestSchemeClassifier()); | |
| 702 result.SortAndCull(input, template_url_service_.get()); | |
| 703 AssertResultMatches(result, data, 4); | |
| 704 } | |
| 705 | |
| 706 { | |
| 707 // Check a similar situation, except in this case the top match is not | |
| 708 // allowed to the default match, so it still needs to be demoted so we | |
| 709 // get a legal default match first. That match will have an inline | |
| 710 // autocompletion because we don't have any better options. | |
| 711 ACMatches matches; | |
| 712 PopulateAutocompleteMatches(data, arraysize(data), &matches); | |
| 713 matches[0].allowed_to_be_default_match = false; | |
| 714 matches[1].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 715 matches[2].allowed_to_be_default_match = false; | |
| 716 matches[3].inline_autocompletion = base::ASCIIToUTF16("completion"); | |
| 717 AutocompleteResult result; | |
| 718 result.AppendMatches(matches); | |
| 719 AutocompleteInput input(base::string16(), base::string16::npos, | |
| 720 base::string16(), GURL(), | |
| 721 OmniboxEventProto::HOME_PAGE, false, false, false, | |
| 722 true, | |
| 723 TestSchemeClassifier()); | |
| 724 result.SortAndCull(input, template_url_service_.get()); | |
| 725 ASSERT_EQ(4U, result.size()); | |
| 726 EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec()); | |
| 727 EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec()); | |
| 728 EXPECT_EQ("http://c/", result.match_at(2)->destination_url.spec()); | |
| 729 EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec()); | |
| 730 } | |
| 731 } | |
| 732 | |
| 733 TEST_F(AutocompleteResultTest, ShouldHideTopMatch) { | |
| 734 base::FieldTrialList::CreateFieldTrial("InstantExtended", | |
| 735 "Group1 hide_verbatim:1"); | |
| 736 ACMatches matches; | |
| 737 | |
| 738 // Case 1: Top match is a verbatim match. | |
| 739 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
| 740 AutocompleteResult result; | |
| 741 result.AppendMatches(matches); | |
| 742 EXPECT_TRUE(result.ShouldHideTopMatch()); | |
| 743 matches.clear(); | |
| 744 result.Reset(); | |
| 745 | |
| 746 // Case 2: If the verbatim first match is followed by another verbatim match, | |
| 747 // don't hide the top verbatim match. | |
| 748 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
| 749 arraysize(kVerbatimMatches), | |
| 750 &matches); | |
| 751 result.AppendMatches(matches); | |
| 752 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
| 753 matches.clear(); | |
| 754 result.Reset(); | |
| 755 | |
| 756 // Case 3: Top match is not a verbatim match. Do not hide the top match. | |
| 757 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
| 758 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
| 759 arraysize(kVerbatimMatches), | |
| 760 &matches); | |
| 761 result.AppendMatches(matches); | |
| 762 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
| 763 } | |
| 764 | |
| 765 TEST_F(AutocompleteResultTest, ShouldHideTopMatchAfterCopy) { | |
| 766 base::FieldTrialList::CreateFieldTrial("InstantExtended", | |
| 767 "Group1 hide_verbatim:1"); | |
| 768 ACMatches matches; | |
| 769 | |
| 770 // Case 1: Top match is a verbatim match followed by only copied matches. | |
| 771 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
| 772 arraysize(kVerbatimMatches), | |
| 773 &matches); | |
| 774 for (size_t i = 1; i < arraysize(kVerbatimMatches); ++i) | |
| 775 matches[i].from_previous = true; | |
| 776 AutocompleteResult result; | |
| 777 result.AppendMatches(matches); | |
| 778 EXPECT_TRUE(result.ShouldHideTopMatch()); | |
| 779 result.Reset(); | |
| 780 | |
| 781 // Case 2: The copied matches are then followed by a non-verbatim match. | |
| 782 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
| 783 result.AppendMatches(matches); | |
| 784 EXPECT_TRUE(result.ShouldHideTopMatch()); | |
| 785 result.Reset(); | |
| 786 | |
| 787 // Case 3: The copied matches are instead followed by a verbatim match. | |
| 788 matches.back().from_previous = true; | |
| 789 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
| 790 result.AppendMatches(matches); | |
| 791 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
| 792 } | |
| 793 | |
| 794 TEST_F(AutocompleteResultTest, DoNotHideTopMatch_FieldTrialFlagDisabled) { | |
| 795 // This test config is identical to ShouldHideTopMatch test ("Case 1") except | |
| 796 // that the "hide_verbatim" flag is disabled in the field trials. | |
| 797 base::FieldTrialList::CreateFieldTrial("InstantExtended", | |
| 798 "Group1 hide_verbatim:0"); | |
| 799 ACMatches matches; | |
| 800 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
| 801 AutocompleteResult result; | |
| 802 result.AppendMatches(matches); | |
| 803 // Field trial flag "hide_verbatim" is disabled. Do not hide top match. | |
| 804 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
| 805 } | |
| 806 | |
| 807 TEST_F(AutocompleteResultTest, TopMatchIsStandaloneVerbatimMatch) { | |
| 808 ACMatches matches; | |
| 809 AutocompleteResult result; | |
| 810 result.AppendMatches(matches); | |
| 811 | |
| 812 // Case 1: Result set is empty. | |
| 813 EXPECT_FALSE(result.TopMatchIsStandaloneVerbatimMatch()); | |
| 814 | |
| 815 // Case 2: Top match is not a verbatim match. | |
| 816 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
| 817 result.AppendMatches(matches); | |
| 818 EXPECT_FALSE(result.TopMatchIsStandaloneVerbatimMatch()); | |
| 819 result.Reset(); | |
| 820 matches.clear(); | |
| 821 | |
| 822 // Case 3: Top match is a verbatim match. | |
| 823 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
| 824 result.AppendMatches(matches); | |
| 825 EXPECT_TRUE(result.TopMatchIsStandaloneVerbatimMatch()); | |
| 826 result.Reset(); | |
| 827 matches.clear(); | |
| 828 | |
| 829 // Case 4: Standalone verbatim match found in AutocompleteResult. | |
| 830 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches); | |
| 831 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches); | |
| 832 result.AppendMatches(matches); | |
| 833 EXPECT_TRUE(result.TopMatchIsStandaloneVerbatimMatch()); | |
| 834 result.Reset(); | |
| 835 matches.clear(); | |
| 836 | |
| 837 // Case 5: Multiple verbatim matches found in AutocompleteResult. | |
| 838 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, | |
| 839 arraysize(kVerbatimMatches), | |
| 840 &matches); | |
| 841 result.AppendMatches(matches); | |
| 842 EXPECT_FALSE(result.ShouldHideTopMatch()); | |
| 843 } | |
| OLD | NEW |