普通文本  |  844行  |  32.74 KB

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/omnibox/autocomplete_result.h"

#include <vector>

#include "base/memory/scoped_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/metrics/proto/omnibox_event.pb.h"
#include "components/omnibox/autocomplete_input.h"
#include "components/omnibox/autocomplete_match.h"
#include "components/omnibox/autocomplete_match_type.h"
#include "components/omnibox/autocomplete_provider.h"
#include "components/omnibox/omnibox_field_trial.h"
#include "components/omnibox/test_scheme_classifier.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/variations_associated_data.h"
#include "testing/gtest/include/gtest/gtest.h"

using metrics::OmniboxEventProto;

namespace {

struct AutocompleteMatchTestData {
  std::string destination_url;
  AutocompleteMatch::Type type;
};

const AutocompleteMatchTestData kVerbatimMatches[] = {
  { "http://search-what-you-typed/",
    AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED },
  { "http://url-what-you-typed/", AutocompleteMatchType::URL_WHAT_YOU_TYPED },
};

const AutocompleteMatchTestData kNonVerbatimMatches[] = {
  { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY },
  { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE },
};

// Adds |count| AutocompleteMatches to |matches|.
void PopulateAutocompleteMatchesFromTestData(
    const AutocompleteMatchTestData* data,
    size_t count,
    ACMatches* matches) {
  ASSERT_TRUE(matches != NULL);
  for (size_t i = 0; i < count; ++i) {
    AutocompleteMatch match;
    match.destination_url = GURL(data[i].destination_url);
    match.relevance =
        matches->empty() ? 1300 : (matches->back().relevance - 100);
    match.allowed_to_be_default_match = true;
    match.type = data[i].type;
    matches->push_back(match);
  }
}

}  // namespace

class AutocompleteResultTest : public testing::Test  {
 public:
  struct TestData {
    // Used to build a url for the AutocompleteMatch. The URL becomes
    // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://b").
    int url_id;

    // ID of the provider.
    int provider_id;

    // Relevance score.
    int relevance;

    // Duplicate matches.
    std::vector<AutocompleteMatch> duplicate_matches;
  };

  AutocompleteResultTest() {
    // Destroy the existing FieldTrialList before creating a new one to avoid
    // a DCHECK.
    field_trial_list_.reset();
    field_trial_list_.reset(new base::FieldTrialList(
        new metrics::SHA1EntropyProvider("foo")));
    variations::testing::ClearAllVariationParams();
  }

  virtual void SetUp() OVERRIDE {
#if defined(OS_ANDROID)
    TemplateURLPrepopulateData::InitCountryCode(
        std::string() /* unknown country code */);
#endif
    template_url_service_.reset(new TemplateURLService(NULL, 0));
    template_url_service_->Load();
  }

  // Configures |match| from |data|.
  static void PopulateAutocompleteMatch(const TestData& data,
                                        AutocompleteMatch* match);

  // Adds |count| AutocompleteMatches to |matches|.
  static void PopulateAutocompleteMatches(const TestData* data,
                                          size_t count,
                                          ACMatches* matches);

  // Asserts that |result| has |expected_count| matches matching |expected|.
  void AssertResultMatches(const AutocompleteResult& result,
                           const TestData* expected,
                           size_t expected_count);

  // Creates an AutocompleteResult from |last| and |current|. The two are
  // merged by |CopyOldMatches| and compared by |AssertResultMatches|.
  void RunCopyOldMatchesTest(const TestData* last, size_t last_size,
                             const TestData* current, size_t current_size,
                             const TestData* expected, size_t expected_size);

 protected:
  scoped_ptr<TemplateURLService> template_url_service_;

 private:
  scoped_ptr<base::FieldTrialList> field_trial_list_;

  DISALLOW_COPY_AND_ASSIGN(AutocompleteResultTest);
};

// static
void AutocompleteResultTest::PopulateAutocompleteMatch(
    const TestData& data,
    AutocompleteMatch* match) {
  match->provider = reinterpret_cast<AutocompleteProvider*>(data.provider_id);
  match->fill_into_edit = base::IntToString16(data.url_id);
  std::string url_id(1, data.url_id + 'a');
  match->destination_url = GURL("http://" + url_id);
  match->relevance = data.relevance;
  match->allowed_to_be_default_match = true;
  match->duplicate_matches = data.duplicate_matches;
}

