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