| // 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/readaloud/android/synthetic_trial.h" |
| #include <memory> |
| #include "base/feature_list.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/flags/android/chrome_feature_list.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/scoped_metrics_service_for_synthetic_trials.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/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/variations/active_field_trials.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using chrome::android::kReadAloud; |
| |
| namespace readaloud { |
| namespace { |
| |
| // Base trial name and group. |
| inline constexpr char kBaseTrial[] = "TestTrial"; |
| inline constexpr char kBaseGroup[] = "TestGroup"; |
| |
| inline constexpr char kSuffix[] = "_Suffix"; |
| |
| // Synthetic trial name is the same as the base trial name plus a suffix. |
| inline constexpr char kSyntheticTrialName[] = "TestTrial_Suffix"; |
| |
| inline constexpr char kPrefKey[] = "ReadAloud|||_Suffix"; |
| |
| } // namespace |
| |
| class SyntheticTrialTest : public ::testing::Test { |
| public: |
| SyntheticTrialTest() = default; |
| |
| SyntheticTrialTest(const SyntheticTrialTest&) = delete; |
| SyntheticTrialTest& operator=(const SyntheticTrialTest&) = delete; |
| |
| void EnableReadAloudWithTrial(const std::string& base_trial, |
| const std::string& base_group) { |
| auto feature_list = std::make_unique<base::FeatureList>(); |
| base::FieldTrial* trial = |
| base::FieldTrialList::CreateFieldTrial(base_trial, base_group); |
| feature_list->RegisterFieldTrialOverride( |
| kReadAloud.name, |
| base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE, trial); |
| scoped_feature_list_.InitWithFeatureList(std::move(feature_list)); |
| } |
| |
| PrefService* local_state() { return g_browser_process->local_state(); } |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| ScopedTestingLocalState testing_local_state_{ |
| TestingBrowserProcess::GetGlobal()}; |
| ScopedMetricsServiceForSyntheticTrials metrics_service_provider_{ |
| TestingBrowserProcess::GetGlobal()}; |
| }; |
| |
| TEST_F(SyntheticTrialTest, TestNoBaseStudy) { |
| EXPECT_FALSE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| |
| // Try init with no field trial set on kReadAloud. |
| auto synth = SyntheticTrial::Create(kReadAloud.name, kSuffix); |
| EXPECT_EQ(nullptr, synth.get()); |
| |
| // Synthetic trial wasn't activated on init. |
| EXPECT_FALSE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| EXPECT_FALSE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| } |
| |
| TEST_F(SyntheticTrialTest, TestActivate) { |
| EXPECT_FALSE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| |
| EnableReadAloudWithTrial(kBaseTrial, kBaseGroup); |
| auto synth = SyntheticTrial::Create(kReadAloud.name, kSuffix); |
| EXPECT_FALSE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| EXPECT_EQ(nullptr, local_state() |
| ->GetDict(prefs::kReadAloudSyntheticTrials) |
| .FindString(kSyntheticTrialName)); |
| |
| synth->Activate(); |
| // Synthetic trial is activated. |
| EXPECT_TRUE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| EXPECT_TRUE( |
| variations::IsInSyntheticTrialGroup(kSyntheticTrialName, kBaseGroup)); |
| // Synthetic trial and group should be saved for reactivation. |
| EXPECT_TRUE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| EXPECT_EQ(kBaseTrial, *local_state() |
| ->GetDict(prefs::kReadAloudSyntheticTrials) |
| .FindString(kPrefKey)); |
| } |
| |
| TEST_F(SyntheticTrialTest, TestReactivatePreviouslyActive) { |
| // Set up the reactivation pref. |
| EXPECT_FALSE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| { |
| ScopedDictPrefUpdate(local_state(), prefs::kReadAloudSyntheticTrials) |
| ->Set(kPrefKey, kBaseTrial); |
| } |
| EXPECT_FALSE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| |
| // Base trial and group agree with the reactivation pref, so creating |
| // SyntheticTrial should reactivate. |
| EnableReadAloudWithTrial(kBaseTrial, kBaseGroup); |
| auto synth = SyntheticTrial::Create(kReadAloud.name, kSuffix); |
| EXPECT_TRUE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| EXPECT_TRUE( |
| variations::IsInSyntheticTrialGroup(kSyntheticTrialName, kBaseGroup)); |
| } |
| |
| TEST_F(SyntheticTrialTest, TestNotActivatedIfTrialNameChanged) { |
| // Set up the reactivation pref. |
| EXPECT_FALSE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| { |
| ScopedDictPrefUpdate(local_state(), prefs::kReadAloudSyntheticTrials) |
| ->Set(kPrefKey, kBaseTrial); |
| } |
| EXPECT_FALSE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| |
| // A different trial is controlling the flag so the synthetic trial should not |
| // reactivate. |
| EnableReadAloudWithTrial("SomeOtherTrial", kBaseGroup); |
| auto synth = SyntheticTrial::Create(kReadAloud.name, kSuffix); |
| EXPECT_FALSE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| |
| // Subsequent Activate() should activate the synthetic trial with the new |
| // name. |
| synth->Activate(); |
| EXPECT_TRUE(variations::HasSyntheticTrial("SomeOtherTrial_Suffix")); |
| EXPECT_TRUE( |
| variations::IsInSyntheticTrialGroup("SomeOtherTrial_Suffix", kBaseGroup)); |
| EXPECT_TRUE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| EXPECT_EQ("SomeOtherTrial", *local_state() |
| ->GetDict(prefs::kReadAloudSyntheticTrials) |
| .FindString(kPrefKey)); |
| } |
| |
| TEST_F(SyntheticTrialTest, TestTwoSyntheticTrials) { |
| EXPECT_FALSE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| |
| EnableReadAloudWithTrial(kBaseTrial, kBaseGroup); |
| auto synth = SyntheticTrial::Create(kReadAloud.name, kSuffix); |
| auto synth2 = SyntheticTrial::Create(kReadAloud.name, "_SomeOtherSuffix"); |
| |
| synth->Activate(); |
| synth2->Activate(); |
| |
| // Both trials should be active. |
| EXPECT_TRUE(variations::HasSyntheticTrial(kSyntheticTrialName)); |
| EXPECT_TRUE( |
| variations::IsInSyntheticTrialGroup(kSyntheticTrialName, kBaseGroup)); |
| EXPECT_TRUE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| EXPECT_EQ(kBaseTrial, *local_state() |
| ->GetDict(prefs::kReadAloudSyntheticTrials) |
| .FindString(kPrefKey)); |
| |
| EXPECT_TRUE(variations::HasSyntheticTrial("TestTrial_SomeOtherSuffix")); |
| EXPECT_TRUE(variations::IsInSyntheticTrialGroup("TestTrial_SomeOtherSuffix", |
| kBaseGroup)); |
| EXPECT_TRUE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| EXPECT_EQ(kBaseTrial, *local_state() |
| ->GetDict(prefs::kReadAloudSyntheticTrials) |
| .FindString("ReadAloud|||_SomeOtherSuffix")); |
| } |
| |
| TEST_F(SyntheticTrialTest, TestClearStalePrefs) { |
| // Set a few reactivation signals. |
| { |
| ScopedDictPrefUpdate update(local_state(), |
| prefs::kReadAloudSyntheticTrials); |
| update->Set("aaa|||_suffix1", "aaatrial"); |
| update->Set("bbb|||_suffix2", "bbbtrial"); |
| update->Set("ccc|||_suffix3", "ccctrial"); |
| } |
| auto feature_list = std::make_unique<base::FeatureList>(); |
| |
| // Feature "aaa" remains associated with trial "aaatrial". |
| feature_list->RegisterFieldTrialOverride( |
| "aaa", base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE, |
| base::FieldTrialList::CreateFieldTrial("aaatrial", "group")); |
| |
| // Feature "bbb" is now associated with a different trial. |
| feature_list->RegisterFieldTrialOverride( |
| "bbb", base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE, |
| base::FieldTrialList::CreateFieldTrial("asdfasdf", "group")); |
| |
| // Feature "ccc" is not associated with any trial. |
| |
| scoped_feature_list_.InitWithFeatureList(std::move(feature_list)); |
| |
| // Clear. |
| SyntheticTrial::ClearStalePrefs(); |
| EXPECT_TRUE(local_state()->HasPrefPath(prefs::kReadAloudSyntheticTrials)); |
| |
| // "aaatrial" should remain. |
| const base::Value::Dict& dict = |
| local_state()->GetDict(prefs::kReadAloudSyntheticTrials); |
| const std::string* aaa_trial_name = dict.FindString("aaa|||_suffix1"); |
| EXPECT_TRUE(aaa_trial_name != nullptr); |
| EXPECT_EQ("aaatrial", *aaa_trial_name); |
| |
| // "bbbtrial" and "ccctrial" should be gone. |
| EXPECT_EQ(nullptr, dict.FindString("bbb|||_suffix2")); |
| EXPECT_EQ(nullptr, dict.FindString("ccc|||_suffix3")); |
| } |
| |
| } // namespace readaloud |