| // 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/app_shim/app_shim_controller.h" |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/scoped_path_override.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/mac/app_mode_common.h" |
| #include "components/variations/variations_switches.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| constexpr char kFeatureOnByDefaultName[] = "AppShimOnByDefault"; |
| BASE_FEATURE(kFeatureOnByDefault, |
| kFeatureOnByDefaultName, |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| constexpr char kFeatureOffByDefaultName[] = "AppShimOffByDefault"; |
| BASE_FEATURE(kFeatureOffByDefault, |
| kFeatureOffByDefaultName, |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| void PersistFeatureState( |
| const variations::VariationsCommandLine& feature_state) { |
| feature_state.WriteToFile(base::PathService::CheckedGet(chrome::DIR_USER_DATA) |
| .Append(app_mode::kFeatureStateFileName)); |
| } |
| |
| } // namespace |
| |
| class AppShimControllerTest : public testing::Test { |
| private: |
| base::ScopedPathOverride user_data_dir_override_{chrome::DIR_USER_DATA}; |
| }; |
| |
| TEST_F(AppShimControllerTest, EarlyAccessFeatureAllowList) { |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| // Reset crash-on-early-access flag. |
| base::FeatureList::ResetEarlyFeatureAccessTrackerForTesting(); |
| |
| // Should not be able to access arbitrary features without getting early |
| // access errors. |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kFeatureOnByDefault)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_EQ(&kFeatureOnByDefault, |
| base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| base::FeatureList::ResetEarlyFeatureAccessTrackerForTesting(); |
| } |
| |
| TEST_F(AppShimControllerTest, FeatureStateFromCommandLine) { |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitchASCII(switches::kEnableFeatures, |
| kFeatureOffByDefaultName); |
| command_line.AppendSwitchASCII(switches::kDisableFeatures, |
| kFeatureOnByDefaultName); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOnByDefaultName); |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOffByDefaultName); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOnByDefault)); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| } |
| |
| TEST_F(AppShimControllerTest, FeatureStateFromFeatureFile) { |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| variations::VariationsCommandLine feature_state; |
| feature_state.enable_features = kFeatureOffByDefaultName; |
| feature_state.disable_features = kFeatureOnByDefaultName; |
| PersistFeatureState(feature_state); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOnByDefaultName); |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOffByDefaultName); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOnByDefault)); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| } |
| |
| TEST_F(AppShimControllerTest, FeatureStateFromFeatureFileAndCommandLine) { |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| variations::VariationsCommandLine feature_state; |
| feature_state.enable_features = kFeatureOffByDefaultName; |
| feature_state.disable_features = kFeatureOnByDefaultName; |
| PersistFeatureState(feature_state); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitchASCII(switches::kDisableFeatures, |
| kFeatureOffByDefaultName); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOnByDefaultName); |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOffByDefaultName); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOnByDefault)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| } |
| |
| TEST_F(AppShimControllerTest, |
| FeatureStateFromFeatureFileIsIgnoredWhenLaunchedByChrome) { |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| variations::VariationsCommandLine feature_state; |
| feature_state.enable_features = kFeatureOffByDefaultName; |
| PersistFeatureState(feature_state); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitch(app_mode::kLaunchedByChromeProcessId); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOffByDefaultName); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| } |
| |
| TEST_F(AppShimControllerTest, FinalizeFeatureState) { |
| base::test::SingleThreadTaskEnvironment task_environment; |
| base::Thread io_thread("CrAppShimIO"); |
| io_thread.Start(); |
| auto io_thread_runner = io_thread.task_runner(); |
| |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitchASCII(switches::kEnableFeatures, |
| kFeatureOffByDefaultName); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOffByDefaultName); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| |
| variations::VariationsCommandLine feature_state; |
| feature_state.enable_features = ""; |
| feature_state.disable_features = base::JoinString( |
| {kFeatureOnByDefaultName, kFeatureOffByDefaultName}, ","); |
| AppShimController::FinalizeFeatureState(std::move(feature_state), |
| io_thread_runner); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOnByDefault)); |
| EXPECT_FALSE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| |
| // Verify that AppShimController did not leave the io thread blocked. |
| base::RunLoop run_loop; |
| io_thread_runner->PostTask(FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| TEST_F(AppShimControllerTest, FinalizeFeatureStateWithFieldTrials) { |
| static constexpr char kTrialName[] = "TrialName"; |
| static constexpr char kTrialGroup1Name[] = "Group1"; |
| static constexpr char kTrialGroup2Name[] = "Group2"; |
| static constexpr char kParam1Name[] = "param1"; |
| static constexpr char kParam2Name[] = "param2"; |
| |
| base::test::SingleThreadTaskEnvironment task_environment; |
| base::Thread io_thread("CrAppShimIO"); |
| io_thread.Start(); |
| auto io_thread_runner = io_thread.task_runner(); |
| |
| base::test::ScopedFeatureList clear_feature_list; |
| clear_feature_list.InitWithNullFeatureAndFieldTrialLists(); |
| |
| static const base::FeatureParam<int> feature_param1{&kFeatureOffByDefault, |
| kParam1Name, 0}; |
| static const base::FeatureParam<int> feature_param2{&kFeatureOffByDefault, |
| kParam2Name, 0}; |
| |
| variations::VariationsCommandLine feature_state_in_file; |
| feature_state_in_file.enable_features = |
| base::StringPrintf("%s<%s", kFeatureOffByDefaultName, kTrialName); |
| feature_state_in_file.disable_features = ""; |
| feature_state_in_file.field_trial_states = |
| base::StringPrintf("%s/%s", kTrialName, kTrialGroup1Name); |
| feature_state_in_file.field_trial_params = base::StringPrintf( |
| "%s.%s:%s/13,%s.%s:%s/5", kTrialName, kTrialGroup1Name, kParam1Name, |
| kTrialName, kTrialGroup2Name, kParam1Name); |
| PersistFeatureState(feature_state_in_file); |
| |
| base::CommandLine command_line(base::CommandLine::NO_PROGRAM); |
| command_line.AppendSwitchASCII( |
| variations::switches::kForceFieldTrialParams, |
| base::StringPrintf("%s.%s:%s/42", kTrialName, kTrialGroup1Name, |
| kParam2Name)); |
| AppShimController::PreInitFeatureState(command_line); |
| |
| base::FeatureList::GetInstance()->AddEarlyAllowedFeatureForTesting( |
| kFeatureOffByDefaultName); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| // With both command line and feature state file data the values on the |
| // command line should take priority. |
| EXPECT_EQ(0, feature_param1.Get()); |
| EXPECT_EQ(42, feature_param2.Get()); |
| |
| variations::VariationsCommandLine feature_state; |
| feature_state.enable_features = |
| base::StringPrintf("%s<%s", kFeatureOffByDefaultName, kTrialName); |
| feature_state.disable_features = ""; |
| feature_state.field_trial_states = |
| base::StringPrintf("%s/%s", kTrialName, kTrialGroup2Name); |
| feature_state.field_trial_params = base::StringPrintf( |
| "%s.%s:%s/2", kTrialName, kTrialGroup2Name, kParam1Name); |
| AppShimController::FinalizeFeatureState(feature_state, io_thread_runner); |
| EXPECT_TRUE(base::FeatureList::IsEnabled(kFeatureOffByDefault)); |
| EXPECT_FALSE(base::FeatureList::GetEarlyAccessedFeatureForTesting()); |
| EXPECT_EQ(2, feature_param1.Get()); |
| EXPECT_EQ(0, feature_param2.Get()); |
| } |