github.com/gottingen/lung@v0.3.0/lung_test.go (about) 1 package lung 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "reflect" 13 "runtime" 14 "sort" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19 20 "github.com/fsnotify/fsnotify" 21 "github.com/gottingen/felix" 22 "github.com/gottingen/gekko/cast" 23 "github.com/mitchellh/mapstructure" 24 25 "github.com/gottingen/gekko/gflag" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 ) 29 30 var yamlExample = []byte(`Hacker: true 31 name: steve 32 hobbies: 33 - skateboarding 34 - snowboarding 35 - go 36 clothing: 37 jacket: leather 38 trousers: denim 39 pants: 40 size: large 41 age: 35 42 eyes : brown 43 beard: true 44 `) 45 46 var yamlExampleWithExtras = []byte(`Existing: true 47 Bogus: true 48 `) 49 50 type testUnmarshalExtra struct { 51 Existing bool 52 } 53 54 var tomlExample = []byte(` 55 title = "TOML Example" 56 57 [owner] 58 organization = "MongoDB" 59 Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 60 dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 61 62 var dotenvExample = []byte(` 63 TITLE_DOTENV="DotEnv Example" 64 TYPE_DOTENV=donut 65 NAME_DOTENV=Cake`) 66 67 var jsonExample = []byte(`{ 68 "id": "0001", 69 "type": "donut", 70 "name": "Cake", 71 "ppu": 0.55, 72 "batters": { 73 "batter": [ 74 { "type": "Regular" }, 75 { "type": "Chocolate" }, 76 { "type": "Blueberry" }, 77 { "type": "Devil's Food" } 78 ] 79 } 80 }`) 81 82 var propertiesExample = []byte(` 83 p_id: 0001 84 p_type: donut 85 p_name: Cake 86 p_ppu: 0.55 87 p_batters.batter.type: Regular 88 `) 89 90 var remoteExample = []byte(`{ 91 "id":"0002", 92 "type":"cronut", 93 "newkey":"remote" 94 }`) 95 96 func initConfigs() { 97 Reset() 98 var r io.Reader 99 SetConfigType("yaml") 100 r = bytes.NewReader(yamlExample) 101 unmarshalReader(r, l.config) 102 103 SetConfigType("json") 104 r = bytes.NewReader(jsonExample) 105 unmarshalReader(r, l.config) 106 107 SetConfigType("properties") 108 r = bytes.NewReader(propertiesExample) 109 unmarshalReader(r, l.config) 110 111 SetConfigType("toml") 112 r = bytes.NewReader(tomlExample) 113 unmarshalReader(r, l.config) 114 115 SetConfigType("env") 116 r = bytes.NewReader(dotenvExample) 117 unmarshalReader(r, l.config) 118 119 SetConfigType("json") 120 remote := bytes.NewReader(remoteExample) 121 unmarshalReader(remote, l.kvstore) 122 } 123 124 func initConfig(typ, config string) { 125 Reset() 126 SetConfigType(typ) 127 r := strings.NewReader(config) 128 129 if err := unmarshalReader(r, l.config); err != nil { 130 panic(err) 131 } 132 } 133 134 func initYAML() { 135 initConfig("yaml", string(yamlExample)) 136 } 137 138 func initJSON() { 139 Reset() 140 SetConfigType("json") 141 r := bytes.NewReader(jsonExample) 142 143 unmarshalReader(r, l.config) 144 } 145 146 func initProperties() { 147 Reset() 148 SetConfigType("properties") 149 r := bytes.NewReader(propertiesExample) 150 151 unmarshalReader(r, l.config) 152 } 153 154 func initTOML() { 155 Reset() 156 SetConfigType("toml") 157 r := bytes.NewReader(tomlExample) 158 159 unmarshalReader(r, l.config) 160 } 161 162 func initDotEnv() { 163 Reset() 164 SetConfigType("env") 165 r := bytes.NewReader(dotenvExample) 166 167 unmarshalReader(r, l.config) 168 } 169 170 // make directories for testing 171 func initDirs(t *testing.T) (string, string, func()) { 172 173 var ( 174 testDirs = []string{`a a`, `b`, `C_`} 175 config = `improbable` 176 ) 177 178 if runtime.GOOS != "windows" { 179 testDirs = append(testDirs, `d\d`) 180 } 181 182 root, err := ioutil.TempDir("", "") 183 require.NoError(t, err, "Failed to create temporary directory") 184 185 cleanup := true 186 defer func() { 187 if cleanup { 188 os.Chdir("..") 189 os.RemoveAll(root) 190 } 191 }() 192 193 assert.Nil(t, err) 194 195 err = os.Chdir(root) 196 require.Nil(t, err) 197 198 for _, dir := range testDirs { 199 err = os.Mkdir(dir, 0750) 200 assert.Nil(t, err) 201 202 err = ioutil.WriteFile( 203 path.Join(dir, config+".toml"), 204 []byte("key = \"value is "+dir+"\"\n"), 205 0640) 206 assert.Nil(t, err) 207 } 208 209 cleanup = false 210 return root, config, func() { 211 os.Chdir("..") 212 os.RemoveAll(root) 213 } 214 } 215 216 // stubs for PFlag Values 217 type stringValue string 218 219 func newStringValue(val string, p *string) *stringValue { 220 *p = val 221 return (*stringValue)(p) 222 } 223 224 func (s *stringValue) Set(val string) error { 225 *s = stringValue(val) 226 return nil 227 } 228 229 func (s *stringValue) Type() string { 230 return "string" 231 } 232 233 func (s *stringValue) String() string { 234 return string(*s) 235 } 236 237 func TestBasics(t *testing.T) { 238 SetConfigFile("/tmp/config.yaml") 239 filename, err := l.getConfigFile() 240 assert.Equal(t, "/tmp/config.yaml", filename) 241 assert.NoError(t, err) 242 } 243 244 func TestDefault(t *testing.T) { 245 SetDefault("age", 45) 246 assert.Equal(t, 45, Get("age")) 247 248 SetDefault("clothing.jacket", "slacks") 249 assert.Equal(t, "slacks", Get("clothing.jacket")) 250 251 SetConfigType("yaml") 252 err := ReadConfig(bytes.NewBuffer(yamlExample)) 253 254 assert.NoError(t, err) 255 assert.Equal(t, "leather", Get("clothing.jacket")) 256 } 257 258 func TestUnmarshaling(t *testing.T) { 259 SetConfigType("yaml") 260 r := bytes.NewReader(yamlExample) 261 262 unmarshalReader(r, l.config) 263 assert.True(t, InConfig("name")) 264 assert.False(t, InConfig("state")) 265 assert.Equal(t, "steve", Get("name")) 266 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) 267 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) 268 assert.Equal(t, 35, Get("age")) 269 } 270 271 func TestUnmarshalExact(t *testing.T) { 272 vip := New() 273 target := &testUnmarshalExtra{} 274 vip.SetConfigType("yaml") 275 r := bytes.NewReader(yamlExampleWithExtras) 276 vip.ReadConfig(r) 277 err := vip.UnmarshalExact(target) 278 if err == nil { 279 t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields") 280 } 281 } 282 283 func TestOverrides(t *testing.T) { 284 Set("age", 40) 285 assert.Equal(t, 40, Get("age")) 286 } 287 288 func TestDefaultPost(t *testing.T) { 289 assert.NotEqual(t, "NYC", Get("state")) 290 SetDefault("state", "NYC") 291 assert.Equal(t, "NYC", Get("state")) 292 } 293 294 func TestAliases(t *testing.T) { 295 RegisterAlias("years", "age") 296 assert.Equal(t, 40, Get("years")) 297 Set("years", 45) 298 assert.Equal(t, 45, Get("age")) 299 } 300 301 func TestAliasInConfigFile(t *testing.T) { 302 // the config file specifies "beard". If we make this an alias for 303 // "hasbeard", we still want the old config file to work with beard. 304 RegisterAlias("beard", "hasbeard") 305 assert.Equal(t, true, Get("hasbeard")) 306 Set("hasbeard", false) 307 assert.Equal(t, false, Get("beard")) 308 } 309 310 func TestYML(t *testing.T) { 311 initYAML() 312 assert.Equal(t, "steve", Get("name")) 313 } 314 315 func TestJSON(t *testing.T) { 316 initJSON() 317 assert.Equal(t, "0001", Get("id")) 318 } 319 320 func TestProperties(t *testing.T) { 321 initProperties() 322 assert.Equal(t, "0001", Get("p_id")) 323 } 324 325 func TestTOML(t *testing.T) { 326 initTOML() 327 assert.Equal(t, "TOML Example", Get("title")) 328 } 329 330 func TestDotEnv(t *testing.T) { 331 initDotEnv() 332 assert.Equal(t, "DotEnv Example", Get("title_dotenv")) 333 } 334 335 func TestRemotePrecedence(t *testing.T) { 336 initJSON() 337 338 remote := bytes.NewReader(remoteExample) 339 assert.Equal(t, "0001", Get("id")) 340 unmarshalReader(remote, l.kvstore) 341 assert.Equal(t, "0001", Get("id")) 342 assert.NotEqual(t, "cronut", Get("type")) 343 assert.Equal(t, "remote", Get("newkey")) 344 Set("newkey", "newvalue") 345 assert.NotEqual(t, "remote", Get("newkey")) 346 assert.Equal(t, "newvalue", Get("newkey")) 347 Set("newkey", "remote") 348 } 349 350 func TestEnv(t *testing.T) { 351 initJSON() 352 353 BindEnv("id") 354 BindEnv("f", "FOOD") 355 356 os.Setenv("ID", "13") 357 os.Setenv("FOOD", "apple") 358 os.Setenv("NAME", "crunk") 359 360 assert.Equal(t, "13", Get("id")) 361 assert.Equal(t, "apple", Get("f")) 362 assert.Equal(t, "Cake", Get("name")) 363 364 AutomaticEnv() 365 366 assert.Equal(t, "crunk", Get("name")) 367 368 } 369 370 func TestEmptyEnv(t *testing.T) { 371 initJSON() 372 373 BindEnv("type") // Empty environment variable 374 BindEnv("name") // Bound, but not set environment variable 375 376 os.Unsetenv("type") 377 os.Unsetenv("TYPE") 378 os.Unsetenv("name") 379 os.Unsetenv("NAME") 380 381 os.Setenv("TYPE", "") 382 383 assert.Equal(t, "donut", Get("type")) 384 assert.Equal(t, "Cake", Get("name")) 385 } 386 387 func TestEmptyEnv_Allowed(t *testing.T) { 388 initJSON() 389 390 AllowEmptyEnv(true) 391 392 BindEnv("type") // Empty environment variable 393 BindEnv("name") // Bound, but not set environment variable 394 395 os.Unsetenv("type") 396 os.Unsetenv("TYPE") 397 os.Unsetenv("name") 398 os.Unsetenv("NAME") 399 400 os.Setenv("TYPE", "") 401 402 assert.Equal(t, "", Get("type")) 403 assert.Equal(t, "Cake", Get("name")) 404 } 405 406 func TestEnvPrefix(t *testing.T) { 407 initJSON() 408 409 SetEnvPrefix("foo") // will be uppercased automatically 410 BindEnv("id") 411 BindEnv("f", "FOOD") // not using prefix 412 413 os.Setenv("FOO_ID", "13") 414 os.Setenv("FOOD", "apple") 415 os.Setenv("FOO_NAME", "crunk") 416 417 assert.Equal(t, "13", Get("id")) 418 assert.Equal(t, "apple", Get("f")) 419 assert.Equal(t, "Cake", Get("name")) 420 421 AutomaticEnv() 422 423 assert.Equal(t, "crunk", Get("name")) 424 } 425 426 func TestAutoEnv(t *testing.T) { 427 Reset() 428 429 AutomaticEnv() 430 os.Setenv("FOO_BAR", "13") 431 assert.Equal(t, "13", Get("foo_bar")) 432 } 433 434 func TestAutoEnvWithPrefix(t *testing.T) { 435 Reset() 436 437 AutomaticEnv() 438 SetEnvPrefix("Baz") 439 os.Setenv("BAZ_BAR", "13") 440 assert.Equal(t, "13", Get("bar")) 441 } 442 443 func TestSetEnvKeyReplacer(t *testing.T) { 444 Reset() 445 446 AutomaticEnv() 447 os.Setenv("REFRESH_INTERVAL", "30s") 448 449 replacer := strings.NewReplacer("-", "_") 450 SetEnvKeyReplacer(replacer) 451 452 assert.Equal(t, "30s", Get("refresh-interval")) 453 } 454 455 func TestAllKeys(t *testing.T) { 456 initConfigs() 457 458 ks := sort.StringSlice{ 459 "title", 460 "newkey", 461 "owner.organization", 462 "owner.dob", 463 "owner.bio", 464 "name", 465 "beard", 466 "ppu", 467 "batters.batter", 468 "hobbies", 469 "clothing.jacket", 470 "clothing.trousers", 471 "clothing.pants.size", 472 "age", 473 "hacker", 474 "id", 475 "type", 476 "eyes", 477 "p_id", 478 "p_ppu", 479 "p_batters.batter.type", 480 "p_type", 481 "p_name", 482 "title_dotenv", 483 "type_dotenv", 484 "name_dotenv", 485 } 486 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 487 all := map[string]interface{}{ 488 "owner": map[string]interface{}{ 489 "organization": "MongoDB", 490 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 491 "dob": dob, 492 }, 493 "title": "TOML Example", 494 "ppu": 0.55, 495 "eyes": "brown", 496 "clothing": map[string]interface{}{ 497 "trousers": "denim", 498 "jacket": "leather", 499 "pants": map[string]interface{}{"size": "large"}, 500 }, 501 "id": "0001", 502 "batters": map[string]interface{}{ 503 "batter": []interface{}{ 504 map[string]interface{}{"type": "Regular"}, 505 map[string]interface{}{"type": "Chocolate"}, 506 map[string]interface{}{"type": "Blueberry"}, 507 map[string]interface{}{"type": "Devil's Food"}, 508 }, 509 }, 510 "hacker": true, 511 "beard": true, 512 "hobbies": []interface{}{ 513 "skateboarding", 514 "snowboarding", 515 "go", 516 }, 517 "age": 35, 518 "type": "donut", 519 "newkey": "remote", 520 "name": "Cake", 521 "p_id": "0001", 522 "p_ppu": "0.55", 523 "p_name": "Cake", 524 "p_batters": map[string]interface{}{ 525 "batter": map[string]interface{}{"type": "Regular"}, 526 }, 527 "p_type": "donut", 528 "title_dotenv": "DotEnv Example", 529 "type_dotenv": "donut", 530 "name_dotenv": "Cake", 531 } 532 533 allkeys := sort.StringSlice(AllKeys()) 534 allkeys.Sort() 535 ks.Sort() 536 537 assert.Equal(t, ks, allkeys) 538 assert.Equal(t, all, AllSettings()) 539 } 540 541 func TestAllKeysWithEnv(t *testing.T) { 542 v := New() 543 544 // bind and define environment variables (including a nested one) 545 v.BindEnv("id") 546 v.BindEnv("foo.bar") 547 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 548 os.Setenv("ID", "13") 549 os.Setenv("FOO_BAR", "baz") 550 551 expectedKeys := sort.StringSlice{"id", "foo.bar"} 552 expectedKeys.Sort() 553 keys := sort.StringSlice(v.AllKeys()) 554 keys.Sort() 555 assert.Equal(t, expectedKeys, keys) 556 } 557 558 func TestAliasesOfAliases(t *testing.T) { 559 Set("Title", "Checking Case") 560 RegisterAlias("Foo", "Bar") 561 RegisterAlias("Bar", "Title") 562 assert.Equal(t, "Checking Case", Get("FOO")) 563 } 564 565 func TestRecursiveAliases(t *testing.T) { 566 RegisterAlias("Baz", "Roo") 567 RegisterAlias("Roo", "baz") 568 } 569 570 func TestUnmarshal(t *testing.T) { 571 SetDefault("port", 1313) 572 Set("name", "Steve") 573 Set("duration", "1s1ms") 574 Set("modes", []int{1, 2, 3}) 575 576 type config struct { 577 Port int 578 Name string 579 Duration time.Duration 580 Modes []int 581 } 582 583 var C config 584 585 err := Unmarshal(&C) 586 if err != nil { 587 t.Fatalf("unable to decode into struct, %v", err) 588 } 589 590 assert.Equal( 591 t, 592 &config{ 593 Name: "Steve", 594 Port: 1313, 595 Duration: time.Second + time.Millisecond, 596 Modes: []int{1, 2, 3}, 597 }, 598 &C, 599 ) 600 601 Set("port", 1234) 602 err = Unmarshal(&C) 603 if err != nil { 604 t.Fatalf("unable to decode into struct, %v", err) 605 } 606 607 assert.Equal( 608 t, 609 &config{ 610 Name: "Steve", 611 Port: 1234, 612 Duration: time.Second + time.Millisecond, 613 Modes: []int{1, 2, 3}, 614 }, 615 &C, 616 ) 617 } 618 619 func TestUnmarshalWithDecoderOptions(t *testing.T) { 620 Set("credentials", "{\"foo\":\"bar\"}") 621 622 opt := DecodeHook(mapstructure.ComposeDecodeHookFunc( 623 mapstructure.StringToTimeDurationHookFunc(), 624 mapstructure.StringToSliceHookFunc(","), 625 // Custom Decode Hook Function 626 func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { 627 if rf != reflect.String || rt != reflect.Map { 628 return data, nil 629 } 630 m := map[string]string{} 631 raw := data.(string) 632 if raw == "" { 633 return m, nil 634 } 635 return m, json.Unmarshal([]byte(raw), &m) 636 }, 637 )) 638 639 type config struct { 640 Credentials map[string]string 641 } 642 643 var C config 644 645 err := Unmarshal(&C, opt) 646 if err != nil { 647 t.Fatalf("unable to decode into struct, %v", err) 648 } 649 650 assert.Equal(t, &config{ 651 Credentials: map[string]string{"foo": "bar"}, 652 }, &C) 653 } 654 655 func TestBindPFlags(t *testing.T) { 656 v := New() // create independent Lung object 657 flagSet := gflag.NewFlagSet("test", gflag.ContinueOnError) 658 659 var testValues = map[string]*string{ 660 "host": nil, 661 "port": nil, 662 "endpoint": nil, 663 } 664 665 var mutatedTestValues = map[string]string{ 666 "host": "localhost", 667 "port": "6060", 668 "endpoint": "/public", 669 } 670 671 for name := range testValues { 672 testValues[name] = flagSet.String(name, "", "test") 673 } 674 675 err := v.BindPFlags(flagSet) 676 if err != nil { 677 t.Fatalf("error binding flag set, %v", err) 678 } 679 680 flagSet.VisitAll(func(flag *gflag.Flag) { 681 flag.Value.Set(mutatedTestValues[flag.Name]) 682 flag.Changed = true 683 }) 684 685 for name, expected := range mutatedTestValues { 686 assert.Equal(t, expected, v.Get(name)) 687 } 688 689 } 690 691 func TestBindPFlagsStringSlice(t *testing.T) { 692 tests := []struct { 693 Expected []string 694 Value string 695 }{ 696 {nil, ""}, 697 {[]string{"jeden"}, "jeden"}, 698 {[]string{"dwa", "trzy"}, "dwa,trzy"}, 699 {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}, 700 } 701 702 v := New() // create independent Lung object 703 defaultVal := []string{"default"} 704 v.SetDefault("stringslice", defaultVal) 705 706 for _, testValue := range tests { 707 flagSet := gflag.NewFlagSet("test", gflag.ContinueOnError) 708 flagSet.StringSlice("stringslice", testValue.Expected, "test") 709 710 for _, changed := range []bool{true, false} { 711 flagSet.VisitAll(func(f *gflag.Flag) { 712 f.Value.Set(testValue.Value) 713 f.Changed = changed 714 }) 715 716 err := v.BindPFlags(flagSet) 717 if err != nil { 718 t.Fatalf("error binding flag set, %v", err) 719 } 720 721 type TestStr struct { 722 StringSlice []string 723 } 724 val := &TestStr{} 725 if err := v.Unmarshal(val); err != nil { 726 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 727 } 728 if changed { 729 assert.Equal(t, testValue.Expected, val.StringSlice) 730 } else { 731 assert.Equal(t, defaultVal, val.StringSlice) 732 } 733 } 734 } 735 } 736 737 func TestBindPFlagsIntSlice(t *testing.T) { 738 tests := []struct { 739 Expected []int 740 Value string 741 }{ 742 {nil, ""}, 743 {[]int{1}, "1"}, 744 {[]int{2, 3}, "2,3"}, 745 } 746 747 v := New() // create independent Lung object 748 defaultVal := []int{0} 749 v.SetDefault("intslice", defaultVal) 750 751 for _, testValue := range tests { 752 flagSet := gflag.NewFlagSet("test", gflag.ContinueOnError) 753 flagSet.IntSlice("intslice", testValue.Expected, "test") 754 755 for _, changed := range []bool{true, false} { 756 flagSet.VisitAll(func(f *gflag.Flag) { 757 f.Value.Set(testValue.Value) 758 f.Changed = changed 759 }) 760 761 err := v.BindPFlags(flagSet) 762 if err != nil { 763 t.Fatalf("error binding flag set, %v", err) 764 } 765 766 type TestInt struct { 767 IntSlice []int 768 } 769 val := &TestInt{} 770 if err := v.Unmarshal(val); err != nil { 771 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 772 } 773 if changed { 774 assert.Equal(t, testValue.Expected, val.IntSlice) 775 } else { 776 assert.Equal(t, defaultVal, val.IntSlice) 777 } 778 } 779 } 780 } 781 782 func TestBindPFlag(t *testing.T) { 783 var testString = "testing" 784 var testValue = newStringValue(testString, &testString) 785 786 flag := &gflag.Flag{ 787 Name: "testflag", 788 Value: testValue, 789 Changed: false, 790 } 791 792 BindPFlag("testvalue", flag) 793 794 assert.Equal(t, testString, Get("testvalue")) 795 796 flag.Value.Set("testing_mutate") 797 flag.Changed = true // hack for gflag usage 798 799 assert.Equal(t, "testing_mutate", Get("testvalue")) 800 801 } 802 803 func TestBoundCaseSensitivity(t *testing.T) { 804 assert.Equal(t, "brown", Get("eyes")) 805 806 BindEnv("eYEs", "TURTLE_EYES") 807 os.Setenv("TURTLE_EYES", "blue") 808 809 assert.Equal(t, "blue", Get("eyes")) 810 811 var testString = "green" 812 var testValue = newStringValue(testString, &testString) 813 814 flag := &gflag.Flag{ 815 Name: "eyeballs", 816 Value: testValue, 817 Changed: true, 818 } 819 820 BindPFlag("eYEs", flag) 821 assert.Equal(t, "green", Get("eyes")) 822 823 } 824 825 func TestSizeInBytes(t *testing.T) { 826 input := map[string]uint{ 827 "": 0, 828 "b": 0, 829 "12 bytes": 0, 830 "200000000000gb": 0, 831 "12 b": 12, 832 "43 MB": 43 * (1 << 20), 833 "10mb": 10 * (1 << 20), 834 "1gb": 1 << 30, 835 } 836 837 for str, expected := range input { 838 assert.Equal(t, expected, parseSizeInBytes(str), str) 839 } 840 } 841 842 func TestFindsNestedKeys(t *testing.T) { 843 initConfigs() 844 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 845 846 Set("super", map[string]interface{}{ 847 "deep": map[string]interface{}{ 848 "nested": "value", 849 }, 850 }) 851 852 expected := map[string]interface{}{ 853 "super": map[string]interface{}{ 854 "deep": map[string]interface{}{ 855 "nested": "value", 856 }, 857 }, 858 "super.deep": map[string]interface{}{ 859 "nested": "value", 860 }, 861 "super.deep.nested": "value", 862 "owner.organization": "MongoDB", 863 "batters.batter": []interface{}{ 864 map[string]interface{}{ 865 "type": "Regular", 866 }, 867 map[string]interface{}{ 868 "type": "Chocolate", 869 }, 870 map[string]interface{}{ 871 "type": "Blueberry", 872 }, 873 map[string]interface{}{ 874 "type": "Devil's Food", 875 }, 876 }, 877 "hobbies": []interface{}{ 878 "skateboarding", "snowboarding", "go", 879 }, 880 "TITLE_DOTENV": "DotEnv Example", 881 "TYPE_DOTENV": "donut", 882 "NAME_DOTENV": "Cake", 883 "title": "TOML Example", 884 "newkey": "remote", 885 "batters": map[string]interface{}{ 886 "batter": []interface{}{ 887 map[string]interface{}{ 888 "type": "Regular", 889 }, 890 map[string]interface{}{ 891 "type": "Chocolate", 892 }, map[string]interface{}{ 893 "type": "Blueberry", 894 }, map[string]interface{}{ 895 "type": "Devil's Food", 896 }, 897 }, 898 }, 899 "eyes": "brown", 900 "age": 35, 901 "owner": map[string]interface{}{ 902 "organization": "MongoDB", 903 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 904 "dob": dob, 905 }, 906 "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", 907 "type": "donut", 908 "id": "0001", 909 "name": "Cake", 910 "hacker": true, 911 "ppu": 0.55, 912 "clothing": map[string]interface{}{ 913 "jacket": "leather", 914 "trousers": "denim", 915 "pants": map[string]interface{}{ 916 "size": "large", 917 }, 918 }, 919 "clothing.jacket": "leather", 920 "clothing.pants.size": "large", 921 "clothing.trousers": "denim", 922 "owner.dob": dob, 923 "beard": true, 924 } 925 926 for key, expectedValue := range expected { 927 928 assert.Equal(t, expectedValue, l.Get(key)) 929 } 930 931 } 932 933 func TestReadBufConfig(t *testing.T) { 934 v := New() 935 v.SetConfigType("yaml") 936 v.ReadConfig(bytes.NewBuffer(yamlExample)) 937 t.Log(v.AllKeys()) 938 939 assert.True(t, v.InConfig("name")) 940 assert.False(t, v.InConfig("state")) 941 assert.Equal(t, "steve", v.Get("name")) 942 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 943 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) 944 assert.Equal(t, 35, v.Get("age")) 945 } 946 947 func TestIsSet(t *testing.T) { 948 v := New() 949 v.SetConfigType("yaml") 950 v.ReadConfig(bytes.NewBuffer(yamlExample)) 951 assert.True(t, v.IsSet("clothing.jacket")) 952 assert.False(t, v.IsSet("clothing.jackets")) 953 assert.False(t, v.IsSet("helloworld")) 954 v.Set("helloworld", "fubar") 955 assert.True(t, v.IsSet("helloworld")) 956 } 957 958 func TestDirsSearch(t *testing.T) { 959 960 root, config, cleanup := initDirs(t) 961 defer cleanup() 962 963 v := New() 964 v.SetConfigName(config) 965 v.SetDefault(`key`, `default`) 966 967 entries, err := ioutil.ReadDir(root) 968 assert.Nil(t, err) 969 for _, e := range entries { 970 if e.IsDir() { 971 v.AddConfigPath(e.Name()) 972 } 973 } 974 975 err = v.ReadInConfig() 976 assert.Nil(t, err) 977 978 assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`)) 979 } 980 981 func TestWrongDirsSearchNotFound(t *testing.T) { 982 983 _, config, cleanup := initDirs(t) 984 defer cleanup() 985 986 v := New() 987 v.SetConfigName(config) 988 v.SetDefault(`key`, `default`) 989 990 v.AddConfigPath(`whattayoutalkingbout`) 991 v.AddConfigPath(`thispathaintthere`) 992 993 err := v.ReadInConfig() 994 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 995 996 // Even though config did not load and the error might have 997 // been ignored by the client, the default still loads 998 assert.Equal(t, `default`, v.GetString(`key`)) 999 } 1000 1001 func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { 1002 1003 _, config, cleanup := initDirs(t) 1004 defer cleanup() 1005 1006 v := New() 1007 v.SetConfigName(config) 1008 v.SetDefault(`key`, `default`) 1009 1010 v.AddConfigPath(`whattayoutalkingbout`) 1011 v.AddConfigPath(`thispathaintthere`) 1012 1013 err := v.MergeInConfig() 1014 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1015 1016 // Even though config did not load and the error might have 1017 // been ignored by the client, the default still loads 1018 assert.Equal(t, `default`, v.GetString(`key`)) 1019 } 1020 1021 func TestSub(t *testing.T) { 1022 v := New() 1023 v.SetConfigType("yaml") 1024 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1025 1026 subv := v.Sub("clothing") 1027 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) 1028 1029 subv = v.Sub("clothing.pants") 1030 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) 1031 1032 subv = v.Sub("clothing.pants.size") 1033 assert.Equal(t, (*Lung)(nil), subv) 1034 1035 subv = v.Sub("missing.key") 1036 assert.Equal(t, (*Lung)(nil), subv) 1037 } 1038 1039 var jsonWriteExpected = []byte(`{ 1040 "batters": { 1041 "batter": [ 1042 { 1043 "type": "Regular" 1044 }, 1045 { 1046 "type": "Chocolate" 1047 }, 1048 { 1049 "type": "Blueberry" 1050 }, 1051 { 1052 "type": "Devil's Food" 1053 } 1054 ] 1055 }, 1056 "id": "0001", 1057 "name": "Cake", 1058 "ppu": 0.55, 1059 "type": "donut" 1060 }`) 1061 1062 func TestWriteConfigJson(t *testing.T) { 1063 v := New() 1064 fs := felix.NewMemVfs() 1065 v.SetFs(fs) 1066 v.SetConfigName("c") 1067 v.SetConfigType("json") 1068 err := v.ReadConfig(bytes.NewBuffer(jsonExample)) 1069 if err != nil { 1070 t.Fatal(err) 1071 } 1072 if err := v.WriteConfigAs("c.json"); err != nil { 1073 t.Fatal(err) 1074 } 1075 read, err := fs.ReadFile("c.json") 1076 if err != nil { 1077 t.Fatal(err) 1078 } 1079 assert.Equal(t, jsonWriteExpected, read) 1080 } 1081 1082 var propertiesWriteExpected = []byte(`p_id = 0001 1083 p_type = donut 1084 p_name = Cake 1085 p_ppu = 0.55 1086 p_batters.batter.type = Regular 1087 `) 1088 1089 func TestWriteConfigProperties(t *testing.T) { 1090 v := New() 1091 fs := felix.NewMemVfs() 1092 v.SetFs(fs) 1093 v.SetConfigName("c") 1094 v.SetConfigType("properties") 1095 err := v.ReadConfig(bytes.NewBuffer(propertiesExample)) 1096 if err != nil { 1097 t.Fatal(err) 1098 } 1099 if err := v.WriteConfigAs("c.properties"); err != nil { 1100 t.Fatal(err) 1101 } 1102 read, err := fs.ReadFile("c.properties") 1103 if err != nil { 1104 t.Fatal(err) 1105 } 1106 assert.Equal(t, propertiesWriteExpected, read) 1107 } 1108 1109 func TestWriteConfigTOML(t *testing.T) { 1110 fs := felix.NewMemVfs() 1111 v := New() 1112 v.SetFs(fs) 1113 v.SetConfigName("c") 1114 v.SetConfigType("toml") 1115 err := v.ReadConfig(bytes.NewBuffer(tomlExample)) 1116 if err != nil { 1117 t.Fatal(err) 1118 } 1119 if err := v.WriteConfigAs("c.toml"); err != nil { 1120 t.Fatal(err) 1121 } 1122 1123 // The TOML String method does not order the contents. 1124 // Therefore, we must read the generated file and compare the data. 1125 v2 := New() 1126 v2.SetFs(fs) 1127 v2.SetConfigName("c") 1128 v2.SetConfigType("toml") 1129 v2.SetConfigFile("c.toml") 1130 err = v2.ReadInConfig() 1131 if err != nil { 1132 t.Fatal(err) 1133 } 1134 1135 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1136 assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) 1137 assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) 1138 assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) 1139 } 1140 1141 var dotenvWriteExpected = []byte(` 1142 TITLE="DotEnv Write Example" 1143 NAME=Oreo 1144 KIND=Biscuit 1145 `) 1146 1147 func TestWriteConfigDotEnv(t *testing.T) { 1148 fs := felix.NewMemVfs() 1149 v := New() 1150 v.SetFs(fs) 1151 v.SetConfigName("c") 1152 v.SetConfigType("env") 1153 err := v.ReadConfig(bytes.NewBuffer(dotenvWriteExpected)) 1154 if err != nil { 1155 t.Fatal(err) 1156 } 1157 if err := v.WriteConfigAs("c.env"); err != nil { 1158 t.Fatal(err) 1159 } 1160 1161 // The TOML String method does not order the contents. 1162 // Therefore, we must read the generated file and compare the data. 1163 v2 := New() 1164 v2.SetFs(fs) 1165 v2.SetConfigName("c") 1166 v2.SetConfigType("env") 1167 v2.SetConfigFile("c.env") 1168 err = v2.ReadInConfig() 1169 if err != nil { 1170 t.Fatal(err) 1171 } 1172 1173 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1174 assert.Equal(t, v.GetString("type"), v2.GetString("type")) 1175 assert.Equal(t, v.GetString("kind"), v2.GetString("kind")) 1176 } 1177 1178 var yamlWriteExpected = []byte(`age: 35 1179 beard: true 1180 clothing: 1181 jacket: leather 1182 pants: 1183 size: large 1184 trousers: denim 1185 eyes: brown 1186 hacker: true 1187 hobbies: 1188 - skateboarding 1189 - snowboarding 1190 - go 1191 name: steve 1192 `) 1193 1194 func TestWriteConfigYAML(t *testing.T) { 1195 v := New() 1196 fs := felix.NewMemVfs() 1197 v.SetFs(fs) 1198 v.SetConfigName("c") 1199 v.SetConfigType("yaml") 1200 err := v.ReadConfig(bytes.NewBuffer(yamlExample)) 1201 if err != nil { 1202 t.Fatal(err) 1203 } 1204 if err := v.WriteConfigAs("c.yaml"); err != nil { 1205 t.Fatal(err) 1206 } 1207 read, err := fs.ReadFile("c.yaml") 1208 if err != nil { 1209 t.Fatal(err) 1210 } 1211 assert.Equal(t, yamlWriteExpected, read) 1212 } 1213 1214 var yamlMergeExampleTgt = []byte(` 1215 hello: 1216 pop: 37890 1217 largenum: 765432101234567 1218 num2pow63: 9223372036854775808 1219 world: 1220 - us 1221 - uk 1222 - fr 1223 - de 1224 `) 1225 1226 var yamlMergeExampleSrc = []byte(` 1227 hello: 1228 pop: 45000 1229 largenum: 7654321001234567 1230 universe: 1231 - mw 1232 - ad 1233 ints: 1234 - 1 1235 - 2 1236 fu: bar 1237 `) 1238 1239 func TestMergeConfig(t *testing.T) { 1240 v := New() 1241 v.SetConfigType("yml") 1242 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1243 t.Fatal(err) 1244 } 1245 1246 if pop := v.GetInt("hello.pop"); pop != 37890 { 1247 t.Fatalf("pop != 37890, = %d", pop) 1248 } 1249 1250 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1251 t.Fatalf("pop != 37890, = %d", pop) 1252 } 1253 1254 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1255 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1256 } 1257 1258 if pop := v.GetUint("hello.pop"); pop != 37890 { 1259 t.Fatalf("uint pop != 37890, = %d", pop) 1260 } 1261 1262 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1263 t.Fatalf("uint32 pop != 37890, = %d", pop) 1264 } 1265 1266 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1267 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1268 } 1269 1270 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1271 t.Fatalf("len(world) != 4, = %d", len(world)) 1272 } 1273 1274 if fu := v.GetString("fu"); fu != "" { 1275 t.Fatalf("fu != \"\", = %s", fu) 1276 } 1277 1278 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1279 t.Fatal(err) 1280 } 1281 1282 if pop := v.GetInt("hello.pop"); pop != 45000 { 1283 t.Fatalf("pop != 45000, = %d", pop) 1284 } 1285 1286 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1287 t.Fatalf("pop != 45000, = %d", pop) 1288 } 1289 1290 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1291 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1292 } 1293 1294 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1295 t.Fatalf("len(world) != 4, = %d", len(world)) 1296 } 1297 1298 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1299 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1300 } 1301 1302 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1303 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1304 } 1305 1306 if fu := v.GetString("fu"); fu != "bar" { 1307 t.Fatalf("fu != \"bar\", = %s", fu) 1308 } 1309 } 1310 1311 func TestMergeConfigNoMerge(t *testing.T) { 1312 v := New() 1313 v.SetConfigType("yml") 1314 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1315 t.Fatal(err) 1316 } 1317 1318 if pop := v.GetInt("hello.pop"); pop != 37890 { 1319 t.Fatalf("pop != 37890, = %d", pop) 1320 } 1321 1322 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1323 t.Fatalf("len(world) != 4, = %d", len(world)) 1324 } 1325 1326 if fu := v.GetString("fu"); fu != "" { 1327 t.Fatalf("fu != \"\", = %s", fu) 1328 } 1329 1330 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1331 t.Fatal(err) 1332 } 1333 1334 if pop := v.GetInt("hello.pop"); pop != 45000 { 1335 t.Fatalf("pop != 45000, = %d", pop) 1336 } 1337 1338 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 1339 t.Fatalf("len(world) != 0, = %d", len(world)) 1340 } 1341 1342 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1343 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1344 } 1345 1346 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1347 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1348 } 1349 1350 if fu := v.GetString("fu"); fu != "bar" { 1351 t.Fatalf("fu != \"bar\", = %s", fu) 1352 } 1353 } 1354 1355 func TestMergeConfigMap(t *testing.T) { 1356 v := New() 1357 v.SetConfigType("yml") 1358 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1359 t.Fatal(err) 1360 } 1361 1362 assert := func(i int) { 1363 large := v.GetInt64("hello.largenum") 1364 pop := v.GetInt("hello.pop") 1365 if large != 765432101234567 { 1366 t.Fatal("Got large num:", large) 1367 } 1368 1369 if pop != i { 1370 t.Fatal("Got pop:", pop) 1371 } 1372 } 1373 1374 assert(37890) 1375 1376 update := map[string]interface{}{ 1377 "Hello": map[string]interface{}{ 1378 "Pop": 1234, 1379 }, 1380 "World": map[interface{}]interface{}{ 1381 "Rock": 345, 1382 }, 1383 } 1384 1385 if err := v.MergeConfigMap(update); err != nil { 1386 t.Fatal(err) 1387 } 1388 1389 if rock := v.GetInt("world.rock"); rock != 345 { 1390 t.Fatal("Got rock:", rock) 1391 } 1392 1393 assert(1234) 1394 1395 } 1396 1397 func TestUnmarshalingWithAliases(t *testing.T) { 1398 v := New() 1399 v.SetDefault("ID", 1) 1400 v.Set("name", "Steve") 1401 v.Set("lastname", "Owen") 1402 1403 v.RegisterAlias("UserID", "ID") 1404 v.RegisterAlias("Firstname", "name") 1405 v.RegisterAlias("Surname", "lastname") 1406 1407 type config struct { 1408 ID int 1409 FirstName string 1410 Surname string 1411 } 1412 1413 var C config 1414 err := v.Unmarshal(&C) 1415 if err != nil { 1416 t.Fatalf("unable to decode into struct, %v", err) 1417 } 1418 1419 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 1420 } 1421 1422 func TestSetConfigNameClearsFileCache(t *testing.T) { 1423 SetConfigFile("/tmp/config.yaml") 1424 SetConfigName("default") 1425 f, err := l.getConfigFile() 1426 if err == nil { 1427 t.Fatalf("config file cache should have been cleared") 1428 } 1429 assert.Empty(t, f) 1430 } 1431 1432 func TestShadowedNestedValue(t *testing.T) { 1433 1434 config := `name: steve 1435 clothing: 1436 jacket: leather 1437 trousers: denim 1438 pants: 1439 size: large 1440 ` 1441 initConfig("yaml", config) 1442 1443 assert.Equal(t, "steve", GetString("name")) 1444 1445 polyester := "polyester" 1446 SetDefault("clothing.shirt", polyester) 1447 SetDefault("clothing.jacket.price", 100) 1448 1449 assert.Equal(t, "leather", GetString("clothing.jacket")) 1450 assert.Nil(t, Get("clothing.jacket.price")) 1451 assert.Equal(t, polyester, GetString("clothing.shirt")) 1452 1453 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 1454 assert.Equal(t, "leather", clothingSettings["jacket"]) 1455 assert.Equal(t, polyester, clothingSettings["shirt"]) 1456 } 1457 1458 func TestDotParameter(t *testing.T) { 1459 initJSON() 1460 // shoud take precedence over batters defined in jsonExample 1461 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 1462 unmarshalReader(r, l.config) 1463 1464 actual := Get("batters.batter") 1465 expected := []interface{}{map[string]interface{}{"type": "Small"}} 1466 assert.Equal(t, expected, actual) 1467 } 1468 1469 func TestCaseInsensitive(t *testing.T) { 1470 for _, config := range []struct { 1471 typ string 1472 content string 1473 }{ 1474 {"yaml", ` 1475 aBcD: 1 1476 eF: 1477 gH: 2 1478 iJk: 3 1479 Lm: 1480 nO: 4 1481 P: 1482 Q: 5 1483 R: 6 1484 `}, 1485 {"json", `{ 1486 "aBcD": 1, 1487 "eF": { 1488 "iJk": 3, 1489 "Lm": { 1490 "P": { 1491 "Q": 5, 1492 "R": 6 1493 }, 1494 "nO": 4 1495 }, 1496 "gH": 2 1497 } 1498 }`}, 1499 {"toml", `aBcD = 1 1500 [eF] 1501 gH = 2 1502 iJk = 3 1503 [eF.Lm] 1504 nO = 4 1505 [eF.Lm.P] 1506 Q = 5 1507 R = 6 1508 `}, 1509 } { 1510 doTestCaseInsensitive(t, config.typ, config.content) 1511 } 1512 } 1513 1514 func TestCaseInsensitiveSet(t *testing.T) { 1515 Reset() 1516 m1 := map[string]interface{}{ 1517 "Foo": 32, 1518 "Bar": map[interface{}]interface { 1519 }{ 1520 "ABc": "A", 1521 "cDE": "B"}, 1522 } 1523 1524 m2 := map[string]interface{}{ 1525 "Foo": 52, 1526 "Bar": map[interface{}]interface { 1527 }{ 1528 "bCd": "A", 1529 "eFG": "B"}, 1530 } 1531 1532 Set("Given1", m1) 1533 Set("Number1", 42) 1534 1535 SetDefault("Given2", m2) 1536 SetDefault("Number2", 52) 1537 1538 // Verify SetDefault 1539 if v := Get("number2"); v != 52 { 1540 t.Fatalf("Expected 52 got %q", v) 1541 } 1542 1543 if v := Get("given2.foo"); v != 52 { 1544 t.Fatalf("Expected 52 got %q", v) 1545 } 1546 1547 if v := Get("given2.bar.bcd"); v != "A" { 1548 t.Fatalf("Expected A got %q", v) 1549 } 1550 1551 if _, ok := m2["Foo"]; !ok { 1552 t.Fatal("Input map changed") 1553 } 1554 1555 // Verify Set 1556 if v := Get("number1"); v != 42 { 1557 t.Fatalf("Expected 42 got %q", v) 1558 } 1559 1560 if v := Get("given1.foo"); v != 32 { 1561 t.Fatalf("Expected 32 got %q", v) 1562 } 1563 1564 if v := Get("given1.bar.abc"); v != "A" { 1565 t.Fatalf("Expected A got %q", v) 1566 } 1567 1568 if _, ok := m1["Foo"]; !ok { 1569 t.Fatal("Input map changed") 1570 } 1571 } 1572 1573 func TestParseNested(t *testing.T) { 1574 type duration struct { 1575 Delay time.Duration 1576 } 1577 1578 type item struct { 1579 Name string 1580 Delay time.Duration 1581 Nested duration 1582 } 1583 1584 config := `[[parent]] 1585 delay="100ms" 1586 [parent.nested] 1587 delay="200ms" 1588 ` 1589 initConfig("toml", config) 1590 1591 var items []item 1592 err := l.UnmarshalKey("parent", &items) 1593 if err != nil { 1594 t.Fatalf("unable to decode into struct, %v", err) 1595 } 1596 1597 assert.Equal(t, 1, len(items)) 1598 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 1599 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 1600 } 1601 1602 func doTestCaseInsensitive(t *testing.T, typ, config string) { 1603 initConfig(typ, config) 1604 Set("RfD", true) 1605 assert.Equal(t, true, Get("rfd")) 1606 assert.Equal(t, true, Get("rFD")) 1607 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 1608 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 1609 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 1610 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 1611 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 1612 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 1613 1614 } 1615 1616 func newLungWithConfigFile(t *testing.T) (*Lung, string, func()) { 1617 watchDir, err := ioutil.TempDir("", "") 1618 require.Nil(t, err) 1619 configFile := path.Join(watchDir, "config.yaml") 1620 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) 1621 require.Nil(t, err) 1622 cleanup := func() { 1623 os.RemoveAll(watchDir) 1624 } 1625 v := New() 1626 v.SetConfigFile(configFile) 1627 err = v.ReadInConfig() 1628 require.Nil(t, err) 1629 require.Equal(t, "bar", v.Get("foo")) 1630 return v, configFile, cleanup 1631 } 1632 1633 func newLungWithSymlinkedConfigFile(t *testing.T) (*Lung, string, string, func()) { 1634 watchDir, err := ioutil.TempDir("", "") 1635 require.Nil(t, err) 1636 dataDir1 := path.Join(watchDir, "data1") 1637 err = os.Mkdir(dataDir1, 0777) 1638 require.Nil(t, err) 1639 realConfigFile := path.Join(dataDir1, "config.yaml") 1640 t.Logf("Real config file location: %s\n", realConfigFile) 1641 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) 1642 require.Nil(t, err) 1643 cleanup := func() { 1644 os.RemoveAll(watchDir) 1645 } 1646 // now, symlink the tm `data1` dir to `data` in the baseDir 1647 os.Symlink(dataDir1, path.Join(watchDir, "data")) 1648 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 1649 configFile := path.Join(watchDir, "config.yaml") 1650 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 1651 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 1652 // init Lung 1653 v := New() 1654 v.SetConfigFile(configFile) 1655 err = v.ReadInConfig() 1656 require.Nil(t, err) 1657 require.Equal(t, "bar", v.Get("foo")) 1658 return v, watchDir, configFile, cleanup 1659 } 1660 1661 func TestWatchFile(t *testing.T) { 1662 if runtime.GOOS == "linux" { 1663 // TODO(bep) FIX ME 1664 t.Skip("Skip test on Linux ...") 1665 } 1666 1667 t.Run("file content changed", func(t *testing.T) { 1668 // given a `config.yaml` file being watched 1669 v, configFile, cleanup := newLungWithConfigFile(t) 1670 defer cleanup() 1671 _, err := os.Stat(configFile) 1672 require.NoError(t, err) 1673 t.Logf("test config file: %s\n", configFile) 1674 wg := sync.WaitGroup{} 1675 wg.Add(1) 1676 v.OnConfigChange(func(in fsnotify.Event) { 1677 t.Logf("config file changed") 1678 wg.Done() 1679 }) 1680 v.WatchConfig() 1681 // when overwriting the file and waiting for the custom change notification handler to be triggered 1682 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) 1683 wg.Wait() 1684 // then the config value should have changed 1685 require.Nil(t, err) 1686 assert.Equal(t, "baz", v.Get("foo")) 1687 }) 1688 1689 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 1690 // skip if not executed on Linux 1691 if runtime.GOOS != "linux" { 1692 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 1693 } 1694 v, watchDir, _, _ := newLungWithSymlinkedConfigFile(t) 1695 // defer cleanup() 1696 wg := sync.WaitGroup{} 1697 v.WatchConfig() 1698 v.OnConfigChange(func(in fsnotify.Event) { 1699 t.Logf("config file changed") 1700 wg.Done() 1701 }) 1702 wg.Add(1) 1703 // when link to another `config.yaml` file 1704 dataDir2 := path.Join(watchDir, "data2") 1705 err := os.Mkdir(dataDir2, 0777) 1706 require.Nil(t, err) 1707 configFile2 := path.Join(dataDir2, "config.yaml") 1708 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) 1709 require.Nil(t, err) 1710 // change the symlink using the `ln -sfn` command 1711 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 1712 require.Nil(t, err) 1713 wg.Wait() 1714 // then 1715 require.Nil(t, err) 1716 assert.Equal(t, "baz", v.Get("foo")) 1717 }) 1718 1719 } 1720 1721 func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 1722 flags := gflag.NewFlagSet("test", gflag.ContinueOnError) 1723 flags.String("foo.bar", "cobra_flag", "") 1724 1725 v := New() 1726 assert.NoError(t, v.BindPFlags(flags)) 1727 1728 config := &struct { 1729 Foo struct { 1730 Bar string 1731 } 1732 }{} 1733 1734 assert.NoError(t, v.Unmarshal(config)) 1735 assert.Equal(t, "cobra_flag", config.Foo.Bar) 1736 } 1737 1738 func BenchmarkGetBool(b *testing.B) { 1739 key := "BenchmarkGetBool" 1740 l = New() 1741 l.Set(key, true) 1742 1743 for i := 0; i < b.N; i++ { 1744 if !l.GetBool(key) { 1745 b.Fatal("GetBool returned false") 1746 } 1747 } 1748 } 1749 1750 func BenchmarkGet(b *testing.B) { 1751 key := "BenchmarkGet" 1752 l = New() 1753 l.Set(key, true) 1754 1755 for i := 0; i < b.N; i++ { 1756 if !l.Get(key).(bool) { 1757 b.Fatal("Get returned false") 1758 } 1759 } 1760 } 1761 1762 // BenchmarkGetBoolFromMap is the "perfect result" for the above. 1763 func BenchmarkGetBoolFromMap(b *testing.B) { 1764 m := make(map[string]bool) 1765 key := "BenchmarkGetBool" 1766 m[key] = true 1767 1768 for i := 0; i < b.N; i++ { 1769 if !m[key] { 1770 b.Fatal("Map value was false") 1771 } 1772 } 1773 }