blob: 1348fa0c4e37d2a1dec0bf93a980efc47e225cc6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/optimization_guide/core/delivery/prediction_manager.h"
#include <memory>
#include "base/base64.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/optimization_guide/browser_test_util.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_key.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/component_updater/pref_names.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/optimization_guide/core/delivery/model_util.h"
#include "components/optimization_guide/core/delivery/prediction_model_download_manager.h"
#include "components/optimization_guide/core/delivery/prediction_model_override.h"
#include "components/optimization_guide/core/hints/optimization_guide_store.h"
#include "components/optimization_guide/core/hints/store_update_data.h"
#include "components/optimization_guide/core/optimization_guide_constants.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/core/optimization_guide_prefs.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"
#include "components/optimization_guide/proto/models.pb.h"
#include "components/prefs/pref_service.h"
#include "components/variations/hashing.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "net/base/ip_address.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_switches.h"
#endif
namespace {
constexpr int kSuccessfulModelVersion = 123;
// Timeout to allow the model file to be downloaded, unzipped and sent to the
// model file observers.
constexpr base::TimeDelta kModelFileDownloadTimeout = base::Seconds(60);
enum class PredictionModelsFetcherRemoteResponseType {
kSuccessfulWithValidModelFile = 0,
kSuccessfulWithInvalidModelFile = 1,
kSuccessfulWithValidModelFileAndInvalidAdditionalFiles = 2,
kSuccessfulWithValidModelFileAndValidAdditionalFiles = 3,
kSuccessfulWithNoModelUpdate = 4,
kSuccessfulWithNullModel = 5,
kUnsuccessful = 6,
};
} // namespace
namespace optimization_guide {
namespace {
// Sets up the |model_file_observer| to receive valid ModelInfo.
void SetUpValidModelInfoReceival(ModelFileObserver* model_file_observer,
base::RunLoop* run_loop,
const std::set<base::FilePath::StringType>&
expected_additional_files = {}) {
model_file_observer->set_model_file_received_callback(base::BindOnce(
[](base::RunLoop* run_loop,
const std::set<base::FilePath::StringType>& expected_additional_files,
proto::OptimizationTarget optimization_target,
base::optional_ref<const ModelInfo> model_info) {
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
EXPECT_EQ(optimization_target,
proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
EXPECT_TRUE(model_info.has_value());
EXPECT_EQ(123, model_info->GetVersion());
EXPECT_TRUE(model_info->GetModelFilePath().IsAbsolute());
EXPECT_TRUE(base::PathExists(model_info->GetModelFilePath()));
EXPECT_EQ(expected_additional_files.size(),
model_info->GetAdditionalFiles().size());
for (const base::FilePath& add_file :
model_info->GetAdditionalFiles()) {
EXPECT_TRUE(add_file.IsAbsolute());
EXPECT_TRUE(base::PathExists(add_file));
EXPECT_TRUE(base::Contains(expected_additional_files,
add_file.BaseName().value()));
}
run_loop->Quit();
},
run_loop, expected_additional_files));
}
// Sets up the |model_file_observer| to not receive any model.
void SetUpNoModelInfoReceival(ModelFileObserver* model_file_observer) {
model_file_observer->set_model_file_received_callback(
base::BindOnce([](proto::OptimizationTarget optimization_target,
base::optional_ref<const ModelInfo> model_info) {
FAIL() << "Should not be called";
}));
}
} // namespace
// Abstract base class for browser testing Prediction Manager.
// Actual class fixtures should implement InitializeFeatureList to set up
// features used in tests.
class PredictionManagerBrowserTestBase : public InProcessBrowserTest {
public:
PredictionManagerBrowserTestBase() = default;
~PredictionManagerBrowserTestBase() override = default;
PredictionManagerBrowserTestBase(const PredictionManagerBrowserTestBase&) =
delete;
PredictionManagerBrowserTestBase& operator=(
const PredictionManagerBrowserTestBase&) = delete;
void SetUp() override {
InitializeFeatureList();
models_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
net::EmbeddedTestServer::ServerCertificateConfig models_server_cert_config;
models_server_cert_config.dns_names = {
GURL(kOptimizationGuideServiceGetModelsDefaultURL).host()};
models_server_cert_config.ip_addresses = {net::IPAddress::IPv4Localhost()};
models_server_->SetSSLConfig(models_server_cert_config);
models_server_->ServeFilesFromSourceDirectory(
"chrome/test/data/optimization_guide");
models_server_->RegisterRequestHandler(base::BindRepeating(
&PredictionManagerBrowserTestBase::HandleGetModelsRequest,
base::Unretained(this)));
ASSERT_TRUE(models_server_->Start());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
ASSERT_TRUE(https_server_->Start());
https_url_with_content_ = https_server_->GetURL("/english_page.html");
https_url_without_content_ = https_server_->GetURL("/empty.html");
model_file_url_ = models_server_->GetURL("/signed_valid_model.crx3");
model_file_with_good_additional_file_url_ =
models_server_->GetURL("/additional_file_exists.crx3");
model_file_with_nonexistent_additional_file_url_ =
models_server_->GetURL("/additional_file_doesnt_exist.crx3");
InProcessBrowserTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server_->ShutdownAndWaitUntilComplete());
EXPECT_TRUE(models_server_->ShutdownAndWaitUntilComplete());
InProcessBrowserTest::TearDownOnMainThread();
}
void SetUpCommandLine(base::CommandLine* cmd) override {
cmd->AppendSwitch(switches::kGoogleApiKeyConfigurationCheckOverride);
cmd->AppendSwitchASCII(
switches::kOptimizationGuideServiceGetModelsURL,
models_server_
->GetURL(GURL(kOptimizationGuideServiceGetModelsDefaultURL).host(),
"/")
.spec());
cmd->AppendSwitchASCII("force-variation-ids", "4");
}
void SetResponseType(
PredictionModelsFetcherRemoteResponseType response_type) {
response_type_ = response_type;
}
void RegisterWithKeyedService(ModelFileObserver* model_file_observer) {
OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
->AddObserverForOptimizationTargetModel(
optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
std::nullopt, model_file_observer);
}
PredictionManager* GetPredictionManager() {
OptimizationGuideKeyedService* optimization_guide_keyed_service =
OptimizationGuideKeyedServiceFactory::GetForProfile(
browser()->profile());
return optimization_guide_keyed_service->GetPredictionManager();
}
protected:
// Virtualize for testing different feature configurations.
virtual void InitializeFeatureList() = 0;
base::test::ScopedFeatureList scoped_feature_list_;
private:
std::unique_ptr<net::test_server::HttpResponse> HandleGetModelsRequest(
const net::test_server::HttpRequest& request) {
// Returning nullptr will cause the test server to fallback to serving the
// file from the test data directory.
if (request.GetURL() == model_file_url_) {
return nullptr;
}
if (request.GetURL() == model_file_with_good_additional_file_url_) {
return nullptr;
}
if (request.GetURL() == model_file_with_nonexistent_additional_file_url_) {
return nullptr;
}
std::unique_ptr<net::test_server::BasicHttpResponse> response;
response = std::make_unique<net::test_server::BasicHttpResponse>();
// The request to the remote Optimization Guide Service should always be a
// POST.
EXPECT_EQ(request.method, net::test_server::METHOD_POST);
EXPECT_NE(request.headers.end(), request.headers.find("X-Client-Data"));
optimization_guide::proto::GetModelsRequest models_request;
EXPECT_TRUE(models_request.ParseFromString(request.content));
response->set_code(net::HTTP_OK);
std::unique_ptr<optimization_guide::proto::GetModelsResponse>
get_models_response = BuildGetModelsResponse();
if (response_type_ == PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithInvalidModelFile) {
get_models_response->mutable_models(0)->mutable_model()->set_download_url(
https_url_with_content_.spec());
} else if (response_type_ == PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithValidModelFile) {
get_models_response->mutable_models(0)->mutable_model()->set_download_url(
model_file_url_.spec());
} else if (response_type_ ==
PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithValidModelFileAndInvalidAdditionalFiles) {
get_models_response->mutable_models(0)->mutable_model()->set_download_url(
model_file_with_nonexistent_additional_file_url_.spec());
} else if (response_type_ ==
PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithValidModelFileAndValidAdditionalFiles) {
get_models_response->mutable_models(0)->mutable_model()->set_download_url(
model_file_with_good_additional_file_url_.spec());
} else if (response_type_ == PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithNoModelUpdate) {
// This simulates the server sending PredictionModel with no Model in it,
// which indicates the client is up-to-date. No model download should
// happen in that case.
get_models_response->mutable_models(0)->clear_model();
} else if (response_type_ == PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithNullModel) {
// This simulates the server sending no PredictionModel in the response,
// which indicates the server had stopped serving the opt target. In that
// case model should be deleted from the store, and the observers notified
// of null model.
get_models_response->clear_models();
} else if (response_type_ ==
PredictionModelsFetcherRemoteResponseType::kUnsuccessful) {
response->set_code(net::HTTP_NOT_FOUND);
}
std::string serialized_response;
get_models_response->SerializeToString(&serialized_response);
response->set_content(serialized_response);
return std::move(response);
}
GURL model_file_url_;
GURL model_file_with_good_additional_file_url_;
GURL model_file_with_nonexistent_additional_file_url_;
GURL https_url_with_content_, https_url_without_content_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
std::unique_ptr<net::EmbeddedTestServer> models_server_;
PredictionModelsFetcherRemoteResponseType response_type_ =
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile;
};
class PredictionManagerBrowserTest : public PredictionManagerBrowserTestBase {
public:
PredictionManagerBrowserTest() = default;
~PredictionManagerBrowserTest() override = default;
PredictionManagerBrowserTest(const PredictionManagerBrowserTest&) = delete;
PredictionManagerBrowserTest& operator=(const PredictionManagerBrowserTest&) =
delete;
private:
void InitializeFeatureList() override {
std::vector<base::test::FeatureRefAndParams> enabled_features = {
{optimization_guide::features::kOptimizationHints, {}},
{optimization_guide::features::kOptimizationTargetPrediction,
{{"fetch_startup_delay_ms", "8000"}}},
};
scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
}
};
IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserTest,
ComponentUpdatesPrefDisabled) {
ModelFileObserver model_file_observer;
SetResponseType(PredictionModelsFetcherRemoteResponseType::kUnsuccessful);
g_browser_process->local_state()->SetBoolean(
::prefs::kComponentUpdatesEnabled, false);
base::HistogramTester histogram_tester;
RegisterWithKeyedService(&model_file_observer);
base::RunLoop().RunUntilIdle();
// Should not have made fetch request.
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status."
"PainfulPageLoad",
0);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserTest,
ModelsAndFeaturesStoreInitialized) {
ModelFileObserver model_file_observer;
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
base::HistogramTester histogram_tester;
RegisterWithKeyedService(&model_file_observer);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.PredictionModelsStored", true, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerBrowserTest,
PredictionModelFetchFailed) {
ModelFileObserver model_file_observer;
SetResponseType(PredictionModelsFetcherRemoteResponseType::kUnsuccessful);
base::HistogramTester histogram_tester;
RegisterWithKeyedService(&model_file_observer);
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status",
net::HTTP_NOT_FOUND, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status."
"PainfulPageLoad",
net::HTTP_NOT_FOUND, 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionManager.PredictionModelsStored", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
}
class PredictionManagerModelDownloadingBrowserTest
: public PredictionManagerBrowserTest {
public:
PredictionManagerModelDownloadingBrowserTest() = default;
~PredictionManagerModelDownloadingBrowserTest() override = default;
void SetUpOnMainThread() override {
model_file_observer_ = std::make_unique<ModelFileObserver>();
PredictionManagerBrowserTest::SetUpOnMainThread();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
PredictionManagerBrowserTest::SetUpCommandLine(command_line);
#if BUILDFLAG(IS_CHROMEOS)
command_line->AppendSwitch(
ash::switches::kIgnoreUserProfileMappingForTests);
#endif
// TODO(crbug.com/40285326): This fails with the field trial testing config.
command_line->AppendSwitch("disable-field-trial-config");
}
void TearDownOnMainThread() override {
PredictionManagerBrowserTest::TearDownOnMainThread();
}
ModelFileObserver* model_file_observer() {
return model_file_observer_.get();
}
void RegisterModelFileObserverWithKeyedService(Profile* profile = nullptr) {
OptimizationGuideKeyedServiceFactory::GetForProfile(
profile ? profile : browser()->profile())
->AddObserverForOptimizationTargetModel(
proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
/*model_metadata=*/std::nullopt, model_file_observer_.get());
}
private:
void InitializeFeatureList() override {
std::vector<base::test::FeatureRefAndParams> enabled_features = {
{features::kOptimizationHints, {}},
{features::kOptimizationTargetPrediction, {}},
{features::kOptimizationGuideModelDownloading,
{{"unrestricted_model_downloading", "true"}}},
};
scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
}
std::unique_ptr<ModelFileObserver> model_file_observer_;
};
// Flaky on various bots. See https://crbug.com/1266318
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
DISABLED_TestIncognitoUsesModelFromRegularProfile) {
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
// Set up model download with regular profile.
{
base::HistogramTester histogram_tester;
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetUpValidModelInfoReceival(model_file_observer(), run_loop.get());
RegisterModelFileObserverWithKeyedService();
// Wait until the observer receives the file. We increase the timeout to 60
// seconds here since the file is on the larger side.
{
base::test::ScopedRunLoopTimeout file_download_timeout(
FROM_HERE, kModelFileDownloadTimeout);
run_loop->Run();
}
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kSuccess, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
}
// Now set up model download with incognito profile. Download should not
// happen, but the OnModelUpdated callback should be triggered.
{
base::HistogramTester otr_histogram_tester;
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetUpValidModelInfoReceival(model_file_observer(), run_loop.get());
Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
RegisterModelFileObserverWithKeyedService(otr_browser->profile());
run_loop->Run();
otr_histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
otr_histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
}
}
// TODO(crbug.com/336399137): Flaky on Linux Chromium OS ASan LSan Tests.
#if BUILDFLAG(IS_CHROMEOS) && defined(ADDRESS_SANITIZER)
#define MAYBE_TestIncognitoDoesntFetchModels \
DISABLED_TestIncognitoDoesntFetchModels
#else
#define MAYBE_TestIncognitoDoesntFetchModels TestIncognitoDoesntFetchModels
#endif
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
MAYBE_TestIncognitoDoesntFetchModels) {
base::HistogramTester histogram_tester;
SetResponseType(PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithInvalidModelFile);
Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
// Registering should not initiate the fetch and the model updated callback
// should not be triggered too.
RegisterModelFileObserverWithKeyedService(otr_browser->profile());
SetUpNoModelInfoReceival(model_file_observer());
RetryForHistogramUntilCountReached(
&histogram_tester, "OptimizationGuide.PredictionManager.StoreInitialized",
1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestDownloadUrlAcceptedByDownloadServiceButInvalid) {
base::HistogramTester histogram_tester;
SetResponseType(PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithInvalidModelFile);
// Registering should initiate the fetch and receive a response with a model
// containing a download URL and then subsequently downloaded.
RegisterModelFileObserverWithKeyedService();
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
2);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
PredictionModelDownloadManager::PredictionModelDownloadState::kRequested,
1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionModelDownloadManager.State.PainfulPageLoad",
PredictionModelDownloadManager::PredictionModelDownloadState::kStarted,
1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStartLatency."
"PainfulPageLoad",
1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kFailedCrxVerification, 1);
// An unverified file should not notify us that it's ready.
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestSuccessfulModelFileFlow) {
base::HistogramTester histogram_tester;
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
model_file_observer()->set_model_file_received_callback(base::BindOnce(
[](base::RunLoop* run_loop, proto::OptimizationTarget optimization_target,
base::optional_ref<const ModelInfo> model_info) {
EXPECT_EQ(optimization_target,
proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
EXPECT_TRUE(model_info.has_value());
run_loop->Quit();
},
run_loop.get()));
// Registering should initiate the fetch and receive a response with a model
// containing a download URL and then subsequently downloaded.
RegisterModelFileObserverWithKeyedService();
// Wait until the observer receives the file. We increase the timeout to 60
// seconds here since the file is on the larger side.
{
base::test::ScopedRunLoopTimeout file_download_timeout(
FROM_HERE, kModelFileDownloadTimeout);
run_loop->Run();
}
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kSuccess, 1);
// No error when moving the file so there will be no record.
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError", 0);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
EXPECT_THAT(
histogram_tester.GetAllSamples("OptimizationGuide.PredictionManager."
"ModelDeliveryEvents.PainfulPageLoad"),
testing::UnorderedElementsAre(
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kGetModelsRequest),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kDownloadServiceRequest),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kModelDownloadStarted),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kModelDownloaded),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kModelDelivered),
1)));
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestSuccessfulModelFileFlowWithAdditionalFile) {
base::HistogramTester histogram_tester;
SetResponseType(PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithValidModelFileAndValidAdditionalFiles);
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetUpValidModelInfoReceival(model_file_observer(), run_loop.get(),
{FILE_PATH_LITERAL("good_additional_file.txt")});
// Registering should initiate the fetch and receive a response with a model
// containing a download URL and then subsequently downloaded.
RegisterModelFileObserverWithKeyedService();
// Wait until the observer receives the file. We increase the timeout to 60
// seconds here since the file is on the larger side.
{
base::test::ScopedRunLoopTimeout file_download_timeout(
FROM_HERE, kModelFileDownloadTimeout);
run_loop->Run();
}
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kSuccess, 1);
// No error when moving the file so there will be no record.
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.ReplaceFileError", 0);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
EXPECT_THAT(
histogram_tester.GetAllSamples("OptimizationGuide.PredictionManager."
"ModelDeliveryEvents.PainfulPageLoad"),
testing::UnorderedElementsAre(
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kGetModelsRequest),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kDownloadServiceRequest),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kModelDownloadStarted),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kModelDownloaded),
1),
base::Bucket(static_cast<base::HistogramBase::Sample32>(
ModelDeliveryEvent::kModelDelivered),
1)));
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestSuccessfulModelFileFlowWithInvalidAdditionalFile) {
base::HistogramTester histogram_tester;
SetResponseType(PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithValidModelFileAndInvalidAdditionalFiles);
// Since the model's additional file is invalid, model observer callback
// should never be run.
SetUpNoModelInfoReceival(model_file_observer());
// Registering should initiate the fetch and receive a response with a model
// containing a download URL and then subsequently downloaded.
RegisterModelFileObserverWithKeyedService();
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 1);
base::RunLoop().RunUntilIdle();
// The additional file does not exist.
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kFailedInvalidAdditionalFile, 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestModelHasNoUpdateFlow) {
std::unique_ptr<base::HistogramTester> histogram_tester =
std::make_unique<base::HistogramTester>();
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
SetUpValidModelInfoReceival(model_file_observer(), run_loop.get());
// Registering should initiate the fetch and receive a response with a model
// containing a download URL and then subsequently downloaded.
RegisterModelFileObserverWithKeyedService();
// Wait until the observer receives the file. We increase the timeout to 60
// seconds here since the file is on the larger side.
{
base::test::ScopedRunLoopTimeout file_download_timeout(
FROM_HERE, kModelFileDownloadTimeout);
run_loop->Run();
}
// Model will be downloaded and loaded.
RetryForHistogramUntilCountReached(
histogram_tester.get(),
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kSuccess, 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionManager.PredictionModelsStored", true, 1);
// Set up the next periodic model fetch to not send any model updates.
histogram_tester = std::make_unique<base::HistogramTester>();
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithNoModelUpdate);
// Since the model was already downloaded and present in the store, model
// observer callback should never be run.
SetUpNoModelInfoReceival(model_file_observer());
// Trigger the periodic fetch timer.
auto* prediction_model_fetch_timer =
GetPredictionManager()->GetPredictionModelFetchTimerForTesting();
EXPECT_EQ(
PredictionModelFetchTimer::PredictionModelFetchTimerState::kPeriodicFetch,
prediction_model_fetch_timer->GetStateForTesting());
prediction_model_fetch_timer->ScheduleImmediateFetchForTesting();
base::RunLoop().RunUntilIdle();
// The model fetch will happen, but no new model will be downloaded.
RetryForHistogramUntilCountReached(
histogram_tester.get(),
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status."
"PainfulPageLoad",
net::HTTP_OK, 1);
histogram_tester->ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
histogram_tester->ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
histogram_tester->ExpectTotalCount(
"OptimizationGuide.PredictionModelRemoved.PainfulPageLoad", 0);
histogram_tester->ExpectTotalCount(
"OptimizationGuide.PredictionModelStore.ModelRemovalReason."
"PainfulPageLoad",
0);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestEmptyModelRemovedFlow) {
std::unique_ptr<base::HistogramTester> histogram_tester =
std::make_unique<base::HistogramTester>();
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
SetUpValidModelInfoReceival(model_file_observer(), run_loop.get());
// Registering should initiate the fetch and receive a response with a model
// containing a download URL and then subsequently downloaded.
RegisterModelFileObserverWithKeyedService();
// Wait until the observer receives the file. We increase the timeout to 60
// seconds here since the file is on the larger side.
{
base::test::ScopedRunLoopTimeout file_download_timeout(
FROM_HERE, kModelFileDownloadTimeout);
run_loop->Run();
}
// Model will be downloaded and loaded.
RetryForHistogramUntilCountReached(
histogram_tester.get(),
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kSuccess, 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad",
kSuccessfulModelVersion, 1);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionManager.PredictionModelsStored", true, 1);
// Set up the next periodic model fetch to send null model, which will trigger
// the model removal from the store, and the observer notified of null model.
histogram_tester = std::make_unique<base::HistogramTester>();
run_loop = std::make_unique<base::RunLoop>();
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithNullModel);
model_file_observer()->set_model_file_received_callback(base::BindOnce(
[](base::RunLoop* run_loop, proto::OptimizationTarget optimization_target,
base::optional_ref<const ModelInfo> model_info) {
EXPECT_EQ(optimization_target,
proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
EXPECT_FALSE(model_info.has_value());
run_loop->Quit();
},
run_loop.get()));
// Trigger the periodic fetch timer.
auto* prediction_model_fetch_timer =
GetPredictionManager()->GetPredictionModelFetchTimerForTesting();
EXPECT_EQ(
PredictionModelFetchTimer::PredictionModelFetchTimerState::kPeriodicFetch,
prediction_model_fetch_timer->GetStateForTesting());
prediction_model_fetch_timer->ScheduleImmediateFetchForTesting();
run_loop->Run();
// The model fetch will happen, and the model will be removed from the store,
// and the observers notified of null model.
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status."
"PainfulPageLoad",
net::HTTP_OK, 1);
histogram_tester->ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
histogram_tester->ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
histogram_tester->ExpectUniqueSample(
"OptimizationGuide.PredictionModelStore.ModelRemovalReason."
"PainfulPageLoad",
PredictionModelStoreModelRemovalReason::kNoModelInGetModelsResponse, 1);
}
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
TestSwitchProfileDoesntCrash) {
ProfileManager* profile_manager = g_browser_process->profile_manager();
base::FilePath other_path =
profile_manager->GenerateNextProfileDirectoryPath();
// Create an additional profile.
Profile& profile =
profiles::testing::CreateProfileSync(profile_manager, other_path);
CreateBrowser(&profile);
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
// CreateGuestBrowser() is not supported for Android or ChromeOS out of the box.
IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
GuestProfileReceivesModel) {
SetResponseType(
PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
{
base::HistogramTester histogram_tester;
// Register in the primary profile and ensure the model returns.
RegisterModelFileObserverWithKeyedService(browser()->profile());
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetUpValidModelInfoReceival(model_file_observer(), run_loop.get());
run_loop->Run();
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
PredictionModelDownloadStatus::kSuccess, 1);
}
{
base::HistogramTester histogram_tester;
// Now hook everything up in the guest profile and we should still get the
// model back but no additional fetches should be made.
Browser* guest_browser = CreateGuestBrowser();
// To prevent any race, ensure the store has be initialized.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.StoreInitialized", 1);
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
ModelFileObserver model_file_observer;
SetUpValidModelInfoReceival(&model_file_observer, run_loop.get());
OptimizationGuideKeyedServiceFactory::GetForProfile(
guest_browser->profile())
->AddObserverForOptimizationTargetModel(
proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
/*model_metadata=*/std::nullopt, &model_file_observer);
// Wait until the opt guide is up and the model is loaded as its shared
// between profiles.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
run_loop->Run();
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
}
}
#endif
class PredictionManagerModelPackageOverrideTest : public InProcessBrowserTest {
public:
PredictionManagerModelPackageOverrideTest() = default;
~PredictionManagerModelPackageOverrideTest() override = default;
void SetUpCommandLine(base::CommandLine* cmd_line) override {
InProcessBrowserTest::SetUpCommandLine(cmd_line);
base::FilePath src_dir;
base::PathService::Get(chrome::DIR_TEST_DATA, &src_dir);
cmd_line->AppendSwitchASCII(
switches::kModelOverride,
base::StrCat({
"OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD",
ModelOverrideSeparator(),
FilePathToString(src_dir.AppendASCII("optimization_guide")
.AppendASCII("additional_file_exists.crx3")),
}));
}
};
IN_PROC_BROWSER_TEST_F(PredictionManagerModelPackageOverrideTest, TestE2E) {
base::RunLoop run_loop;
ModelFileObserver model_file_observer;
SetUpValidModelInfoReceival(&model_file_observer, &run_loop,
{FILE_PATH_LITERAL("good_additional_file.txt")});
OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
->AddObserverForOptimizationTargetModel(
proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
/*model_metadata=*/std::nullopt, &model_file_observer);
run_loop.Run();
}
} // namespace optimization_guide