// static
void AutocompleteResultTest::PopulateAutocompleteMatches(
    const TestData* data,
    size_t count,
    ACMatches* matches) {
  for (size_t i = 0; i < count; ++i) {
    AutocompleteMatch match;
    PopulateAutocompleteMatch(data[i], &match);
    matches->push_back(match);
  }
}

void AutocompleteResultTest::AssertResultMatches(
    const AutocompleteResult& result,
    const TestData* expected,
    size_t expected_count) {
  ASSERT_EQ(expected_count, result.size());
  for (size_t i = 0; i < expected_count; ++i) {
    AutocompleteMatch expected_match;
    PopulateAutocompleteMatch(expected[i], &expected_match);
    const AutocompleteMatch& match = *(result.begin() + i);
    EXPECT_EQ(expected_match.provider, match.provider) << i;
    EXPECT_EQ(expected_match.relevance, match.relevance) << i;
    EXPECT_EQ(expected_match.destination_url.spec(),
              match.destination_url.spec()) << i;
  }
}

void AutocompleteResultTest::RunCopyOldMatchesTest(
    const TestData* last, size_t last_size,
    const TestData* current, size_t current_size,
    const TestData* expected, size_t expected_size) {
  AutocompleteInput input(base::ASCIIToUTF16("a"), base::string16::npos,
                          base::string16(), GURL(),
                          OmniboxEventProto::INVALID_SPEC, false, false, false,
                          true,
                          TestSchemeClassifier());

  ACMatches last_matches;
  PopulateAutocompleteMatches(last, last_size, &last_matches);
  AutocompleteResult last_result;
  last_result.AppendMatches(last_matches);
  last_result.SortAndCull(input, template_url_service_.get());

  ACMatches current_matches;
  PopulateAutocompleteMatches(current, current_size, &current_matches);
  AutocompleteResult current_result;
  current_result.AppendMatches(current_matches);
  current_result.SortAndCull(input, template_url_service_.get());
  current_result.CopyOldMatches(
      input, last_result, template_url_service_.get());

  AssertResultMatches(current_result, expected, expected_size);
}

// Assertion testing for AutocompleteResult::Swap.
TEST_F(AutocompleteResultTest, Swap) {
  AutocompleteResult r1;
  AutocompleteResult r2;

  // Swap with empty shouldn't do anything interesting.
  r1.Swap(&r2);
  EXPECT_EQ(r1.end(), r1.default_match());
  EXPECT_EQ(r2.end(), r2.default_match());

  // Swap with a single match.
  ACMatches matches;
  AutocompleteMatch match;
  match.relevance = 1;
  match.allowed_to_be_default_match = true;
  AutocompleteInput input(base::ASCIIToUTF16("a"), base::string16::npos,
                          base::string16(), GURL(),
                          OmniboxEventProto::INVALID_SPEC, false, false, false,
                          true, TestSchemeClassifier());
  matches.push_back(match);
  r1.AppendMatches(matches);
  r1.SortAndCull(input, template_url_service_.get());
  EXPECT_EQ(r1.begin(), r1.default_match());
  EXPECT_EQ("http://a/", r1.alternate_nav_url().spec());
  r1.Swap(&r2);
  EXPECT_TRUE(r1.empty());
  EXPECT_EQ(r1.end(), r1.default_match());
  EXPECT_TRUE(r1.alternate_nav_url().is_empty());
  ASSERT_FALSE(r2.empty());
  EXPECT_EQ(r2.begin(), r2.default_match());
  EXPECT_EQ("http://a/", r2.alternate_nav_url().spec());
}

