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