github.com/lyft/flytestdlib@v0.3.12-0.20210213045714-8cdd111ecda1/config/tests/accessor_test.go (about) 1 package tests 2 3 import ( 4 "context" 5 "crypto/rand" 6 "encoding/binary" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path" 12 "path/filepath" 13 "reflect" 14 "runtime" 15 "strings" 16 "testing" 17 "time" 18 19 k8sRand "k8s.io/apimachinery/pkg/util/rand" 20 21 "github.com/lyft/flytestdlib/config" 22 "github.com/lyft/flytestdlib/internal/utils" 23 "github.com/spf13/pflag" 24 25 "github.com/ghodss/yaml" 26 "github.com/stretchr/testify/assert" 27 ) 28 29 type accessorCreatorFn func(registry config.Section, configPath string) config.Accessor 30 31 func getRandInt() uint64 { 32 c := 10 33 b := make([]byte, c) 34 _, err := rand.Read(b) 35 if err != nil { 36 return 0 37 } 38 39 return binary.BigEndian.Uint64(b) 40 } 41 42 func tempFileName(pattern string) string { 43 // TODO: Remove this hack after we use Go1.11 everywhere: 44 // https://github.com/golang/go/commit/191efbc419d7e5dec842c20841f6f716da4b561d 45 46 var prefix, suffix string 47 if pos := strings.LastIndex(pattern, "*"); pos != -1 { 48 prefix, suffix = pattern[:pos], pattern[pos+1:] 49 } else { 50 prefix = pattern 51 } 52 53 return filepath.Join(os.TempDir(), prefix+k8sRand.String(6)+suffix) 54 } 55 56 func populateConfigData(configPath string) (TestConfig, error) { 57 expected := TestConfig{ 58 MyComponentConfig: MyComponentConfig{ 59 StringValue: fmt.Sprintf("Hello World %v", getRandInt()), 60 }, 61 OtherComponentConfig: OtherComponentConfig{ 62 StringValue: fmt.Sprintf("Hello World %v", getRandInt()), 63 URLValue: config.URL{URL: utils.MustParseURL("http://something.com")}, 64 DurationValue: config.Duration{Duration: time.Second * 20}, 65 }, 66 } 67 68 raw, err := yaml.Marshal(expected) 69 if err != nil { 70 return TestConfig{}, err 71 } 72 73 return expected, ioutil.WriteFile(configPath, raw, os.ModePerm) 74 } 75 76 func TestGetEmptySection(t *testing.T) { 77 t.Run("empty", func(t *testing.T) { 78 r := config.GetSection("Empty") 79 assert.Nil(t, r) 80 }) 81 } 82 83 type ComplexType struct { 84 IntValue int `json:"int-val"` 85 } 86 87 type ComplexTypeArray []ComplexType 88 89 type ConfigWithLists struct { 90 ListOfStuff []ComplexType `json:"list"` 91 StringValue string `json:"string-val"` 92 } 93 94 type ConfigWithMaps struct { 95 MapOfStuff map[string]ComplexType `json:"m"` 96 MapWithoutJSON map[string]ComplexType 97 } 98 99 type ConfigWithJSONTypes struct { 100 Duration config.Duration `json:"duration"` 101 } 102 103 func TestAccessor_InitializePflags(t *testing.T) { 104 for _, provider := range providers { 105 t.Run(fmt.Sprintf("[%v] Unused flag", provider(config.Options{}).ID()), func(t *testing.T) { 106 reg := config.NewRootSection() 107 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 108 assert.NoError(t, err) 109 v := provider(config.Options{ 110 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 111 RootSection: reg, 112 }) 113 114 set := pflag.NewFlagSet("test", pflag.ContinueOnError) 115 set.String("flag1", "123", "") 116 v.InitializePflags(set) 117 assert.NoError(t, v.UpdateConfig(context.TODO())) 118 }) 119 120 t.Run(fmt.Sprintf("[%v] Override string value", provider(config.Options{}).ID()), func(t *testing.T) { 121 reg := config.NewRootSection() 122 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 123 assert.NoError(t, err) 124 125 v := provider(config.Options{ 126 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 127 RootSection: reg, 128 }) 129 130 set := pflag.NewFlagSet("test", pflag.ContinueOnError) 131 v.InitializePflags(set) 132 key := "MY_COMPONENT.STR2" 133 assert.NoError(t, os.Setenv(key, "123")) 134 defer func() { assert.NoError(t, os.Unsetenv(key)) }() 135 assert.NoError(t, v.UpdateConfig(context.TODO())) 136 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 137 assert.Equal(t, "123", r.StringValue2) 138 }) 139 140 t.Run(fmt.Sprintf("[%v] Parse from config file", provider(config.Options{}).ID()), func(t *testing.T) { 141 reg := config.NewRootSection() 142 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 143 assert.NoError(t, err) 144 145 _, err = reg.RegisterSection(OtherComponentSectionKey, &OtherComponentConfig{}) 146 assert.NoError(t, err) 147 148 v := provider(config.Options{ 149 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 150 RootSection: reg, 151 }) 152 153 set := pflag.NewFlagSet("test", pflag.ExitOnError) 154 v.InitializePflags(set) 155 assert.NoError(t, v.UpdateConfig(context.TODO())) 156 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 157 assert.Equal(t, "Hello World", r.StringValue) 158 otherC := reg.GetSection(OtherComponentSectionKey).GetConfig().(*OtherComponentConfig) 159 assert.Equal(t, 4, otherC.IntValue) 160 assert.Equal(t, []string{"default value"}, otherC.StringArrayWithDefaults) 161 }) 162 163 t.Run(fmt.Sprintf("[%v] Sub-sections", provider(config.Options{}).ID()), func(t *testing.T) { 164 reg := config.NewRootSection() 165 sec, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 166 assert.NoError(t, err) 167 168 _, err = sec.RegisterSection("nested", &OtherComponentConfig{}) 169 assert.NoError(t, err) 170 171 v := provider(config.Options{ 172 SearchPaths: []string{filepath.Join("testdata", "nested_config.yaml")}, 173 RootSection: reg, 174 }) 175 176 set := pflag.NewFlagSet("test", pflag.ExitOnError) 177 v.InitializePflags(set) 178 assert.NoError(t, set.Parse([]string{"--my-component.nested.int-val=3"})) 179 assert.True(t, set.Parsed()) 180 181 flagValue, err := set.GetInt("my-component.nested.int-val") 182 assert.NoError(t, err) 183 assert.Equal(t, 3, flagValue) 184 185 assert.NoError(t, v.UpdateConfig(context.TODO())) 186 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 187 assert.Equal(t, "Hello World", r.StringValue) 188 189 nested := sec.GetSection("nested").GetConfig().(*OtherComponentConfig) 190 assert.Equal(t, 3, nested.IntValue) 191 }) 192 } 193 } 194 195 func TestStrictAccessor(t *testing.T) { 196 for _, provider := range providers { 197 t.Run(fmt.Sprintf("[%v] Bad config", provider(config.Options{}).ID()), func(t *testing.T) { 198 reg := config.NewRootSection() 199 v := provider(config.Options{ 200 StrictMode: true, 201 SearchPaths: []string{filepath.Join("testdata", "bad_config.yaml")}, 202 RootSection: reg, 203 }) 204 205 set := pflag.NewFlagSet("test", pflag.ExitOnError) 206 v.InitializePflags(set) 207 assert.Error(t, v.UpdateConfig(context.TODO())) 208 }) 209 210 t.Run(fmt.Sprintf("[%v] Set through env", provider(config.Options{}).ID()), func(t *testing.T) { 211 reg := config.NewRootSection() 212 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 213 assert.NoError(t, err) 214 215 _, err = reg.RegisterSection(OtherComponentSectionKey, &OtherComponentConfig{}) 216 assert.NoError(t, err) 217 218 v := provider(config.Options{ 219 StrictMode: true, 220 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 221 RootSection: reg, 222 }) 223 224 set := pflag.NewFlagSet("other-component.string-value", pflag.ExitOnError) 225 v.InitializePflags(set) 226 227 key := "OTHER_COMPONENT.STRING_VALUE" 228 assert.NoError(t, os.Setenv(key, "set from env")) 229 defer func() { assert.NoError(t, os.Unsetenv(key)) }() 230 assert.NoError(t, v.UpdateConfig(context.TODO())) 231 }) 232 } 233 } 234 235 func TestAccessor_UpdateConfig(t *testing.T) { 236 for _, provider := range providers { 237 t.Run(fmt.Sprintf("[%v] Static File", provider(config.Options{}).ID()), func(t *testing.T) { 238 reg := config.NewRootSection() 239 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 240 assert.NoError(t, err) 241 242 v := provider(config.Options{ 243 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 244 RootSection: reg, 245 }) 246 247 assert.NoError(t, v.UpdateConfig(context.TODO())) 248 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 249 assert.Equal(t, "Hello World", r.StringValue) 250 }) 251 252 t.Run(fmt.Sprintf("[%v] Nested", provider(config.Options{}).ID()), func(t *testing.T) { 253 root := config.NewRootSection() 254 section, err := root.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 255 assert.NoError(t, err) 256 257 _, err = section.RegisterSection("nested", &OtherComponentConfig{}) 258 assert.NoError(t, err) 259 260 v := provider(config.Options{ 261 SearchPaths: []string{filepath.Join("testdata", "nested_config.yaml")}, 262 RootSection: root, 263 }) 264 265 assert.NoError(t, v.UpdateConfig(context.TODO())) 266 r := root.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 267 assert.Equal(t, "Hello World", r.StringValue) 268 269 nested := section.GetSection("nested").GetConfig().(*OtherComponentConfig) 270 assert.Equal(t, "Hey there!", nested.StringValue) 271 }) 272 273 t.Run(fmt.Sprintf("[%v] Array Configs", provider(config.Options{}).ID()), func(t *testing.T) { 274 root := config.NewRootSection() 275 section, err := root.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 276 assert.NoError(t, err) 277 278 _, err = section.RegisterSection("nested", &ComplexTypeArray{}) 279 assert.NoError(t, err) 280 281 _, err = root.RegisterSection("array-config", &ComplexTypeArray{}) 282 assert.NoError(t, err) 283 284 v := provider(config.Options{ 285 SearchPaths: []string{filepath.Join("testdata", "array_configs.yaml")}, 286 RootSection: root, 287 }) 288 289 assert.NoError(t, v.UpdateConfig(context.TODO())) 290 r := root.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 291 assert.Equal(t, "Hello World", r.StringValue) 292 293 nested := section.GetSection("nested").GetConfig().(*ComplexTypeArray) 294 assert.Len(t, *nested, 1) 295 assert.Equal(t, 1, (*nested)[0].IntValue) 296 297 topLevel := root.GetSection("array-config").GetConfig().(*ComplexTypeArray) 298 assert.Len(t, *topLevel, 2) 299 assert.Equal(t, 4, (*topLevel)[1].IntValue) 300 }) 301 302 t.Run(fmt.Sprintf("[%v] Override in Env Var", provider(config.Options{}).ID()), func(t *testing.T) { 303 reg := config.NewRootSection() 304 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 305 assert.NoError(t, err) 306 307 v := provider(config.Options{ 308 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 309 RootSection: reg, 310 }) 311 key := strings.ToUpper("my-component.str") 312 assert.NoError(t, os.Setenv(key, "Set From Env")) 313 defer func() { assert.NoError(t, os.Unsetenv(key)) }() 314 assert.NoError(t, v.UpdateConfig(context.TODO())) 315 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 316 assert.Equal(t, "Set From Env", r.StringValue) 317 }) 318 319 t.Run(fmt.Sprintf("[%v] Override in Env Var no config file", provider(config.Options{}).ID()), func(t *testing.T) { 320 reg := config.NewRootSection() 321 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 322 assert.NoError(t, err) 323 324 v := provider(config.Options{RootSection: reg}) 325 key := strings.ToUpper("my-component.str3") 326 assert.NoError(t, os.Setenv(key, "Set From Env")) 327 defer func() { assert.NoError(t, os.Unsetenv(key)) }() 328 assert.NoError(t, v.UpdateConfig(context.TODO())) 329 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 330 assert.Equal(t, "Set From Env", r.StringValue3) 331 }) 332 333 t.Run(fmt.Sprintf("[%v] Change handler", provider(config.Options{}).ID()), func(t *testing.T) { 334 configFile := tempFileName("config-*.yaml") 335 defer func() { assert.NoError(t, os.Remove(configFile)) }() 336 cfg, err := populateConfigData(configFile) 337 assert.NoError(t, err) 338 339 reg := config.NewRootSection() 340 called := false 341 _, err = reg.RegisterSectionWithUpdates(MyComponentSectionKey, &cfg.MyComponentConfig, 342 func(ctx context.Context, newValue config.Config) { 343 called = true 344 }) 345 assert.NoError(t, err) 346 347 opts := config.Options{ 348 SearchPaths: []string{configFile}, 349 RootSection: reg, 350 } 351 v := provider(opts) 352 err = v.UpdateConfig(context.TODO()) 353 assert.NoError(t, err) 354 355 assert.True(t, called) 356 }) 357 358 t.Run(fmt.Sprintf("[%v] Change handler on change", provider(config.Options{}).ID()), func(t *testing.T) { 359 configFile := tempFileName("config-*.yaml") 360 defer func() { assert.NoError(t, os.Remove(configFile)) }() 361 _, err := populateConfigData(configFile) 362 assert.NoError(t, err) 363 364 reg := config.NewRootSection() 365 _, err = reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 366 assert.NoError(t, err) 367 368 opts := config.Options{ 369 SearchPaths: []string{configFile}, 370 RootSection: reg, 371 } 372 v := provider(opts) 373 err = v.UpdateConfig(context.TODO()) 374 assert.NoError(t, err) 375 376 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 377 firstValue := r.StringValue 378 379 _, err = populateConfigData(configFile) 380 assert.NoError(t, err) 381 382 // Wait enough for the file change notification to propagate. 383 time.Sleep(5 * time.Second) 384 385 r = reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 386 secondValue := r.StringValue 387 assert.NotEqual(t, firstValue, secondValue) 388 }) 389 390 t.Run(fmt.Sprintf("[%v] Change handler k8s configmaps", provider(config.Options{}).ID()), func(t *testing.T) { 391 reg := config.NewRootSection() 392 section, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{}) 393 assert.NoError(t, err) 394 395 var firstValue string 396 397 // 1. Create Dir structure 398 watchDir, configFile, cleanup := newSymlinkedConfigFile(t) 399 defer cleanup() 400 401 // 2. Start accessor with the symlink as config location 402 opts := config.Options{ 403 SearchPaths: []string{configFile}, 404 RootSection: reg, 405 } 406 v := provider(opts) 407 err = v.UpdateConfig(context.TODO()) 408 assert.NoError(t, err) 409 410 r := section.GetConfig().(*MyComponentConfig) 411 firstValue = r.StringValue 412 t.Logf("First value: %v", firstValue) 413 414 // 3. Now update /data symlink to point to data2 415 dataDir2 := path.Join(watchDir, "data2") 416 err = os.Mkdir(dataDir2, os.ModePerm) 417 assert.NoError(t, err) 418 419 configFile2 := path.Join(dataDir2, "config.yaml") 420 newData, err := populateConfigData(configFile2) 421 assert.NoError(t, err) 422 t.Logf("New value written to file: %v", newData.MyComponentConfig.StringValue) 423 424 // change the symlink using the `ln -sfn` command 425 err = changeSymLink(dataDir2, path.Join(watchDir, "data")) 426 assert.NoError(t, err) 427 428 t.Logf("New config Location: %v", configFile2) 429 430 time.Sleep(5 * time.Second) 431 432 r = section.GetConfig().(*MyComponentConfig) 433 secondValue := r.StringValue 434 // Make sure values have changed 435 assert.NotEqual(t, firstValue, secondValue) 436 }) 437 438 t.Run(fmt.Sprintf("[%v] Default variables", provider(config.Options{}).ID()), func(t *testing.T) { 439 reg := config.NewRootSection() 440 _, err := reg.RegisterSection(MyComponentSectionKey, &MyComponentConfig{ 441 StringValue: "default value 1", 442 StringValue2: "default value 2", 443 }) 444 assert.NoError(t, err) 445 446 v := provider(config.Options{ 447 SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, 448 RootSection: reg, 449 }) 450 key := strings.ToUpper("my-component.str") 451 assert.NoError(t, os.Setenv(key, "Set From Env")) 452 defer func() { assert.NoError(t, os.Unsetenv(key)) }() 453 assert.NoError(t, v.UpdateConfig(context.TODO())) 454 r := reg.GetSection(MyComponentSectionKey).GetConfig().(*MyComponentConfig) 455 assert.Equal(t, "Set From Env", r.StringValue) 456 assert.Equal(t, "default value 2", r.StringValue2) 457 }) 458 } 459 } 460 461 func changeSymLink(targetPath, symLink string) error { 462 tmpLink := tempFileName("temp-sym-link-*") 463 if runtime.GOOS == "windows" { 464 // #nosec G204 465 err := exec.Command("mklink", filepath.Clean(tmpLink), filepath.Clean(targetPath)).Run() 466 if err != nil { 467 return err 468 } 469 470 // #nosec G204 471 err = exec.Command("copy", "/l", "/y", filepath.Clean(tmpLink), filepath.Clean(symLink)).Run() 472 if err != nil { 473 return err 474 } 475 476 // #nosec G204 477 return exec.Command("del", filepath.Clean(tmpLink)).Run() 478 } 479 480 //// ln -sfn is not an atomic operation. Under the hood, it first calls the system unlink then symlink calls. During 481 //// that, there will be a brief moment when there is no symlink at all. 482 // #nosec G204 483 return exec.Command("ln", "-sfn", filepath.Clean(targetPath), filepath.Clean(symLink)).Run() 484 } 485 486 // 1. Create Dir structure: 487 // |_ data1 488 // |_ config.yaml 489 // |_ data (symlink for data1) 490 // |_ config.yaml (symlink for data/config.yaml -recursively a symlink of data1/config.yaml) 491 func newSymlinkedConfigFile(t *testing.T) (watchDir, configFile string, cleanup func()) { 492 watchDir, err := ioutil.TempDir("", "config-test-") 493 assert.NoError(t, err) 494 495 dataDir1 := path.Join(watchDir, "data1") 496 err = os.Mkdir(dataDir1, os.ModePerm) 497 assert.NoError(t, err) 498 499 realConfigFile := path.Join(dataDir1, "config.yaml") 500 t.Logf("Real config file location: %s\n", realConfigFile) 501 _, err = populateConfigData(realConfigFile) 502 assert.NoError(t, err) 503 504 cleanup = func() { 505 t.Logf("Removing watchDir [%v]", watchDir) 506 assert.NoError(t, os.RemoveAll(watchDir)) 507 } 508 509 // now, symlink the tm `data1` dir to `data` in the baseDir 510 assert.NoError(t, os.Symlink(dataDir1, path.Join(watchDir, "data"))) 511 512 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 513 configFile = path.Join(watchDir, "config.yaml") 514 assert.NoError(t, os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile)) 515 516 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 517 return watchDir, configFile, cleanup 518 } 519 520 func testTypes(accessor accessorCreatorFn) func(t *testing.T) { 521 return func(t *testing.T) { 522 t.Run("ArrayConfigType", func(t *testing.T) { 523 expected := ComplexTypeArray{ 524 {IntValue: 1}, 525 {IntValue: 4}, 526 } 527 528 runEqualTest(t, accessor, &expected, &ComplexTypeArray{}) 529 }) 530 531 t.Run("Lists", func(t *testing.T) { 532 expected := ConfigWithLists{ 533 ListOfStuff: []ComplexType{ 534 {IntValue: 1}, 535 {IntValue: 4}, 536 }, 537 } 538 539 runEqualTest(t, accessor, &expected, &ConfigWithLists{}) 540 }) 541 542 t.Run("Maps", func(t *testing.T) { 543 expected := ConfigWithMaps{ 544 MapOfStuff: map[string]ComplexType{ 545 "item1": {IntValue: 1}, 546 "item2": {IntValue: 3}, 547 }, 548 MapWithoutJSON: map[string]ComplexType{ 549 "it-1": {IntValue: 5}, 550 }, 551 } 552 553 runEqualTest(t, accessor, &expected, &ConfigWithMaps{}) 554 }) 555 556 t.Run("JsonUnmarshalableTypes", func(t *testing.T) { 557 expected := ConfigWithJSONTypes{ 558 Duration: config.Duration{ 559 Duration: time.Second * 10, 560 }, 561 } 562 563 runEqualTest(t, accessor, &expected, &ConfigWithJSONTypes{}) 564 }) 565 } 566 } 567 568 func runEqualTest(t *testing.T, accessor accessorCreatorFn, expected interface{}, emptyType interface{}) { 569 assert.NotPanics(t, func() { 570 reflect.TypeOf(expected).Elem() 571 }, "expected must be a Pointer type. Instead, it was %v", reflect.TypeOf(expected)) 572 573 assert.Equal(t, reflect.TypeOf(expected), reflect.TypeOf(emptyType)) 574 575 rootSection := config.NewRootSection() 576 sectionKey := fmt.Sprintf("rand-key-%v", getRandInt()%2000) 577 _, err := rootSection.RegisterSection(sectionKey, emptyType) 578 assert.NoError(t, err) 579 580 m := map[string]interface{}{ 581 sectionKey: expected, 582 } 583 584 raw, err := yaml.Marshal(m) 585 assert.NoError(t, err) 586 f := tempFileName("test_type_*.yaml") 587 assert.NoError(t, err) 588 defer func() { assert.NoError(t, os.Remove(f)) }() 589 590 assert.NoError(t, ioutil.WriteFile(f, raw, os.ModePerm)) 591 t.Logf("Generated yaml: %v", string(raw)) 592 assert.NoError(t, accessor(rootSection, f).UpdateConfig(context.TODO())) 593 594 res := rootSection.GetSection(sectionKey).GetConfig() 595 t.Logf("Expected: %+v", expected) 596 t.Logf("Actual: %+v", res) 597 assert.True(t, reflect.DeepEqual(res, expected)) 598 } 599 600 func TestAccessor_Integration(t *testing.T) { 601 accessorsToTest := make([]accessorCreatorFn, 0, len(providers)) 602 for _, provider := range providers { 603 accessorsToTest = append(accessorsToTest, func(r config.Section, configPath string) config.Accessor { 604 return provider(config.Options{ 605 SearchPaths: []string{configPath}, 606 RootSection: r, 607 }) 608 }) 609 } 610 611 for _, accessor := range accessorsToTest { 612 t.Run(fmt.Sprintf(testNameFormatter, accessor(nil, "").ID(), "Types"), testTypes(accessor)) 613 } 614 }