blob: e6b0a588791adcebc191c54eae7a69ab8e9ba1a5 [file] [log] [blame]
// Copyright 2023 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/ui/startup/first_run_service.h"
#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/signin/signin_features.h"
#include "chrome/browser/ui/startup/first_run_test_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_service_client.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/test/test_enabled_state_provider.h"
#include "components/metrics/test/test_metrics_service_client.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/synthetic_trial_registry.h"
#include "components/variations/synthetic_trials.h"
#include "components/variations/synthetic_trials_active_group_id_provider.h"
#include "components/variations/variations_crash_keys.h"
#include "components/variations/variations_test_utils.h"
#include "components/version_info/channel.h"
#include "components/version_info/version_info.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !BUILDFLAG(ENABLE_DICE_SUPPORT)
#error "Unsupported platform"
#endif
namespace {
class ScopedTestingMetricsService {
public:
// Sets up a `metrics::MetricsService` instance and makes it available in its
// scope via `testing_browser_process->metrics_service()`.
//
// This service only supports feature related to the usage of synthetic field
// trials.
//
// Requires:
// - the local state prefs to be usable from `testing_browser_process`
// - a task runner to be available (see //docs/threading_and_tasks_testing.md)
explicit ScopedTestingMetricsService(
TestingBrowserProcess* testing_browser_process)
: browser_process_(testing_browser_process) {
CHECK(browser_process_);
auto* local_state = browser_process_->local_state();
CHECK(local_state)
<< "Error: local state prefs are required. In a unit test, this can be "
"set up using base::test::ScopedFeatureList.";
// The `SyntheticTrialsActiveGroupIdProvider` needs to be notified of
// changes from the registry for them to be used through the variations API.
synthetic_trial_registry_observation_.Observe(&synthetic_trial_registry_);
metrics_service_client_.set_synthetic_trial_registry(
&synthetic_trial_registry_);
metrics_state_manager_ = metrics::MetricsStateManager::Create(
local_state, &enabled_state_provider_,
/*backup_registry_key=*/std::wstring(),
/*user_data_dir=*/base::FilePath());
// Needs to be set up, will be updated at each synthetic trial change.
variations::InitCrashKeys();
// Required by `MetricsService` to record UserActions. We don't rely on
// these here, since we never make it start recording metrics, but the task
// runner is still required during the shutdown sequence.
base::SetRecordActionTaskRunner(
base::SingleThreadTaskRunner::GetCurrentDefault());
metrics_service_ = std::make_unique<metrics::MetricsService>(
metrics_state_manager_.get(), &metrics_service_client_, local_state);
browser_process_->SetMetricsService(metrics_service_.get());
}
~ScopedTestingMetricsService() {
// The scope is closing, undo the set up that was done in the constuctor:
// `MetricsService` and other necessary parts like crash keys.
browser_process_->SetMetricsService(nullptr);
variations::ClearCrashKeysInstanceForTesting();
// Note: Clears all the synthetic trials, not juste the ones registered
// during the lifetime of this object.
variations::SyntheticTrialsActiveGroupIdProvider::GetInstance()
->ResetForTesting();
}
metrics::MetricsService* Get() { return metrics_service_.get(); }
private:
raw_ptr<TestingBrowserProcess> browser_process_ = nullptr;
metrics::TestEnabledStateProvider enabled_state_provider_{/*consent=*/true,
/*enabled=*/true};
variations::SyntheticTrialRegistry synthetic_trial_registry_;
base::ScopedObservation<variations::SyntheticTrialRegistry,
variations::SyntheticTrialObserver>
synthetic_trial_registry_observation_{
variations::SyntheticTrialsActiveGroupIdProvider::GetInstance()};
metrics::TestMetricsServiceClient metrics_service_client_;
std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
std::unique_ptr<metrics::MetricsService> metrics_service_;
};
struct FirstRunFieldTrialTestParams {
double entropy_value;
version_info::Channel channel;
bool expect_study_enabled;
bool expect_feature_enabled;
};
} // namespace
class FirstRunServiceTest : public testing::Test {
private:
content::BrowserTaskEnvironment task_environment_;
};
TEST_F(FirstRunServiceTest, ShouldOpenFirstRun) {
TestingProfileManager profile_manager{TestingBrowserProcess::GetGlobal()};
ASSERT_TRUE(profile_manager.SetUp());
auto* profile = profile_manager.CreateTestingProfile("Test Profile");
EXPECT_TRUE(ShouldOpenFirstRun(profile));
SetIsFirstRun(false);
EXPECT_FALSE(ShouldOpenFirstRun(profile));
SetIsFirstRun(true);
EXPECT_TRUE(ShouldOpenFirstRun(profile));
g_browser_process->local_state()->SetBoolean(prefs::kFirstRunFinished, true);
EXPECT_FALSE(ShouldOpenFirstRun(profile));
}
class FirstRunFieldTrialCreatorTest
: public testing::Test,
public testing::WithParamInterface<FirstRunFieldTrialTestParams> {
public:
base::test::ScopedFeatureList& scoped_feature_list() {
return scoped_feature_list_;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(FirstRunFieldTrialCreatorTest, SetUpFromClientSide) {
{
base::MockEntropyProvider low_entropy_provider{GetParam().entropy_value};
auto feature_list = std::make_unique<base::FeatureList>();
FirstRunService::SetUpClientSideFieldTrial(
low_entropy_provider, feature_list.get(), GetParam().channel);
// Substitute the existing feature list with the one with field trial
// configurations we are testing, so we can check the assertions.
scoped_feature_list().InitWithFeatureList(std::move(feature_list));
}
EXPECT_TRUE(base::FieldTrialList::IsTrialActive("ForYouFreStudy"));
EXPECT_EQ(GetParam().expect_study_enabled,
base::FeatureList::IsEnabled(kForYouFreSyntheticTrialRegistration));
EXPECT_EQ(GetParam().expect_feature_enabled,
base::FeatureList::IsEnabled(kForYouFre));
EXPECT_EQ(true, kForYouFreCloseShouldProceed.Get());
EXPECT_EQ(SigninPromoVariant::kSignIn, kForYouFreSignInPromoVariant.Get());
EXPECT_EQ(GetParam().expect_study_enabled
? (GetParam().expect_feature_enabled ? "ClientSideEnabled-2"
: "ClientSideDisabled-2")
: "",
kForYouFreStudyGroup.Get());
}
INSTANTIATE_TEST_SUITE_P(
,
FirstRunFieldTrialCreatorTest,
testing::Values(
FirstRunFieldTrialTestParams{.entropy_value = 0.6,
.channel = version_info::Channel::BETA,
.expect_study_enabled = true,
.expect_feature_enabled = false},
FirstRunFieldTrialTestParams{.entropy_value = 0.01,
.channel = version_info::Channel::BETA,
.expect_study_enabled = true,
.expect_feature_enabled = true},
FirstRunFieldTrialTestParams{.entropy_value = 0.99,
.channel = version_info::Channel::STABLE,
.expect_study_enabled = false,
.expect_feature_enabled = false},
FirstRunFieldTrialTestParams{.entropy_value = 0.016,
.channel = version_info::Channel::STABLE,
.expect_study_enabled = true,
.expect_feature_enabled = false},
FirstRunFieldTrialTestParams{.entropy_value = 0.009,
.channel = version_info::Channel::STABLE,
.expect_study_enabled = true,
.expect_feature_enabled = true}),
[](const ::testing::TestParamInfo<FirstRunFieldTrialTestParams>& params) {
return base::StringPrintf(
"%02.0fpctEntropy%s", params.param.entropy_value * 100,
version_info::GetChannelString(params.param.channel).data());
});
// Tests to verify the logic for synthetic trial registration that we use to
// assign a given client in a cohort for our long term tracking metrics.
class FirstRunCohortSetupTest : public testing::Test {
public:
static constexpr char kStudyTestGroupName1[] = "test_group_1";
static constexpr char kStudyTestGroupName2[] = "test_group_2";
private:
base::test::TaskEnvironment task_environment_;
ScopedTestingLocalState testing_local_state_{
TestingBrowserProcess::GetGlobal()};
ScopedTestingMetricsService testing_metrics_service_{
TestingBrowserProcess::GetGlobal()};
};
// `JoinFirstRunCohort` is run when the FRE is finished, if a group name is
// provided through the feature flags, should result in registering the
// synthetic trial with that group name and store it for subsequent startups.
TEST_F(FirstRunCohortSetupTest, JoinFirstRunCohort) {
PrefService* local_state = g_browser_process->local_state();
EXPECT_FALSE(local_state->HasPrefPath(prefs::kFirstRunStudyGroup));
EXPECT_FALSE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
// No group name available through the features, should no-op.
FirstRunService::JoinFirstRunCohort();
EXPECT_FALSE(local_state->HasPrefPath(prefs::kFirstRunStudyGroup));
EXPECT_FALSE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
/*enabled_features=*/
{
{kForYouFreSyntheticTrialRegistration,
{{"group_name", kStudyTestGroupName1}}},
{kForYouFre, {}},
},
/*disabled_features=*/{});
// A group name is available, the trial should get registered.
FirstRunService::JoinFirstRunCohort();
EXPECT_EQ(kStudyTestGroupName1,
local_state->GetString(prefs::kFirstRunStudyGroup));
EXPECT_TRUE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
EXPECT_TRUE(variations::IsInSyntheticTrialGroup(
FirstRunService::kSyntheticTrialName, kStudyTestGroupName1));
}
// `EnsureStickToFirstRunCohort` is run on startup, and should result in
// registering the synthetic trial if the client saw the FRE and we recorded a
// group name to assign to it.
TEST_F(FirstRunCohortSetupTest, EnsureStickToFirstRunCohort) {
PrefService* local_state = g_browser_process->local_state();
EXPECT_FALSE(local_state->HasPrefPath(prefs::kFirstRunStudyGroup));
EXPECT_FALSE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
/*enabled_features=*/
{
{kForYouFreSyntheticTrialRegistration,
{{"group_name", kStudyTestGroupName1}}},
{kForYouFre, {}},
},
/*disabled_features=*/{});
// `EnsureStickToFirstRunCohort()` no-ops without some specific prefs.
FirstRunService::EnsureStickToFirstRunCohort();
EXPECT_FALSE(local_state->HasPrefPath(prefs::kFirstRunStudyGroup));
EXPECT_FALSE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
// Setting the group name pref: the first, but not sufficient requirement.
// We also set it to a different name from the feature flag to verify which
// one is used.
local_state->SetString(prefs::kFirstRunStudyGroup, kStudyTestGroupName2);
FirstRunService::EnsureStickToFirstRunCohort();
EXPECT_FALSE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
// Marking the FRE finished: the second and final requirement.
local_state->SetBoolean(prefs::kFirstRunFinished, true);
FirstRunService::EnsureStickToFirstRunCohort();
EXPECT_TRUE(
variations::HasSyntheticTrial(FirstRunService::kSyntheticTrialName));
// The registered group is read from the prefs, not from the feature param.
EXPECT_TRUE(variations::IsInSyntheticTrialGroup(
FirstRunService::kSyntheticTrialName, kStudyTestGroupName2));
EXPECT_FALSE(variations::IsInSyntheticTrialGroup(
FirstRunService::kSyntheticTrialName, kStudyTestGroupName1));
}