blob: 30b83be7e3cb0884d1eb51a3d94f3fbb5e959050 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/predictors/autocomplete_action_predictor.h"
#include <stddef.h>
#include <algorithm>
#include <array>
#include <string>
#include <vector>
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/memory/ref_counted.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/uuid.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/preloading/chrome_preloading.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/in_memory_database.h"
#include "components/history/core/browser/url_database.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_field_trial.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/common/omnibox_features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::ASCIIToUTF16;
using content::WebContents;
using predictors::AutocompleteActionPredictor;
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
using ukm::builders::Preloading_Prediction;
namespace {
struct TestUrlInfo {
GURL url;
std::u16string title;
int days_from_now;
std::u16string user_text;
int number_of_hits;
int number_of_misses;
AutocompleteActionPredictor::Action expected_action;
};
AutocompleteActionPredictor::Action ExpectedActionBasedOnConfidenceOnly(
int number_of_hits,
int number_of_misses) {
int total = number_of_hits + number_of_misses;
EXPECT_GT(total, 0);
double confidence = number_of_hits / (double)total;
return AutocompleteActionPredictor::DecideActionByConfidence(confidence);
}
const std::vector<TestUrlInfo>& TestUrlDb() {
static base::NoDestructor<std::vector<TestUrlInfo>> db{
{{GURL("http://www.testsite.com/a.html"), u"Test - site - just a test", 1,
u"j", 5, 0, ExpectedActionBasedOnConfidenceOnly(5, 0)},
{GURL("http://www.testsite.com/b.html"), u"Test - site - just a test", 1,
u"ju", 3, 0, ExpectedActionBasedOnConfidenceOnly(3, 0)},
{GURL("http://www.testsite.com/c.html"), u"Test - site - just a test", 5,
u"just", 3, 1, ExpectedActionBasedOnConfidenceOnly(3, 1)},
{GURL("http://www.testsite.com/d.html"), u"Test - site - just a test", 5,
u"just", 3, 0, ExpectedActionBasedOnConfidenceOnly(3, 0)},
{GURL("http://www.testsite.com/e.html"), u"Test - site - just a test", 8,
u"just", 3, 1, ExpectedActionBasedOnConfidenceOnly(3, 1)},
{GURL("http://www.testsite.com/f.html"), u"Test - site - just a test", 8,
u"just", 3, 0, ExpectedActionBasedOnConfidenceOnly(3, 0)},
{GURL("http://www.testsite.com/g.html"), u"Test - site - just a test", 8,
u"just", 3, 4, ExpectedActionBasedOnConfidenceOnly(3, 4)},
{GURL("http://www.testsite.com/h.html"), u"Test - site - just a test",
12, std::u16string(), 5, 0, AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/i.html"), u"Test - site - just a test",
21, u"just a test", 2, 0, AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/j.html"), u"Test - site - just a test",
28, u"just a test", 2, 0, AutocompleteActionPredictor::ACTION_NONE}}};
return *db;
}
// List of urls sorted by the confidence score in ascending order.
const std::vector<TestUrlInfo>& TestUrlConfidenceDb() {
static base::NoDestructor<std::vector<TestUrlInfo>> db{{
{GURL("http://www.testsite.com/g.html"), u"Test", 1, u"test", 0, 2,
AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/f.html"), u"Test", 1, u"test", 1, 2,
AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/e.html"), u"Test", 1, u"test", 2, 2,
AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/d.html"), u"Test", 1, u"test", 3, 3,
AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/c.html"), u"Test", 1, u"test", 3, 2,
AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/b.html"), u"Test", 1, u"test", 3, 0,
AutocompleteActionPredictor::ACTION_NONE},
{GURL("http://www.testsite.com/a.html"), u"Test", 1, u"test", 5, 0,
AutocompleteActionPredictor::ACTION_NONE},
}};
return *db;
}
} // end namespace
namespace predictors {
class AutocompleteActionPredictorTest
: public testing::Test,
public ::testing::WithParamInterface<bool> {
public:
AutocompleteActionPredictorTest() : predictor_(nullptr) {
if (GetParam()) {
feature_list_.InitAndEnableFeature(
omnibox::kPreconnectNonSearchOmniboxSuggestions);
} else {
feature_list_.InitAndDisableFeature(
omnibox::kPreconnectNonSearchOmniboxSuggestions);
}
TestingProfile::Builder profile_builder;
profile_builder.AddTestingFactory(
HistoryServiceFactory::GetInstance(),
HistoryServiceFactory::GetDefaultFactory());
profile_ = profile_builder.Build();
web_contents_ = content::WebContentsTester::CreateTestWebContents(
profile_.get(), nullptr);
ukm_entry_builder_ =
std::make_unique<content::test::PreloadingPredictionUkmEntryBuilder>(
chrome_preloading_predictor::kOmniboxDirectURLInput);
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
predictor_ = std::make_unique<AutocompleteActionPredictor>(profile_.get());
profile_->BlockUntilHistoryProcessesPendingRequests();
content::RunAllTasksUntilIdle();
CHECK(predictor_->initialized_);
CHECK(db_cache()->empty());
CHECK(db_id_cache()->empty());
}
~AutocompleteActionPredictorTest() override {
web_contents_.reset();
// Wait for all pending tasks on the DB sequence.
content::RunAllTasksUntilIdle();
// Since we instantiated the predictor instead of going through a factory
// and dependencies, no one else is going to call Shutdown(), which is
// supposed to be called as part of being a KeyedService. The behavior of
// this method is not explicitly verified.
predictor_->Shutdown();
}
protected:
typedef AutocompleteActionPredictor::DBCacheKey DBCacheKey;
typedef AutocompleteActionPredictor::DBCacheValue DBCacheValue;
typedef AutocompleteActionPredictor::DBCacheMap DBCacheMap;
typedef AutocompleteActionPredictor::DBIdCacheMap DBIdCacheMap;
history::URLID AddRowToHistory(const TestUrlInfo& test_row) {
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
CHECK(history);
history::URLDatabase* url_db = history->InMemoryDatabase();
CHECK(url_db);
const base::Time visit_time =
base::Time::Now() - base::Days(test_row.days_from_now);
history::URLRow row(test_row.url);
row.set_title(test_row.title);
row.set_last_visit(visit_time);
return url_db->AddURL(row);
}
AutocompleteActionPredictorTable::Row CreateRowFromTestUrlInfo(
const TestUrlInfo& test_row) const {
AutocompleteActionPredictorTable::Row row;
row.id = base::Uuid::GenerateRandomV4().AsLowercaseString();
row.user_text = test_row.user_text;
row.url = test_row.url;
row.number_of_hits = test_row.number_of_hits;
row.number_of_misses = test_row.number_of_misses;
return row;
}
void AddAllRows() {
for (size_t i = 0; i < std::size(TestUrlDb()); ++i)
AddRow(TestUrlDb()[i]);
}
std::string AddRow(const TestUrlInfo& test_row) {
AutocompleteActionPredictorTable::Row row =
CreateRowFromTestUrlInfo(test_row);
predictor_->AddAndUpdateRows(
AutocompleteActionPredictorTable::Rows(1, row),
AutocompleteActionPredictorTable::Rows());
return row.id;
}
WebContents* web_contents() { return web_contents_.get(); }
void UpdateRow(const AutocompleteActionPredictorTable::Row& row) {
AutocompleteActionPredictor::DBCacheKey key = { row.user_text, row.url };
ASSERT_TRUE(db_cache()->find(key) != db_cache()->end());
predictor_->AddAndUpdateRows(
AutocompleteActionPredictorTable::Rows(),
AutocompleteActionPredictorTable::Rows(1, row));
}
void OnURLsDeletedTest(bool expired) {
ASSERT_NO_FATAL_FAILURE(AddAllRows());
EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());
std::vector<size_t> expected;
history::URLRows rows;
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
bool expect_deleted = false;
if (i < 2) {
rows.push_back(history::URLRow(TestUrlDb()[i].url));
expect_deleted = true;
}
if (!expired &&
TestUrlDb()[i].days_from_now > maximum_days_to_keep_entry()) {
expect_deleted = true;
}
if (i != 3 && i != 4)
ASSERT_TRUE(AddRowToHistory(TestUrlDb()[i]));
else if (!expired)
expect_deleted = true;
if (!expect_deleted)
expected.push_back(i);
}
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(
profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
ASSERT_TRUE(history_service);
predictor_->OnHistoryDeletions(
history_service,
history::DeletionInfo(history::DeletionTimeRange::Invalid(), expired,
rows, std::set<GURL>(), std::nullopt));
EXPECT_EQ(expected.size(), db_cache()->size());
EXPECT_EQ(expected.size(), db_id_cache()->size());
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
DBCacheKey key = {TestUrlDb()[i].user_text, TestUrlDb()[i].url};
bool deleted = !base::Contains(expected, i);
EXPECT_EQ(deleted, db_cache()->find(key) == db_cache()->end());
EXPECT_EQ(deleted, db_id_cache()->find(key) == db_id_cache()->end());
}
}
void DeleteAllRows() {
predictor_->DeleteAllRows();
}
void DeleteRowsFromCaches(
const history::URLRows& rows,
std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
predictor_->DeleteRowsFromCaches(rows, id_list);
}
void DeleteOldIdsFromCaches(
std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(
profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
ASSERT_TRUE(history_service);
history::URLDatabase* url_db = history_service->InMemoryDatabase();
ASSERT_TRUE(url_db);
predictor_->DeleteOldIdsFromCaches(url_db, id_list);
}
void DeleteLowestConfidenceRowsFromCaches(
size_t count,
std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
predictor_->DeleteLowestConfidenceRowsFromCaches(count, id_list);
}
AutocompleteActionPredictor* predictor() { return predictor_.get(); }
DBCacheMap* db_cache() { return &predictor_->db_cache_; }
DBIdCacheMap* db_id_cache() { return &predictor_->db_id_cache_; }
std::vector<AutocompleteActionPredictor::TransitionalMatch>*
transitional_matches() {
return &predictor_->transitional_matches_;
}
static int maximum_days_to_keep_entry() {
return AutocompleteActionPredictor::kMaximumDaysToKeepEntry;
}
static size_t minimum_user_text_length() {
return AutocompleteActionPredictor::kMinimumUserTextLength;
}
static size_t maximum_string_length() {
return AutocompleteActionPredictor::kMaximumStringLength;
}
ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
return test_ukm_recorder_.get();
}
const content::test::PreloadingPredictionUkmEntryBuilder&
ukm_entry_builder() {
return *ukm_entry_builder_;
}
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<AutocompleteActionPredictor> predictor_;
std::unique_ptr<content::test::PreloadingPredictionUkmEntryBuilder>
ukm_entry_builder_;
std::unique_ptr<WebContents> web_contents_;
content::RenderViewHostTestEnabler rvh_test_enabler_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
base::ScopedMockElapsedTimersForTest test_timer_;
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(AutocompleteActionPredictorTest,
AutocompleteActionPredictorTest,
testing::Bool());
TEST_P(AutocompleteActionPredictorTest, AddRow) {
// Add a test entry to the predictor.
std::string guid = AddRow(TestUrlDb()[0]);
// Get the data back out of the cache.
const DBCacheKey key = {TestUrlDb()[0].user_text, TestUrlDb()[0].url};
DBCacheMap::const_iterator it = db_cache()->find(key);
EXPECT_TRUE(it != db_cache()->end());
const DBCacheValue value = {TestUrlDb()[0].number_of_hits,
TestUrlDb()[0].number_of_misses};
EXPECT_EQ(value.number_of_hits, it->second.number_of_hits);
EXPECT_EQ(value.number_of_misses, it->second.number_of_misses);
DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key);
EXPECT_TRUE(id_it != db_id_cache()->end());
EXPECT_EQ(guid, id_it->second);
}
TEST_P(AutocompleteActionPredictorTest, UpdateRow) {
ASSERT_NO_FATAL_FAILURE(AddAllRows());
EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());
// Get the data back out of the cache.
const DBCacheKey key = {TestUrlDb()[0].user_text, TestUrlDb()[0].url};
DBCacheMap::const_iterator it = db_cache()->find(key);
EXPECT_TRUE(it != db_cache()->end());
DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key);
EXPECT_TRUE(id_it != db_id_cache()->end());
AutocompleteActionPredictorTable::Row update_row;
update_row.id = id_it->second;
update_row.user_text = key.user_text;
update_row.url = key.url;
update_row.number_of_hits = it->second.number_of_hits + 1;
update_row.number_of_misses = it->second.number_of_misses + 2;
UpdateRow(update_row);
// Get the updated version.
DBCacheMap::const_iterator update_it = db_cache()->find(key);
EXPECT_TRUE(update_it != db_cache()->end());
EXPECT_EQ(update_row.number_of_hits, update_it->second.number_of_hits);
EXPECT_EQ(update_row.number_of_misses, update_it->second.number_of_misses);
DBIdCacheMap::const_iterator update_id_it = db_id_cache()->find(key);
EXPECT_TRUE(update_id_it != db_id_cache()->end());
EXPECT_EQ(id_it->second, update_id_it->second);
}
TEST_P(AutocompleteActionPredictorTest, DeleteAllRows) {
ASSERT_NO_FATAL_FAILURE(AddAllRows());
EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());
DeleteAllRows();
EXPECT_TRUE(db_cache()->empty());
EXPECT_TRUE(db_id_cache()->empty());
}
TEST_P(AutocompleteActionPredictorTest, DeleteRowsFromCaches) {
std::vector<AutocompleteActionPredictorTable::Row::Id> all_ids;
history::URLRows rows;
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
std::string row_id = AddRow(TestUrlDb()[i]);
all_ids.push_back(row_id);
if (i < 2)
rows.push_back(history::URLRow(TestUrlDb()[i].url));
}
EXPECT_EQ(std::size(TestUrlDb()), all_ids.size());
EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());
std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
DeleteRowsFromCaches(rows, &id_list);
EXPECT_EQ(std::size(TestUrlDb()) - 2, db_cache()->size());
EXPECT_EQ(std::size(TestUrlDb()) - 2, db_id_cache()->size());
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
DBCacheKey key = {TestUrlDb()[i].user_text, TestUrlDb()[i].url};
bool deleted = (i < 2);
EXPECT_EQ(deleted, db_cache()->find(key) == db_cache()->end());
EXPECT_EQ(deleted, db_id_cache()->find(key) == db_id_cache()->end());
EXPECT_EQ(deleted, base::Contains(id_list, all_ids[i]));
}
}
TEST_P(AutocompleteActionPredictorTest, DeleteOldIdsFromCaches) {
std::vector<AutocompleteActionPredictorTable::Row::Id> expected;
std::vector<AutocompleteActionPredictorTable::Row::Id> all_ids;
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
std::string row_id = AddRow(TestUrlDb()[i]);
all_ids.push_back(row_id);
bool exclude_url =
base::StartsWith(TestUrlDb()[i].url.path(), "/d",
base::CompareCase::SENSITIVE) ||
(TestUrlDb()[i].days_from_now > maximum_days_to_keep_entry());
if (exclude_url)
expected.push_back(row_id);
else
ASSERT_TRUE(AddRowToHistory(TestUrlDb()[i]));
}
std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
DeleteOldIdsFromCaches(&id_list);
EXPECT_EQ(expected.size(), id_list.size());
EXPECT_EQ(all_ids.size() - expected.size(), db_cache()->size());
EXPECT_EQ(all_ids.size() - expected.size(), db_id_cache()->size());
for (auto it = all_ids.begin(); it != all_ids.end(); ++it) {
bool in_expected = base::Contains(expected, *it);
bool in_list = base::Contains(id_list, *it);
EXPECT_EQ(in_expected, in_list);
}
}
TEST_P(AutocompleteActionPredictorTest,
DeleteLowestConfidenceRowsFromCaches_OneByOne) {
std::vector<AutocompleteActionPredictorTable::Row::Id> test_url_ids;
for (const auto& info : TestUrlConfidenceDb())
test_url_ids.push_back(AddRow(info));
std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
std::vector<AutocompleteActionPredictorTable::Row::Id> expected;
for (size_t i = 0; i < std::size(TestUrlConfidenceDb()); ++i) {
DeleteLowestConfidenceRowsFromCaches(1, &id_list);
expected.push_back(test_url_ids[i]);
EXPECT_THAT(id_list, ::testing::UnorderedElementsAreArray(expected));
DBCacheKey deleted_key = {TestUrlConfidenceDb()[i].user_text,
TestUrlConfidenceDb()[i].url};
EXPECT_FALSE(base::Contains(*db_cache(), deleted_key));
EXPECT_FALSE(base::Contains(*db_id_cache(), deleted_key));
}
}
TEST_P(AutocompleteActionPredictorTest,
DeleteLowestConfidenceRowsFromCaches_Bulk) {
std::vector<AutocompleteActionPredictorTable::Row::Id> test_url_ids;
for (const auto& info : TestUrlConfidenceDb())
test_url_ids.push_back(AddRow(info));
std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
std::vector<AutocompleteActionPredictorTable::Row::Id> expected;
const size_t count_to_remove = 4;
CHECK_LT(count_to_remove, std::size(TestUrlConfidenceDb()));
for (size_t i = 0; i < count_to_remove; ++i)
expected.push_back(test_url_ids[i]);
DeleteLowestConfidenceRowsFromCaches(count_to_remove, &id_list);
ASSERT_THAT(id_list, ::testing::UnorderedElementsAreArray(expected));
for (size_t i = 0; i < count_to_remove; ++i) {
DBCacheKey deleted_key = {TestUrlConfidenceDb()[i].user_text,
TestUrlConfidenceDb()[i].url};
EXPECT_FALSE(base::Contains(*db_cache(), deleted_key));
EXPECT_FALSE(base::Contains(*db_id_cache(), deleted_key));
}
}
TEST_P(AutocompleteActionPredictorTest, OnURLsDeletedExpired) {
OnURLsDeletedTest(true);
}
TEST_P(AutocompleteActionPredictorTest, OnURLsDeletedNonExpired) {
OnURLsDeletedTest(false);
}
TEST_P(AutocompleteActionPredictorTest, RecommendActionURL) {
ASSERT_NO_FATAL_FAILURE(AddAllRows());
// Navigate to kInitial URL.
GURL kInitialUrl("https://example.com");
content::WebContentsTester::For(web_contents())
->NavigateAndCommit(kInitialUrl);
content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
->InitializeRenderFrameIfNeeded();
AutocompleteMatch match;
match.type = AutocompleteMatchType::HISTORY_URL;
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
match.destination_url = GURL(TestUrlDb()[i].url);
EXPECT_EQ(TestUrlDb()[i].expected_action,
predictor()->RecommendAction(TestUrlDb()[i].user_text, match,
web_contents()))
<< "Unexpected action for " << match.destination_url;
}
// Calculate confidence_interval for the first entry to cross-check with
// metrics.
match.destination_url = GURL(TestUrlDb()[0].url);
double confidence =
predictor()->CalculateConfidence(TestUrlDb()[0].user_text, match);
// Set the first url in the database as the destination url to cross-check the
// metrics for the first Preloading.Prediction UKM.
GURL kDestinationUrl(TestUrlDb()[0].url);
content::WebContentsTester::For(web_contents())
->NavigateAndCommit(kDestinationUrl);
ukm::SourceId ukm_source_id =
web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
// Check that we have recorded Preloading.Prediction for all entries in the
// TestUrlDb.
auto ukm_entries = test_ukm_recorder()->GetEntries(
Preloading_Prediction::kEntryName,
content::test::kPreloadingPredictionUkmMetrics);
EXPECT_EQ(ukm_entries.size(), std::size(TestUrlDb()));
// Cross-check that we have logged the correct metrics for Prediction,
// confidence, accurate_prediction on successful activation.
UkmEntry expected_entry = ukm_entry_builder().BuildEntry(
ukm_source_id, /*confidence=*/confidence * 100,
/*accurate_prediction=*/true);
EXPECT_EQ(ukm_entries[0], expected_entry)
<< content::test::ActualVsExpectedUkmEntryToString(ukm_entries[0],
expected_entry);
}
TEST_P(AutocompleteActionPredictorTest, RecommendActionSearch) {
ASSERT_NO_FATAL_FAILURE(AddAllRows());
AutocompleteMatch match;
match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
match.destination_url = GURL(TestUrlDb()[i].url);
AutocompleteActionPredictor::Action expected_action =
(TestUrlDb()[i].expected_action ==
AutocompleteActionPredictor::ACTION_PRERENDER)
? AutocompleteActionPredictor::ACTION_PRECONNECT
: TestUrlDb()[i].expected_action;
EXPECT_EQ(expected_action, predictor()->RecommendAction(
TestUrlDb()[i].user_text, match, nullptr))
<< "Unexpected action for " << match.destination_url;
}
}
TEST_P(AutocompleteActionPredictorTest, RecommendActionNonSearch) {
ASSERT_NO_FATAL_FAILURE(AddAllRows());
AutocompleteMatch match;
match.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
for (const auto& i : TestUrlDb()) {
match.destination_url = GURL(i.url);
EXPECT_EQ(i.expected_action,
predictor()->RecommendAction(i.user_text, match, nullptr))
<< "Unexpected action for " << match.destination_url;
}
}
TEST_P(AutocompleteActionPredictorTest, PreconnectableTypes) {
AutocompleteMatch match;
for (int i = 0; i < AutocompleteMatchType::NUM_TYPES; i++) {
EXPECT_TRUE(AutocompleteMatchType::FromInteger(i, &match.type));
EXPECT_EQ(AutocompleteActionPredictor::IsPreconnectable(match),
GetParam() ? AutocompleteMatch::IsPreconnectableType(match.type)
: AutocompleteMatch::IsSearchType(match.type));
}
}
TEST_P(AutocompleteActionPredictorTest,
RegisterTransitionalMatchesUserTextSizeLimits) {
auto test = [this](const std::u16string& user_text,
bool should_be_registered) {
predictor()->RegisterTransitionalMatches(user_text, AutocompleteResult());
bool registered = base::Contains(
*transitional_matches(), user_text,
&AutocompleteActionPredictor::TransitionalMatch::user_text);
EXPECT_EQ(registered, should_be_registered);
};
std::u16string short_text =
ASCIIToUTF16(std::string(minimum_user_text_length(), 'g'));
test(short_text, true);
std::u16string too_short_text =
ASCIIToUTF16(std::string(minimum_user_text_length() - 1, 'g'));
test(too_short_text, false);
std::u16string long_text =
ASCIIToUTF16(std::string(maximum_string_length(), 'g'));
test(long_text, true);
std::u16string too_long_text =
ASCIIToUTF16(std::string(maximum_string_length() + 1, 'g'));
test(too_long_text, false);
}
TEST_P(AutocompleteActionPredictorTest,
RegisterTransitionalMatchesURLSizeLimits) {
const auto test_url = [](size_t size) {
const std::string kPrefix = "http://b/";
return GURL(kPrefix + std::string(size - kPrefix.size(), 'c'));
};
auto urls = std::to_array<GURL>({
test_url(10),
test_url(maximum_string_length()),
test_url(maximum_string_length() + 1),
test_url(maximum_string_length() * 10),
});
ACMatches matches;
for (const auto& url : urls) {
AutocompleteMatch match;
match.destination_url = url;
matches.push_back(match);
}
AutocompleteResult result;
result.AppendMatches(matches);
std::u16string user_text = u"google";
predictor()->RegisterTransitionalMatches(user_text, result);
auto it = std::ranges::find(
*transitional_matches(), user_text,
&AutocompleteActionPredictor::TransitionalMatch::user_text);
ASSERT_NE(it, transitional_matches()->end());
EXPECT_THAT(it->urls, ::testing::ElementsAre(urls[0], urls[1]));
}
TEST_P(AutocompleteActionPredictorTest, UpdateDatabaseFromTransitionalMatches) {
ACMatches matches;
AutocompleteMatch match;
GURL clicked_url = GURL("https://foo-clicked.com");
GURL not_clicked_url = GURL("https://foo-not-clicked.com");
match.destination_url = clicked_url;
matches.push_back(match);
match.destination_url = not_clicked_url;
matches.push_back(match);
AutocompleteResult result;
result.AppendMatches(matches);
std::u16string user_text = u"foo";
predictor()->RegisterTransitionalMatches(user_text, result);
ASSERT_EQ(transitional_matches()->size(), 1ul);
predictor()->UpdateDatabaseFromTransitionalMatches(clicked_url);
ASSERT_TRUE(transitional_matches()->empty());
// Make sure the clicked URL has one hit.
DBCacheKey key = {user_text, clicked_url};
DBCacheMap::const_iterator it = db_cache()->find(key);
EXPECT_TRUE(it != db_cache()->end());
ASSERT_EQ(it->second.number_of_hits, 1);
ASSERT_EQ(it->second.number_of_misses, 0);
// Make sure the not clicked URL has one miss.
key.url = not_clicked_url;
it = db_cache()->find(key);
EXPECT_TRUE(it != db_cache()->end());
ASSERT_EQ(it->second.number_of_hits, 0);
ASSERT_EQ(it->second.number_of_misses, 1);
}
} // namespace predictors