// Tests that if the new results have a lower max relevance score than last,
// any copied results have their relevance shifted down.
TEST_F(AutocompleteResultTest, CopyOldMatches) {
  TestData last[] = {
    { 0, 0, 1000 },
    { 1, 0, 500 },
  };
  TestData current[] = {
    { 2, 0, 400 },
  };
  TestData result[] = {
    { 2, 0, 400 },
    { 1, 0, 399 },
  };

  ASSERT_NO_FATAL_FAILURE(
      RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last),
                            current, ARRAYSIZE_UNSAFE(current),
                            result, ARRAYSIZE_UNSAFE(result)));
}

// Tests that matches are copied correctly from two distinct providers.
TEST_F(AutocompleteResultTest, CopyOldMatches2) {
  TestData last[] = {
    { 0, 0, 1000 },
    { 1, 1, 500 },
    { 2, 0, 400 },
    { 3, 1, 300 },
  };
  TestData current[] = {
    { 4, 0, 1100 },
    { 5, 1, 550 },
  };
  TestData result[] = {
    { 4, 0, 1100 },
    { 5, 1, 550 },
    { 2, 0, 400 },
    { 3, 1, 300 },
  };

  ASSERT_NO_FATAL_FAILURE(
      RunCopyOldMatchesTest(last, ARRAYSIZE_UNSAFE(last),
                            current, ARRAYSIZE_UNSAFE(current),
                            result, ARRAYSIZE_UNSAFE(result)));
}

// Tests that matches with empty destination URLs aren't treated as duplicates
// and culled.
TEST_F(AutocompleteResultTest, SortAndCullEmptyDestinationURLs) {
  TestData data[] = {
    { 1, 0, 500 },
    { 0, 0, 1100 },
    { 1, 0, 1000 },
    { 0, 0, 1300 },
    { 0, 0, 1200 },
  };

  ACMatches matches;
  PopulateAutocompleteMatches(data, arraysize(data), &matches);
  matches[1].destination_url = GURL();
  matches[3].destination_url = GURL();
  matches[4].destination_url = GURL();

  AutocompleteResult result;
  result.AppendMatches(matches);
  AutocompleteInput input(base::string16(), base::string16::npos,
                          base::string16(), GURL(),
                          OmniboxEventProto::INVALID_SPEC, false, false, false,
                          true,
                          TestSchemeClassifier());
  result.SortAndCull(input, template_url_service_.get());

  // Of the two results with the same non-empty destination URL, the
  // lower-relevance one should be dropped.  All of the results with empty URLs
  // should be kept.
  ASSERT_EQ(4U, result.size());
  EXPECT_TRUE(result.match_at(0)->destination_url.is_empty());
  EXPECT_EQ(1300, result.match_at(0)->relevance);
  EXPECT_TRUE(result.match_at(1)->destination_url.is_empty());
  EXPECT_EQ(1200, result.match_at(1)->relevance);
  EXPECT_TRUE(result.match_at(2)->destination_url.is_empty());
  EXPECT_EQ(1100, result.match_at(2)->relevance);
  EXPECT_EQ("http://b/", result.match_at(3)->destination_url.spec());
  EXPECT_EQ(1000, result.match_at(3)->relevance);
}

TEST_F(AutocompleteResultTest, SortAndCullDuplicateSearchURLs) {
  // Register a template URL that corresponds to 'foo' search engine.
  TemplateURLData url_data;
  url_data.short_name = base::ASCIIToUTF16("unittest");
  url_data.SetKeyword(base::ASCIIToUTF16("foo"));
  url_data.SetURL("http://www.foo.com/s?q={searchTerms}");
  template_url_service_.get()->Add(new TemplateURL(url_data));

  TestData data[] = {
    { 0, 0, 1300 },
    { 1, 0, 1200 },
    { 2, 0, 1100 },
    { 3, 0, 1000 },
    { 4, 1, 900 },
  };

  ACMatches matches;
  PopulateAutocompleteMatches(data, arraysize(data), &matches);
  matches[0].destination_url = GURL("http://www.foo.com/s?q=foo");
  matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2");
  matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f");
  matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0");
  matches[4].destination_url = GURL("http://www.foo.com/");

  AutocompleteResult result;
  result.AppendMatches(matches);
  AutocompleteInput input(base::string16(), base::string16::npos,
                          base::string16(), GURL(),
                          OmniboxEventProto::INVALID_SPEC, false, false, false,
                          true,
                          TestSchemeClassifier());
  result.SortAndCull(input, template_url_service_.get());

  // We expect the 3rd and 4th results to be removed.
  ASSERT_EQ(3U, result.size());
  EXPECT_EQ("http://www.foo.com/s?q=foo",
            result.match_at(0)->destination_url.spec());
  EXPECT_EQ(1300, result.match_at(0)->relevance);
  EXPECT_EQ("http://www.foo.com/s?q=foo2",
            result.match_at(1)->destination_url.spec());
  EXPECT_EQ(1200, result.match_at(1)->relevance);
  EXPECT_EQ("http://www.foo.com/",
            result.match_at(2)->destination_url.spec());
  EXPECT_EQ(900, result.match_at(2)->relevance);
}

