1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/cronet/android/cronet_base_feature.h"
6 #include "base/feature_list.h"
7 #include "base/metrics/field_trial_params.h"
8 #include "base/test/scoped_feature_list.h"
9 #include "testing/gmock/include/gmock/gmock.h"
10 #include "testing/gtest/include/gtest/gtest.h"
11
12 namespace cronet {
13 namespace {
14
15 using ::org::chromium::net::httpflags::BaseFeatureOverrides;
16 using ::testing::IsEmpty;
17 using ::testing::Pair;
18 using ::testing::UnorderedElementsAre;
19
20 // base::Feature uses a caching mechanism where repeated
21 // `base::FeatureList::IsEnabled()` calls do not actually check the overrides
22 // list and instead return the previously returned value according to the cache
23 // state in the base::Feature itself. See `base::Feature::cached_value` and
24 // `base::FeatureList::caching_context_`.
25 //
26 // This caching mechanism breaks isolation between our unit tests, so we need
27 // to flush the cache between tests. We can do that by making sure that the
28 // feature list caching context (basically, a cache generation counter) is set
29 // to a value that is different from the one it had in the last
30 // base::FeatureList::IsEnabled() call. This will cause a cache miss on the next
31 // base::FeatureList::IsEnabled() call. If we don't do anything this won't be
32 // the case, as the code under test always installs a base::FeatureList with a
33 // caching context value of 1 (which is the default).
34 //
35 // base::test::ScopedFeatureList uses the exact same approach internally, but we
36 // can't piggy-back on that because we need to change the caching context of the
37 // base::FeatureList that is instantiated by the code under test, not the one
38 // that base::test::ScopedFeatureList installs.
FlushBaseFeatureCache()39 void FlushBaseFeatureCache() {
40 auto* const feature_list = base::FeatureList::GetInstance();
41 if (feature_list == nullptr) {
42 return;
43 }
44 // Start at 32768 to avoid collisions with ScopedFeatureList's own
45 // `g_current_caching_context` (which starts at 1).
46 static uint16_t g_feature_list_caching_context = 32768;
47 feature_list->SetCachingContextForTesting(g_feature_list_caching_context++);
48 }
49
50 constexpr char kTestFeatureDisabledByDefaultName[] =
51 "CronetBaseFeatureTestFeatureDisabledByDefault";
52 BASE_FEATURE(kTestFeatureDisabledByDefault,
53 kTestFeatureDisabledByDefaultName,
54 base::FEATURE_DISABLED_BY_DEFAULT);
55
56 constexpr char kTestFeatureEnabledByDefaultName[] =
57 "CronetBaseFeatureTestFeatureEnabledByDefault";
58 BASE_FEATURE(kTestFeatureEnabledByDefault,
59 kTestFeatureEnabledByDefaultName,
60 base::FEATURE_ENABLED_BY_DEFAULT);
61
62 constexpr char kTestFeatureWithParamsName[] =
63 "CronetBaseFeatureTestFeatureWithParams";
64 BASE_FEATURE(kTestFeatureWithParams,
65 kTestFeatureWithParamsName,
66 base::FEATURE_DISABLED_BY_DEFAULT);
67 constexpr char kTestParamName[] = "test_param";
68 constexpr char kTestParamDefaultValue[] = "test_param_default_value";
69 constexpr base::FeatureParam<std::string> kTestParam{
70 &kTestFeatureWithParams, kTestParamName, kTestParamDefaultValue};
71
TEST(ApplyBaseFeatureOverrides,NoOpOnEmptyOverrides)72 TEST(ApplyBaseFeatureOverrides, NoOpOnEmptyOverrides) {
73 base::test::ScopedFeatureList scoped_feature_list;
74 scoped_feature_list.InitWithNullFeatureAndFieldTrialLists();
75 ApplyBaseFeatureOverrides(BaseFeatureOverrides());
76 FlushBaseFeatureCache();
77 EXPECT_FALSE(base::FeatureList::IsEnabled(kTestFeatureDisabledByDefault));
78 EXPECT_TRUE(base::FeatureList::IsEnabled(kTestFeatureEnabledByDefault));
79 }
80
TEST(ApplyBaseFeatureOverrides,OverridesFeatureToEnabled)81 TEST(ApplyBaseFeatureOverrides, OverridesFeatureToEnabled) {
82 base::test::ScopedFeatureList scoped_feature_list;
83 scoped_feature_list.InitWithNullFeatureAndFieldTrialLists();
84 BaseFeatureOverrides overrides;
85 (*overrides.mutable_feature_states())[kTestFeatureDisabledByDefaultName]
86 .set_enabled(true);
87 ApplyBaseFeatureOverrides(overrides);
88 FlushBaseFeatureCache();
89 EXPECT_TRUE(base::FeatureList::IsEnabled(kTestFeatureDisabledByDefault));
90 }
91
TEST(ApplyBaseFeatureOverrides,OverridesFeatureToDisabled)92 TEST(ApplyBaseFeatureOverrides, OverridesFeatureToDisabled) {
93 base::test::ScopedFeatureList scoped_feature_list;
94 scoped_feature_list.InitWithNullFeatureAndFieldTrialLists();
95 BaseFeatureOverrides overrides;
96 (*overrides.mutable_feature_states())[kTestFeatureEnabledByDefaultName]
97 .set_enabled(false);
98 ApplyBaseFeatureOverrides(overrides);
99 FlushBaseFeatureCache();
100 EXPECT_FALSE(base::FeatureList::IsEnabled(kTestFeatureEnabledByDefault));
101 }
102
TEST(ApplyBaseFeatureOverrides,DoesNotOverrideFeature)103 TEST(ApplyBaseFeatureOverrides, DoesNotOverrideFeature) {
104 base::test::ScopedFeatureList scoped_feature_list;
105 scoped_feature_list.InitWithNullFeatureAndFieldTrialLists();
106 BaseFeatureOverrides overrides;
107 (*overrides.mutable_feature_states())[kTestFeatureEnabledByDefaultName];
108 ApplyBaseFeatureOverrides(overrides);
109 FlushBaseFeatureCache();
110 EXPECT_TRUE(base::FeatureList::IsEnabled(kTestFeatureEnabledByDefault));
111 }
112
TEST(ApplyBaseFeatureOverrides,NoOpIfBaseFeatureAlreadyInitialized)113 TEST(ApplyBaseFeatureOverrides, NoOpIfBaseFeatureAlreadyInitialized) {
114 base::test::ScopedFeatureList scoped_feature_list;
115 scoped_feature_list.InitWithEmptyFeatureAndFieldTrialLists();
116 BaseFeatureOverrides overrides;
117 (*overrides.mutable_feature_states())[kTestFeatureDisabledByDefaultName]
118 .set_enabled(true);
119 ApplyBaseFeatureOverrides(overrides);
120 FlushBaseFeatureCache();
121 EXPECT_FALSE(base::FeatureList::IsEnabled(kTestFeatureDisabledByDefault));
122 }
123
TEST(ApplyBaseFeatureOverrides,DoesNotAssociateFeatureParamsIfNoParamsAreProvided)124 TEST(ApplyBaseFeatureOverrides,
125 DoesNotAssociateFeatureParamsIfNoParamsAreProvided) {
126 base::test::ScopedFeatureList scoped_feature_list;
127 scoped_feature_list.InitWithNullFeatureAndFieldTrialLists();
128 BaseFeatureOverrides overrides;
129 (*overrides.mutable_feature_states())[kTestFeatureWithParamsName].set_enabled(
130 true);
131 ApplyBaseFeatureOverrides(overrides);
132 FlushBaseFeatureCache();
133 base::FieldTrialParams params;
134 EXPECT_FALSE(
135 base::GetFieldTrialParamsByFeature(kTestFeatureWithParams, ¶ms));
136 EXPECT_THAT(params, IsEmpty());
137 EXPECT_EQ(kTestParam.Get(), kTestParamDefaultValue);
138 }
139
TEST(ApplyBaseFeatureOverrides,AssociatesFeatureParams)140 TEST(ApplyBaseFeatureOverrides, AssociatesFeatureParams) {
141 base::test::ScopedFeatureList scoped_feature_list;
142 scoped_feature_list.InitWithNullFeatureAndFieldTrialLists();
143 BaseFeatureOverrides overrides;
144 constexpr char kParamValue[] = "test_param_value";
145 auto& feature_state =
146 (*overrides.mutable_feature_states())[kTestFeatureWithParamsName];
147 feature_state.set_enabled(true);
148 (*feature_state.mutable_params())[kTestParamName] = kParamValue;
149 ApplyBaseFeatureOverrides(overrides);
150 FlushBaseFeatureCache();
151 base::FieldTrialParams params;
152 EXPECT_TRUE(
153 base::GetFieldTrialParamsByFeature(kTestFeatureWithParams, ¶ms));
154 EXPECT_THAT(params, UnorderedElementsAre(Pair(kTestParamName, kParamValue)));
155 EXPECT_EQ(kTestParam.Get(), kParamValue);
156 }
157
158 } // namespace
159 } // namespace cronet
160