1// Copyright (c) 2011 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/memory/scoped_ptr.h" 6#include "base/message_loop.h" 7#include "base/string_number_conversions.h" 8#include "base/string_util.h" 9#include "base/utf_string_conversions.h" 10#include "chrome/browser/autocomplete/autocomplete.h" 11#include "chrome/browser/autocomplete/autocomplete_match.h" 12#include "chrome/browser/autocomplete/keyword_provider.h" 13#include "chrome/browser/autocomplete/search_provider.h" 14#include "chrome/browser/search_engines/template_url.h" 15#include "chrome/browser/search_engines/template_url_model.h" 16#include "chrome/test/testing_browser_process.h" 17#include "chrome/test/testing_browser_process_test.h" 18#include "chrome/test/testing_profile.h" 19#include "content/common/notification_observer.h" 20#include "content/common/notification_registrar.h" 21#include "content/common/notification_service.h" 22#include "testing/gtest/include/gtest/gtest.h" 23 24static std::ostream& operator<<(std::ostream& os, 25 const AutocompleteResult::const_iterator& it) { 26 return os << static_cast<const AutocompleteMatch*>(&(*it)); 27} 28 29namespace { 30 31const size_t num_results_per_provider = 3; 32 33// Autocomplete provider that provides known results. Note that this is 34// refcounted so that it can also be a task on the message loop. 35class TestProvider : public AutocompleteProvider { 36 public: 37 TestProvider(int relevance, const string16& prefix) 38 : AutocompleteProvider(NULL, NULL, ""), 39 relevance_(relevance), 40 prefix_(prefix) { 41 } 42 43 virtual void Start(const AutocompleteInput& input, 44 bool minimal_changes); 45 46 void set_listener(ACProviderListener* listener) { 47 listener_ = listener; 48 } 49 50 private: 51 ~TestProvider() {} 52 53 void Run(); 54 55 void AddResults(int start_at, int num); 56 57 int relevance_; 58 const string16 prefix_; 59}; 60 61void TestProvider::Start(const AutocompleteInput& input, 62 bool minimal_changes) { 63 if (minimal_changes) 64 return; 65 66 matches_.clear(); 67 68 // Generate one result synchronously, the rest later. 69 AddResults(0, 1); 70 71 if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) { 72 done_ = false; 73 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 74 this, &TestProvider::Run)); 75 } 76} 77 78void TestProvider::Run() { 79 DCHECK_GT(num_results_per_provider, 0U); 80 AddResults(1, num_results_per_provider); 81 done_ = true; 82 DCHECK(listener_); 83 listener_->OnProviderUpdate(true); 84} 85 86void TestProvider::AddResults(int start_at, int num) { 87 for (int i = start_at; i < num; i++) { 88 AutocompleteMatch match(this, relevance_ - i, false, 89 AutocompleteMatch::URL_WHAT_YOU_TYPED); 90 91 match.fill_into_edit = prefix_ + UTF8ToUTF16(base::IntToString(i)); 92 match.destination_url = GURL(UTF16ToUTF8(match.fill_into_edit)); 93 94 match.contents = match.fill_into_edit; 95 match.contents_class.push_back( 96 ACMatchClassification(0, ACMatchClassification::NONE)); 97 match.description = match.fill_into_edit; 98 match.description_class.push_back( 99 ACMatchClassification(0, ACMatchClassification::NONE)); 100 101 matches_.push_back(match); 102 } 103} 104 105class AutocompleteProviderTest : public testing::Test, 106 public NotificationObserver { 107 protected: 108 void ResetControllerWithTestProviders(bool same_destinations); 109 110 // Runs a query on the input "a", and makes sure both providers' input is 111 // properly collected. 112 void RunTest(); 113 114 void ResetControllerWithTestProvidersWithKeywordAndSearchProviders(); 115 void RunExactKeymatchTest(bool allow_exact_keyword_match); 116 117 // These providers are owned by the controller once it's created. 118 ACProviders providers_; 119 120 AutocompleteResult result_; 121 122 private: 123 // NotificationObserver 124 virtual void Observe(NotificationType type, 125 const NotificationSource& source, 126 const NotificationDetails& details); 127 128 ScopedTestingBrowserProcess browser_process_; 129 130 MessageLoopForUI message_loop_; 131 scoped_ptr<AutocompleteController> controller_; 132 NotificationRegistrar registrar_; 133 TestingProfile profile_; 134}; 135 136void AutocompleteProviderTest::ResetControllerWithTestProviders( 137 bool same_destinations) { 138 // Forget about any existing providers. The controller owns them and will 139 // Release() them below, when we delete it during the call to reset(). 140 providers_.clear(); 141 142 // Construct two new providers, with either the same or different prefixes. 143 TestProvider* providerA = new TestProvider(num_results_per_provider, 144 ASCIIToUTF16("http://a")); 145 providerA->AddRef(); 146 providers_.push_back(providerA); 147 148 TestProvider* providerB = new TestProvider(num_results_per_provider * 2, 149 same_destinations ? ASCIIToUTF16("http://a") : ASCIIToUTF16("http://b")); 150 providerB->AddRef(); 151 providers_.push_back(providerB); 152 153 // Reset the controller to contain our new providers. 154 AutocompleteController* controller = new AutocompleteController(providers_); 155 controller_.reset(controller); 156 providerA->set_listener(controller); 157 providerB->set_listener(controller); 158 159 // The providers don't complete synchronously, so listen for "result updated" 160 // notifications. 161 registrar_.Add(this, NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_READY, 162 NotificationService::AllSources()); 163} 164 165void AutocompleteProviderTest:: 166 ResetControllerWithTestProvidersWithKeywordAndSearchProviders() { 167 profile_.CreateTemplateURLModel(); 168 169 // Reset the default TemplateURL. 170 TemplateURL* default_t_url = new TemplateURL(); 171 default_t_url->SetURL("http://defaultturl/{searchTerms}", 0, 0); 172 TemplateURLModel* turl_model = profile_.GetTemplateURLModel(); 173 turl_model->Add(default_t_url); 174 turl_model->SetDefaultSearchProvider(default_t_url); 175 TemplateURLID default_provider_id = default_t_url->id(); 176 ASSERT_NE(0, default_provider_id); 177 178 // Create another TemplateURL for KeywordProvider. 179 TemplateURL* keyword_t_url = new TemplateURL(); 180 keyword_t_url->set_short_name(ASCIIToUTF16("k")); 181 keyword_t_url->set_keyword(ASCIIToUTF16("k")); 182 keyword_t_url->SetURL("http://keyword/{searchTerms}", 0, 0); 183 profile_.GetTemplateURLModel()->Add(keyword_t_url); 184 ASSERT_NE(0, keyword_t_url->id()); 185 186 // Forget about any existing providers. The controller owns them and will 187 // Release() them below, when we delete it during the call to reset(). 188 providers_.clear(); 189 190 // Create both a keyword and search provider, and add them in that order. 191 // (Order is important; see comments in RunExactKeymatchTest().) 192 AutocompleteProvider* keyword_provider = new KeywordProvider(NULL, 193 &profile_); 194 keyword_provider->AddRef(); 195 providers_.push_back(keyword_provider); 196 AutocompleteProvider* search_provider = new SearchProvider(NULL, &profile_); 197 search_provider->AddRef(); 198 providers_.push_back(search_provider); 199 200 AutocompleteController* controller = new AutocompleteController(providers_); 201 controller_.reset(controller); 202} 203 204void AutocompleteProviderTest::RunTest() { 205 result_.Reset(); 206 controller_->Start(ASCIIToUTF16("a"), string16(), true, false, true, 207 AutocompleteInput::ALL_MATCHES); 208 209 // The message loop will terminate when all autocomplete input has been 210 // collected. 211 MessageLoop::current()->Run(); 212} 213 214void AutocompleteProviderTest::RunExactKeymatchTest( 215 bool allow_exact_keyword_match) { 216 // Send the controller input which exactly matches the keyword provider we 217 // created in ResetControllerWithKeywordAndSearchProviders(). The default 218 // match should thus be a keyword match iff |allow_exact_keyword_match| is 219 // true. 220 controller_->Start(ASCIIToUTF16("k test"), string16(), true, false, 221 allow_exact_keyword_match, 222 AutocompleteInput::SYNCHRONOUS_MATCHES); 223 EXPECT_TRUE(controller_->done()); 224 // ResetControllerWithKeywordAndSearchProviders() adds the keyword provider 225 // first, then the search provider. So if the default match is a keyword 226 // match, it will come from provider 0, otherwise from provider 1. 227 EXPECT_EQ(providers_[allow_exact_keyword_match ? 0 : 1], 228 controller_->result().default_match()->provider); 229} 230 231void AutocompleteProviderTest::Observe(NotificationType type, 232 const NotificationSource& source, 233 const NotificationDetails& details) { 234 if (controller_->done()) { 235 result_.CopyFrom(controller_->result()); 236 MessageLoop::current()->Quit(); 237 } 238} 239 240// Tests that the default selection is set properly when updating results. 241TEST_F(AutocompleteProviderTest, Query) { 242 ResetControllerWithTestProviders(false); 243 RunTest(); 244 245 // Make sure the default match gets set to the highest relevance match. The 246 // highest relevance matches should come from the second provider. 247 EXPECT_EQ(num_results_per_provider * 2, result_.size()); // two providers 248 ASSERT_NE(result_.end(), result_.default_match()); 249 EXPECT_EQ(providers_[1], result_.default_match()->provider); 250} 251 252TEST_F(AutocompleteProviderTest, RemoveDuplicates) { 253 ResetControllerWithTestProviders(true); 254 RunTest(); 255 256 // Make sure all the first provider's results were eliminated by the second 257 // provider's. 258 EXPECT_EQ(num_results_per_provider, result_.size()); 259 for (AutocompleteResult::const_iterator i(result_.begin()); 260 i != result_.end(); ++i) 261 EXPECT_EQ(providers_[1], i->provider); 262} 263 264TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) { 265 ResetControllerWithTestProvidersWithKeywordAndSearchProviders(); 266 RunExactKeymatchTest(true); 267 RunExactKeymatchTest(false); 268} 269 270typedef TestingBrowserProcessTest AutocompleteTest; 271 272TEST_F(AutocompleteTest, InputType) { 273 struct test_data { 274 const string16 input; 275 const AutocompleteInput::Type type; 276 } input_cases[] = { 277 { ASCIIToUTF16(""), AutocompleteInput::INVALID }, 278 { ASCIIToUTF16("?"), AutocompleteInput::FORCED_QUERY }, 279 { ASCIIToUTF16("?foo"), AutocompleteInput::FORCED_QUERY }, 280 { ASCIIToUTF16("?foo bar"), AutocompleteInput::FORCED_QUERY }, 281 { ASCIIToUTF16("?http://foo.com/bar"), AutocompleteInput::FORCED_QUERY }, 282 { ASCIIToUTF16("foo"), AutocompleteInput::UNKNOWN }, 283 { ASCIIToUTF16("foo.c"), AutocompleteInput::UNKNOWN }, 284 { ASCIIToUTF16("foo.com"), AutocompleteInput::URL }, 285 { ASCIIToUTF16("-.com"), AutocompleteInput::UNKNOWN }, 286 { ASCIIToUTF16("foo/bar"), AutocompleteInput::URL }, 287 { ASCIIToUTF16("foo;bar"), AutocompleteInput::QUERY }, 288 { ASCIIToUTF16("foo/bar baz"), AutocompleteInput::UNKNOWN }, 289 { ASCIIToUTF16("foo bar.com"), AutocompleteInput::QUERY }, 290 { ASCIIToUTF16("foo bar"), AutocompleteInput::QUERY }, 291 { ASCIIToUTF16("foo+bar"), AutocompleteInput::QUERY }, 292 { ASCIIToUTF16("foo+bar.com"), AutocompleteInput::UNKNOWN }, 293 { ASCIIToUTF16("\"foo:bar\""), AutocompleteInput::QUERY }, 294 { ASCIIToUTF16("link:foo.com"), AutocompleteInput::UNKNOWN }, 295 { ASCIIToUTF16("foo:81"), AutocompleteInput::URL }, 296 { ASCIIToUTF16("www.foo.com:81"), AutocompleteInput::URL }, 297 { ASCIIToUTF16("localhost:8080"), AutocompleteInput::URL }, 298 { ASCIIToUTF16("foo.com:123456"), AutocompleteInput::QUERY }, 299 { ASCIIToUTF16("foo.com:abc"), AutocompleteInput::QUERY }, 300 { ASCIIToUTF16("1.2.3.4:abc"), AutocompleteInput::QUERY }, 301 { ASCIIToUTF16("user@foo.com"), AutocompleteInput::UNKNOWN }, 302 { ASCIIToUTF16("user:pass@"), AutocompleteInput::UNKNOWN }, 303 { ASCIIToUTF16("user:pass@!foo.com"), AutocompleteInput::UNKNOWN }, 304 { ASCIIToUTF16("user:pass@foo"), AutocompleteInput::URL }, 305 { ASCIIToUTF16("user:pass@foo.c"), AutocompleteInput::URL }, 306 { ASCIIToUTF16("user:pass@foo.com"), AutocompleteInput::URL }, 307 { ASCIIToUTF16("user:pass@foo.com:81"), AutocompleteInput::URL }, 308 { ASCIIToUTF16("user:pass@foo:81"), AutocompleteInput::URL }, 309 { ASCIIToUTF16("1.2"), AutocompleteInput::UNKNOWN }, 310 { ASCIIToUTF16("1.2/45"), AutocompleteInput::UNKNOWN }, 311 { ASCIIToUTF16("1.2:45"), AutocompleteInput::UNKNOWN }, 312 { ASCIIToUTF16("user@1.2:45"), AutocompleteInput::UNKNOWN }, 313 { ASCIIToUTF16("user:pass@1.2:45"), AutocompleteInput::URL }, 314 { ASCIIToUTF16("ps/2 games"), AutocompleteInput::UNKNOWN }, 315 { ASCIIToUTF16("en.wikipedia.org/wiki/James Bond"), 316 AutocompleteInput::URL }, 317 // In Chrome itself, mailto: will get handled by ShellExecute, but in 318 // unittest mode, we don't have the data loaded in the external protocol 319 // handler to know this. 320 // { ASCIIToUTF16("mailto:abuse@foo.com"), AutocompleteInput::URL }, 321 { ASCIIToUTF16("view-source:http://www.foo.com/"), AutocompleteInput::URL }, 322 { ASCIIToUTF16("javascript:alert(\"Hey there!\");"), 323 AutocompleteInput::URL }, 324#if defined(OS_WIN) 325 { ASCIIToUTF16("C:\\Program Files"), AutocompleteInput::URL }, 326 { ASCIIToUTF16("\\\\Server\\Folder\\File"), AutocompleteInput::URL }, 327#endif // defined(OS_WIN) 328 { ASCIIToUTF16("http:foo"), AutocompleteInput::URL }, 329 { ASCIIToUTF16("http://foo"), AutocompleteInput::URL }, 330 { ASCIIToUTF16("http://foo.c"), AutocompleteInput::URL }, 331 { ASCIIToUTF16("http://foo.com"), AutocompleteInput::URL }, 332 { ASCIIToUTF16("http://foo_bar.com"), AutocompleteInput::URL }, 333 { ASCIIToUTF16("http://foo/bar baz"), AutocompleteInput::URL }, 334 { ASCIIToUTF16("http://-.com"), AutocompleteInput::UNKNOWN }, 335 { ASCIIToUTF16("http://_foo_.com"), AutocompleteInput::UNKNOWN }, 336 { ASCIIToUTF16("http://foo.com:abc"), AutocompleteInput::QUERY }, 337 { ASCIIToUTF16("http://foo.com:123456"), AutocompleteInput::QUERY }, 338 { ASCIIToUTF16("http://1.2.3.4:abc"), AutocompleteInput::QUERY }, 339 { ASCIIToUTF16("http:user@foo.com"), AutocompleteInput::URL }, 340 { ASCIIToUTF16("http://user@foo.com"), AutocompleteInput::URL }, 341 { ASCIIToUTF16("http:user:pass@foo.com"), AutocompleteInput::URL }, 342 { ASCIIToUTF16("http://user:pass@foo.com"), AutocompleteInput::URL }, 343 { ASCIIToUTF16("http://1.2"), AutocompleteInput::URL }, 344 { ASCIIToUTF16("http://1.2/45"), AutocompleteInput::URL }, 345 { ASCIIToUTF16("http:ps/2 games"), AutocompleteInput::URL }, 346 { ASCIIToUTF16("http://ps/2 games"), AutocompleteInput::URL }, 347 { ASCIIToUTF16("https://foo.com"), AutocompleteInput::URL }, 348 { ASCIIToUTF16("127.0.0.1"), AutocompleteInput::URL }, 349 { ASCIIToUTF16("127.0.1"), AutocompleteInput::UNKNOWN }, 350 { ASCIIToUTF16("127.0.1/"), AutocompleteInput::UNKNOWN }, 351 { ASCIIToUTF16("browser.tabs.closeButtons"), AutocompleteInput::UNKNOWN }, 352 { WideToUTF16(L"\u6d4b\u8bd5"), AutocompleteInput::UNKNOWN }, 353 { ASCIIToUTF16("[2001:]"), AutocompleteInput::QUERY }, // Not a valid IP 354 { ASCIIToUTF16("[2001:dB8::1]"), AutocompleteInput::URL }, 355 { ASCIIToUTF16("192.168.0.256"), 356 AutocompleteInput::QUERY }, // Invalid IPv4 literal. 357 { ASCIIToUTF16("[foo.com]"), 358 AutocompleteInput::QUERY }, // Invalid IPv6 literal. 359 }; 360 361 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) { 362 SCOPED_TRACE(input_cases[i].input); 363 AutocompleteInput input(input_cases[i].input, string16(), true, false, 364 true, AutocompleteInput::ALL_MATCHES); 365 EXPECT_EQ(input_cases[i].type, input.type()); 366 } 367} 368 369TEST_F(AutocompleteTest, InputTypeWithDesiredTLD) { 370 struct test_data { 371 const string16 input; 372 const AutocompleteInput::Type type; 373 } input_cases[] = { 374 { ASCIIToUTF16("401k"), AutocompleteInput::REQUESTED_URL }, 375 { ASCIIToUTF16("999999999999999"), AutocompleteInput::REQUESTED_URL }, 376 }; 377 378 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) { 379 AutocompleteInput input(input_cases[i].input, ASCIIToUTF16("com"), true, 380 false, true, AutocompleteInput::ALL_MATCHES); 381 EXPECT_EQ(input_cases[i].type, input.type()) << "Input: " << 382 input_cases[i].input; 383 } 384} 385 386// This tests for a regression where certain input in the omnibox caused us to 387// crash. As long as the test completes without crashing, we're fine. 388TEST_F(AutocompleteTest, InputCrash) { 389 AutocompleteInput input(WideToUTF16(L"\uff65@s"), string16(), true, false, 390 true, AutocompleteInput::ALL_MATCHES); 391} 392 393// Test comparing matches relevance. 394TEST(AutocompleteMatch, MoreRelevant) { 395 struct RelevantCases { 396 int r1; 397 int r2; 398 bool expected_result; 399 } cases[] = { 400 { 10, 0, true }, 401 { 10, -5, true }, 402 { -5, 10, false }, 403 { 0, 10, false }, 404 { -10, -5, false }, 405 { -5, -10, true }, 406 }; 407 408 AutocompleteMatch m1(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED); 409 AutocompleteMatch m2(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED); 410 411 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { 412 m1.relevance = cases[i].r1; 413 m2.relevance = cases[i].r2; 414 EXPECT_EQ(cases[i].expected_result, 415 AutocompleteMatch::MoreRelevant(m1, m2)); 416 } 417} 418 419TEST(AutocompleteInput, ParseForEmphasizeComponent) { 420 using url_parse::Component; 421 Component kInvalidComponent(0, -1); 422 struct test_data { 423 const string16 input; 424 const Component scheme; 425 const Component host; 426 } input_cases[] = { 427 { ASCIIToUTF16(""), kInvalidComponent, kInvalidComponent }, 428 { ASCIIToUTF16("?"), kInvalidComponent, kInvalidComponent }, 429 { ASCIIToUTF16("?http://foo.com/bar"), kInvalidComponent, 430 kInvalidComponent }, 431 { ASCIIToUTF16("foo/bar baz"), kInvalidComponent, Component(0, 3) }, 432 { ASCIIToUTF16("http://foo/bar baz"), Component(0, 4), Component(7, 3) }, 433 { ASCIIToUTF16("link:foo.com"), Component(0, 4), kInvalidComponent }, 434 { ASCIIToUTF16("www.foo.com:81"), kInvalidComponent, Component(0, 11) }, 435 { WideToUTF16(L"\u6d4b\u8bd5"), kInvalidComponent, Component(0, 2) }, 436 { ASCIIToUTF16("view-source:http://www.foo.com/"), Component(12, 4), 437 Component(19, 11) }, 438 { ASCIIToUTF16("view-source:https://example.com/"), 439 Component(12, 5), Component(20, 11) }, 440 { ASCIIToUTF16("view-source:www.foo.com"), kInvalidComponent, 441 Component(12, 11) }, 442 { ASCIIToUTF16("view-source:"), Component(0, 11), kInvalidComponent }, 443 { ASCIIToUTF16("view-source:garbage"), kInvalidComponent, 444 Component(12, 7) }, 445 { ASCIIToUTF16("view-source:http://http://foo"), Component(12, 4), 446 Component(19, 4) }, 447 { ASCIIToUTF16("view-source:view-source:http://example.com/"), 448 Component(12, 11), kInvalidComponent } 449 }; 450 451 ScopedTestingBrowserProcess browser_process; 452 453 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) { 454 Component scheme, host; 455 AutocompleteInput::ParseForEmphasizeComponents(input_cases[i].input, 456 string16(), 457 &scheme, 458 &host); 459 AutocompleteInput input(input_cases[i].input, string16(), true, false, 460 true, AutocompleteInput::ALL_MATCHES); 461 EXPECT_EQ(input_cases[i].scheme.begin, scheme.begin) << "Input: " << 462 input_cases[i].input; 463 EXPECT_EQ(input_cases[i].scheme.len, scheme.len) << "Input: " << 464 input_cases[i].input; 465 EXPECT_EQ(input_cases[i].host.begin, host.begin) << "Input: " << 466 input_cases[i].input; 467 EXPECT_EQ(input_cases[i].host.len, host.len) << "Input: " << 468 input_cases[i].input; 469 } 470} 471 472} // namespace 473