k8s.io/client-go@v0.31.1/features/envvar_test.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package features 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/stretchr/testify/require" 24 ) 25 26 var defaultTestFeatures = map[Feature]FeatureSpec{ 27 "TestAlpha": { 28 Default: false, 29 LockToDefault: false, 30 PreRelease: "Alpha", 31 }, 32 "TestBeta": { 33 Default: true, 34 LockToDefault: false, 35 PreRelease: "Beta", 36 }, 37 } 38 39 func TestEnvVarFeatureGates(t *testing.T) { 40 expectedDefaultFeaturesState := map[Feature]bool{"TestAlpha": false, "TestBeta": true} 41 copyExpectedStateMap := func(toCopy map[Feature]bool) map[Feature]bool { 42 m := map[Feature]bool{} 43 for k, v := range toCopy { 44 m[k] = v 45 } 46 return m 47 } 48 49 scenarios := []struct { 50 name string 51 features map[Feature]FeatureSpec 52 envVariables map[string]string 53 setMethodFeatures map[Feature]bool 54 55 expectedFeaturesState map[Feature]bool 56 expectedInternalEnabledViaEnvVarFeatureState map[Feature]bool 57 expectedInternalEnabledViaSetMethodFeatureState map[Feature]bool 58 }{ 59 { 60 name: "can add empty features", 61 }, 62 { 63 name: "no env var, features get Defaults assigned", 64 features: defaultTestFeatures, 65 expectedFeaturesState: expectedDefaultFeaturesState, 66 }, 67 { 68 name: "incorrect env var, feature gets Default assigned", 69 features: defaultTestFeatures, 70 envVariables: map[string]string{"TestAlpha": "true"}, 71 expectedFeaturesState: expectedDefaultFeaturesState, 72 }, 73 { 74 name: "correct env var changes the feature gets state", 75 features: defaultTestFeatures, 76 envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "true"}, 77 expectedFeaturesState: func() map[Feature]bool { 78 expectedDefaultFeaturesStateCopy := copyExpectedStateMap(expectedDefaultFeaturesState) 79 expectedDefaultFeaturesStateCopy["TestAlpha"] = true 80 return expectedDefaultFeaturesStateCopy 81 }(), 82 expectedInternalEnabledViaEnvVarFeatureState: map[Feature]bool{"TestAlpha": true}, 83 }, 84 { 85 name: "incorrect env var value gets ignored", 86 features: defaultTestFeatures, 87 envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "TrueFalse"}, 88 expectedFeaturesState: expectedDefaultFeaturesState, 89 }, 90 { 91 name: "empty env var value gets ignored", 92 features: defaultTestFeatures, 93 envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": ""}, 94 expectedFeaturesState: expectedDefaultFeaturesState, 95 }, 96 { 97 name: "a feature LockToDefault wins", 98 features: map[Feature]FeatureSpec{ 99 "TestAlpha": { 100 Default: true, 101 LockToDefault: true, 102 PreRelease: "Alpha", 103 }, 104 }, 105 envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "False"}, 106 expectedFeaturesState: map[Feature]bool{"TestAlpha": true}, 107 }, 108 { 109 name: "setting a feature to LockToDefault changes the internal state", 110 features: map[Feature]FeatureSpec{ 111 "TestAlpha": { 112 Default: true, 113 LockToDefault: true, 114 PreRelease: "Alpha", 115 }, 116 }, 117 envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "True"}, 118 expectedFeaturesState: map[Feature]bool{"TestAlpha": true}, 119 expectedInternalEnabledViaEnvVarFeatureState: map[Feature]bool{"TestAlpha": true}, 120 }, 121 { 122 name: "setting a feature via the Set method works", 123 features: defaultTestFeatures, 124 setMethodFeatures: map[Feature]bool{"TestAlpha": true}, 125 expectedFeaturesState: map[Feature]bool{"TestAlpha": true}, 126 expectedInternalEnabledViaSetMethodFeatureState: map[Feature]bool{"TestAlpha": true}, 127 }, 128 { 129 name: "setting a feature via the Set method wins", 130 features: defaultTestFeatures, 131 setMethodFeatures: map[Feature]bool{"TestAlpha": false}, 132 envVariables: map[string]string{"KUBE_FEATURE_TestAlpha": "True"}, 133 expectedFeaturesState: map[Feature]bool{"TestAlpha": false}, 134 expectedInternalEnabledViaEnvVarFeatureState: map[Feature]bool{"TestAlpha": true}, 135 expectedInternalEnabledViaSetMethodFeatureState: map[Feature]bool{"TestAlpha": false}, 136 }, 137 } 138 for _, scenario := range scenarios { 139 t.Run(scenario.name, func(t *testing.T) { 140 for k, v := range scenario.envVariables { 141 t.Setenv(k, v) 142 } 143 target := newEnvVarFeatureGates(scenario.features) 144 145 for k, v := range scenario.setMethodFeatures { 146 err := target.Set(k, v) 147 require.NoError(t, err) 148 } 149 for expectedFeature, expectedValue := range scenario.expectedFeaturesState { 150 actualValue := target.Enabled(expectedFeature) 151 require.Equal(t, actualValue, expectedValue, "expected feature=%v, to be=%v, not=%v", expectedFeature, expectedValue, actualValue) 152 } 153 154 enabledViaEnvVarInternalMap := target.enabledViaEnvVar.Load().(map[Feature]bool) 155 require.Len(t, enabledViaEnvVarInternalMap, len(scenario.expectedInternalEnabledViaEnvVarFeatureState)) 156 for expectedFeatureName, expectedFeatureValue := range scenario.expectedInternalEnabledViaEnvVarFeatureState { 157 actualFeatureValue, wasExpectedFeatureFound := enabledViaEnvVarInternalMap[expectedFeatureName] 158 if !wasExpectedFeatureFound { 159 t.Errorf("feature %v has not been found in enabledViaEnvVarInternalMap", expectedFeatureName) 160 } 161 require.Equal(t, expectedFeatureValue, actualFeatureValue, "feature %v has incorrect value = %v, expected = %v", expectedFeatureName, actualFeatureValue, expectedFeatureValue) 162 } 163 164 enabledViaSetMethodInternalMap := target.enabledViaSetMethod 165 require.Len(t, enabledViaSetMethodInternalMap, len(scenario.expectedInternalEnabledViaSetMethodFeatureState)) 166 for expectedFeatureName, expectedFeatureValue := range scenario.expectedInternalEnabledViaSetMethodFeatureState { 167 actualFeatureValue, wasExpectedFeatureFound := enabledViaSetMethodInternalMap[expectedFeatureName] 168 if !wasExpectedFeatureFound { 169 t.Errorf("feature %v has not been found in enabledViaSetMethod", expectedFeatureName) 170 } 171 require.Equal(t, expectedFeatureValue, actualFeatureValue, "feature %v has incorrect value = %v, expected = %v", expectedFeatureName, actualFeatureValue, expectedFeatureValue) 172 } 173 }) 174 } 175 } 176 177 func TestEnvVarFeatureGatesEnabledPanic(t *testing.T) { 178 target := newEnvVarFeatureGates(nil) 179 require.PanicsWithError(t, fmt.Errorf("feature %q is not registered in FeatureGates %q", "UnknownFeature", target.callSiteName).Error(), func() { target.Enabled("UnknownFeature") }) 180 } 181 182 func TestHasAlreadyReadEnvVar(t *testing.T) { 183 target := newEnvVarFeatureGates(nil) 184 require.False(t, target.hasAlreadyReadEnvVar()) 185 186 _ = target.getEnabledMapFromEnvVar() 187 require.True(t, target.hasAlreadyReadEnvVar()) 188 } 189 190 func TestEnvVarFeatureGatesSetNegative(t *testing.T) { 191 scenarios := []struct { 192 name string 193 features map[Feature]FeatureSpec 194 featureName Feature 195 featureValue bool 196 197 expectedErr func(string) error 198 }{ 199 { 200 name: "empty feature name returns an error", 201 features: defaultTestFeatures, 202 expectedErr: func(callSiteName string) error { 203 return fmt.Errorf("feature %q is not registered in FeatureGates %q", "", callSiteName) 204 }, 205 }, 206 { 207 name: "setting unknown feature returns an error", 208 features: defaultTestFeatures, 209 featureName: "Unknown", 210 expectedErr: func(callSiteName string) error { 211 return fmt.Errorf("feature %q is not registered in FeatureGates %q", "Unknown", callSiteName) 212 }, 213 }, 214 { 215 name: "setting locked feature returns an error", 216 features: map[Feature]FeatureSpec{"LockedFeature": {LockToDefault: true, Default: true}}, 217 featureName: "LockedFeature", 218 featureValue: false, 219 expectedErr: func(_ string) error { 220 return fmt.Errorf("cannot set feature gate %q to %v, feature is locked to %v", "LockedFeature", false, true) 221 }, 222 }, 223 } 224 225 for _, scenario := range scenarios { 226 t.Run(scenario.name, func(t *testing.T) { 227 target := newEnvVarFeatureGates(scenario.features) 228 229 err := target.Set(scenario.featureName, scenario.featureValue) 230 require.Equal(t, scenario.expectedErr(target.callSiteName), err) 231 }) 232 } 233 }