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  }