blob: bf14023ac2c935d5d1ad38224f9cfc8a12053b91 [file] [log] [blame]
// Copyright 2019 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 "components/omnibox/browser/local_history_zero_suggest_provider.h"
#include <memory>
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/url_database.h"
#include "components/history/core/test/history_service_test_util.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/fake_autocomplete_provider_client.h"
#include "components/omnibox/browser/in_memory_url_index_test_util.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/search_engines_test_util.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::Time;
using base::TimeDelta;
namespace {
// Used to populate the URLDatabase.
struct TestURLData {
const TemplateURL* search_provider;
std::string search_terms;
std::string other_query_params;
int age_in_days;
std::string title = "";
int visit_count = 1;
int typed_count = 1;
bool hidden = false;
};
// Used to validate expected autocomplete matches.
struct TestMatchData {
std::string content;
int relevance;
bool allowed_to_be_default_match = false;
};
} // namespace
// Flaky leaks on ASAN LSAN (crbug.com/1010691).
#if defined(ADDRESS_SANITIZER)
#define MAYBE_LocalHistoryZeroSuggestProviderTest \
DISABLED_LocalHistoryZeroSuggestProviderTest
#else
#define MAYBE_LocalHistoryZeroSuggestProviderTest \
LocalHistoryZeroSuggestProviderTest
#endif
class MAYBE_LocalHistoryZeroSuggestProviderTest
: public testing::Test,
public AutocompleteProviderListener {
public:
MAYBE_LocalHistoryZeroSuggestProviderTest()
: client_(std::make_unique<FakeAutocompleteProviderClient>()),
provider_(base::WrapRefCounted(
LocalHistoryZeroSuggestProvider::Create(client_.get(), this))) {
SetZeroSuggestVariant(
metrics::OmniboxEventProto::NTP_REALBOX,
LocalHistoryZeroSuggestProvider::kZeroSuggestLocalVariant);
}
~MAYBE_LocalHistoryZeroSuggestProviderTest() override {}
protected:
// testing::Test
void SetUp() override {
// Verify that Google is the default search provider.
ASSERT_EQ(SEARCH_ENGINE_GOOGLE,
default_search_provider()->GetEngineType(
client_->GetTemplateURLService()->search_terms_data()));
}
void TearDown() override { task_environment_.RunUntilIdle(); }
// AutocompleteProviderListener
void OnProviderUpdate(bool updated_matches) override;
// Configures the ZeroSuggestVariant field trial param with the given value
// for the given context.
void SetZeroSuggestVariant(PageClassification page_classification,
std::string zero_suggest_variant_value);
// Fills the URLDatabase with search URLs using the provided information.
void LoadURLs(std::vector<TestURLData> url_data_list);
// Waits for history::HistoryService's async operations.
void WaitForHistoryService();
// Creates an input using the provided information and queries the provider.
void StartProviderAndWaitUntilDone(const std::string& text,
bool from_omnibox_focus,
PageClassification page_classification);
// Verifies that provider matches are as expected.
void ExpectMatches(std::vector<TestMatchData> match_data_list);
const TemplateURL* default_search_provider() {
return client_->GetTemplateURLService()->GetDefaultSearchProvider();
}
base::test::TaskEnvironment task_environment_;
// Used to spin the message loop until |provider_| is done with its async ops.
std::unique_ptr<base::RunLoop> provider_run_loop_;
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
std::unique_ptr<FakeAutocompleteProviderClient> client_;
scoped_refptr<LocalHistoryZeroSuggestProvider> provider_;
private:
DISALLOW_COPY_AND_ASSIGN(MAYBE_LocalHistoryZeroSuggestProviderTest);
};
void MAYBE_LocalHistoryZeroSuggestProviderTest::SetZeroSuggestVariant(
PageClassification page_classification,
std::string zero_suggest_variant_value) {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitAndEnableFeatureWithParameters(
omnibox::kOnFocusSuggestions,
{{std::string(OmniboxFieldTrial::kZeroSuggestVariantRule) + ":" +
base::NumberToString(static_cast<int>(page_classification)) + ":*",
zero_suggest_variant_value}});
}
void MAYBE_LocalHistoryZeroSuggestProviderTest::LoadURLs(
std::vector<TestURLData> url_data_list) {
const Time now = Time::Now();
history::URLRows rows;
for (const auto& entry : url_data_list) {
TemplateURLRef::SearchTermsArgs search_terms_args(
base::UTF8ToUTF16(entry.search_terms));
const auto& search_terms_data =
client_->GetTemplateURLService()->search_terms_data();
std::string search_url =
entry.search_provider->url_ref().ReplaceSearchTerms(search_terms_args,
search_terms_data);
history::URLRow row(GURL{search_url});
row.set_title(base::UTF8ToUTF16(entry.title));
row.set_visit_count(entry.visit_count);
row.set_typed_count(entry.typed_count);
row.set_last_visit(now - TimeDelta::FromDays(entry.age_in_days));
row.set_hidden(entry.hidden);
rows.push_back(row);
}
client_->GetHistoryService()->AddPagesWithDetails(rows,
history::SOURCE_BROWSED);
WaitForHistoryService();
}
void MAYBE_LocalHistoryZeroSuggestProviderTest::WaitForHistoryService() {
history::BlockUntilHistoryProcessesPendingRequests(
client_->GetHistoryService());
// MemoryURLIndex schedules tasks to rebuild its index on the history thread.
// Block here to make sure they are complete.
BlockUntilInMemoryURLIndexIsRefreshed(client_->GetInMemoryURLIndex());
}
void MAYBE_LocalHistoryZeroSuggestProviderTest::StartProviderAndWaitUntilDone(
const std::string& text = "",
bool from_omnibox_focus = true,
PageClassification page_classification =
metrics::OmniboxEventProto::NTP_REALBOX) {
AutocompleteInput input(base::ASCIIToUTF16(text), page_classification,
TestSchemeClassifier());
input.set_from_omnibox_focus(from_omnibox_focus);
provider_->Start(input, false);
if (!provider_->done()) {
provider_run_loop_ = std::make_unique<base::RunLoop>();
// Quits in OnProviderUpdate when the provider is done.
provider_run_loop_->Run();
}
}
void MAYBE_LocalHistoryZeroSuggestProviderTest::OnProviderUpdate(
bool updated_matches) {
if (provider_->done() && provider_run_loop_)
provider_run_loop_->Quit();
}
void MAYBE_LocalHistoryZeroSuggestProviderTest::ExpectMatches(
std::vector<TestMatchData> match_data_list) {
ASSERT_EQ(match_data_list.size(), provider_->matches().size());
size_t index = 0;
for (const auto& expected : match_data_list) {
const auto& match = provider_->matches()[index];
EXPECT_EQ(expected.relevance, match.relevance);
EXPECT_EQ(base::UTF8ToUTF16(expected.content), match.contents);
EXPECT_EQ(expected.allowed_to_be_default_match,
match.allowed_to_be_default_match);
index++;
}
}
// Tests that suggestions are returned only if when input is empty and focused.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, Input) {
LoadURLs({
{default_search_provider(), "hello world", "&foo=bar", 1},
});
StartProviderAndWaitUntilDone(/*text=*/"blah");
ExpectMatches({});
StartProviderAndWaitUntilDone(/*text=*/"", /*from_omnibox_focus=*/false);
ExpectMatches({});
StartProviderAndWaitUntilDone();
ExpectMatches({{"hello world", 500}});
}
// Tests that suggestions are returned only if ZeroSuggestVariant is configured
// to return local history suggestions in the NTP.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, ZeroSuggestVariant) {
LoadURLs({
{default_search_provider(), "hello world", "&foo=bar", 1},
});
SetZeroSuggestVariant(
metrics::OmniboxEventProto::OTHER,
LocalHistoryZeroSuggestProvider::kZeroSuggestLocalVariant);
StartProviderAndWaitUntilDone(
/*text=*/"", /*from_omnibox_focus=*/true,
/*page_classification=*/metrics::OmniboxEventProto::OTHER);
ExpectMatches({});
SetZeroSuggestVariant(metrics::OmniboxEventProto::NTP_REALBOX,
/*zero_suggest_variant_value=*/"blah");
StartProviderAndWaitUntilDone();
ExpectMatches({});
SetZeroSuggestVariant(
metrics::OmniboxEventProto::NTP_REALBOX,
LocalHistoryZeroSuggestProvider::kZeroSuggestLocalVariant);
StartProviderAndWaitUntilDone();
ExpectMatches({{"hello world", 500}});
SetZeroSuggestVariant(
metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS,
LocalHistoryZeroSuggestProvider::kZeroSuggestLocalVariant);
StartProviderAndWaitUntilDone(
/*text=*/"", /*from_omnibox_focus=*/true,
/*page_classification=*/
metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS);
ExpectMatches({{"hello world", 500}});
}
// Tests that search terms are extracted from the default search provider's
// search history only and only when Google is the default search provider.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, DefaultSearchProvider) {
auto* template_url_service = client_->GetTemplateURLService();
auto* other_search_provider = template_url_service->Add(
std::make_unique<TemplateURL>(*GenerateDummyTemplateURLData("other")));
LoadURLs({
{default_search_provider(), "hello world", "&foo=bar", 1},
{default_search_provider(), "", "&foo=bar", 1},
{other_search_provider, "does not matter", "&foo=bar", 1},
});
StartProviderAndWaitUntilDone();
ExpectMatches({{"hello world", 500}});
template_url_service->SetUserSelectedDefaultSearchProvider(
other_search_provider);
// Verify that Google is not the default search provider.
ASSERT_NE(SEARCH_ENGINE_GOOGLE,
default_search_provider()->GetEngineType(
template_url_service->search_terms_data()));
StartProviderAndWaitUntilDone();
ExpectMatches({});
}
// Tests that search terms are extracted with the correct encoding, whitespaces
// are collapsed, search terms are lowercased and duplicated, and empty searches
// are ignored.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, SearchTerms) {
LoadURLs({
{default_search_provider(), "hello world", "&foo=bar", 1},
{default_search_provider(), "hello world", "&foo=bar", 1},
{default_search_provider(), "hello world", "&foo=bar", 1},
{default_search_provider(), "hello world", "&foo=bar", 1},
{default_search_provider(), "HELLO WORLD", "&foo=bar", 1},
{default_search_provider(), " ", "&foo=bar", 1},
{default_search_provider(), "سلام دنیا", "&foo=bar", 2},
});
StartProviderAndWaitUntilDone();
ExpectMatches({{"hello world", 500}, {"سلام دنیا", 499}});
}
// Tests that the suggestions are ordered by recency.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, Suggestions_Recency) {
LoadURLs({
{default_search_provider(), "less recent search", "&foo=bar", 2},
{default_search_provider(), "more recent search", "&foo=bar", 1},
});
StartProviderAndWaitUntilDone();
ExpectMatches({{"more recent search", 500}, {"less recent search", 499}});
}
// Tests that suggestions are created from fresh search histories only.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, Suggestions_Freshness) {
int fresh = (Time::Now() - history::AutocompleteAgeThreshold()).InDays() - 1;
int stale = (Time::Now() - history::AutocompleteAgeThreshold()).InDays() + 1;
LoadURLs({
{default_search_provider(), "stale search", "&foo=bar", stale},
{default_search_provider(), "fresh search", "&foo=bar", fresh},
});
StartProviderAndWaitUntilDone();
ExpectMatches({{"fresh search", 500}});
}
// Tests that all the search URLs that would produce a given suggestion get
// deleted when the autocomplete match is deleted.
TEST_F(MAYBE_LocalHistoryZeroSuggestProviderTest, DISABLED_Delete) {
LoadURLs({
{default_search_provider(), "hello world", "&foo=bar&aqs=1", 1},
{default_search_provider(), "HELLO WORLD", "&foo=bar&aqs=12", 1},
{default_search_provider(), "hello world", "&foo=bar&aqs=123", 1},
{default_search_provider(), "hello world", "&foo=bar&aqs=1234", 1},
});
StartProviderAndWaitUntilDone();
ExpectMatches({{"hello world", 500}});
provider_->DeleteMatch(provider_->matches()[0]);
// Make sure the deletion takes effect immediately in the provider before the
// history service asynchronously performs the deletion or even before the
// provider is started again.
ExpectMatches({});
StartProviderAndWaitUntilDone();
ExpectMatches({});
// Wait until the history service performs the deletion.
WaitForHistoryService();
StartProviderAndWaitUntilDone();
ExpectMatches({});
}