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