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