// Copyright (c) 2011 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/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/autocomplete/autocomplete.h" #include "chrome/browser/autocomplete/autocomplete_match.h" #include "chrome/browser/autocomplete/keyword_provider.h" #include "chrome/browser/autocomplete/search_provider.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/test/testing_browser_process.h" #include "chrome/test/testing_browser_process_test.h" #include "chrome/test/testing_profile.h" #include "content/common/notification_observer.h" #include "content/common/notification_registrar.h" #include "content/common/notification_service.h" #include "testing/gtest/include/gtest/gtest.h" static std::ostream& operator<<(std::ostream& os, const AutocompleteResult::const_iterator& it) { return os << static_cast<const AutocompleteMatch*>(&(*it)); } 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 string16& 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 string16 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.matches_requested() == AutocompleteInput::ALL_MATCHES) { 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_ + UTF8ToUTF16(base::IntToString(i)); match.destination_url = GURL(UTF16ToUTF8(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: void ResetControllerWithTestProviders(bool same_destinations); // Runs a query on the input "a", and makes sure both providers' input is // properly collected. void RunTest(); void ResetControllerWithTestProvidersWithKeywordAndSearchProviders(); void RunExactKeymatchTest(bool allow_exact_keyword_match); // 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); ScopedTestingBrowserProcess browser_process_; MessageLoopForUI message_loop_; scoped_ptr<AutocompleteController> controller_; NotificationRegistrar registrar_; TestingProfile profile_; }; void AutocompleteProviderTest::ResetControllerWithTestProviders( 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, ASCIIToUTF16("http://a")); providerA->AddRef(); providers_.push_back(providerA); TestProvider* providerB = new TestProvider(num_results_per_provider * 2, same_destinations ? ASCIIToUTF16("http://a") : ASCIIToUTF16("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); // The providers don't complete synchronously, so listen for "result updated" // notifications. registrar_.Add(this, NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_READY, NotificationService::AllSources()); } void AutocompleteProviderTest:: ResetControllerWithTestProvidersWithKeywordAndSearchProviders() { profile_.CreateTemplateURLModel(); // Reset the default TemplateURL. TemplateURL* default_t_url = new TemplateURL(); default_t_url->SetURL("http://defaultturl/{searchTerms}", 0, 0); TemplateURLModel* turl_model = profile_.GetTemplateURLModel(); turl_model->Add(default_t_url); turl_model->SetDefaultSearchProvider(default_t_url); TemplateURLID default_provider_id = default_t_url->id(); ASSERT_NE(0, default_provider_id); // Create another TemplateURL for KeywordProvider. TemplateURL* keyword_t_url = new TemplateURL(); keyword_t_url->set_short_name(ASCIIToUTF16("k")); keyword_t_url->set_keyword(ASCIIToUTF16("k")); keyword_t_url->SetURL("http://keyword/{searchTerms}", 0, 0); profile_.GetTemplateURLModel()->Add(keyword_t_url); ASSERT_NE(0, keyword_t_url->id()); // 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(); // Create both a keyword and search provider, and add them in that order. // (Order is important; see comments in RunExactKeymatchTest().) AutocompleteProvider* keyword_provider = new KeywordProvider(NULL, &profile_); keyword_provider->AddRef(); providers_.push_back(keyword_provider); AutocompleteProvider* search_provider = new SearchProvider(NULL, &profile_); search_provider->AddRef(); providers_.push_back(search_provider); AutocompleteController* controller = new AutocompleteController(providers_); controller_.reset(controller); } void AutocompleteProviderTest::RunTest() { result_.Reset(); controller_->Start(ASCIIToUTF16("a"), string16(), true, false, true, AutocompleteInput::ALL_MATCHES); // The message loop will terminate when all autocomplete input has been // collected. MessageLoop::current()->Run(); } void AutocompleteProviderTest::RunExactKeymatchTest( bool allow_exact_keyword_match) { // Send the controller input which exactly matches the keyword provider we // created in ResetControllerWithKeywordAndSearchProviders(). The default // match should thus be a keyword match iff |allow_exact_keyword_match| is // true. controller_->Start(ASCIIToUTF16("k test"), string16(), true, false, allow_exact_keyword_match, AutocompleteInput::SYNCHRONOUS_MATCHES); EXPECT_TRUE(controller_->done()); // ResetControllerWithKeywordAndSearchProviders() adds the keyword provider // first, then the search provider. So if the default match is a keyword // match, it will come from provider 0, otherwise from provider 1. EXPECT_EQ(providers_[allow_exact_keyword_match ? 0 : 1], controller_->result().default_match()->provider); } void AutocompleteProviderTest::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (controller_->done()) { result_.CopyFrom(controller_->result()); MessageLoop::current()->Quit(); } } // Tests that the default selection is set properly when updating results. TEST_F(AutocompleteProviderTest, Query) { ResetControllerWithTestProviders(false); 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) { ResetControllerWithTestProviders(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); } TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) { ResetControllerWithTestProvidersWithKeywordAndSearchProviders(); RunExactKeymatchTest(true); RunExactKeymatchTest(false); } typedef TestingBrowserProcessTest AutocompleteTest; TEST_F(AutocompleteTest, InputType) { struct test_data { const string16 input; const AutocompleteInput::Type type; } input_cases[] = { { ASCIIToUTF16(""), AutocompleteInput::INVALID }, { ASCIIToUTF16("?"), AutocompleteInput::FORCED_QUERY }, { ASCIIToUTF16("?foo"), AutocompleteInput::FORCED_QUERY }, { ASCIIToUTF16("?foo bar"), AutocompleteInput::FORCED_QUERY }, { ASCIIToUTF16("?http://foo.com/bar"), AutocompleteInput::FORCED_QUERY }, { ASCIIToUTF16("foo"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("foo.c"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("-.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("foo/bar"), AutocompleteInput::URL }, { ASCIIToUTF16("foo;bar"), AutocompleteInput::QUERY }, { ASCIIToUTF16("foo/bar baz"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("foo bar.com"), AutocompleteInput::QUERY }, { ASCIIToUTF16("foo bar"), AutocompleteInput::QUERY }, { ASCIIToUTF16("foo+bar"), AutocompleteInput::QUERY }, { ASCIIToUTF16("foo+bar.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("\"foo:bar\""), AutocompleteInput::QUERY }, { ASCIIToUTF16("link:foo.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("foo:81"), AutocompleteInput::URL }, { ASCIIToUTF16("www.foo.com:81"), AutocompleteInput::URL }, { ASCIIToUTF16("localhost:8080"), AutocompleteInput::URL }, { ASCIIToUTF16("foo.com:123456"), AutocompleteInput::QUERY }, { ASCIIToUTF16("foo.com:abc"), AutocompleteInput::QUERY }, { ASCIIToUTF16("1.2.3.4:abc"), AutocompleteInput::QUERY }, { ASCIIToUTF16("user@foo.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("user:pass@"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("user:pass@!foo.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("user:pass@foo"), AutocompleteInput::URL }, { ASCIIToUTF16("user:pass@foo.c"), AutocompleteInput::URL }, { ASCIIToUTF16("user:pass@foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("user:pass@foo.com:81"), AutocompleteInput::URL }, { ASCIIToUTF16("user:pass@foo:81"), AutocompleteInput::URL }, { ASCIIToUTF16("1.2"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("1.2/45"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("1.2:45"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("user@1.2:45"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("user:pass@1.2:45"), AutocompleteInput::URL }, { ASCIIToUTF16("ps/2 games"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("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. // { ASCIIToUTF16("mailto:abuse@foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("view-source:http://www.foo.com/"), AutocompleteInput::URL }, { ASCIIToUTF16("javascript:alert(\"Hey there!\");"), AutocompleteInput::URL }, #if defined(OS_WIN) { ASCIIToUTF16("C:\\Program Files"), AutocompleteInput::URL }, { ASCIIToUTF16("\\\\Server\\Folder\\File"), AutocompleteInput::URL }, #endif // defined(OS_WIN) { ASCIIToUTF16("http:foo"), AutocompleteInput::URL }, { ASCIIToUTF16("http://foo"), AutocompleteInput::URL }, { ASCIIToUTF16("http://foo.c"), AutocompleteInput::URL }, { ASCIIToUTF16("http://foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("http://foo_bar.com"), AutocompleteInput::URL }, { ASCIIToUTF16("http://foo/bar baz"), AutocompleteInput::URL }, { ASCIIToUTF16("http://-.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("http://_foo_.com"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("http://foo.com:abc"), AutocompleteInput::QUERY }, { ASCIIToUTF16("http://foo.com:123456"), AutocompleteInput::QUERY }, { ASCIIToUTF16("http://1.2.3.4:abc"), AutocompleteInput::QUERY }, { ASCIIToUTF16("http:user@foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("http://user@foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("http:user:pass@foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("http://user:pass@foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("http://1.2"), AutocompleteInput::URL }, { ASCIIToUTF16("http://1.2/45"), AutocompleteInput::URL }, { ASCIIToUTF16("http:ps/2 games"), AutocompleteInput::URL }, { ASCIIToUTF16("http://ps/2 games"), AutocompleteInput::URL }, { ASCIIToUTF16("https://foo.com"), AutocompleteInput::URL }, { ASCIIToUTF16("127.0.0.1"), AutocompleteInput::URL }, { ASCIIToUTF16("127.0.1"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("127.0.1/"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("browser.tabs.closeButtons"), AutocompleteInput::UNKNOWN }, { WideToUTF16(L"\u6d4b\u8bd5"), AutocompleteInput::UNKNOWN }, { ASCIIToUTF16("[2001:]"), AutocompleteInput::QUERY }, // Not a valid IP { ASCIIToUTF16("[2001:dB8::1]"), AutocompleteInput::URL }, { ASCIIToUTF16("192.168.0.256"), AutocompleteInput::QUERY }, // Invalid IPv4 literal. { ASCIIToUTF16("[foo.com]"), AutocompleteInput::QUERY }, // Invalid IPv6 literal. }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) { SCOPED_TRACE(input_cases[i].input); AutocompleteInput input(input_cases[i].input, string16(), true, false, true, AutocompleteInput::ALL_MATCHES); EXPECT_EQ(input_cases[i].type, input.type()); } } TEST_F(AutocompleteTest, InputTypeWithDesiredTLD) { struct test_data { const string16 input; const AutocompleteInput::Type type; } input_cases[] = { { ASCIIToUTF16("401k"), AutocompleteInput::REQUESTED_URL }, { ASCIIToUTF16("999999999999999"), AutocompleteInput::REQUESTED_URL }, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) { AutocompleteInput input(input_cases[i].input, ASCIIToUTF16("com"), true, false, true, AutocompleteInput::ALL_MATCHES); 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_F(AutocompleteTest, InputCrash) { AutocompleteInput input(WideToUTF16(L"\uff65@s"), string16(), true, false, true, AutocompleteInput::ALL_MATCHES); } // Test comparing matches relevance. 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, false }, { -5, -10, true }, }; 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 string16 input; const Component scheme; const Component host; } input_cases[] = { { ASCIIToUTF16(""), kInvalidComponent, kInvalidComponent }, { ASCIIToUTF16("?"), kInvalidComponent, kInvalidComponent }, { ASCIIToUTF16("?http://foo.com/bar"), kInvalidComponent, kInvalidComponent }, { ASCIIToUTF16("foo/bar baz"), kInvalidComponent, Component(0, 3) }, { ASCIIToUTF16("http://foo/bar baz"), Component(0, 4), Component(7, 3) }, { ASCIIToUTF16("link:foo.com"), Component(0, 4), kInvalidComponent }, { ASCIIToUTF16("www.foo.com:81"), kInvalidComponent, Component(0, 11) }, { WideToUTF16(L"\u6d4b\u8bd5"), kInvalidComponent, Component(0, 2) }, { ASCIIToUTF16("view-source:http://www.foo.com/"), Component(12, 4), Component(19, 11) }, { ASCIIToUTF16("view-source:https://example.com/"), Component(12, 5), Component(20, 11) }, { ASCIIToUTF16("view-source:www.foo.com"), kInvalidComponent, Component(12, 11) }, { ASCIIToUTF16("view-source:"), Component(0, 11), kInvalidComponent }, { ASCIIToUTF16("view-source:garbage"), kInvalidComponent, Component(12, 7) }, { ASCIIToUTF16("view-source:http://http://foo"), Component(12, 4), Component(19, 4) }, { ASCIIToUTF16("view-source:view-source:http://example.com/"), Component(12, 11), kInvalidComponent } }; ScopedTestingBrowserProcess browser_process; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) { Component scheme, host; AutocompleteInput::ParseForEmphasizeComponents(input_cases[i].input, string16(), &scheme, &host); AutocompleteInput input(input_cases[i].input, string16(), true, false, true, AutocompleteInput::ALL_MATCHES); 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