Home | History | Annotate | Download | only in autocomplete
      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, &current_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