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