blob: 885f3e3a6328fb6bb7e6c4d21f5346ee7a7eff72 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_file.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/test/launcher/test_launcher.h"
#include "base/test/task_environment.h"
#include "base/test/test_switches.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/metrics/metrics_service.h"
#include "components/prefs/json_pref_store.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service_factory.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/hashing.h"
#include "components/variations/limited_entropy_mode_gate.h"
#include "components/variations/pref_names.h"
#include "components/variations/service/limited_entropy_synthetic_trial.h"
#include "components/variations/service/variations_service.h"
#include "components/variations/synthetic_trial_registry.h"
#include "components/variations/variations_switches.h"
#include "components/variations/variations_test_utils.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace variations {
const char kTestStudyName[] = "TestStudy";
namespace {
VariationsSeed CreateTestSeedWithLimitedEntropyLayer() {
VariationsSeed seed;
seed.set_serial_number("999");
auto* layer = seed.add_layers();
layer->set_id(1);
layer->set_num_slots(100);
layer->set_entropy_mode(Layer::LIMITED);
auto* layer_member = layer->add_members();
layer_member->set_id(1);
auto* slot = layer_member->add_slots();
slot->set_start(0);
slot->set_end(99);
auto* study = seed.add_study();
study->set_name(kTestStudyName);
study->set_consistency(Study_Consistency_PERMANENT);
auto* filter = study->mutable_filter();
filter->set_min_version("91.*");
filter->add_channel(Study_Channel_CANARY);
filter->add_channel(Study_Channel_BETA);
filter->add_channel(Study_Channel_DEV);
filter->add_channel(Study_Channel_STABLE);
filter->add_channel(Study_Channel_UNKNOWN);
filter->add_platform(Study_Platform_PLATFORM_ANDROID);
filter->add_platform(Study_Platform_PLATFORM_IOS);
filter->add_platform(Study_Platform_PLATFORM_ANDROID_WEBVIEW);
filter->add_platform(Study_Platform_PLATFORM_WINDOWS);
filter->add_platform(Study_Platform_PLATFORM_MAC);
filter->add_platform(Study_Platform_PLATFORM_LINUX);
filter->add_platform(Study_Platform_PLATFORM_CHROMEOS);
filter->add_platform(Study_Platform_PLATFORM_CHROMEOS_LACROS);
auto* experiment = study->add_experiment();
experiment->set_name("TestExperiment");
experiment->set_probability_weight(100);
auto* layer_member_reference = study->mutable_layer();
layer_member_reference->set_layer_id(1);
layer_member_reference->add_layer_member_ids(1);
return seed;
}
struct CommandLineOutput {
int exit_code;
std::string standard_output;
};
CommandLineOutput RunWithCommandLine(const base::CommandLine& command_line) {
std::string standard_output;
int exit_code;
base::GetAppOutputWithExitCode(command_line, &standard_output, &exit_code);
return {exit_code, standard_output};
}
} // namespace
class LimitedEntropyRandomizationBrowserTest : public InProcessBrowserTest {
public:
LimitedEntropyRandomizationBrowserTest() {
EnableLimitedEntropyModeForTesting();
DisableTestingConfig();
}
~LimitedEntropyRandomizationBrowserTest() override = default;
};
// This is a MANUAL test that's only expected to be triggered from
// `LimitedEntropyRandomizationBrowserTestHelper`, which will set up the prefs
// values needed for this test. This test has two purposes: 1) verify that both
// the test study and the synthetic trial are registered, and 2) verify that the
// browser doesn't crash in any of the intermediate steps.
IN_PROC_BROWSER_TEST_F(LimitedEntropyRandomizationBrowserTest,
MANUAL_SyntheticTrialAndStudyRegistrationSubTest) {
EXPECT_TRUE(base::FieldTrialList::TrialExists(kTestStudyName));
std::vector<ActiveGroupId> synthetic_trials;
g_browser_process->metrics_service()
->GetSyntheticTrialRegistry()
->GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(),
&synthetic_trials);
EXPECT_TRUE(
ContainsTrialName(synthetic_trials, kLimitedEntropySyntheticTrialName));
}
class LimitedEntropyRandomizationBrowserTestHelper : public ::testing::Test {
public:
void SetUp() override {
::testing::Test::SetUp();
base::ScopedAllowBlockingForTesting allow_blocking;
pref_registry_ = base::MakeRefCounted<PrefRegistrySimple>();
metrics::MetricsService::RegisterPrefs(pref_registry_.get());
VariationsService::RegisterPrefs(pref_registry_.get());
// Creates an empty locate state file in preparation for any prefs value
// needed for the test.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
user_data_dir_ = temp_dir_.GetPath().AppendASCII("user-data-dir");
ASSERT_TRUE(base::CreateDirectory(user_data_dir_));
local_state_file_ = user_data_dir_.AppendASCII("Local State");
base::File local_state_file(
local_state_file_, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(local_state_file.IsValid())
<< "Failed to create local state file: "
<< base::File::ErrorToString(local_state_file.error_details());
}
base::CommandLine GetArgsForBrowserTest() {
base::CommandLine args =
base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram());
args.AppendSwitch(switches::kAcceptEmptySeedSignatureForTesting);
args.AppendSwitchASCII(switches::kFakeVariationsChannel, "canary");
args.AppendSwitchASCII(base::kGTestFilterFlag,
"LimitedEntropyRandomizationBrowserTest."
"MANUAL_SyntheticTrialAndStudyRegistrationSubTest");
args.AppendSwitch(::switches::kRunManualTestsFlag);
args.AppendSwitch(::switches::kSingleProcessTests);
// Make sure the sub test loads its prefs values from the same disk location
// in `LimitedEntropyRandomizationBrowserTestHelper`.
args.AppendSwitchPath(::switches::kUserDataDir, user_data_dir());
args.AppendSwitchASCII("gtest_color", "no");
return args;
}
const base::FilePath& user_data_dir() const { return user_data_dir_; }
const base::FilePath& local_state_file() const { return local_state_file_; }
// Instantiates a pref store backed by a file at the given `path`.
std::unique_ptr<PrefService> LoadLocalState(const base::FilePath& path) {
PrefServiceFactory pref_service_factory;
pref_service_factory.set_async(false);
pref_service_factory.SetUserPrefsFile(
path, task_environment_.GetMainThreadTaskRunner().get());
return pref_service_factory.Create(pref_registry_);
}
private:
base::test::TaskEnvironment task_environment_;
scoped_refptr<PrefRegistrySimple> pref_registry_;
base::ScopedTempDir temp_dir_;
base::FilePath user_data_dir_;
base::FilePath local_state_file_;
};
TEST_F(LimitedEntropyRandomizationBrowserTestHelper,
SyntheticTrialAndStudyRegistration) {
std::unique_ptr<PrefService> local_state = LoadLocalState(local_state_file());
auto args = GetArgsForBrowserTest();
// Set up the test seed that includes a layer with `EntropyMode.LIMITED`. An
// empty signature is given since the sub test will be start with
// `switches::kAcceptEmptySeedSignatureForTesting`.
local_state->SetString(prefs::kVariationsSeedSignature, "");
local_state->SetString(
prefs::kVariationsCompressedSeed,
GZipAndB64EncodeToHexString(CreateTestSeedWithLimitedEntropyLayer()));
// This will pick the "Enabled" group in the synthetic trial.
local_state->SetUint64(prefs::kVariationsLimitedEntropySyntheticTrialSeed,
10);
// Block until the above prefs values are committed to disk. The sub test will
// spin up a separate browser process which will load its prefs values from
// the same disk location.
base::RunLoop loop;
local_state->CommitPendingWrite(
base::BindOnce([](base::RunLoop* loop) { loop->Quit(); }, &loop));
loop.Run();
// The test verifications are in the sub test,
// i.e. "MANUAL_SyntheticTrialAndStudyRegistrationSubTest", which is triggered
// from the command line run. If any of the verifications fail, the command
// line run will fail and the details of the error will be printed through
// `output.standard_output`.
auto output = RunWithCommandLine(args);
EXPECT_EQ(0, output.exit_code) << "Test failed: " << output.standard_output;
}
} // namespace variations