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