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.
5 #include "chrome/browser/autocomplete/autocomplete_result.h"
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/variations/variations_util.h"
21 #include "chrome/test/base/testing_profile.h"
22 #include "components/variations/entropy_provider.h"
23 #include "testing/gtest/include/gtest/gtest.h"
27 struct AutocompleteMatchTestData
{
28 std::string destination_url
;
29 AutocompleteMatch::Type type
;
32 const AutocompleteMatchTestData kVerbatimMatches
[] = {
33 { "http://search-what-you-typed/",
34 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
},
35 { "http://url-what-you-typed/", AutocompleteMatchType::URL_WHAT_YOU_TYPED
},
38 const AutocompleteMatchTestData kNonVerbatimMatches
[] = {
39 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY
},
40 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE
},
43 // Adds |count| AutocompleteMatches to |matches|.
44 void PopulateAutocompleteMatchesFromTestData(
45 const AutocompleteMatchTestData
* data
,
48 ASSERT_TRUE(matches
!= NULL
);
49 for (size_t i
= 0; i
< count
; ++i
) {
50 AutocompleteMatch match
;
51 match
.destination_url
= GURL(data
[i
].destination_url
);
53 matches
->empty() ? 1300 : (matches
->back().relevance
- 100);
54 match
.allowed_to_be_default_match
= true;
55 match
.type
= data
[i
].type
;
56 matches
->push_back(match
);
62 class AutocompleteResultTest
: public testing::Test
{
65 // Used to build a url for the AutocompleteMatch. The URL becomes
66 // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://b").
69 // ID of the provider.
76 AutocompleteResultTest() {
77 // Destroy the existing FieldTrialList before creating a new one to avoid
79 field_trial_list_
.reset();
80 field_trial_list_
.reset(new base::FieldTrialList(
81 new metrics::SHA1EntropyProvider("foo")));
82 chrome_variations::testing::ClearAllVariationParams();
85 virtual void SetUp() OVERRIDE
{
86 #if defined(OS_ANDROID)
87 TemplateURLPrepopulateData::InitCountryCode(
88 std::string() /* unknown country code */);
91 test_util_
.VerifyLoad();
94 virtual void TearDown() OVERRIDE
{
95 test_util_
.TearDown();
98 // Configures |match| from |data|.
99 static void PopulateAutocompleteMatch(const TestData
& data
,
100 AutocompleteMatch
* match
);
102 // Adds |count| AutocompleteMatches to |matches|.
103 static void PopulateAutocompleteMatches(const TestData
* data
,
107 // Asserts that |result| has |expected_count| matches matching |expected|.
108 void AssertResultMatches(const AutocompleteResult
& result
,
109 const TestData
* expected
,
110 size_t expected_count
);
112 // Creates an AutocompleteResult from |last| and |current|. The two are
113 // merged by |CopyOldMatches| and compared by |AssertResultMatches|.
114 void RunCopyOldMatchesTest(const TestData
* last
, size_t last_size
,
115 const TestData
* current
, size_t current_size
,
116 const TestData
* expected
, size_t expected_size
);
119 TemplateURLServiceTestUtil test_util_
;
122 scoped_ptr
<base::FieldTrialList
> field_trial_list_
;
124 DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest
);
128 void AutocompleteResultTest::PopulateAutocompleteMatch(
129 const TestData
& data
,
130 AutocompleteMatch
* match
) {
131 match
->provider
= reinterpret_cast<AutocompleteProvider
*>(data
.provider_id
);
132 match
->fill_into_edit
= base::IntToString16(data
.url_id
);
133 std::string
url_id(1, data
.url_id
+ 'a');
134 match
->destination_url
= GURL("http://" + url_id
);
135 match
->relevance
= data
.relevance
;
136 match
->allowed_to_be_default_match
= true;
140 void AutocompleteResultTest::PopulateAutocompleteMatches(
141 const TestData
* data
,
143 ACMatches
* matches
) {
144 for (size_t i
= 0; i
< count
; ++i
) {
145 AutocompleteMatch match
;
146 PopulateAutocompleteMatch(data
[i
], &match
);
147 matches
->push_back(match
);
151 void AutocompleteResultTest::AssertResultMatches(
152 const AutocompleteResult
& result
,
153 const TestData
* expected
,
154 size_t expected_count
) {
155 ASSERT_EQ(expected_count
, result
.size());
156 for (size_t i
= 0; i
< expected_count
; ++i
) {
157 AutocompleteMatch expected_match
;
158 PopulateAutocompleteMatch(expected
[i
], &expected_match
);
159 const AutocompleteMatch
& match
= *(result
.begin() + i
);
160 EXPECT_EQ(expected_match
.provider
, match
.provider
) << i
;
161 EXPECT_EQ(expected_match
.relevance
, match
.relevance
) << i
;
162 EXPECT_EQ(expected_match
.destination_url
.spec(),
163 match
.destination_url
.spec()) << i
;
167 void AutocompleteResultTest::RunCopyOldMatchesTest(
168 const TestData
* last
, size_t last_size
,
169 const TestData
* current
, size_t current_size
,
170 const TestData
* expected
, size_t expected_size
) {
171 AutocompleteInput
input(base::ASCIIToUTF16("a"), base::string16::npos
,
172 base::string16(), GURL(),
173 AutocompleteInput::INVALID_SPEC
, false, false, false,
174 AutocompleteInput::ALL_MATCHES
);
176 ACMatches last_matches
;
177 PopulateAutocompleteMatches(last
, last_size
, &last_matches
);
178 AutocompleteResult last_result
;
179 last_result
.AppendMatches(last_matches
);
180 last_result
.SortAndCull(input
, test_util_
.profile());
182 ACMatches current_matches
;
183 PopulateAutocompleteMatches(current
, current_size
, ¤t_matches
);
184 AutocompleteResult current_result
;
185 current_result
.AppendMatches(current_matches
);
186 current_result
.SortAndCull(input
, test_util_
.profile());
187 current_result
.CopyOldMatches(input
, last_result
, test_util_
.profile());
189 AssertResultMatches(current_result
, expected
, expected_size
);
192 // Assertion testing for AutocompleteResult::Swap.
193 TEST_F(AutocompleteResultTest
, Swap
) {
194 AutocompleteResult r1
;
195 AutocompleteResult r2
;
197 // Swap with empty shouldn't do anything interesting.
199 EXPECT_EQ(r1
.end(), r1
.default_match());
200 EXPECT_EQ(r2
.end(), r2
.default_match());
202 // Swap with a single match.
204 AutocompleteMatch match
;
206 match
.allowed_to_be_default_match
= true;
207 AutocompleteInput
input(base::ASCIIToUTF16("a"), base::string16::npos
,
208 base::string16(), GURL(),
209 AutocompleteInput::INVALID_SPEC
, false, false, false,
210 AutocompleteInput::ALL_MATCHES
);
211 matches
.push_back(match
);
212 r1
.AppendMatches(matches
);
213 r1
.SortAndCull(input
, test_util_
.profile());
214 EXPECT_EQ(r1
.begin(), r1
.default_match());
215 EXPECT_EQ("http://a/", r1
.alternate_nav_url().spec());
217 EXPECT_TRUE(r1
.empty());
218 EXPECT_EQ(r1
.end(), r1
.default_match());
219 EXPECT_TRUE(r1
.alternate_nav_url().is_empty());
220 ASSERT_FALSE(r2
.empty());
221 EXPECT_EQ(r2
.begin(), r2
.default_match());
222 EXPECT_EQ("http://a/", r2
.alternate_nav_url().spec());
225 // Tests that if the new results have a lower max relevance score than last,
226 // any copied results have their relevance shifted down.
227 TEST_F(AutocompleteResultTest
, CopyOldMatches
) {
232 TestData current
[] = {
235 TestData result
[] = {
240 ASSERT_NO_FATAL_FAILURE(
241 RunCopyOldMatchesTest(last
, ARRAYSIZE_UNSAFE(last
),
242 current
, ARRAYSIZE_UNSAFE(current
),
243 result
, ARRAYSIZE_UNSAFE(result
)));
246 // Tests that matches are copied correctly from two distinct providers.
247 TEST_F(AutocompleteResultTest
, CopyOldMatches2
) {
254 TestData current
[] = {
258 TestData result
[] = {
265 ASSERT_NO_FATAL_FAILURE(
266 RunCopyOldMatchesTest(last
, ARRAYSIZE_UNSAFE(last
),
267 current
, ARRAYSIZE_UNSAFE(current
),
268 result
, ARRAYSIZE_UNSAFE(result
)));
271 // Tests that matches with empty destination URLs aren't treated as duplicates
273 TEST_F(AutocompleteResultTest
, SortAndCullEmptyDestinationURLs
) {
283 PopulateAutocompleteMatches(data
, arraysize(data
), &matches
);
284 matches
[1].destination_url
= GURL();
285 matches
[3].destination_url
= GURL();
286 matches
[4].destination_url
= GURL();
288 AutocompleteResult result
;
289 result
.AppendMatches(matches
);
290 AutocompleteInput
input(base::string16(), base::string16::npos
,
291 base::string16(), GURL(),
292 AutocompleteInput::INVALID_SPEC
, false, false, false,
293 AutocompleteInput::ALL_MATCHES
);
294 result
.SortAndCull(input
, test_util_
.profile());
296 // Of the two results with the same non-empty destination URL, the
297 // lower-relevance one should be dropped. All of the results with empty URLs
299 ASSERT_EQ(4U, result
.size());
300 EXPECT_TRUE(result
.match_at(0)->destination_url
.is_empty());
301 EXPECT_EQ(1300, result
.match_at(0)->relevance
);
302 EXPECT_TRUE(result
.match_at(1)->destination_url
.is_empty());
303 EXPECT_EQ(1200, result
.match_at(1)->relevance
);
304 EXPECT_TRUE(result
.match_at(2)->destination_url
.is_empty());
305 EXPECT_EQ(1100, result
.match_at(2)->relevance
);
306 EXPECT_EQ("http://b/", result
.match_at(3)->destination_url
.spec());
307 EXPECT_EQ(1000, result
.match_at(3)->relevance
);
310 TEST_F(AutocompleteResultTest
, SortAndCullDuplicateSearchURLs
) {
311 // Register a template URL that corresponds to 'foo' search engine.
312 TemplateURLData url_data
;
313 url_data
.short_name
= base::ASCIIToUTF16("unittest");
314 url_data
.SetKeyword(base::ASCIIToUTF16("foo"));
315 url_data
.SetURL("http://www.foo.com/s?q={searchTerms}");
316 test_util_
.model()->Add(new TemplateURL(test_util_
.profile(), url_data
));
327 PopulateAutocompleteMatches(data
, arraysize(data
), &matches
);
328 matches
[0].destination_url
= GURL("http://www.foo.com/s?q=foo");
329 matches
[1].destination_url
= GURL("http://www.foo.com/s?q=foo2");
330 matches
[2].destination_url
= GURL("http://www.foo.com/s?q=foo&oq=f");
331 matches
[3].destination_url
= GURL("http://www.foo.com/s?q=foo&aqs=0");
332 matches
[4].destination_url
= GURL("http://www.foo.com/");
334 AutocompleteResult result
;
335 result
.AppendMatches(matches
);
336 AutocompleteInput
input(base::string16(), base::string16::npos
,
337 base::string16(), GURL(),
338 AutocompleteInput::INVALID_SPEC
, false, false, false,
339 AutocompleteInput::ALL_MATCHES
);
340 result
.SortAndCull(input
, test_util_
.profile());
342 // We expect the 3rd and 4th results to be removed.
343 ASSERT_EQ(3U, result
.size());
344 EXPECT_EQ("http://www.foo.com/s?q=foo",
345 result
.match_at(0)->destination_url
.spec());
346 EXPECT_EQ(1300, result
.match_at(0)->relevance
);
347 EXPECT_EQ("http://www.foo.com/s?q=foo2",
348 result
.match_at(1)->destination_url
.spec());
349 EXPECT_EQ(1200, result
.match_at(1)->relevance
);
350 EXPECT_EQ("http://www.foo.com/",
351 result
.match_at(2)->destination_url
.spec());
352 EXPECT_EQ(900, result
.match_at(2)->relevance
);
355 TEST_F(AutocompleteResultTest
, SortAndCullWithDemotionsByType
) {
358 const AutocompleteMatchTestData data
[] = {
359 { "http://history-url/", AutocompleteMatchType::HISTORY_URL
},
360 { "http://search-what-you-typed/",
361 AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
},
362 { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE
},
363 { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY
},
365 PopulateAutocompleteMatchesFromTestData(data
, arraysize(data
), &matches
);
367 // Demote the search history match relevance score.
368 matches
.back().relevance
= 500;
370 // Add a rule demoting history-url and killing history-title.
372 std::map
<std::string
, std::string
> params
;
373 params
[std::string(OmniboxFieldTrial::kDemoteByTypeRule
) + ":3:*"] =
374 "1:50,7:100,2:0"; // 3 == HOME_PAGE
375 ASSERT_TRUE(chrome_variations::AssociateVariationParams(
376 OmniboxFieldTrial::kBundledExperimentFieldTrialName
, "A", params
));
378 base::FieldTrialList::CreateFieldTrial(
379 OmniboxFieldTrial::kBundledExperimentFieldTrialName
, "A");
381 AutocompleteResult result
;
382 result
.AppendMatches(matches
);
383 AutocompleteInput
input(base::string16(), base::string16::npos
,
384 base::string16(), GURL(),
385 AutocompleteInput::HOME_PAGE
, false, false, false,
386 AutocompleteInput::ALL_MATCHES
);
387 result
.SortAndCull(input
, test_util_
.profile());
389 // Check the new ordering. The history-title results should be omitted.
390 // We cannot check relevance scores because the matches are sorted by
391 // demoted relevance but the actual relevance scores are not modified.
392 ASSERT_EQ(3u, result
.size());
393 EXPECT_EQ("http://search-what-you-typed/",
394 result
.match_at(0)->destination_url
.spec());
395 EXPECT_EQ("http://history-url/",
396 result
.match_at(1)->destination_url
.spec());
397 EXPECT_EQ("http://search-history/",
398 result
.match_at(2)->destination_url
.spec());
401 TEST_F(AutocompleteResultTest
, SortAndCullWithUndemotableTypes
) {
403 ACMatches
matches(3);
404 matches
[0].destination_url
= GURL("http://top-history-url/");
405 matches
[0].relevance
= 1400;
406 matches
[0].allowed_to_be_default_match
= true;
407 matches
[0].type
= AutocompleteMatchType::HISTORY_URL
;
408 matches
[1].destination_url
= GURL("http://history-url2/");
409 matches
[1].relevance
= 1300;
410 matches
[1].allowed_to_be_default_match
= true;
411 matches
[1].type
= AutocompleteMatchType::HISTORY_URL
;
412 matches
[2].destination_url
= GURL("http://search-what-you-typed/");
413 matches
[2].relevance
= 1200;
414 matches
[2].allowed_to_be_default_match
= true;
415 matches
[2].type
= AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
;
417 // Add a rule demoting history-url, but don't demote the top match.
419 std::map
<std::string
, std::string
> params
;
421 params
[std::string(OmniboxFieldTrial::kDemoteByTypeRule
) + ":3:*"] =
423 params
[std::string(OmniboxFieldTrial::kUndemotableTopTypeRule
) + ":3:*"] =
425 ASSERT_TRUE(chrome_variations::AssociateVariationParams(
426 OmniboxFieldTrial::kBundledExperimentFieldTrialName
, "B", params
));
428 base::FieldTrialList::CreateFieldTrial(
429 OmniboxFieldTrial::kBundledExperimentFieldTrialName
, "B");
431 AutocompleteResult result
;
432 result
.AppendMatches(matches
);
433 AutocompleteInput
input(base::string16(), base::string16::npos
,
434 base::string16(), GURL(),
435 AutocompleteInput::HOME_PAGE
, false, false, false,
436 AutocompleteInput::ALL_MATCHES
);
437 result
.SortAndCull(input
, test_util_
.profile());
439 // Check the new ordering. The first history-url result should not be
440 // demoted, but the second result should be.
441 // We cannot check relevance scores because the matches are sorted by
442 // demoted relevance but the actual relevance scores are not modified.
443 ASSERT_EQ(3u, result
.size());
444 EXPECT_EQ("http://top-history-url/",
445 result
.match_at(0)->destination_url
.spec());
446 EXPECT_EQ("http://search-what-you-typed/",
447 result
.match_at(1)->destination_url
.spec());
448 EXPECT_EQ("http://history-url2/",
449 result
.match_at(2)->destination_url
.spec());
452 TEST_F(AutocompleteResultTest
, SortAndCullReorderForDefaultMatch
) {
460 std::map
<std::string
, std::string
> params
;
461 // Enable reorder for omnibox inputs on the user's home page.
462 params
[std::string(OmniboxFieldTrial::kReorderForLegalDefaultMatchRule
) +
463 ":3:*"] = OmniboxFieldTrial::kReorderForLegalDefaultMatchRuleEnabled
;
464 ASSERT_TRUE(chrome_variations::AssociateVariationParams(
465 OmniboxFieldTrial::kBundledExperimentFieldTrialName
, "A", params
));
466 base::FieldTrialList::CreateFieldTrial(
467 OmniboxFieldTrial::kBundledExperimentFieldTrialName
, "A");
470 // Check that reorder doesn't do anything if the top result
471 // is already a legal default match (which is the default from
472 // PopulateAutocompleteMatches()).
474 PopulateAutocompleteMatches(data
, arraysize(data
), &matches
);
475 AutocompleteResult result
;
476 result
.AppendMatches(matches
);
477 AutocompleteInput
input(base::string16(), base::string16::npos
,
478 base::string16(), GURL(),
479 AutocompleteInput::HOME_PAGE
, false, false, false,
480 AutocompleteInput::ALL_MATCHES
);
481 result
.SortAndCull(input
, test_util_
.profile());
482 AssertResultMatches(result
, data
, 4);
486 // Check that reorder swaps up a result appropriately.
488 PopulateAutocompleteMatches(data
, arraysize(data
), &matches
);
489 matches
[0].allowed_to_be_default_match
= false;
490 matches
[1].allowed_to_be_default_match
= false;
491 AutocompleteResult result
;
492 result
.AppendMatches(matches
);
493 AutocompleteInput
input(base::string16(), base::string16::npos
,
494 base::string16(), GURL(),
495 AutocompleteInput::HOME_PAGE
, false, false, false,
496 AutocompleteInput::ALL_MATCHES
);
497 result
.SortAndCull(input
, test_util_
.profile());
498 ASSERT_EQ(4U, result
.size());
499 EXPECT_EQ("http://c/", result
.match_at(0)->destination_url
.spec());
500 EXPECT_EQ("http://a/", result
.match_at(1)->destination_url
.spec());
501 EXPECT_EQ("http://b/", result
.match_at(2)->destination_url
.spec());
502 EXPECT_EQ("http://d/", result
.match_at(3)->destination_url
.spec());
506 TEST_F(AutocompleteResultTest
, ShouldHideTopMatch
) {
507 base::FieldTrialList::CreateFieldTrial("InstantExtended",
508 "Group1 hide_verbatim:1");
511 // Case 1: Top match is a verbatim match.
512 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
, 1, &matches
);
513 AutocompleteResult result
;
514 result
.AppendMatches(matches
);
515 EXPECT_TRUE(result
.ShouldHideTopMatch());
519 // Case 2: If the verbatim first match is followed by another verbatim match,
520 // don't hide the top verbatim match.
521 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
,
522 arraysize(kVerbatimMatches
),
524 result
.AppendMatches(matches
);
525 EXPECT_FALSE(result
.ShouldHideTopMatch());
529 // Case 3: Top match is not a verbatim match. Do not hide the top match.
530 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches
, 1, &matches
);
531 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
,
532 arraysize(kVerbatimMatches
),
534 result
.AppendMatches(matches
);
535 EXPECT_FALSE(result
.ShouldHideTopMatch());
538 TEST_F(AutocompleteResultTest
, DoNotHideTopMatch_FieldTrialFlagDisabled
) {
539 // This test config is identical to ShouldHideTopMatch test ("Case 1") except
540 // that the "hide_verbatim" flag is disabled in the field trials.
541 base::FieldTrialList::CreateFieldTrial("InstantExtended",
542 "Group1 hide_verbatim:0");
544 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
, 1, &matches
);
545 AutocompleteResult result
;
546 result
.AppendMatches(matches
);
547 // Field trial flag "hide_verbatim" is disabled. Do not hide top match.
548 EXPECT_FALSE(result
.ShouldHideTopMatch());
551 TEST_F(AutocompleteResultTest
, TopMatchIsStandaloneVerbatimMatch
) {
553 AutocompleteResult result
;
554 result
.AppendMatches(matches
);
556 // Case 1: Result set is empty.
557 EXPECT_FALSE(result
.TopMatchIsStandaloneVerbatimMatch());
559 // Case 2: Top match is not a verbatim match.
560 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches
, 1, &matches
);
561 result
.AppendMatches(matches
);
562 EXPECT_FALSE(result
.TopMatchIsStandaloneVerbatimMatch());
566 // Case 3: Top match is a verbatim match.
567 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
, 1, &matches
);
568 result
.AppendMatches(matches
);
569 EXPECT_TRUE(result
.TopMatchIsStandaloneVerbatimMatch());
573 // Case 4: Standalone verbatim match found in AutocompleteResult.
574 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
, 1, &matches
);
575 PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches
, 1, &matches
);
576 result
.AppendMatches(matches
);
577 EXPECT_TRUE(result
.TopMatchIsStandaloneVerbatimMatch());
581 // Case 5: Multiple verbatim matches found in AutocompleteResult.
582 PopulateAutocompleteMatchesFromTestData(kVerbatimMatches
,
583 arraysize(kVerbatimMatches
),
585 result
.AppendMatches(matches
);
586 EXPECT_FALSE(result
.ShouldHideTopMatch());