blob: 63eba96bce6c5ee5f6a9b71e8c2ec56416ab77df [file] [log] [blame]
// Copyright 2014 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 "chrome/browser/autocomplete/zero_suggest_provider.h"
#include "base/metrics/field_trial.h"
#include "base/prefs/pref_service.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/history/top_sites.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/metrics/proto/omnibox_event.pb.h"
#include "components/omnibox/autocomplete_provider_listener.h"
#include "components/omnibox/omnibox_field_trial.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class FakeEmptyTopSites : public history::TopSites {
public:
FakeEmptyTopSites() {
}
// history::TopSites:
bool SetPageThumbnail(const GURL& url, const gfx::Image& thumbnail,
const ThumbnailScore& score) override {
return false;
}
bool SetPageThumbnailToJPEGBytes(const GURL& url,
const base::RefCountedMemory* memory,
const ThumbnailScore& score) override {
return false;
}
void GetMostVisitedURLs(const GetMostVisitedURLsCallback& callback,
bool include_forced_urls) override;
bool GetPageThumbnail(const GURL& url, bool prefix_match,
scoped_refptr<base::RefCountedMemory>* bytes) override {
return false;
}
bool GetPageThumbnailScore(const GURL& url, ThumbnailScore* score) override {
return false;
}
bool GetTemporaryPageThumbnailScore(const GURL& url, ThumbnailScore* score)
override {
return false;
}
void SyncWithHistory() override {}
bool HasBlacklistedItems() const override {
return false;
}
void AddBlacklistedURL(const GURL& url) override {}
void RemoveBlacklistedURL(const GURL& url) override {}
bool IsBlacklisted(const GURL& url) override {
return false;
}
void ClearBlacklistedURLs() override {}
void Shutdown() override {}
base::CancelableTaskTracker::TaskId StartQueryForMostVisited() override {
return 0;
}
bool IsKnownURL(const GURL& url) override {
return false;
}
const std::string& GetCanonicalURLString(const GURL& url) const override {
CHECK(false);
return *(new std::string());
}
bool IsNonForcedFull() override {
return false;
}
bool IsForcedFull() override {
return false;
}
bool loaded() const override {
return false;
}
history::MostVisitedURLList GetPrepopulatePages() override {
return history::MostVisitedURLList();
}
bool AddForcedURL(const GURL& url, const base::Time& time) override {
return false;
}
// content::NotificationObserver:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {}
// A test-specific field for controlling when most visited callback is run
// after top sites have been requested.
GetMostVisitedURLsCallback mv_callback;
protected:
~FakeEmptyTopSites() override {}
};
void FakeEmptyTopSites::GetMostVisitedURLs(
const GetMostVisitedURLsCallback& callback,
bool include_forced_urls) {
mv_callback = callback;
}
} // namespace
class ZeroSuggestProviderTest : public testing::Test,
public AutocompleteProviderListener {
public:
ZeroSuggestProviderTest();
void SetUp() override;
void TearDown() override;
protected:
// AutocompleteProviderListener:
void OnProviderUpdate(bool updated_matches) override;
void ResetFieldTrialList();
void CreatePersonalizedFieldTrial();
void CreateMostVisitedFieldTrial();
// Set up threads for testing; this needs to be instantiated before
// |profile_|.
content::TestBrowserThreadBundle thread_bundle_;
// Needed for OmniboxFieldTrial::ActivateStaticTrials().
scoped_ptr<base::FieldTrialList> field_trial_list_;
// URLFetcherFactory implementation registered.
net::TestURLFetcherFactory test_factory_;
// Profile we use.
TestingProfile profile_;
// ZeroSuggestProvider object under test.
scoped_refptr<ZeroSuggestProvider> provider_;
// Default template URL.
TemplateURL* default_t_url_;
};
ZeroSuggestProviderTest::ZeroSuggestProviderTest() {
ResetFieldTrialList();
}
void ZeroSuggestProviderTest::SetUp() {
// Make sure that fetchers are automatically ungregistered upon destruction.
test_factory_.set_remove_fetcher_on_delete(true);
TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
&profile_, &TemplateURLServiceFactory::BuildInstanceFor);
AutocompleteClassifierFactory::GetInstance()->SetTestingFactoryAndUse(
&profile_, &AutocompleteClassifierFactory::BuildInstanceFor);
TemplateURLService* turl_model =
TemplateURLServiceFactory::GetForProfile(&profile_);
turl_model->Load();
TemplateURLData data;
data.short_name = base::ASCIIToUTF16("t");
data.SetURL("https://www.google.com/?q={searchTerms}");
data.suggestions_url = "https://www.google.com/complete/?q={searchTerms}";
data.instant_url = "https://does/not/exist?strk=1";
data.search_terms_replacement_key = "strk";
default_t_url_ = new TemplateURL(data);
turl_model->Add(default_t_url_);
turl_model->SetUserSelectedDefaultSearchProvider(default_t_url_);
profile_.SetTopSites(new FakeEmptyTopSites());
provider_ = ZeroSuggestProvider::Create(this, turl_model, &profile_);
}
void ZeroSuggestProviderTest::TearDown() {
// Shutdown the provider before the profile.
provider_ = NULL;
}
void ZeroSuggestProviderTest::OnProviderUpdate(bool updated_matches) {
}
void ZeroSuggestProviderTest::ResetFieldTrialList() {
// Destroy the existing FieldTrialList before creating a new one to avoid
// a DCHECK.
field_trial_list_.reset();
field_trial_list_.reset(new base::FieldTrialList(
new metrics::SHA1EntropyProvider("foo")));
variations::testing::ClearAllVariationParams();
}
void ZeroSuggestProviderTest::CreatePersonalizedFieldTrial() {
std::map<std::string, std::string> params;
params[std::string(OmniboxFieldTrial::kZeroSuggestRule)] = "true";
params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
"Personalized";
variations::AssociateVariationParams(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
base::FieldTrialList::CreateFieldTrial(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
}
void ZeroSuggestProviderTest::CreateMostVisitedFieldTrial() {
std::map<std::string, std::string> params;
params[std::string(OmniboxFieldTrial::kZeroSuggestRule)] = "true";
params[std::string(OmniboxFieldTrial::kZeroSuggestVariantRule)] =
"MostVisitedWithoutSERP";
variations::AssociateVariationParams(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params);
base::FieldTrialList::CreateFieldTrial(
OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
}
TEST_F(ZeroSuggestProviderTest, TestMostVisitedCallback) {
CreateMostVisitedFieldTrial();
std::string current_url("http://www.foxnews.com/");
std::string input_url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(input_url), base::string16::npos,
std::string(), GURL(current_url),
metrics::OmniboxEventProto::OTHER, false, false,
true, true,
ChromeAutocompleteSchemeClassifier(&profile_));
history::MostVisitedURLList urls;
history::MostVisitedURL url(GURL("http://foo.com/"),
base::ASCIIToUTF16(std::string("Foo")));
urls.push_back(url);
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
static_cast<FakeEmptyTopSites*>(profile_.GetTopSites())->mv_callback.Run(
urls);
// Should have verbatim match + most visited url match.
EXPECT_EQ(2U, provider_->matches().size());
provider_->Stop(false);
provider_->Start(input, false);
provider_->Stop(false);
EXPECT_TRUE(provider_->matches().empty());
// Most visited results arriving after Stop() has been called, ensure they
// are not displayed.
static_cast<FakeEmptyTopSites*>(profile_.GetTopSites())->mv_callback.Run(
urls);
EXPECT_TRUE(provider_->matches().empty());
}
TEST_F(ZeroSuggestProviderTest, TestMostVisitedNavigateToSearchPage) {
CreateMostVisitedFieldTrial();
std::string current_url("http://www.foxnews.com/");
std::string input_url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(input_url), base::string16::npos,
std::string(), GURL(current_url),
metrics::OmniboxEventProto::OTHER, false, false,
true, true,
ChromeAutocompleteSchemeClassifier(&profile_));
history::MostVisitedURLList urls;
history::MostVisitedURL url(GURL("http://foo.com/"),
base::ASCIIToUTF16(std::string("Foo")));
urls.push_back(url);
provider_->Start(input, false);
EXPECT_TRUE(provider_->matches().empty());
// Stop() doesn't always get called.
std::string search_url("https://www.google.com/?q=flowers");
AutocompleteInput srp_input(
base::ASCIIToUTF16(search_url),
base::string16::npos,
std::string(),
GURL(search_url),
metrics::OmniboxEventProto::
SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT,
false,
false,
true,
true,
ChromeAutocompleteSchemeClassifier(&profile_));
provider_->Start(srp_input, false);
EXPECT_TRUE(provider_->matches().empty());
// Most visited results arriving after a new request has been started.
static_cast<FakeEmptyTopSites*>(profile_.GetTopSites())->mv_callback.Run(
urls);
EXPECT_TRUE(provider_->matches().empty());
}
TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRun) {
CreatePersonalizedFieldTrial();
// Ensure the cache is empty.
PrefService* prefs = profile_.GetPrefs();
prefs->SetString(prefs::kZeroSuggestCachedResults, std::string());
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true,
ChromeAutocompleteSchemeClassifier(&profile_));
provider_->Start(input, false);
EXPECT_TRUE(prefs->GetString(prefs::kZeroSuggestCachedResults).empty());
EXPECT_TRUE(provider_->matches().empty());
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(200);
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
fetcher->SetResponseString(json_response);
fetcher->delegate()->OnURLFetchComplete(fetcher);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(4U, provider_->matches().size()); // 3 results + verbatim
EXPECT_EQ(json_response, prefs->GetString(prefs::kZeroSuggestCachedResults));
}
TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResults) {
CreatePersonalizedFieldTrial();
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true,
ChromeAutocompleteSchemeClassifier(&profile_));
// Set up the pref to cache the response from the previous run.
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
PrefService* prefs = profile_.GetPrefs();
prefs->SetString(prefs::kZeroSuggestCachedResults, json_response);
provider_->Start(input, false);
// Expect that matches get populated synchronously out of the cache.
ASSERT_EQ(4U, provider_->matches().size());
EXPECT_EQ(base::ASCIIToUTF16("search1"), provider_->matches()[1].contents);
EXPECT_EQ(base::ASCIIToUTF16("search2"), provider_->matches()[2].contents);
EXPECT_EQ(base::ASCIIToUTF16("search3"), provider_->matches()[3].contents);
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(200);
std::string json_response2("[\"\",[\"search4\", \"search5\", \"search6\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
fetcher->SetResponseString(json_response2);
fetcher->delegate()->OnURLFetchComplete(fetcher);
base::RunLoop().RunUntilIdle();
// Expect the same 4 results after the response has been handled.
ASSERT_EQ(4U, provider_->matches().size());
EXPECT_EQ(base::ASCIIToUTF16("search1"), provider_->matches()[1].contents);
EXPECT_EQ(base::ASCIIToUTF16("search2"), provider_->matches()[2].contents);
EXPECT_EQ(base::ASCIIToUTF16("search3"), provider_->matches()[3].contents);
// Expect the new results have been stored.
EXPECT_EQ(json_response2,
prefs->GetString(prefs::kZeroSuggestCachedResults));
}
TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestReceivedEmptyResults) {
CreatePersonalizedFieldTrial();
std::string url("http://www.cnn.com/");
AutocompleteInput input(base::ASCIIToUTF16(url), base::string16::npos,
std::string(), GURL(url),
metrics::OmniboxEventProto::INVALID_SPEC, true, false,
true, true,
ChromeAutocompleteSchemeClassifier(&profile_));
// Set up the pref to cache the response from the previous run.
std::string json_response("[\"\",[\"search1\", \"search2\", \"search3\"],"
"[],[],{\"google:suggestrelevance\":[602, 601, 600],"
"\"google:verbatimrelevance\":1300}]");
PrefService* prefs = profile_.GetPrefs();
prefs->SetString(prefs::kZeroSuggestCachedResults, json_response);
provider_->Start(input, false);
// Expect that matches get populated synchronously out of the cache.
ASSERT_EQ(4U, provider_->matches().size());
EXPECT_EQ(base::ASCIIToUTF16("search1"), provider_->matches()[1].contents);
EXPECT_EQ(base::ASCIIToUTF16("search2"), provider_->matches()[2].contents);
EXPECT_EQ(base::ASCIIToUTF16("search3"), provider_->matches()[3].contents);
net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(1);
ASSERT_TRUE(fetcher);
fetcher->set_response_code(200);
std::string empty_response("[\"\",[],[],[],{}]");
fetcher->SetResponseString(empty_response);
fetcher->delegate()->OnURLFetchComplete(fetcher);
base::RunLoop().RunUntilIdle();
// Expect that the matches have been cleared.
ASSERT_TRUE(provider_->matches().empty());
// Expect the new results have been stored.
EXPECT_EQ(empty_response,
prefs->GetString(prefs::kZeroSuggestCachedResults));
}