TEST_F(AutocompleteResultTest, SortAndCullWithMatchDups) {
  // Register a template URL that corresponds to 'foo' search engine.
  TemplateURLData url_data;
  url_data.short_name = base::ASCIIToUTF16("unittest");
  url_data.SetKeyword(base::ASCIIToUTF16("foo"));
  url_data.SetURL("http://www.foo.com/s?q={searchTerms}");
  template_url_service_.get()->Add(new TemplateURL(url_data));

  AutocompleteMatch dup_match;
  dup_match.destination_url = GURL("http://www.foo.com/s?q=foo&oq=dup");
  std::vector<AutocompleteMatch> dups;
  dups.push_back(dup_match);

  TestData data[] = {
    { 0, 0, 1300, dups },
    { 1, 0, 1200 },
    { 2, 0, 1100 },
    { 3, 0, 1000, dups },
    { 4, 1, 900 },
    { 5, 0, 800 },
  };

  ACMatches matches;
  PopulateAutocompleteMatches(data, arraysize(data), &matches);
  matches[0].destination_url = GURL("http://www.foo.com/s?q=foo");
  matches[1].destination_url = GURL("http://www.foo.com/s?q=foo2");
  matches[2].destination_url = GURL("http://www.foo.com/s?q=foo&oq=f");
  matches[3].destination_url = GURL("http://www.foo.com/s?q=foo&aqs=0");
  matches[4].destination_url = GURL("http://www.foo.com/");
  matches[5].destination_url = GURL("http://www.foo.com/s?q=foo2&oq=f");

  AutocompleteResult result;
  result.AppendMatches(matches);
  AutocompleteInput input(base::string16(), base::string16::npos,
                          base::string16(), GURL(),
                          OmniboxEventProto::INVALID_SPEC, false, false, false,
                          true,
                          TestSchemeClassifier());
  result.SortAndCull(input, template_url_service_.get());

  // Expect 3 unique results after SortAndCull().
  ASSERT_EQ(3U, result.size());

  // Check that 3rd and 4th result got added to the first result as dups
  // and also duplicates of the 4th match got copied.
  ASSERT_EQ(4U, result.match_at(0)->duplicate_matches.size());
  const AutocompleteMatch* first_match = result.match_at(0);
  EXPECT_EQ(matches[2].destination_url,
            first_match->duplicate_matches.at(1).destination_url);
  EXPECT_EQ(dup_match.destination_url,
            first_match->duplicate_matches.at(2).destination_url);
  EXPECT_EQ(matches[3].destination_url,
            first_match->duplicate_matches.at(3).destination_url);

  // Check that 6th result started a new list of dups for the second result.
  ASSERT_EQ(1U, result.match_at(1)->duplicate_matches.size());
  EXPECT_EQ(matches[5].destination_url,
            result.match_at(1)->duplicate_matches.at(0).destination_url);
}

