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(©) 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 }