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