k8s.io/kubernetes@v1.29.3/test/e2e/framework/config/config_test.go (about)

     1  /*
     2  Copyright 2018 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 config
    18  
    19  import (
    20  	"bytes"
    21  	"flag"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  func TestInt(t *testing.T) {
    30  	flags := flag.NewFlagSet("test", 0)
    31  	var context struct {
    32  		Number int `default:"5" usage:"some number"`
    33  	}
    34  	require.NotPanics(t, func() {
    35  		AddOptionsToSet(flags, &context, "")
    36  	})
    37  	require.Equal(t, []simpleFlag{
    38  		{
    39  			name:     "number",
    40  			usage:    "some number",
    41  			defValue: "5",
    42  		}},
    43  		allFlags(flags))
    44  	assert.Equal(t, 5, context.Number)
    45  }
    46  
    47  func TestLower(t *testing.T) {
    48  	flags := flag.NewFlagSet("test", 0)
    49  	var context struct {
    50  		Ahem      string
    51  		MixedCase string
    52  	}
    53  	require.NotPanics(t, func() {
    54  		AddOptionsToSet(flags, &context, "")
    55  	})
    56  	require.Equal(t, []simpleFlag{
    57  		{
    58  			name: "ahem",
    59  		},
    60  		{
    61  			name: "mixedCase",
    62  		},
    63  	},
    64  		allFlags(flags))
    65  }
    66  
    67  func TestPrefix(t *testing.T) {
    68  	flags := flag.NewFlagSet("test", 0)
    69  	var context struct {
    70  		Number int `usage:"some number"`
    71  	}
    72  	require.NotPanics(t, func() {
    73  		AddOptionsToSet(flags, &context, "some.prefix")
    74  	})
    75  	require.Equal(t, []simpleFlag{
    76  		{
    77  			name:     "some.prefix.number",
    78  			usage:    "some number",
    79  			defValue: "0",
    80  		}},
    81  		allFlags(flags))
    82  }
    83  
    84  func TestRecursion(t *testing.T) {
    85  	flags := flag.NewFlagSet("test", 0)
    86  	type Nested struct {
    87  		Number1 int `usage:"embedded number"`
    88  	}
    89  	var context struct {
    90  		Nested
    91  		A struct {
    92  			B struct {
    93  				C struct {
    94  					Number2 int `usage:"some number"`
    95  				}
    96  			}
    97  		}
    98  	}
    99  	require.NotPanics(t, func() {
   100  		AddOptionsToSet(flags, &context, "")
   101  	})
   102  	require.Equal(t, []simpleFlag{
   103  		{
   104  			name:     "a.b.c.number2",
   105  			usage:    "some number",
   106  			defValue: "0",
   107  		},
   108  		{
   109  			name:     "number1",
   110  			usage:    "embedded number",
   111  			defValue: "0",
   112  		},
   113  	},
   114  		allFlags(flags))
   115  }
   116  
   117  func TestPanics(t *testing.T) {
   118  	flags := flag.NewFlagSet("test", 0)
   119  	assert.PanicsWithValue(t, `invalid default "a" for int entry prefix.number: strconv.Atoi: parsing "a": invalid syntax`, func() {
   120  		var context struct {
   121  			Number int `default:"a"`
   122  		}
   123  		AddOptionsToSet(flags, &context, "prefix")
   124  	})
   125  
   126  	assert.PanicsWithValue(t, `invalid default "10000000000000000000" for int entry prefix.number: strconv.Atoi: parsing "10000000000000000000": value out of range`, func() {
   127  		var context struct {
   128  			Number int `default:"10000000000000000000"`
   129  		}
   130  		AddOptionsToSet(flags, &context, "prefix")
   131  	})
   132  
   133  	assert.PanicsWithValue(t, `options parameter without a type - nil?!`, func() {
   134  		AddOptionsToSet(flags, nil, "")
   135  	})
   136  
   137  	assert.PanicsWithValue(t, `need a pointer to a struct, got instead: *int`, func() {
   138  		number := 0
   139  		AddOptionsToSet(flags, &number, "")
   140  	})
   141  
   142  	assert.PanicsWithValue(t, `struct entry "prefix.number" not exported`, func() {
   143  		var context struct {
   144  			number int
   145  		}
   146  		AddOptionsToSet(flags, &context, "prefix")
   147  	})
   148  
   149  	assert.PanicsWithValue(t, `unsupported struct entry type "prefix.someNumber": config.MyInt`, func() {
   150  		type MyInt int
   151  		var context struct {
   152  			SomeNumber MyInt
   153  		}
   154  		AddOptionsToSet(flags, &context, "prefix")
   155  	})
   156  }
   157  
   158  type TypesTestCase struct {
   159  	name      string
   160  	copyFlags bool
   161  }
   162  
   163  func TestTypes(t *testing.T) {
   164  	testcases := []TypesTestCase{
   165  		{name: "directly"},
   166  		{name: "CopyFlags", copyFlags: true},
   167  	}
   168  
   169  	for _, testcase := range testcases {
   170  		testTypes(t, testcase)
   171  	}
   172  }
   173  
   174  func testTypes(t *testing.T, testcase TypesTestCase) {
   175  	flags := flag.NewFlagSet("test", 0)
   176  	type Context struct {
   177  		Bool     bool          `default:"true"`
   178  		Duration time.Duration `default:"1ms"`
   179  		Float64  float64       `default:"1.23456789"`
   180  		String   string        `default:"hello world"`
   181  		Int      int           `default:"-1" usage:"some number"`
   182  		Int64    int64         `default:"-1234567890123456789"`
   183  		Uint     uint          `default:"1"`
   184  		Uint64   uint64        `default:"1234567890123456789"`
   185  	}
   186  	var context Context
   187  	require.NotPanics(t, func() {
   188  		AddOptionsToSet(flags, &context, "")
   189  	})
   190  
   191  	if testcase.copyFlags {
   192  		original := bytes.Buffer{}
   193  		flags.SetOutput(&original)
   194  		flags.PrintDefaults()
   195  
   196  		flags2 := flag.NewFlagSet("test", 0)
   197  		CopyFlags(flags, flags2)
   198  		flags = flags2
   199  
   200  		copy := bytes.Buffer{}
   201  		flags.SetOutput(&copy)
   202  		flags.PrintDefaults()
   203  		assert.Equal(t, original.String(), copy.String(), testcase.name+": help messages equal")
   204  		assert.Contains(t, copy.String(), "some number", testcase.name+": copied help message contains defaults")
   205  	}
   206  
   207  	require.Equal(t, []simpleFlag{
   208  		{
   209  			name:     "bool",
   210  			defValue: "true",
   211  			isBool:   true,
   212  		},
   213  		{
   214  			name:     "duration",
   215  			defValue: "1ms",
   216  		},
   217  		{
   218  			name:     "float64",
   219  			defValue: "1.23456789",
   220  		},
   221  		{
   222  			name:     "int",
   223  			usage:    "some number",
   224  			defValue: "-1",
   225  		},
   226  		{
   227  			name:     "int64",
   228  			defValue: "-1234567890123456789",
   229  		},
   230  		{
   231  			name:     "string",
   232  			defValue: "hello world",
   233  		},
   234  		{
   235  			name:     "uint",
   236  			defValue: "1",
   237  		},
   238  		{
   239  			name:     "uint64",
   240  			defValue: "1234567890123456789",
   241  		},
   242  	},
   243  		allFlags(flags), testcase.name)
   244  	assert.Equal(t,
   245  		Context{true, time.Millisecond, 1.23456789, "hello world",
   246  			-1, -1234567890123456789, 1, 1234567890123456789,
   247  		},
   248  		context,
   249  		"default values must match")
   250  	require.NoError(t, flags.Parse([]string{
   251  		"-int", "-2",
   252  		"-int64", "-9123456789012345678",
   253  		"-uint", "2",
   254  		"-uint64", "9123456789012345678",
   255  		"-string", "pong",
   256  		"-float64", "-1.23456789",
   257  		"-bool=false",
   258  		"-duration=1s",
   259  	}), testcase.name)
   260  	assert.Equal(t,
   261  		Context{false, time.Second, -1.23456789, "pong",
   262  			-2, -9123456789012345678, 2, 9123456789012345678,
   263  		},
   264  		context,
   265  		testcase.name+": parsed values must match")
   266  }
   267  
   268  func allFlags(fs *flag.FlagSet) []simpleFlag {
   269  	var flags []simpleFlag
   270  	fs.VisitAll(func(f *flag.Flag) {
   271  		s := simpleFlag{
   272  			name:     f.Name,
   273  			usage:    f.Usage,
   274  			defValue: f.DefValue,
   275  		}
   276  		type boolFlag interface {
   277  			flag.Value
   278  			IsBoolFlag() bool
   279  		}
   280  		if fv, ok := f.Value.(boolFlag); ok && fv.IsBoolFlag() {
   281  			s.isBool = true
   282  		}
   283  		flags = append(flags, s)
   284  	})
   285  	return flags
   286  }
   287  
   288  type simpleFlag struct {
   289  	name     string
   290  	usage    string
   291  	defValue string
   292  	isBool   bool
   293  }