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
operator <<(std::ostream & os,const AutocompleteResult::const_iterator & it)24 static std::ostream& operator<<(std::ostream& os,
25 const AutocompleteResult::const_iterator& it) {
26 return os << static_cast<const AutocompleteMatch*>(&(*it));
27 }
28
29 namespace {
30
31 const 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.
35 class TestProvider : public AutocompleteProvider {
36 public:
TestProvider(int relevance,const string16 & prefix)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
set_listener(ACProviderListener * listener)46 void set_listener(ACProviderListener* listener) {
47 listener_ = listener;
48 }
49
50 private:
~TestProvider()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
Start(const AutocompleteInput & input,bool minimal_changes)61 void 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
Run()78 void 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
AddResults(int start_at,int num)86 void 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
105 class 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
ResetControllerWithTestProviders(bool same_destinations)136 void 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
165 void AutocompleteProviderTest::
ResetControllerWithTestProvidersWithKeywordAndSearchProviders()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
RunTest()204 void 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
RunExactKeymatchTest(bool allow_exact_keyword_match)214 void 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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)231 void 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.
TEST_F(AutocompleteProviderTest,Query)241 TEST_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
TEST_F(AutocompleteProviderTest,RemoveDuplicates)252 TEST_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
TEST_F(AutocompleteProviderTest,AllowExactKeywordMatch)264 TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) {
265 ResetControllerWithTestProvidersWithKeywordAndSearchProviders();
266 RunExactKeymatchTest(true);
267 RunExactKeymatchTest(false);
268 }
269
270 typedef TestingBrowserProcessTest AutocompleteTest;
271
TEST_F(AutocompleteTest,InputType)272 TEST_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
TEST_F(AutocompleteTest,InputTypeWithDesiredTLD)369 TEST_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.
TEST_F(AutocompleteTest,InputCrash)388 TEST_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.
TEST(AutocompleteMatch,MoreRelevant)394 TEST(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
TEST(AutocompleteInput,ParseForEmphasizeComponent)419 TEST(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