k8s.io/client-go@v0.31.1/features/testing/features_init_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 testing 18 19 import ( 20 "fmt" 21 "strings" 22 "testing" 23 24 "github.com/stretchr/testify/require" 25 26 "k8s.io/client-go/features" 27 ) 28 29 func TestDriveInitDefaultFeatureGates(t *testing.T) { 30 featureGates := features.FeatureGates() 31 assertFunctionPanicsWithMessage(t, func() { featureGates.Enabled("FakeFeatureGate") }, "features.FeatureGates().Enabled", fmt.Sprintf("feature %q is not registered in FeatureGate", "FakeFeatureGate")) 32 33 fakeGates := &fakeFeatureGates{features: map[features.Feature]bool{"FakeFeatureGate": true}} 34 require.True(t, fakeGates.Enabled("FakeFeatureGate")) 35 36 features.ReplaceFeatureGates(fakeGates) 37 featureGates = features.FeatureGates() 38 39 assertFeatureGatesType(t, featureGates) 40 require.True(t, featureGates.Enabled("FakeFeatureGate")) 41 } 42 43 func TestSetFeatureGatesDuringTest(t *testing.T) { 44 featureA := features.Feature("FeatureA") 45 featureB := features.Feature("FeatureB") 46 fakeGates := &fakeFeatureGates{map[features.Feature]bool{featureA: true, featureB: true}} 47 features.ReplaceFeatureGates(fakeGates) 48 t.Cleanup(func() { 49 // since cleanup functions will be called in last added, first called order. 50 // check if the original feature wasn't restored 51 require.True(t, features.FeatureGates().Enabled(featureA), "the original feature = %v wasn't restored", featureA) 52 }) 53 54 SetFeatureDuringTest(t, featureA, false) 55 56 require.False(t, features.FeatureGates().Enabled(featureA)) 57 require.True(t, features.FeatureGates().Enabled(featureB)) 58 } 59 60 func TestSetFeatureGatesDuringTestPanics(t *testing.T) { 61 fakeGates := &fakeFeatureGates{features: map[features.Feature]bool{"FakeFeatureGate": true}} 62 63 features.ReplaceFeatureGates(fakeGates) 64 assertFunctionPanicsWithMessage(t, func() { SetFeatureDuringTest(t, "UnknownFeature", false) }, "SetFeatureDuringTest", fmt.Sprintf("feature %q is not registered in featureGates", "UnknownFeature")) 65 66 readOnlyGates := &readOnlyAlwaysDisabledFeatureGates{} 67 features.ReplaceFeatureGates(readOnlyGates) 68 assertFunctionPanicsWithMessage(t, func() { SetFeatureDuringTest(t, "FakeFeature", false) }, "SetFeatureDuringTest", fmt.Sprintf("clientfeatures.FeatureGates(): %T does not implement featureGatesSetter interface", readOnlyGates)) 69 } 70 71 func TestOverridesForSetFeatureGatesDuringTest(t *testing.T) { 72 scenarios := []struct { 73 name string 74 firstTestName string 75 secondTestName string 76 expectError bool 77 }{ 78 { 79 name: "concurrent tests setting the same feature fail", 80 firstTestName: "fooTest", 81 secondTestName: "barTest", 82 expectError: true, 83 }, 84 85 { 86 name: "same test setting the same feature does not fail", 87 firstTestName: "fooTest", 88 secondTestName: "fooTest", 89 expectError: false, 90 }, 91 92 { 93 name: "subtests setting the same feature don't not fail", 94 firstTestName: "fooTest", 95 secondTestName: "fooTest/scenario1", 96 expectError: false, 97 }, 98 } 99 for _, scenario := range scenarios { 100 t.Run(scenario.name, func(t *testing.T) { 101 featureA := features.Feature("FeatureA") 102 fakeGates := &fakeFeatureGates{map[features.Feature]bool{featureA: true}} 103 fakeTesting := &fakeT{fakeTestName: scenario.firstTestName, TB: t} 104 105 features.ReplaceFeatureGates(fakeGates) 106 require.NoError(t, setFeatureDuringTestInternal(fakeTesting, featureA, true)) 107 require.True(t, features.FeatureGates().Enabled(featureA)) 108 109 fakeTesting.fakeTestName = scenario.secondTestName 110 err := setFeatureDuringTestInternal(fakeTesting, featureA, false) 111 require.Equal(t, scenario.expectError, err != nil) 112 }) 113 } 114 } 115 116 type fakeFeatureGates struct { 117 features map[features.Feature]bool 118 } 119 120 func (f *fakeFeatureGates) Enabled(feature features.Feature) bool { 121 featureValue, ok := f.features[feature] 122 if !ok { 123 panic(fmt.Errorf("feature %q is not registered in featureGates", feature)) 124 } 125 return featureValue 126 } 127 128 func (f *fakeFeatureGates) Set(feature features.Feature, value bool) error { 129 f.features[feature] = value 130 return nil 131 } 132 133 type readOnlyAlwaysDisabledFeatureGates struct{} 134 135 func (f *readOnlyAlwaysDisabledFeatureGates) Enabled(feature features.Feature) bool { 136 return false 137 } 138 139 type fakeT struct { 140 fakeTestName string 141 testing.TB 142 } 143 144 func (t *fakeT) Name() string { 145 return t.fakeTestName 146 } 147 148 func assertFeatureGatesType(t *testing.T, fg features.Gates) { 149 _, ok := fg.(*fakeFeatureGates) 150 if !ok { 151 t.Fatalf("passed features.FeatureGates() is NOT of type *alwaysEnabledFakeGates, it is of type = %T", fg) 152 } 153 } 154 155 func assertFunctionPanicsWithMessage(t *testing.T, f func(), fName, errMessage string) { 156 didPanic, panicMessage := didFunctionPanic(f) 157 if !didPanic { 158 t.Fatalf("function %q did not panicked", fName) 159 } 160 161 panicError, ok := panicMessage.(error) 162 if !ok || !strings.Contains(panicError.Error(), errMessage) { 163 t.Fatalf("func %q should panic with error message:\t%#v\n\tPanic value:\t%#v\n", fName, errMessage, panicMessage) 164 } 165 } 166 167 func didFunctionPanic(f func()) (didPanic bool, panicMessage interface{}) { 168 didPanic = true 169 170 defer func() { 171 panicMessage = recover() 172 }() 173 174 f() 175 didPanic = false 176 177 return 178 }