github.com/abayer/test-infra@v0.0.5/mungegithub/options/options_test.go (about) 1 /* 2 Copyright 2017 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 options 18 19 import ( 20 "bufio" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "reflect" 25 "strings" 26 "testing" 27 "time" 28 29 "k8s.io/apimachinery/pkg/util/sets" 30 ) 31 32 var ( 33 str1, str2 string 34 slice1, slice2 []string 35 int1 int 36 uint1 uint64 37 bool1 bool 38 bool2 bool 39 dur1 time.Duration 40 41 entries = []optEntry{ 42 {key: "strvar", val: "def", ptr: &str1}, 43 {key: "strslicevar", val: []string{"def", "def2"}, ptr: &slice1}, 44 {key: "intvar", val: 5, ptr: &int1}, 45 {key: "uintvar", val: uint64(5), ptr: &uint1}, 46 {key: "boolvar", val: true, ptr: &bool1}, 47 {key: "boolvar2", val: false, ptr: &bool2}, 48 {key: "durvar", val: time.Second * 2, ptr: &dur1}, 49 {key: "emptyslicevar", val: []string{"def"}, ptr: &slice2}, 50 {key: "emptystringvar", val: "def", ptr: &str2}, 51 } 52 53 sample_yaml = `strvar: Hello world. 54 strslicevar: Hello world., string2 55 intvar: 5 56 uintvar: 6 57 boolvar: false 58 boolvar2: true 59 durvar: 15s 60 emptyslicevar: 61 emptystringvar: ""` 62 expected = []optEntry{ 63 {key: "strvar", val: "Hello world.", ptr: &str1}, 64 {key: "strslicevar", val: []string{"Hello world.", "string2"}, ptr: &slice1}, 65 {key: "intvar", val: 5, ptr: &int1}, 66 {key: "uintvar", val: uint64(6), ptr: &uint1}, 67 {key: "boolvar", val: false, ptr: &bool1}, 68 {key: "boolvar2", val: true, ptr: &bool2}, 69 {key: "durvar", val: time.Second * 15, ptr: &dur1}, 70 {key: "emptyslicevar", val: []string{}, ptr: &slice2}, 71 {key: "emptystringvar", val: "", ptr: &str2}, 72 } 73 74 sample_yaml2 = `intvar: 700 75 durvar: 5s 76 emptyslicevar: ""` 77 expected2 = []optEntry{ 78 {key: "strvar", val: "def", ptr: &str1}, 79 {key: "strslicevar", val: []string{"def", "def2"}, ptr: &slice1}, 80 {key: "intvar", val: 700, ptr: &int1}, 81 {key: "uintvar", val: uint64(5), ptr: &uint1}, 82 {key: "boolvar", val: true, ptr: &bool1}, 83 {key: "boolvar2", val: false, ptr: &bool2}, 84 {key: "durvar", val: time.Second * 5, ptr: &dur1}, 85 {key: "emptyslicevar", val: []string{}, ptr: &slice2}, 86 {key: "emptystringvar", val: "def", ptr: &str2}, 87 } 88 ) 89 90 type tempConfig struct { 91 file *os.File 92 writer *bufio.Writer 93 } 94 95 func newTempConfig() (*tempConfig, error) { 96 tempfile, err := ioutil.TempFile(os.TempDir(), "options_test") 97 if err != nil { 98 return nil, err 99 } 100 return &tempConfig{file: tempfile, writer: bufio.NewWriter(tempfile)}, nil 101 } 102 103 func (t *tempConfig) SetContent(content string) error { 104 // Clear file and reset writing offset 105 t.file.Truncate(0) 106 t.file.Seek(0, os.SEEK_SET) 107 t.writer.Reset(t.file) 108 if _, err := t.writer.WriteString(content); err != nil { 109 return err 110 } 111 if err := t.writer.Flush(); err != nil { 112 return err 113 } 114 return nil 115 } 116 117 func (t *tempConfig) Clean() { 118 t.file.Close() 119 os.Remove(t.file.Name()) 120 } 121 122 type optEntry struct { 123 key string 124 val interface{} 125 ptr interface{} 126 } 127 128 func registerAll(opts *Options) { 129 for _, entry := range entries { 130 switch defVal := entry.val.(type) { 131 case string: 132 opts.RegisterString(entry.ptr.(*string), entry.key, defVal, "Desc: "+entry.key) 133 case []string: 134 opts.RegisterStringSlice(entry.ptr.(*[]string), entry.key, defVal, "Desc: "+entry.key) 135 case int: 136 opts.RegisterInt(entry.ptr.(*int), entry.key, defVal, "Desc: "+entry.key) 137 case uint64: 138 opts.RegisterUint64(entry.ptr.(*uint64), entry.key, defVal, "Desc: "+entry.key) 139 case bool: 140 opts.RegisterBool(entry.ptr.(*bool), entry.key, defVal, "Desc: "+entry.key) 141 case time.Duration: 142 opts.RegisterDuration(entry.ptr.(*time.Duration), entry.key, defVal, "Desc: "+entry.key) 143 } 144 } 145 } 146 147 func checkAll(opts *Options, expected []optEntry) error { 148 for _, entry := range expected { 149 switch expectedVal := entry.val.(type) { 150 case string: 151 v := opts.GetString(entry.key) 152 if v != entry.ptr { 153 return fmt.Errorf("string opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v) 154 } 155 if *v != expectedVal { 156 return fmt.Errorf("string opt '%s' has value %q, expected %q", entry.key, *v, expectedVal) 157 } 158 case []string: 159 v := opts.GetStringSlice(entry.key) 160 if v != entry.ptr { 161 return fmt.Errorf("[]string opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v) 162 } 163 if !reflect.DeepEqual(*v, expectedVal) { 164 return fmt.Errorf("[]string opt '%s' has value %q, expected %q", entry.key, *v, expectedVal) 165 } 166 case int: 167 v := opts.GetInt(entry.key) 168 if v != entry.ptr { 169 return fmt.Errorf("int opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v) 170 } 171 if *v != expectedVal { 172 return fmt.Errorf("int opt '%s' has value %d, expected %d", entry.key, *v, expectedVal) 173 } 174 case uint64: 175 v := opts.GetUint64(entry.key) 176 if v != entry.ptr { 177 return fmt.Errorf("uint64 opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v) 178 } 179 if *v != expectedVal { 180 return fmt.Errorf("uint64 opt '%s' has value %d, expected %d", entry.key, *v, expectedVal) 181 } 182 case bool: 183 v := opts.GetBool(entry.key) 184 if v != entry.ptr { 185 return fmt.Errorf("bool opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v) 186 } 187 if *v != expectedVal { 188 return fmt.Errorf("bool opt '%s' has value %v, expected %v", entry.key, *v, expectedVal) 189 } 190 case time.Duration: 191 v := opts.GetDuration(entry.key) 192 if v != entry.ptr { 193 return fmt.Errorf("duration opt '%s' moved! Was at %p, is now at %p", entry.key, entry.ptr, v) 194 } 195 if *v != expectedVal { 196 return fmt.Errorf("duration opt '%s' has value %v, expected %v", entry.key, *v, expectedVal) 197 } 198 } 199 } 200 return nil 201 } 202 203 // TestOptions test that all option types are recalled properly. 204 // Specifically, options are checked to be correct after various combinations and orderings of 205 // loads, registers, reloads, and reregisters. 206 func TestOptions(t *testing.T) { 207 cfg, err := newTempConfig() 208 if err != nil { 209 t.Error(err) 210 } 211 defer cfg.Clean() 212 213 if err = cfg.SetContent(sample_yaml); err != nil { 214 t.Error(err) 215 } 216 // Test simple load + register in both orders. 217 oLoadFirst := New() 218 oRegisterFirst := New() 219 oRegisterOnce := New() 220 221 if _, err = oLoadFirst.Load(cfg.file.Name()); err != nil { 222 t.Errorf("Load failed: %v", err) 223 } 224 registerAll(oLoadFirst) 225 registerAll(oRegisterFirst) 226 if _, err = oRegisterFirst.Load(cfg.file.Name()); err != nil { 227 t.Errorf("Load failed: %v", err) 228 } 229 //Test Register only (no Load). 230 registerAll(oRegisterOnce) 231 if err = checkAll(oRegisterOnce, entries); err != nil { 232 t.Errorf("checkAll failed: %v", err) 233 } 234 if _, err = oRegisterOnce.Load(cfg.file.Name()); err != nil { 235 t.Errorf("Load failed: %v", err) 236 } 237 238 if err = checkAll(oLoadFirst, expected); err != nil { 239 t.Errorf("checkAll failed: %v", err) 240 } 241 if err = checkAll(oRegisterFirst, expected); err != nil { 242 t.Errorf("checkAll failed: %v", err) 243 } 244 if err = checkAll(oRegisterOnce, expected); err != nil { 245 t.Errorf("checkAll failed: %v", err) 246 } 247 248 if err = cfg.SetContent(sample_yaml2); err != nil { 249 t.Error(err) 250 } 251 // Test back to back loads and registers. 252 registerAll(oLoadFirst) 253 if _, err = oLoadFirst.Load(cfg.file.Name()); err != nil { 254 t.Errorf("Load failed: %v", err) 255 } 256 if _, err = oRegisterFirst.Load(cfg.file.Name()); err != nil { 257 t.Errorf("Load failed: %v", err) 258 } 259 registerAll(oRegisterFirst) 260 // Test reload without reregister. 261 if _, err = oRegisterOnce.Load(cfg.file.Name()); err != nil { 262 t.Errorf("Load failed: %v", err) 263 } 264 265 if err = checkAll(oLoadFirst, expected2); err != nil { 266 t.Errorf("checkAll failed: %v", err) 267 } 268 if err = checkAll(oRegisterFirst, expected2); err != nil { 269 t.Errorf("checkAll failed: %v", err) 270 } 271 if err = checkAll(oRegisterOnce, expected2); err != nil { 272 t.Errorf("checkAll failed: %v", err) 273 } 274 275 // Test no-op reload of same config (sample_yaml2 again). 276 if _, err = oRegisterOnce.Load(cfg.file.Name()); err != nil { 277 t.Errorf("Load failed: %v", err) 278 } 279 if err = checkAll(oRegisterOnce, expected2); err != nil { 280 t.Errorf("checkAll failed: %v", err) 281 } 282 } 283 284 // TestDefaults verifies that Options use their default values when a value is not specified. 285 func TestDefaults(t *testing.T) { 286 cfg, err := newTempConfig() 287 if err != nil { 288 t.Error(err) 289 } 290 defer cfg.Clean() 291 292 if err = cfg.SetContent(sample_yaml2); err != nil { 293 t.Error(err) 294 } 295 296 // Test Load then Register. 297 oUseDefaults := New() 298 if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil { 299 t.Errorf("Load failed: %v", err) 300 } 301 registerAll(oUseDefaults) 302 303 if err = checkAll(oUseDefaults, expected2); err != nil { 304 t.Errorf("checkAll failed: %v", err) 305 } 306 // Test Register then Load. 307 oUseDefaults = New() 308 registerAll(oUseDefaults) 309 if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil { 310 t.Errorf("Load failed: %v", err) 311 } 312 if err = checkAll(oUseDefaults, expected2); err != nil { 313 t.Errorf("checkAll failed: %v", err) 314 } 315 316 // Test that an option reverts back to default if a value is no longer specified after a reload. 317 if err = cfg.SetContent(sample_yaml); err != nil { 318 t.Error(err) 319 } 320 if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil { 321 t.Errorf("Load failed: %v", err) 322 } 323 if err = checkAll(oUseDefaults, expected); err != nil { 324 t.Errorf("checkAll failed: %v", err) 325 } 326 if err = cfg.SetContent(sample_yaml2); err != nil { 327 t.Error(err) 328 } 329 if _, err = oUseDefaults.Load(cfg.file.Name()); err != nil { 330 t.Errorf("Load failed: %v", err) 331 } 332 if err = checkAll(oUseDefaults, expected2); err != nil { 333 t.Errorf("checkAll failed: %v", err) 334 } 335 } 336 337 func TestDescriptionsAndToString(t *testing.T) { 338 cfg, err := newTempConfig() 339 if err != nil { 340 t.Error(err) 341 } 342 defer cfg.Clean() 343 344 if err = cfg.SetContent(sample_yaml); err != nil { 345 t.Error(err) 346 } 347 348 o := New() 349 registerAll(o) 350 desc := o.Descriptions() 351 for _, entry := range entries { 352 if !strings.Contains(desc, "\"Desc: "+entry.key+"\"") || 353 !strings.Contains(desc, entry.key) { 354 t.Errorf("Description is malformed for key '%s':\n%s\n", entry.key, desc) 355 } 356 } 357 // Check that default values are formatted as expected. 358 checks := []string{"(\"def\")", "([\"def\", \"def2\"])", "(2s)", "(5)", "(true)"} 359 for _, check := range checks { 360 if !strings.Contains(desc, check) { 361 t.Errorf("Description does not contain default value '%s':\n%s\n", check, desc) 362 } 363 } 364 } 365 366 func TestUpdateCallback(t *testing.T) { 367 cfg, err := newTempConfig() 368 if err != nil { 369 t.Error(err) 370 } 371 defer cfg.Clean() 372 373 if err = cfg.SetContent(sample_yaml); err != nil { 374 t.Error(err) 375 } 376 377 o := New() 378 registerAll(o) 379 o.RegisterUpdateCallback(func(changed sets.String) error { 380 return fmt.Errorf("some error") 381 }) 382 if _, err = o.Load(cfg.file.Name()); err != nil { 383 if _, ok := err.(*UpdateCallbackError); ok { 384 t.Errorf("Unexpected UpdateCallbackError from first load (should not have called callback): %v", err) 385 } else { 386 t.Errorf("Load failed: %v", err) 387 } 388 } 389 390 if err = cfg.SetContent(sample_yaml2); err != nil { 391 t.Error(err) 392 } 393 if _, err = o.Load(cfg.file.Name()); err == nil { 394 t.Errorf("Expected an UpdateCallbackError but no error was returned.") 395 } else if _, ok := err.(*UpdateCallbackError); !ok { 396 t.Errorf("Expected an UpdateCallbackError but got a different error: %v", err) 397 } 398 } 399 400 func TestChanged(t *testing.T) { 401 cfg, err := newTempConfig() 402 if err != nil { 403 t.Error(err) 404 } 405 defer cfg.Clean() 406 407 if err = cfg.SetContent(sample_yaml); err != nil { 408 t.Error(err) 409 } 410 411 o := New() 412 registerAll(o) 413 var changed sets.String 414 if changed, err = o.Load(cfg.file.Name()); err != nil { 415 t.Errorf("Load failed: %v", err) 416 } 417 418 if err = cfg.SetContent(sample_yaml2); err != nil { 419 t.Error(err) 420 } 421 expectedChanges := sets.NewString("durvar", "intvar", "strvar", "strslicevar", "uintvar", "boolvar", "boolvar2", "emptystringvar") 422 if changed, err = o.Load(cfg.file.Name()); err != nil { 423 t.Errorf("Load failed: %v", err) 424 } 425 if !changed.Equal(expectedChanges) { 426 t.Errorf( 427 "Error: expected options %q to be changed, but %q were changed.", 428 expectedChanges.List(), 429 changed.List(), 430 ) 431 } 432 }