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