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