blob: 2507310bbf2d78b806425056b645576a0878e181 [file] [log] [blame]
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
#include "testing/gtest/include/gtest/gtest.h"
// identifiers for known autocomplete providers
#define HISTORY_IDENTIFIER L"Chrome:History"
#define SEARCH_IDENTIFIER L"google.com/websearch/en"
namespace {
const size_t num_results_per_provider = 3;
// Autocomplete provider that provides known results. Note that this is
// refcounted so that it can also be a task on the message loop.
class TestProvider : public AutocompleteProvider {
public:
TestProvider(int relevance, const std::wstring& prefix)
: AutocompleteProvider(NULL, NULL, ""),
relevance_(relevance),
prefix_(prefix) {
}
virtual void Start(const AutocompleteInput& input,
bool minimal_changes);
void set_listener(ACProviderListener* listener) {
listener_ = listener;
}
private:
~TestProvider() {}
void Run();
void AddResults(int start_at, int num);
int relevance_;
const std::wstring prefix_;
};
void TestProvider::Start(const AutocompleteInput& input,
bool minimal_changes) {
if (minimal_changes)
return;
matches_.clear();
// Generate one result synchronously, the rest later.
AddResults(0, 1);
if (!input.synchronous_only()) {
done_ = false;
MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
this, &TestProvider::Run));
}
}
void TestProvider::Run() {
DCHECK_GT(num_results_per_provider, 0U);
AddResults(1, num_results_per_provider);
done_ = true;
DCHECK(listener_);
listener_->OnProviderUpdate(true);
}
void TestProvider::AddResults(int start_at, int num) {
for (int i = start_at; i < num; i++) {
AutocompleteMatch match(this, relevance_ - i, false,
AutocompleteMatch::URL_WHAT_YOU_TYPED);
match.fill_into_edit = prefix_ + IntToWString(i);
match.destination_url = GURL(WideToUTF8(match.fill_into_edit));
match.contents = match.fill_into_edit;
match.contents_class.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
match.description = match.fill_into_edit;
match.description_class.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
matches_.push_back(match);
}
}
class AutocompleteProviderTest : public testing::Test,
public NotificationObserver {
protected:
// testing::Test
virtual void SetUp();
void ResetController(bool same_destinations);
// Runs a query on the input "a", and makes sure both providers' input is
// properly collected.
void RunTest();
// These providers are owned by the controller once it's created.
ACProviders providers_;
AutocompleteResult result_;
private:
// NotificationObserver
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
MessageLoopForUI message_loop_;
scoped_ptr<AutocompleteController> controller_;
NotificationRegistrar registrar_;
};
void AutocompleteProviderTest::SetUp() {
registrar_.Add(this, NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED,
NotificationService::AllSources());
registrar_.Add(this,
NotificationType::AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED,
NotificationService::AllSources());
ResetController(false);
}
void AutocompleteProviderTest::ResetController(bool same_destinations) {
// Forget about any existing providers. The controller owns them and will
// Release() them below, when we delete it during the call to reset().
providers_.clear();
// Construct two new providers, with either the same or different prefixes.
TestProvider* providerA = new TestProvider(num_results_per_provider,
L"http://a");
providerA->AddRef();
providers_.push_back(providerA);
TestProvider* providerB = new TestProvider(num_results_per_provider * 2,
same_destinations ? L"http://a" : L"http://b");
providerB->AddRef();
providers_.push_back(providerB);
// Reset the controller to contain our new providers.
AutocompleteController* controller = new AutocompleteController(providers_);
controller_.reset(controller);
providerA->set_listener(controller);
providerB->set_listener(controller);
}
void AutocompleteProviderTest::RunTest() {
result_.Reset();
controller_->Start(L"a", std::wstring(), true, false, false);
// The message loop will terminate when all autocomplete input has been
// collected.
MessageLoop::current()->Run();
}
void AutocompleteProviderTest::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (controller_->done()) {
result_.CopyFrom(*(Details<const AutocompleteResult>(details).ptr()));
MessageLoop::current()->Quit();
}
}
std::ostream& operator<<(std::ostream& os,
const AutocompleteResult::const_iterator& iter) {
return os << static_cast<const AutocompleteMatch*>(&(*iter));
}
// Tests that the default selection is set properly when updating results.
TEST_F(AutocompleteProviderTest, Query) {
RunTest();
// Make sure the default match gets set to the highest relevance match. The
// highest relevance matches should come from the second provider.
EXPECT_EQ(num_results_per_provider * 2, result_.size()); // two providers
ASSERT_NE(result_.end(), result_.default_match());
EXPECT_EQ(providers_[1], result_.default_match()->provider);
}
TEST_F(AutocompleteProviderTest, RemoveDuplicates) {
// Set up the providers to provide duplicate results.
ResetController(true);
RunTest();
// Make sure all the first provider's results were eliminated by the second
// provider's.
EXPECT_EQ(num_results_per_provider, result_.size());
for (AutocompleteResult::const_iterator i(result_.begin());
i != result_.end(); ++i)
EXPECT_EQ(providers_[1], i->provider);
// Set things back to the default for the benefit of any tests that run after
// us.
ResetController(false);
}
TEST(AutocompleteTest, InputType) {
struct test_data {
const wchar_t* input;
const AutocompleteInput::Type type;
} input_cases[] = {
{ L"", AutocompleteInput::INVALID },
{ L"?", AutocompleteInput::FORCED_QUERY },
{ L"?foo", AutocompleteInput::FORCED_QUERY },
{ L"?foo bar", AutocompleteInput::FORCED_QUERY },
{ L"?http://foo.com/bar", AutocompleteInput::FORCED_QUERY },
{ L"foo", AutocompleteInput::UNKNOWN },
{ L"foo.c", AutocompleteInput::UNKNOWN },
{ L"foo.com", AutocompleteInput::URL },
{ L"-.com", AutocompleteInput::UNKNOWN },
{ L"foo/bar", AutocompleteInput::URL },
{ L"foo/bar baz", AutocompleteInput::UNKNOWN },
{ L"foo bar.com", AutocompleteInput::QUERY },
{ L"http://foo/bar baz", AutocompleteInput::URL },
{ L"foo bar", AutocompleteInput::QUERY },
{ L"foo+bar", AutocompleteInput::QUERY },
{ L"foo+bar.com", AutocompleteInput::UNKNOWN },
{ L"\"foo:bar\"", AutocompleteInput::QUERY },
{ L"link:foo.com", AutocompleteInput::UNKNOWN },
{ L"www.foo.com:81", AutocompleteInput::URL },
{ L"localhost:8080", AutocompleteInput::URL },
{ L"foo.com:123456", AutocompleteInput::QUERY },
{ L"foo.com:abc", AutocompleteInput::QUERY },
{ L"[email protected]", AutocompleteInput::UNKNOWN },
{ L"user:[email protected]", AutocompleteInput::UNKNOWN },
{ L"1.2", AutocompleteInput::UNKNOWN },
{ L"1.2/45", AutocompleteInput::UNKNOWN },
{ L"ps/2 games", AutocompleteInput::UNKNOWN },
{ L"en.wikipedia.org/wiki/James Bond", AutocompleteInput::URL },
// In Chrome itself, mailto: will get handled by ShellExecute, but in
// unittest mode, we don't have the data loaded in the external protocol
// handler to know this.
// { L"mailto:[email protected]", AutocompleteInput::URL },
{ L"view-source:http://www.foo.com/", AutocompleteInput::URL },
{ L"javascript:alert(\"Hey there!\");", AutocompleteInput::URL },
#if defined(OS_WIN)
{ L"C:\\Program Files", AutocompleteInput::URL },
{ L"\\\\Server\\Folder\\File", AutocompleteInput::URL },
#endif // defined(OS_WIN)
{ L"http:foo", AutocompleteInput::URL },
{ L"http://foo", AutocompleteInput::URL },
{ L"http://foo.c", AutocompleteInput::URL },
{ L"http://foo.com", AutocompleteInput::URL },
{ L"http://foo_bar.com", AutocompleteInput::URL },
{ L"http://-.com", AutocompleteInput::UNKNOWN },
{ L"http://_foo_.com", AutocompleteInput::UNKNOWN },
{ L"http://foo.com:abc", AutocompleteInput::QUERY },
{ L"http://foo.com:123456", AutocompleteInput::QUERY },
{ L"http:[email protected]", AutocompleteInput::URL },
{ L"http://[email protected]", AutocompleteInput::URL },
{ L"http://user:[email protected]", AutocompleteInput::URL },
{ L"http://1.2", AutocompleteInput::URL },
{ L"http://1.2/45", AutocompleteInput::URL },
{ L"http:ps/2 games", AutocompleteInput::URL },
{ L"http://ps/2 games", AutocompleteInput::URL },
{ L"127.0.0.1", AutocompleteInput::URL },
{ L"127.0.1", AutocompleteInput::UNKNOWN },
{ L"127.0.1/", AutocompleteInput::UNKNOWN },
{ L"browser.tabs.closeButtons", AutocompleteInput::UNKNOWN },
{ L"\u6d4b\u8bd5", AutocompleteInput::UNKNOWN },
{ L"[2001:]", AutocompleteInput::QUERY }, // Not a valid IP
{ L"[2001:dB8::1]", AutocompleteInput::URL },
{ L"192.168.0.256", AutocompleteInput::QUERY }, // Invalid IPv4 literal.
{ L"[foo.com]", AutocompleteInput::QUERY }, // Invalid IPv6 literal.
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
AutocompleteInput input(input_cases[i].input, std::wstring(), true, false,
false);
EXPECT_EQ(input_cases[i].type, input.type()) << "Input: " <<
input_cases[i].input;
}
}
// This tests for a regression where certain input in the omnibox caused us to
// crash. As long as the test completes without crashing, we're fine.
TEST(AutocompleteTest, InputCrash) {
AutocompleteInput input(L"\uff65@s", std::wstring(), true, false, false);
}
// Test that we can properly compare matches' relevance when at least one is
// negative.
TEST(AutocompleteMatch, MoreRelevant) {
struct RelevantCases {
int r1;
int r2;
bool expected_result;
} cases[] = {
{ 10, 0, true },
{ 10, -5, true },
{ -5, 10, false },
{ 0, 10, false },
{ -10, -5, true },
{ -5, -10, false },
};
AutocompleteMatch m1(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED);
AutocompleteMatch m2(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED);
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
m1.relevance = cases[i].r1;
m2.relevance = cases[i].r2;
EXPECT_EQ(cases[i].expected_result,
AutocompleteMatch::MoreRelevant(m1, m2));
}
}
TEST(AutocompleteInput, ParseForEmphasizeComponent) {
using url_parse::Component;
Component kInvalidComponent(0, -1);
struct test_data {
const wchar_t* input;
const Component scheme;
const Component host;
} input_cases[] = {
{ L"", kInvalidComponent, kInvalidComponent },
{ L"?", kInvalidComponent, kInvalidComponent },
{ L"?http://foo.com/bar", kInvalidComponent, kInvalidComponent },
{ L"foo/bar baz", kInvalidComponent, Component(0, 3) },
{ L"http://foo/bar baz", Component(0, 4), Component(7, 3) },
{ L"link:foo.com", Component(0, 4), kInvalidComponent },
{ L"www.foo.com:81", kInvalidComponent, Component(0, 11) },
{ L"\u6d4b\u8bd5", kInvalidComponent, Component(0, 2) },
{ L"view-source:http://www.foo.com/", Component(12, 4), Component(19, 11) },
{ L"view-source:https://example.com/",
Component(12, 5), Component(20, 11) },
{ L"view-source:", Component(0, 11), kInvalidComponent },
{ L"view-source:garbage", kInvalidComponent, Component(12, 7) },
{ L"view-source:http://http://foo", Component(12, 4), Component(19, 4) },
{ L"view-source:view-source:http://example.com/",
Component(12, 11), kInvalidComponent }
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
Component scheme, host;
AutocompleteInput::ParseForEmphasizeComponents(input_cases[i].input,
std::wstring(),
&scheme,
&host);
AutocompleteInput input(input_cases[i].input, std::wstring(), true, false,
false);
EXPECT_EQ(input_cases[i].scheme.begin, scheme.begin) << "Input: " <<
input_cases[i].input;
EXPECT_EQ(input_cases[i].scheme.len, scheme.len) << "Input: " <<
input_cases[i].input;
EXPECT_EQ(input_cases[i].host.begin, host.begin) << "Input: " <<
input_cases[i].input;
EXPECT_EQ(input_cases[i].host.len, host.len) << "Input: " <<
input_cases[i].input;
}
}
} // namespace