TEST_F(AutocompleteResultTest, SortAndCullWithDemotionsByType) {
  // Add some matches.
  ACMatches matches;
  const AutocompleteMatchTestData data[] = {
    { "http://history-url/", AutocompleteMatchType::HISTORY_URL },
    { "http://search-what-you-typed/",
      AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED },
    { "http://history-title/", AutocompleteMatchType::HISTORY_TITLE },
    { "http://search-history/", AutocompleteMatchType::SEARCH_HISTORY },
  };
  PopulateAutocompleteMatchesFromTestData(data, arraysize(data), &matches);

  // Demote the search history match relevance score.
  matches.back().relevance = 500;

  // Add a rule demoting history-url and killing history-title.
  {
    std::map<std::string, std::string> params;
    params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":3:*"] =
        "1:50,7:100,2:0";  // 3 == HOME_PAGE
    ASSERT_TRUE(variations::AssociateVariationParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
  }
  base::FieldTrialList::CreateFieldTrial(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");

  AutocompleteResult result;
  result.AppendMatches(matches);
  AutocompleteInput input(base::string16(), base::string16::npos,
                          base::string16(), GURL(),
                          OmniboxEventProto::HOME_PAGE, false, false, false,
                          true,
                          TestSchemeClassifier());
  result.SortAndCull(input, template_url_service_.get());

  // Check the new ordering.  The history-title results should be omitted.
  // We cannot check relevance scores because the matches are sorted by
  // demoted relevance but the actual relevance scores are not modified.
  ASSERT_EQ(3u, result.size());
  EXPECT_EQ("http://search-what-you-typed/",
            result.match_at(0)->destination_url.spec());
  EXPECT_EQ("http://history-url/",
            result.match_at(1)->destination_url.spec());
  EXPECT_EQ("http://search-history/",
            result.match_at(2)->destination_url.spec());
}

TEST_F(AutocompleteResultTest, SortAndCullWithMatchDupsAndDemotionsByType) {
  // Add some matches.
  ACMatches matches;
  const AutocompleteMatchTestData data[] = {
    { "http://search-what-you-typed/",
      AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED },
    { "http://dup-url/", AutocompleteMatchType::HISTORY_URL },
    { "http://dup-url/", AutocompleteMatchType::NAVSUGGEST },
    { "http://search-url/", AutocompleteMatchType::SEARCH_SUGGEST },
    { "http://history-url/", AutocompleteMatchType::HISTORY_URL },
  };
  PopulateAutocompleteMatchesFromTestData(data, arraysize(data), &matches);

  // Add a rule demoting HISTORY_URL.
  {
    std::map<std::string, std::string> params;
    params[std::string(OmniboxFieldTrial::kDemoteByTypeRule) + ":8:*"] =
        "1:50";  // 8 == INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS
    ASSERT_TRUE(variations::AssociateVariationParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "C", params));
  }
  base::FieldTrialList::CreateFieldTrial(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "C");

  {
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(
        base::string16(), base::string16::npos, base::string16(), GURL(),
        OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS, false,
        false, false, true,
        TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());

    // The NAVSUGGEST dup-url stay above search-url since the navsuggest
    // variant should not be demoted.
    ASSERT_EQ(4u, result.size());
    EXPECT_EQ("http://search-what-you-typed/",
              result.match_at(0)->destination_url.spec());
    EXPECT_EQ("http://dup-url/",
              result.match_at(1)->destination_url.spec());
    EXPECT_EQ(AutocompleteMatchType::NAVSUGGEST,
              result.match_at(1)->type);
    EXPECT_EQ("http://search-url/",
              result.match_at(2)->destination_url.spec());
    EXPECT_EQ("http://history-url/",
              result.match_at(3)->destination_url.spec());
  }
}

TEST_F(AutocompleteResultTest, SortAndCullReorderForDefaultMatch) {
  TestData data[] = {
    { 0, 0, 1300 },
    { 1, 0, 1200 },
    { 2, 0, 1100 },
    { 3, 0, 1000 }
  };

  {
    // Check that reorder doesn't do anything if the top result
    // is already a legal default match (which is the default from
    // PopulateAutocompleteMatches()).
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    AssertResultMatches(result, data, 4);
  }

  {
    // Check that reorder swaps up a result appropriately.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].allowed_to_be_default_match = false;
    matches[1].allowed_to_be_default_match = false;
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    ASSERT_EQ(4U, result.size());
    EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec());
    EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec());
    EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec());
    EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec());
  }
}



