search_unittest.cc revision 9ab5563a3196760eb381d102cbb2bc0f7abc6a50
1// Copyright (c) 2013 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 "base/command_line.h"
6#include "base/metrics/field_trial.h"
7#include "base/metrics/histogram_base.h"
8#include "base/metrics/histogram_samples.h"
9#include "base/metrics/statistics_recorder.h"
10#include "base/prefs/pref_service.h"
11#include "chrome/browser/search/search.h"
12#include "chrome/browser/search_engines/search_terms_data.h"
13#include "chrome/browser/search_engines/template_url_service.h"
14#include "chrome/browser/search_engines/template_url_service_factory.h"
15#include "chrome/browser/ui/tabs/tab_strip_model.h"
16#include "chrome/common/chrome_switches.h"
17#include "chrome/common/metrics/entropy_provider.h"
18#include "chrome/common/pref_names.h"
19#include "chrome/common/url_constants.h"
20#include "chrome/test/base/browser_with_test_window_test.h"
21#include "chrome/test/base/ui_test_utils.h"
22#include "content/public/browser/web_contents.h"
23
24namespace chrome {
25
26TEST(EmbeddedSearchFieldTrialTest, GetFieldTrialInfo) {
27  FieldTrialFlags flags;
28  uint64 group_number = 0;
29  const uint64 ZERO = 0;
30
31  EXPECT_FALSE(GetFieldTrialInfo(std::string(), &flags, &group_number));
32  EXPECT_EQ(ZERO, group_number);
33  EXPECT_EQ(ZERO, flags.size());
34
35  EXPECT_TRUE(GetFieldTrialInfo("Group77", &flags, &group_number));
36  EXPECT_EQ(uint64(77), group_number);
37  EXPECT_EQ(ZERO, flags.size());
38
39  group_number = 0;
40  EXPECT_FALSE(GetFieldTrialInfo("Group77.2", &flags, &group_number));
41  EXPECT_EQ(ZERO, group_number);
42  EXPECT_EQ(ZERO, flags.size());
43
44  EXPECT_FALSE(GetFieldTrialInfo("Invalid77", &flags, &group_number));
45  EXPECT_EQ(ZERO, group_number);
46  EXPECT_EQ(ZERO, flags.size());
47
48  EXPECT_FALSE(GetFieldTrialInfo("Invalid77", &flags, NULL));
49  EXPECT_EQ(ZERO, flags.size());
50
51  EXPECT_TRUE(GetFieldTrialInfo("Group77 ", &flags, &group_number));
52  EXPECT_EQ(uint64(77), group_number);
53  EXPECT_EQ(ZERO, flags.size());
54
55  group_number = 0;
56  flags.clear();
57  EXPECT_EQ(uint64(9999), GetUInt64ValueForFlagWithDefault("foo", 9999, flags));
58  EXPECT_TRUE(GetFieldTrialInfo("Group77 foo:6", &flags, &group_number));
59  EXPECT_EQ(uint64(77), group_number);
60  EXPECT_EQ(uint64(1), flags.size());
61  EXPECT_EQ(uint64(6), GetUInt64ValueForFlagWithDefault("foo", 9999, flags));
62
63  group_number = 0;
64  flags.clear();
65  EXPECT_TRUE(GetFieldTrialInfo(
66      "Group77 bar:1 baz:7 cat:dogs", &flags, &group_number));
67  EXPECT_EQ(uint64(77), group_number);
68  EXPECT_EQ(uint64(3), flags.size());
69  EXPECT_EQ(true, GetBoolValueForFlagWithDefault("bar", false, flags));
70  EXPECT_EQ(uint64(7), GetUInt64ValueForFlagWithDefault("baz", 0, flags));
71  EXPECT_EQ("dogs",
72            GetStringValueForFlagWithDefault("cat", std::string(), flags));
73  EXPECT_EQ("default",
74            GetStringValueForFlagWithDefault("moose", "default", flags));
75
76  group_number = 0;
77  flags.clear();
78  EXPECT_FALSE(GetFieldTrialInfo(
79      "Group77 bar:1 baz:7 cat:dogs DISABLED", &flags, &group_number));
80  EXPECT_EQ(ZERO, group_number);
81  EXPECT_EQ(ZERO, flags.size());
82}
83
84class InstantExtendedAPIEnabledTest : public testing::Test {
85 public:
86  InstantExtendedAPIEnabledTest() : histogram_(NULL) {
87  }
88 protected:
89  virtual void SetUp() {
90    field_trial_list_.reset(new base::FieldTrialList(
91        new metrics::SHA1EntropyProvider("42")));
92    base::StatisticsRecorder::Initialize();
93    ResetInstantExtendedOptInStateGateForTest();
94    previous_metrics_count_.resize(INSTANT_EXTENDED_OPT_IN_STATE_ENUM_COUNT, 0);
95    base::HistogramBase* histogram = GetHistogram();
96    if (histogram) {
97      scoped_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples());
98      if (samples.get()) {
99        for (int state = INSTANT_EXTENDED_NOT_SET;
100             state < INSTANT_EXTENDED_OPT_IN_STATE_ENUM_COUNT; ++state) {
101          previous_metrics_count_[state] = samples->GetCount(state);
102        }
103      }
104    }
105  }
106
107  virtual CommandLine* GetCommandLine() const {
108    return CommandLine::ForCurrentProcess();
109  }
110
111  void ValidateMetrics(base::HistogramBase::Sample value) {
112    base::HistogramBase* histogram = GetHistogram();
113    if (histogram) {
114      scoped_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples());
115      if (samples.get()) {
116        for (int state = INSTANT_EXTENDED_NOT_SET;
117             state < INSTANT_EXTENDED_OPT_IN_STATE_ENUM_COUNT; ++state) {
118          if (state == value) {
119            EXPECT_EQ(previous_metrics_count_[state] + 1,
120                      samples->GetCount(state));
121          } else {
122            EXPECT_EQ(previous_metrics_count_[state], samples->GetCount(state));
123          }
124        }
125      }
126    }
127  }
128
129 private:
130  base::HistogramBase* GetHistogram() {
131    if (!histogram_) {
132      histogram_ = base::StatisticsRecorder::FindHistogram(
133          "InstantExtended.OptInState");
134    }
135    return histogram_;
136  }
137  base::HistogramBase* histogram_;
138  scoped_ptr<base::FieldTrialList> field_trial_list_;
139  std::vector<int> previous_metrics_count_;
140};
141
142TEST_F(InstantExtendedAPIEnabledTest, EnabledViaCommandLineFlag) {
143  GetCommandLine()->AppendSwitch(switches::kEnableInstantExtendedAPI);
144  EXPECT_TRUE(IsInstantExtendedAPIEnabled());
145#if defined(OS_IOS) || defined(OS_ANDROID)
146  EXPECT_EQ(1ul, EmbeddedSearchPageVersion());
147#else
148  EXPECT_EQ(2ul, EmbeddedSearchPageVersion());
149#endif
150  ValidateMetrics(INSTANT_EXTENDED_OPT_IN);
151}
152
153TEST_F(InstantExtendedAPIEnabledTest, EnabledViaFinchFlag) {
154  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("InstantExtended",
155                                                     "Group1 espv:42"));
156  EXPECT_TRUE(IsInstantExtendedAPIEnabled());
157  EXPECT_EQ(42ul, EmbeddedSearchPageVersion());
158  ValidateMetrics(INSTANT_EXTENDED_NOT_SET);
159}
160
161TEST_F(InstantExtendedAPIEnabledTest, DisabledViaCommandLineFlag) {
162  GetCommandLine()->AppendSwitch(switches::kDisableInstantExtendedAPI);
163  ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial("InstantExtended",
164                                                     "Group1 espv:2"));
165  EXPECT_FALSE(IsInstantExtendedAPIEnabled());
166  EXPECT_EQ(0ul, EmbeddedSearchPageVersion());
167  ValidateMetrics(INSTANT_EXTENDED_OPT_OUT);
168}
169
170class SearchTest : public BrowserWithTestWindowTest {
171 protected:
172  virtual void SetUp() OVERRIDE {
173    BrowserWithTestWindowTest::SetUp();
174    field_trial_list_.reset(new base::FieldTrialList(
175        new metrics::SHA1EntropyProvider("42")));
176    TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
177        profile(), &TemplateURLServiceFactory::BuildInstanceFor);
178    TemplateURLService* template_url_service =
179        TemplateURLServiceFactory::GetForProfile(profile());
180    ui_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
181    SetSearchProvider();
182  }
183
184  void SetSearchProvider() {
185    TemplateURLService* template_url_service =
186        TemplateURLServiceFactory::GetForProfile(profile());
187    TemplateURLData data;
188    data.SetURL("http://foo.com/url?bar={searchTerms}");
189    data.instant_url = "http://foo.com/instant?"
190        "{google:omniboxStartMarginParameter}foo=foo#foo=foo&strk";
191    data.alternate_urls.push_back("http://foo.com/alt#quux={searchTerms}");
192    data.search_terms_replacement_key = "strk";
193
194    TemplateURL* template_url = new TemplateURL(profile(), data);
195    // Takes ownership of |template_url|.
196    template_url_service->Add(template_url);
197    template_url_service->SetDefaultSearchProvider(template_url);
198  }
199
200  // Build an Instant URL with or without a valid search terms replacement key
201  // as per |has_search_term_replacement_key|. Set that URL as the instant URL
202  // for the default search provider.
203  void SetDefaultInstantTemplateUrl(bool has_search_term_replacement_key) {
204    TemplateURLService* template_url_service =
205        TemplateURLServiceFactory::GetForProfile(profile());
206
207    static const char kInstantURLWithStrk[] =
208        "http://foo.com/instant?foo=foo#foo=foo&strk";
209    static const char kInstantURLNoStrk[] =
210        "http://foo.com/instant?foo=foo#foo=foo";
211
212    TemplateURLData data;
213    data.SetURL("http://foo.com/url?bar={searchTerms}");
214    data.instant_url = (has_search_term_replacement_key ?
215        kInstantURLWithStrk : kInstantURLNoStrk);
216    data.search_terms_replacement_key = "strk";
217
218    TemplateURL* template_url = new TemplateURL(profile(), data);
219    // Takes ownership of |template_url|.
220    template_url_service->Add(template_url);
221    template_url_service->SetDefaultSearchProvider(template_url);
222  }
223
224  scoped_ptr<base::FieldTrialList> field_trial_list_;
225};
226
227struct SearchTestCase {
228  const char* url;
229  bool expected_result;
230  const char* comment;
231};
232
233TEST_F(SearchTest, ShouldAssignURLToInstantRendererExtendedDisabled) {
234  DisableInstantExtendedAPIForTesting();
235
236  const SearchTestCase kTestCases[] = {
237    {"chrome-search://foo/bar",                 false,  ""},
238    {"http://foo.com/instant",                  false,  ""},
239    {"http://foo.com/instant?foo=bar",          false,  ""},
240    {"https://foo.com/instant",                 false,  ""},
241    {"https://foo.com/instant#foo=bar",         false,  ""},
242    {"HtTpS://fOo.CoM/instant",                 false,  ""},
243    {"http://foo.com:80/instant",               false,  ""},
244    {"invalid URL",                             false, "Invalid URL"},
245    {"unknown://scheme/path",                   false, "Unknown scheme"},
246    {"ftp://foo.com/instant",                   false, "Non-HTTP scheme"},
247    {"http://sub.foo.com/instant",              false, "Non-exact host"},
248    {"http://foo.com:26/instant",               false, "Non-default port"},
249    {"http://foo.com/instant/bar",              false, "Non-exact path"},
250    {"http://foo.com/Instant",                  false, "Case sensitive path"},
251    {"http://foo.com/",                         false, "Non-exact path"},
252    {"https://foo.com/",                        false, "Non-exact path"},
253    {"https://foo.com/url?strk",                false, "Non-extended mode"},
254    {"https://foo.com/alt?strk",                false, "Non-extended mode"},
255  };
256
257  for (size_t i = 0; i < arraysize(kTestCases); ++i) {
258    const SearchTestCase& test = kTestCases[i];
259    EXPECT_EQ(test.expected_result,
260              ShouldAssignURLToInstantRenderer(GURL(test.url), profile()))
261        << test.url << " " << test.comment;
262  }
263}
264
265TEST_F(SearchTest, ShouldAssignURLToInstantRendererExtendedEnabled) {
266  EnableInstantExtendedAPIForTesting();
267
268  const SearchTestCase kTestCases[] = {
269    {chrome::kChromeSearchLocalNtpUrl, true,  ""},
270    {"https://foo.com/instant?strk",   true,  ""},
271    {"https://foo.com/instant#strk",   true,  ""},
272    {"https://foo.com/instant?strk=0", true,  ""},
273    {"https://foo.com/url?strk",       true,  ""},
274    {"https://foo.com/alt?strk",       true,  ""},
275    {"http://foo.com/instant",         false, "Non-HTTPS"},
276    {"http://foo.com/instant?strk",    false, "Non-HTTPS"},
277    {"http://foo.com/instant?strk=1",  false, "Non-HTTPS"},
278    {"https://foo.com/instant",        false, "No search terms replacement"},
279    {"https://foo.com/?strk",          false, "Non-exact path"},
280  };
281
282  for (size_t i = 0; i < arraysize(kTestCases); ++i) {
283    const SearchTestCase& test = kTestCases[i];
284    EXPECT_EQ(test.expected_result,
285              ShouldAssignURLToInstantRenderer(GURL(test.url), profile()))
286        << test.url << " " << test.comment;
287  }
288}
289
290const SearchTestCase kInstantNTPTestCases[] = {
291  {"https://foo.com/instant?strk",         true,  "Valid Instant URL"},
292  {"https://foo.com/instant#strk",         true,  "Valid Instant URL"},
293  {"https://foo.com/url?strk",             true,  "Valid search URL"},
294  {"https://foo.com/url#strk",             true,  "Valid search URL"},
295  {"https://foo.com/alt?strk",             true,  "Valid alternative URL"},
296  {"https://foo.com/alt#strk",             true,  "Valid alternative URL"},
297  {"https://foo.com/url?strk&bar=",        true,  "No query terms"},
298  {"https://foo.com/url?strk&q=abc",       true,  "No query terms key"},
299  {"https://foo.com/url?strk#bar=abc",     true,  "Query terms key in ref"},
300  {"https://foo.com/url?strk&bar=abc",     false, "Has query terms"},
301  {"http://foo.com/instant?strk=1",        false, "Insecure URL"},
302  {"https://foo.com/instant",              false, "No search term replacement"},
303  {"chrome://blank/",                      false, "Chrome scheme"},
304  {"chrome-search://foo",                  false, "Chrome-search scheme"},
305  {chrome::kChromeSearchLocalNtpUrl,       true,  "Local new tab page"},
306  {"https://bar.com/instant?strk=1",       false, "Random non-search page"},
307};
308
309TEST_F(SearchTest, InstantNTPExtendedEnabled) {
310  EnableInstantExtendedAPIForTesting();
311  AddTab(browser(), GURL("chrome://blank"));
312  for (size_t i = 0; i < arraysize(kInstantNTPTestCases); ++i) {
313    const SearchTestCase& test = kInstantNTPTestCases[i];
314    NavigateAndCommitActiveTab(GURL(test.url));
315    const content::WebContents* contents =
316        browser()->tab_strip_model()->GetWebContentsAt(0);
317    EXPECT_EQ(test.expected_result, IsInstantNTP(contents))
318        << test.url << " " << test.comment;
319  }
320}
321
322TEST_F(SearchTest, InstantNTPExtendedDisabled) {
323  AddTab(browser(), GURL("chrome://blank"));
324  for (size_t i = 0; i < arraysize(kInstantNTPTestCases); ++i) {
325    const SearchTestCase& test = kInstantNTPTestCases[i];
326    NavigateAndCommitActiveTab(GURL(test.url));
327    const content::WebContents* contents =
328        browser()->tab_strip_model()->GetWebContentsAt(0);
329    EXPECT_FALSE(IsInstantNTP(contents)) << test.url << " " << test.comment;
330  }
331}
332
333TEST_F(SearchTest, InstantNTPCustomNavigationEntry) {
334  EnableInstantExtendedAPIForTesting();
335  AddTab(browser(), GURL("chrome://blank"));
336  for (size_t i = 0; i < arraysize(kInstantNTPTestCases); ++i) {
337    const SearchTestCase& test = kInstantNTPTestCases[i];
338    NavigateAndCommitActiveTab(GURL(test.url));
339    content::WebContents* contents =
340        browser()->tab_strip_model()->GetWebContentsAt(0);
341    content::NavigationController& controller = contents->GetController();
342    controller.SetTransientEntry(
343        controller.CreateNavigationEntry(GURL("chrome://blank"),
344                                         content::Referrer(),
345                                         content::PAGE_TRANSITION_LINK,
346                                         false,
347                                         std::string(),
348                                         contents->GetBrowserContext()));
349    // The active entry is chrome://blank and not an NTP.
350    EXPECT_FALSE(IsInstantNTP(contents));
351    EXPECT_EQ(test.expected_result,
352              NavEntryIsInstantNTP(contents,
353                                   controller.GetLastCommittedEntry()))
354        << test.url << " " << test.comment;
355  }
356}
357
358TEST_F(SearchTest, GetInstantURLExtendedEnabled) {
359  // Instant is disabled, so no Instant URL.
360  EXPECT_EQ(GURL(), GetInstantURL(profile(), kDisableStartMargin));
361
362  // Enable Instant. Still no Instant URL because "strk" is missing.
363  EnableInstantExtendedAPIForTesting();
364  SetDefaultInstantTemplateUrl(false);
365  EXPECT_EQ(GURL(), GetInstantURL(profile(), kDisableStartMargin));
366
367  // Set an Instant URL with a valid search terms replacement key.
368  SetDefaultInstantTemplateUrl(true);
369
370  // Now there should be a valid Instant URL. Note the HTTPS "upgrade".
371  EXPECT_EQ(GURL("https://foo.com/instant?foo=foo#foo=foo&strk"),
372            GetInstantURL(profile(), kDisableStartMargin));
373
374  // Enable suggest. No difference.
375  profile()->GetPrefs()->SetBoolean(prefs::kSearchSuggestEnabled, true);
376  EXPECT_EQ(GURL("https://foo.com/instant?foo=foo#foo=foo&strk"),
377            GetInstantURL(profile(), kDisableStartMargin));
378
379  // Disable suggest. No Instant URL.
380  profile()->GetPrefs()->SetBoolean(prefs::kSearchSuggestEnabled, false);
381  EXPECT_EQ(GURL(), GetInstantURL(profile(), kDisableStartMargin));
382}
383
384TEST_F(SearchTest, StartMarginCGI) {
385  // Instant is disabled, so no Instant URL.
386  EXPECT_EQ(GURL(), GetInstantURL(profile(), kDisableStartMargin));
387
388  // Enable Instant. No margin.
389  EnableInstantExtendedAPIForTesting();
390  profile()->GetPrefs()->SetBoolean(prefs::kSearchSuggestEnabled, true);
391
392  EXPECT_EQ(GURL("https://foo.com/instant?foo=foo#foo=foo&strk"),
393            GetInstantURL(profile(), kDisableStartMargin));
394
395  // With start margin.
396  EXPECT_EQ(GURL("https://foo.com/instant?es_sm=10&foo=foo#foo=foo&strk"),
397            GetInstantURL(profile(), 10));
398}
399
400TEST_F(SearchTest, CommandLineOverrides) {
401  EnableInstantExtendedAPIForTesting();
402
403  GURL local_instant_url(GetLocalInstantURL(profile()));
404  EXPECT_EQ(GURL(chrome::kChromeSearchLocalNtpUrl), local_instant_url);
405
406  TemplateURLService* template_url_service =
407      TemplateURLServiceFactory::GetForProfile(profile());
408  TemplateURLData data;
409  data.SetURL("{google:baseURL}search?q={searchTerms}");
410  data.instant_url = "{google:baseURL}webhp?strk";
411  data.search_terms_replacement_key = "strk";
412  TemplateURL* template_url = new TemplateURL(profile(), data);
413  // Takes ownership of |template_url|.
414  template_url_service->Add(template_url);
415  template_url_service->SetDefaultSearchProvider(template_url);
416
417  // By default, Instant Extended forces the instant URL to be HTTPS, so even if
418  // we set a Google base URL that is HTTP, we should get an HTTPS URL.
419  UIThreadSearchTermsData::SetGoogleBaseURL("http://www.foo.com/");
420  GURL instant_url(GetInstantURL(profile(), kDisableStartMargin));
421  ASSERT_TRUE(instant_url.is_valid());
422  EXPECT_EQ("https://www.foo.com/webhp?strk", instant_url.spec());
423
424  // However, if the Google base URL is specified on the command line, the
425  // instant URL should just use it, even if it's HTTP.
426  UIThreadSearchTermsData::SetGoogleBaseURL(std::string());
427  CommandLine::ForCurrentProcess()->AppendSwitchASCII(switches::kGoogleBaseURL,
428                                                      "http://www.bar.com/");
429  instant_url = GetInstantURL(profile(), kDisableStartMargin);
430  ASSERT_TRUE(instant_url.is_valid());
431  EXPECT_EQ("http://www.bar.com/webhp?strk", instant_url.spec());
432
433  // Similarly, setting a Google base URL on the command line should allow you
434  // to get the Google version of the local NTP, even though search provider's
435  // URL doesn't contain "google".
436  local_instant_url = GetLocalInstantURL(profile());
437  EXPECT_EQ(GURL(chrome::kChromeSearchLocalNtpUrl), local_instant_url);
438
439  // If we specify extra search query params, they should be inserted into the
440  // query portion of the instant URL.
441  CommandLine::ForCurrentProcess()->AppendSwitchASCII(
442      switches::kExtraSearchQueryParams, "a=b");
443  instant_url = GetInstantURL(profile(), kDisableStartMargin);
444  ASSERT_TRUE(instant_url.is_valid());
445  EXPECT_EQ("http://www.bar.com/webhp?a=b&strk", instant_url.spec());
446}
447
448}  // namespace chrome
449