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"
25 class AutocompleteResultTest : public testing::Test {
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").
32 // ID of the provider.
39 AutocompleteResultTest() {
40 // Destroy the existing FieldTrialList before creating a new one to avoid
42 field_trial_list_.reset();
43 field_trial_list_.reset(new base::FieldTrialList(
44 new metrics::SHA1EntropyProvider("foo")));
45 chrome_variations::testing::ClearAllVariationParams();
48 virtual void SetUp() OVERRIDE {
49 #if defined(OS_ANDROID)
50 TemplateURLPrepopulateData::InitCountryCode(
51 std::string() /* unknown country code */);
54 test_util_.VerifyLoad();
57 virtual void TearDown() OVERRIDE {
58 test_util_.TearDown();
61 // Configures |match| from |data|.
62 static void PopulateAutocompleteMatch(const TestData& data,
63 AutocompleteMatch* match);
65 // Adds |count| AutocompleteMatches to |matches|.
66 static void PopulateAutocompleteMatches(const TestData* data,
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);
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);
82 TemplateURLServiceTestUtil test_util_;
85 scoped_ptr<base::FieldTrialList> field_trial_list_;
87 DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest);
91 void AutocompleteResultTest::PopulateAutocompleteMatch(
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;
103 void AutocompleteResultTest::PopulateAutocompleteMatches(
104 const TestData* data,
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);
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;
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);
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());
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());
151 AssertResultMatches(current_result, expected, expected_size);
154 // Assertion testing for AutocompleteResult::Swap.
155 TEST_F(AutocompleteResultTest, Swap) {
156 AutocompleteResult r1;
157 AutocompleteResult r2;
159 // Swap with empty shouldn't do anything interesting.
161 EXPECT_EQ(r1.end(), r1.default_match());
162 EXPECT_EQ(r2.end(), r2.default_match());
164 // Swap with a single match.
166 AutocompleteMatch match;
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());
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());
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) {
193 TestData current[] = {
196 TestData result[] = {
201 ASSERT_NO_FATAL_FAILURE(
202 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last),
203 current, ARRAYSIZE_UNSAFE(current),
204 result, ARRAYSIZE_UNSAFE(result)));
207 // Tests that matches are copied correctly from two distinct providers.
208 TEST_F(AutocompleteResultTest, CopyOldMatches2) {
215 TestData current[] = {
219 TestData result[] = {
226 ASSERT_NO_FATAL_FAILURE(
227 RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last),
228 current, ARRAYSIZE_UNSAFE(current),
229 result, ARRAYSIZE_UNSAFE(result)));
232 // Tests that matches with empty destination URLs aren't treated as duplicates
234 TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) {
244 PopulateAutocompleteMatches(data, arraysize(data), &matches);
245 matches[1].destination_url = GURL();
246 matches[3].destination_url = GURL();
247 matches[4].destination_url = GURL();
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());
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
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);
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));
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/");
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());
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);
314 TEST_F(AutocompleteResultTest, SortAndCullWithDemotionsByType) {
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);
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);
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);
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);
350 // Add a rule demoting history-url and killing history-title.
352 std::map<std::string, std::string> params;
353 params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":3:*"] =
354 "1:50,7:100,2:0"; // 3 == HOME_PAGE
355 ASSERT_TRUE(chrome_variations::AssociateVariationParams(
356 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
358 base::FieldTrialList::CreateFieldTrial(
359 OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
361 AutocompleteResult result;
362 result.AppendMatches(matches);
363 AutocompleteInput input(string16(), string16::npos, string16(), GURL(),
364 AutocompleteInput::HOME_PAGE, false, false, false,
365 AutocompleteInput::ALL_MATCHES);
366 result.SortAndCull(input, test_util_.profile());
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());
380 TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) {
388 std::map<std::string, std::string> params;
389 // Enable reorder for omnibox inputs on the user's home page.
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");
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()).
402 PopulateAutocompleteMatches(data, arraysize(data), &matches);
403 AutocompleteResult result;
404 result.AppendMatches(matches);
405 AutocompleteInput input(string16(), string16::npos, string16(), GURL(),
406 AutocompleteInput::HOME_PAGE, false, false, false,
407 AutocompleteInput::ALL_MATCHES);
408 result.SortAndCull(input, test_util_.profile());
409 AssertResultMatches(result, data, 4);
413 // Check that reorder swaps up a result appropriately.
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::HOME_PAGE, 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());