TEST_F(AutocompleteResultTest, SortAndCullWithDisableInlining) {
  TestData data[] = {
    { 0, 0, 1300 },
    { 1, 0, 1200 },
    { 2, 0, 1100 },
    { 3, 0, 1000 }
  };

  {
    // Check that with the field trial disabled, we keep keep the first match
    // first even if it has an inline autocompletion.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].inline_autocompletion = base::ASCIIToUTF16("completion");
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    AssertResultMatches(result, data, 4);
  }

  // Enable the field trial to disable inlining.
  {
    std::map<std::string, std::string> params;
    params[OmniboxFieldTrial::kDisableInliningRule] = "true";
    ASSERT_TRUE(variations::AssociateVariationParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "D", params));
  }
  base::FieldTrialList::CreateFieldTrial(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "D");

  {
    // Now the first match should be demoted past the second.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].inline_autocompletion = base::ASCIIToUTF16("completion");
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    ASSERT_EQ(4U, result.size());
    EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec());
    EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec());
    EXPECT_EQ("http://c/", result.match_at(2)->destination_url.spec());
    EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec());
  }

  {
    // But if there was no inline autocompletion on the first match, then
    // the order should stay the same.  This is true even if there are
    // inline autocompletions elsewhere.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[2].inline_autocompletion = base::ASCIIToUTF16("completion");
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    AssertResultMatches(result, data, 4);
  }

  {
    // Try a more complicated situation.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].allowed_to_be_default_match = false;
    matches[1].inline_autocompletion = base::ASCIIToUTF16("completion");
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    ASSERT_EQ(4U, result.size());
    EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec());
    EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec());
    EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec());
    EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec());
  }

  {
    // Try another complicated situation.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].inline_autocompletion = base::ASCIIToUTF16("completion");
    matches[1].allowed_to_be_default_match = false;
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    ASSERT_EQ(4U, result.size());
    EXPECT_EQ("http://c/", result.match_at(0)->destination_url.spec());
    EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec());
    EXPECT_EQ("http://b/", result.match_at(2)->destination_url.spec());
    EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec());
  }

  {
    // Check that disaster doesn't strike if we can't demote the top inline
    // autocompletion because every match either has a completion or isn't
    // allowed to be the default match.  In this case, we should leave
    // everything untouched.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].inline_autocompletion = base::ASCIIToUTF16("completion");
    matches[1].allowed_to_be_default_match = false;
    matches[2].allowed_to_be_default_match = false;
    matches[3].inline_autocompletion = base::ASCIIToUTF16("completion");
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    AssertResultMatches(result, data, 4);
  }

  {
    // Check a similar situation, except in this case the top match is not
    // allowed to the default match, so it still needs to be demoted so we
    // get a legal default match first.  That match will have an inline
    // autocompletion because we don't have any better options.
    ACMatches matches;
    PopulateAutocompleteMatches(data, arraysize(data), &matches);
    matches[0].allowed_to_be_default_match = false;
    matches[1].inline_autocompletion = base::ASCIIToUTF16("completion");
    matches[2].allowed_to_be_default_match = false;
    matches[3].inline_autocompletion = base::ASCIIToUTF16("completion");
    AutocompleteResult result;
    result.AppendMatches(matches);
    AutocompleteInput input(base::string16(), base::string16::npos,
                            base::string16(), GURL(),
                            OmniboxEventProto::HOME_PAGE, false, false, false,
                            true,
                            TestSchemeClassifier());
    result.SortAndCull(input, template_url_service_.get());
    ASSERT_EQ(4U, result.size());
    EXPECT_EQ("http://b/", result.match_at(0)->destination_url.spec());
    EXPECT_EQ("http://a/", result.match_at(1)->destination_url.spec());
    EXPECT_EQ("http://c/", result.match_at(2)->destination_url.spec());
    EXPECT_EQ("http://d/", result.match_at(3)->destination_url.spec());
  }
}

