github.com/gk008/viper@v1.0.2/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 largenum: 765432101234567 1120 num2pow63: 9223372036854775808 1121 world: 1122 - us 1123 - uk 1124 - fr 1125 - de 1126 `) 1127 1128 var yamlMergeExampleSrc = []byte(` 1129 hello: 1130 pop: 45000 1131 largenum: 7654321001234567 1132 universe: 1133 - mw 1134 - ad 1135 fu: bar 1136 `) 1137 1138 func TestMergeConfig(t *testing.T) { 1139 v := New() 1140 v.SetConfigType("yml") 1141 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1142 t.Fatal(err) 1143 } 1144 1145 if pop := v.GetInt("hello.pop"); pop != 37890 { 1146 t.Fatalf("pop != 37890, = %d", pop) 1147 } 1148 1149 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1150 t.Fatalf("pop != 37890, = %d", pop) 1151 } 1152 1153 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1154 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1155 } 1156 1157 if pop := v.GetUint("hello.pop"); pop != 37890 { 1158 t.Fatalf("uint pop != 37890, = %d", pop) 1159 } 1160 1161 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1162 t.Fatalf("uint32 pop != 37890, = %d", pop) 1163 } 1164 1165 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1166 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1167 } 1168 1169 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1170 t.Fatalf("len(world) != 4, = %d", len(world)) 1171 } 1172 1173 if fu := v.GetString("fu"); fu != "" { 1174 t.Fatalf("fu != \"\", = %s", fu) 1175 } 1176 1177 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1178 t.Fatal(err) 1179 } 1180 1181 if pop := v.GetInt("hello.pop"); pop != 45000 { 1182 t.Fatalf("pop != 45000, = %d", pop) 1183 } 1184 1185 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1186 t.Fatalf("pop != 45000, = %d", pop) 1187 } 1188 1189 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1190 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1191 } 1192 1193 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1194 t.Fatalf("len(world) != 4, = %d", len(world)) 1195 } 1196 1197 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1198 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1199 } 1200 1201 if fu := v.GetString("fu"); fu != "bar" { 1202 t.Fatalf("fu != \"bar\", = %s", fu) 1203 } 1204 } 1205 1206 func TestMergeConfigNoMerge(t *testing.T) { 1207 v := New() 1208 v.SetConfigType("yml") 1209 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1210 t.Fatal(err) 1211 } 1212 1213 if pop := v.GetInt("hello.pop"); pop != 37890 { 1214 t.Fatalf("pop != 37890, = %d", pop) 1215 } 1216 1217 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1218 t.Fatalf("len(world) != 4, = %d", len(world)) 1219 } 1220 1221 if fu := v.GetString("fu"); fu != "" { 1222 t.Fatalf("fu != \"\", = %s", fu) 1223 } 1224 1225 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1226 t.Fatal(err) 1227 } 1228 1229 if pop := v.GetInt("hello.pop"); pop != 45000 { 1230 t.Fatalf("pop != 45000, = %d", pop) 1231 } 1232 1233 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 1234 t.Fatalf("len(world) != 0, = %d", len(world)) 1235 } 1236 1237 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1238 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1239 } 1240 1241 if fu := v.GetString("fu"); fu != "bar" { 1242 t.Fatalf("fu != \"bar\", = %s", fu) 1243 } 1244 } 1245 1246 func TestMergeConfigMap(t *testing.T) { 1247 v := New() 1248 v.SetConfigType("yml") 1249 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1250 t.Fatal(err) 1251 } 1252 1253 assert := func(i int) { 1254 large := v.GetInt64("hello.largenum") 1255 pop := v.GetInt("hello.pop") 1256 if large != 765432101234567 { 1257 t.Fatal("Got large num:", large) 1258 } 1259 1260 if pop != i { 1261 t.Fatal("Got pop:", pop) 1262 } 1263 } 1264 1265 assert(37890) 1266 1267 update := map[string]interface{}{ 1268 "Hello": map[string]interface{}{ 1269 "Pop": 1234, 1270 }, 1271 "World": map[interface{}]interface{}{ 1272 "Rock": 345, 1273 }, 1274 } 1275 1276 if err := v.MergeConfigMap(update); err != nil { 1277 t.Fatal(err) 1278 } 1279 1280 if rock := v.GetInt("world.rock"); rock != 345 { 1281 t.Fatal("Got rock:", rock) 1282 } 1283 1284 assert(1234) 1285 1286 } 1287 1288 func TestUnmarshalingWithAliases(t *testing.T) { 1289 v := New() 1290 v.SetDefault("ID", 1) 1291 v.Set("name", "Steve") 1292 v.Set("lastname", "Owen") 1293 1294 v.RegisterAlias("UserID", "ID") 1295 v.RegisterAlias("Firstname", "name") 1296 v.RegisterAlias("Surname", "lastname") 1297 1298 type config struct { 1299 ID int 1300 FirstName string 1301 Surname string 1302 } 1303 1304 var C config 1305 err := v.Unmarshal(&C) 1306 if err != nil { 1307 t.Fatalf("unable to decode into struct, %v", err) 1308 } 1309 1310 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 1311 } 1312 1313 func TestSetConfigNameClearsFileCache(t *testing.T) { 1314 SetConfigFile("/tmp/config.yaml") 1315 SetConfigName("default") 1316 f, err := v.getConfigFile() 1317 if err == nil { 1318 t.Fatalf("config file cache should have been cleared") 1319 } 1320 assert.Empty(t, f) 1321 } 1322 1323 func TestShadowedNestedValue(t *testing.T) { 1324 1325 config := `name: steve 1326 clothing: 1327 jacket: leather 1328 trousers: denim 1329 pants: 1330 size: large 1331 ` 1332 initConfig("yaml", config) 1333 1334 assert.Equal(t, "steve", GetString("name")) 1335 1336 polyester := "polyester" 1337 SetDefault("clothing.shirt", polyester) 1338 SetDefault("clothing.jacket.price", 100) 1339 1340 assert.Equal(t, "leather", GetString("clothing.jacket")) 1341 assert.Nil(t, Get("clothing.jacket.price")) 1342 assert.Equal(t, polyester, GetString("clothing.shirt")) 1343 1344 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 1345 assert.Equal(t, "leather", clothingSettings["jacket"]) 1346 assert.Equal(t, polyester, clothingSettings["shirt"]) 1347 } 1348 1349 func TestDotParameter(t *testing.T) { 1350 initJSON() 1351 // shoud take precedence over batters defined in jsonExample 1352 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 1353 unmarshalReader(r, v.config) 1354 1355 actual := Get("batters.batter") 1356 expected := []interface{}{map[string]interface{}{"type": "Small"}} 1357 assert.Equal(t, expected, actual) 1358 } 1359 1360 func TestCaseInsensitive(t *testing.T) { 1361 for _, config := range []struct { 1362 typ string 1363 content string 1364 }{ 1365 {"yaml", ` 1366 aBcD: 1 1367 eF: 1368 gH: 2 1369 iJk: 3 1370 Lm: 1371 nO: 4 1372 P: 1373 Q: 5 1374 R: 6 1375 `}, 1376 {"json", `{ 1377 "aBcD": 1, 1378 "eF": { 1379 "iJk": 3, 1380 "Lm": { 1381 "P": { 1382 "Q": 5, 1383 "R": 6 1384 }, 1385 "nO": 4 1386 }, 1387 "gH": 2 1388 } 1389 }`}, 1390 {"toml", `aBcD = 1 1391 [eF] 1392 gH = 2 1393 iJk = 3 1394 [eF.Lm] 1395 nO = 4 1396 [eF.Lm.P] 1397 Q = 5 1398 R = 6 1399 `}, 1400 } { 1401 doTestCaseInsensitive(t, config.typ, config.content) 1402 } 1403 } 1404 1405 func TestCaseSensitive(t *testing.T) { 1406 for _, config := range []struct { 1407 typ string 1408 content string 1409 }{ 1410 {"yaml", ` 1411 aBcD: 1 1412 eF: 1413 gH: 2 1414 iJk: 3 1415 Lm: 1416 nO: 4 1417 P: 1418 Q: 5 1419 R: 6 1420 `}, 1421 {"json", `{ 1422 "aBcD": 1, 1423 "eF": { 1424 "iJk": 3, 1425 "Lm": { 1426 "P": { 1427 "Q": 5, 1428 "R": 6 1429 }, 1430 "nO": 4 1431 }, 1432 "gH": 2 1433 } 1434 }`}, 1435 {"toml", `aBcD = 1 1436 [eF] 1437 gH = 2 1438 iJk = 3 1439 [eF.Lm] 1440 nO = 4 1441 [eF.Lm.P] 1442 Q = 5 1443 R = 6 1444 `}, 1445 } { 1446 doTestCaseSensitive(t, config.typ, config.content) 1447 } 1448 } 1449 1450 func TestCaseInsensitiveSet(t *testing.T) { 1451 Reset() 1452 m1 := map[string]interface{}{ 1453 "Foo": 32, 1454 "Bar": map[interface{}]interface { 1455 }{ 1456 "ABc": "A", 1457 "cDE": "B"}, 1458 } 1459 1460 m2 := map[string]interface{}{ 1461 "Foo": 52, 1462 "Bar": map[interface{}]interface { 1463 }{ 1464 "bCd": "A", 1465 "eFG": "B"}, 1466 } 1467 1468 Set("Given1", m1) 1469 Set("Number1", 42) 1470 1471 SetDefault("Given2", m2) 1472 SetDefault("Number2", 52) 1473 1474 // Verify SetDefault 1475 if v := Get("number2"); v != 52 { 1476 t.Fatalf("Expected 52 got %q", v) 1477 } 1478 1479 if v := Get("given2.foo"); v != 52 { 1480 t.Fatalf("Expected 52 got %q", v) 1481 } 1482 1483 if v := Get("given2.bar.bcd"); v != "A" { 1484 t.Fatalf("Expected A got %q", v) 1485 } 1486 1487 if _, ok := m2["Foo"]; !ok { 1488 t.Fatal("Input map changed") 1489 } 1490 1491 // Verify Set 1492 if v := Get("number1"); v != 42 { 1493 t.Fatalf("Expected 42 got %q", v) 1494 } 1495 1496 if v := Get("given1.foo"); v != 32 { 1497 t.Fatalf("Expected 32 got %q", v) 1498 } 1499 1500 if v := Get("given1.bar.abc"); v != "A" { 1501 t.Fatalf("Expected A got %q", v) 1502 } 1503 1504 if _, ok := m1["Foo"]; !ok { 1505 t.Fatal("Input map changed") 1506 } 1507 } 1508 1509 func TestParseNested(t *testing.T) { 1510 type duration struct { 1511 Delay time.Duration 1512 } 1513 1514 type item struct { 1515 Name string 1516 Delay time.Duration 1517 Nested duration 1518 } 1519 1520 config := `[[parent]] 1521 delay="100ms" 1522 [parent.nested] 1523 delay="200ms" 1524 ` 1525 initConfig("toml", config) 1526 1527 var items []item 1528 err := v.UnmarshalKey("parent", &items) 1529 if err != nil { 1530 t.Fatalf("unable to decode into struct, %v", err) 1531 } 1532 1533 assert.Equal(t, 1, len(items)) 1534 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 1535 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 1536 } 1537 1538 func doTestCaseInsensitive(t *testing.T, typ, config string) { 1539 initConfig(typ, config) 1540 Set("RfD", true) 1541 assert.Equal(t, true, Get("rfd")) 1542 assert.Equal(t, true, Get("rFD")) 1543 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 1544 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 1545 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 1546 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 1547 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 1548 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 1549 } 1550 1551 func doTestCaseSensitive(t *testing.T, typ, config string) { 1552 Reset() 1553 SetConfigType(typ) 1554 1555 // Turn on case sensitivy. 1556 SetKeysCaseSensitive(true) 1557 r := strings.NewReader(config) 1558 if err := unmarshalReader(r, v.config); err != nil { 1559 panic(err) 1560 } 1561 1562 Set("RfD", true) 1563 assert.Equal(t, nil, Get("rfd")) 1564 assert.Equal(t, true, Get("RfD")) 1565 assert.Equal(t, 0, cast.ToInt(Get("abcd"))) 1566 assert.Equal(t, 1, cast.ToInt(Get("aBcD"))) 1567 assert.Equal(t, 0, cast.ToInt(Get("ef.gh"))) 1568 assert.Equal(t, 2, cast.ToInt(Get("eF.gH"))) 1569 assert.Equal(t, 0, cast.ToInt(Get("ef.ijk"))) 1570 assert.Equal(t, 3, cast.ToInt(Get("eF.iJk"))) 1571 assert.Equal(t, 0, cast.ToInt(Get("ef.lm.no"))) 1572 assert.Equal(t, 4, cast.ToInt(Get("eF.Lm.nO"))) 1573 assert.Equal(t, 0, cast.ToInt(Get("ef.lm.p.q"))) 1574 assert.Equal(t, 5, cast.ToInt(Get("eF.Lm.P.Q"))) 1575 1576 } 1577 1578 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 1579 watchDir, err := ioutil.TempDir("", "") 1580 require.Nil(t, err) 1581 configFile := path.Join(watchDir, "config.yaml") 1582 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) 1583 require.Nil(t, err) 1584 cleanup := func() { 1585 os.RemoveAll(watchDir) 1586 } 1587 v := New() 1588 v.SetConfigFile(configFile) 1589 err = v.ReadInConfig() 1590 require.Nil(t, err) 1591 require.Equal(t, "bar", v.Get("foo")) 1592 return v, configFile, cleanup 1593 } 1594 1595 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 1596 watchDir, err := ioutil.TempDir("", "") 1597 require.Nil(t, err) 1598 dataDir1 := path.Join(watchDir, "data1") 1599 err = os.Mkdir(dataDir1, 0777) 1600 require.Nil(t, err) 1601 realConfigFile := path.Join(dataDir1, "config.yaml") 1602 t.Logf("Real config file location: %s\n", realConfigFile) 1603 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) 1604 require.Nil(t, err) 1605 cleanup := func() { 1606 os.RemoveAll(watchDir) 1607 } 1608 // now, symlink the tm `data1` dir to `data` in the baseDir 1609 os.Symlink(dataDir1, path.Join(watchDir, "data")) 1610 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 1611 configFile := path.Join(watchDir, "config.yaml") 1612 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 1613 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 1614 // init Viper 1615 v := New() 1616 v.SetConfigFile(configFile) 1617 err = v.ReadInConfig() 1618 require.Nil(t, err) 1619 require.Equal(t, "bar", v.Get("foo")) 1620 return v, watchDir, configFile, cleanup 1621 } 1622 1623 func TestWatchFile(t *testing.T) { 1624 if runtime.GOOS == "linux" { 1625 // TODO(bep) FIX ME 1626 t.Skip("Skip test on Linux ...") 1627 } 1628 1629 t.Run("file content changed", func(t *testing.T) { 1630 // given a `config.yaml` file being watched 1631 v, configFile, cleanup := newViperWithConfigFile(t) 1632 defer cleanup() 1633 _, err := os.Stat(configFile) 1634 require.NoError(t, err) 1635 t.Logf("test config file: %s\n", configFile) 1636 wg := sync.WaitGroup{} 1637 wg.Add(1) 1638 v.OnConfigChange(func(in fsnotify.Event) { 1639 t.Logf("config file changed") 1640 wg.Done() 1641 }) 1642 v.WatchConfig() 1643 // when overwriting the file and waiting for the custom change notification handler to be triggered 1644 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) 1645 wg.Wait() 1646 // then the config value should have changed 1647 require.Nil(t, err) 1648 assert.Equal(t, "baz", v.Get("foo")) 1649 }) 1650 1651 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 1652 // skip if not executed on Linux 1653 if runtime.GOOS != "linux" { 1654 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 1655 } 1656 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 1657 // defer cleanup() 1658 wg := sync.WaitGroup{} 1659 v.WatchConfig() 1660 v.OnConfigChange(func(in fsnotify.Event) { 1661 t.Logf("config file changed") 1662 wg.Done() 1663 }) 1664 wg.Add(1) 1665 // when link to another `config.yaml` file 1666 dataDir2 := path.Join(watchDir, "data2") 1667 err := os.Mkdir(dataDir2, 0777) 1668 require.Nil(t, err) 1669 configFile2 := path.Join(dataDir2, "config.yaml") 1670 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) 1671 require.Nil(t, err) 1672 // change the symlink using the `ln -sfn` command 1673 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 1674 require.Nil(t, err) 1675 wg.Wait() 1676 // then 1677 require.Nil(t, err) 1678 assert.Equal(t, "baz", v.Get("foo")) 1679 }) 1680 1681 } 1682 1683 func BenchmarkGetBool(b *testing.B) { 1684 key := "BenchmarkGetBool" 1685 v = New() 1686 v.Set(key, true) 1687 1688 for i := 0; i < b.N; i++ { 1689 if !v.GetBool(key) { 1690 b.Fatal("GetBool returned false") 1691 } 1692 } 1693 } 1694 1695 func BenchmarkGet(b *testing.B) { 1696 key := "BenchmarkGet" 1697 v = New() 1698 v.Set(key, true) 1699 1700 for i := 0; i < b.N; i++ { 1701 if !v.Get(key).(bool) { 1702 b.Fatal("Get returned false") 1703 } 1704 } 1705 } 1706 1707 // This is the "perfect result" for the above. 1708 func BenchmarkGetBoolFromMap(b *testing.B) { 1709 m := make(map[string]bool) 1710 key := "BenchmarkGetBool" 1711 m[key] = true 1712 1713 for i := 0; i < b.N; i++ { 1714 if !m[key] { 1715 b.Fatal("Map value was false") 1716 } 1717 } 1718 }