TEST_F(AutocompleteResultTest, ShouldHideTopMatch) {
  base::FieldTrialList::CreateFieldTrial("InstantExtended",
                                         "Group1 hide_verbatim:1");
  ACMatches matches;

  // Case 1: Top match is a verbatim match.
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches);
  AutocompleteResult result;
  result.AppendMatches(matches);
  EXPECT_TRUE(result.ShouldHideTopMatch());
  matches.clear();
  result.Reset();

  // Case 2: If the verbatim first match is followed by another verbatim match,
  // don't hide the top verbatim match.
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches,
                                          arraysize(kVerbatimMatches),
                                          &matches);
  result.AppendMatches(matches);
  EXPECT_FALSE(result.ShouldHideTopMatch());
  matches.clear();
  result.Reset();

  // Case 3: Top match is not a verbatim match. Do not hide the top match.
  PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches);
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches,
                                          arraysize(kVerbatimMatches),
                                          &matches);
  result.AppendMatches(matches);
  EXPECT_FALSE(result.ShouldHideTopMatch());
}

TEST_F(AutocompleteResultTest, ShouldHideTopMatchAfterCopy) {
  base::FieldTrialList::CreateFieldTrial("InstantExtended",
                                         "Group1 hide_verbatim:1");
  ACMatches matches;

  // Case 1: Top match is a verbatim match followed by only copied matches.
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches,
                                          arraysize(kVerbatimMatches),
                                          &matches);
  for (size_t i = 1; i < arraysize(kVerbatimMatches); ++i)
    matches[i].from_previous = true;
  AutocompleteResult result;
  result.AppendMatches(matches);
  EXPECT_TRUE(result.ShouldHideTopMatch());
  result.Reset();

  // Case 2: The copied matches are then followed by a non-verbatim match.
  PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches);
  result.AppendMatches(matches);
  EXPECT_TRUE(result.ShouldHideTopMatch());
  result.Reset();

  // Case 3: The copied matches are instead followed by a verbatim match.
  matches.back().from_previous = true;
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches);
  result.AppendMatches(matches);
  EXPECT_FALSE(result.ShouldHideTopMatch());
}

TEST_F(AutocompleteResultTest, DoNotHideTopMatch_FieldTrialFlagDisabled) {
  // This test config is identical to ShouldHideTopMatch test ("Case 1") except
  // that the "hide_verbatim" flag is disabled in the field trials.
  base::FieldTrialList::CreateFieldTrial("InstantExtended",
                                         "Group1 hide_verbatim:0");
  ACMatches matches;
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches);
  AutocompleteResult result;
  result.AppendMatches(matches);
  // Field trial flag "hide_verbatim" is disabled. Do not hide top match.
  EXPECT_FALSE(result.ShouldHideTopMatch());
}

TEST_F(AutocompleteResultTest, TopMatchIsStandaloneVerbatimMatch) {
  ACMatches matches;
  AutocompleteResult result;
  result.AppendMatches(matches);

  // Case 1: Result set is empty.
  EXPECT_FALSE(result.TopMatchIsStandaloneVerbatimMatch());

  // Case 2: Top match is not a verbatim match.
  PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches);
  result.AppendMatches(matches);
  EXPECT_FALSE(result.TopMatchIsStandaloneVerbatimMatch());
  result.Reset();
  matches.clear();

  // Case 3: Top match is a verbatim match.
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches);
  result.AppendMatches(matches);
  EXPECT_TRUE(result.TopMatchIsStandaloneVerbatimMatch());
  result.Reset();
  matches.clear();

  // Case 4: Standalone verbatim match found in AutocompleteResult.
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches, 1, &matches);
  PopulateAutocompleteMatchesFromTestData(kNonVerbatimMatches, 1, &matches);
  result.AppendMatches(matches);
  EXPECT_TRUE(result.TopMatchIsStandaloneVerbatimMatch());
  result.Reset();
  matches.clear();

  // Case 5: Multiple verbatim matches found in AutocompleteResult.
  PopulateAutocompleteMatchesFromTestData(kVerbatimMatches,
                                          arraysize(kVerbatimMatches),
                                          &matches);
  result.AppendMatches(matches);
  EXPECT_FALSE(result.ShouldHideTopMatch());
}