github.com/FuzzyStatic/viper@v1.7.3/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 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 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 "github.com/spf13/pflag" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 33 "github.com/FuzzyStatic/viper/internal/testutil" 34 ) 35 36 var yamlExample = []byte(`Hacker: true 37 name: steve 38 hobbies: 39 - skateboarding 40 - snowboarding 41 - go 42 clothing: 43 jacket: leather 44 trousers: denim 45 pants: 46 size: large 47 age: 35 48 eyes : brown 49 beard: true 50 `) 51 52 var yamlExampleWithExtras = []byte(`Existing: true 53 Bogus: true 54 `) 55 56 type testUnmarshalExtra struct { 57 Existing bool 58 } 59 60 var tomlExample = []byte(` 61 title = "TOML Example" 62 63 [owner] 64 organization = "MongoDB" 65 Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 66 dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 67 68 var dotenvExample = []byte(` 69 TITLE_DOTENV="DotEnv Example" 70 TYPE_DOTENV=donut 71 NAME_DOTENV=Cake`) 72 73 var jsonExample = []byte(`{ 74 "id": "0001", 75 "type": "donut", 76 "name": "Cake", 77 "ppu": 0.55, 78 "batters": { 79 "batter": [ 80 { "type": "Regular" }, 81 { "type": "Chocolate" }, 82 { "type": "Blueberry" }, 83 { "type": "Devil's Food" } 84 ] 85 } 86 }`) 87 88 var hclExample = []byte(` 89 id = "0001" 90 type = "donut" 91 name = "Cake" 92 ppu = 0.55 93 foos { 94 foo { 95 key = 1 96 } 97 foo { 98 key = 2 99 } 100 foo { 101 key = 3 102 } 103 foo { 104 key = 4 105 } 106 }`) 107 108 var propertiesExample = []byte(` 109 p_id: 0001 110 p_type: donut 111 p_name: Cake 112 p_ppu: 0.55 113 p_batters.batter.type: Regular 114 `) 115 116 var remoteExample = []byte(`{ 117 "id":"0002", 118 "type":"cronut", 119 "newkey":"remote" 120 }`) 121 122 var iniExample = []byte(`; Package name 123 NAME = ini 124 ; Package version 125 VERSION = v1 126 ; Package import path 127 IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s 128 129 # Information about package author 130 # Bio can be written in multiple lines. 131 [author] 132 NAME = Unknown ; Succeeding comment 133 E-MAIL = fake@localhost 134 GITHUB = https://github.com/%(NAME)s 135 BIO = """Gopher. 136 Coding addict. 137 Good man. 138 """ # Succeeding comment`) 139 140 func initConfigs() { 141 Reset() 142 var r io.Reader 143 SetConfigType("yaml") 144 r = bytes.NewReader(yamlExample) 145 unmarshalReader(r, v.config) 146 147 SetConfigType("json") 148 r = bytes.NewReader(jsonExample) 149 unmarshalReader(r, v.config) 150 151 SetConfigType("hcl") 152 r = bytes.NewReader(hclExample) 153 unmarshalReader(r, v.config) 154 155 SetConfigType("properties") 156 r = bytes.NewReader(propertiesExample) 157 unmarshalReader(r, v.config) 158 159 SetConfigType("toml") 160 r = bytes.NewReader(tomlExample) 161 unmarshalReader(r, v.config) 162 163 SetConfigType("env") 164 r = bytes.NewReader(dotenvExample) 165 unmarshalReader(r, v.config) 166 167 SetConfigType("json") 168 remote := bytes.NewReader(remoteExample) 169 unmarshalReader(remote, v.kvstore) 170 171 SetConfigType("ini") 172 r = bytes.NewReader(iniExample) 173 unmarshalReader(r, v.config) 174 } 175 176 func initConfig(typ, config string) { 177 Reset() 178 SetConfigType(typ) 179 r := strings.NewReader(config) 180 181 if err := unmarshalReader(r, v.config); err != nil { 182 panic(err) 183 } 184 } 185 186 func initYAML() { 187 initConfig("yaml", string(yamlExample)) 188 } 189 190 func initJSON() { 191 Reset() 192 SetConfigType("json") 193 r := bytes.NewReader(jsonExample) 194 195 unmarshalReader(r, v.config) 196 } 197 198 func initProperties() { 199 Reset() 200 SetConfigType("properties") 201 r := bytes.NewReader(propertiesExample) 202 203 unmarshalReader(r, v.config) 204 } 205 206 func initTOML() { 207 Reset() 208 SetConfigType("toml") 209 r := bytes.NewReader(tomlExample) 210 211 unmarshalReader(r, v.config) 212 } 213 214 func initDotEnv() { 215 Reset() 216 SetConfigType("env") 217 r := bytes.NewReader(dotenvExample) 218 219 unmarshalReader(r, v.config) 220 } 221 222 func initHcl() { 223 Reset() 224 SetConfigType("hcl") 225 r := bytes.NewReader(hclExample) 226 227 unmarshalReader(r, v.config) 228 } 229 230 func initIni() { 231 Reset() 232 SetConfigType("ini") 233 r := bytes.NewReader(iniExample) 234 235 unmarshalReader(r, v.config) 236 } 237 238 // make directories for testing 239 func initDirs(t *testing.T) (string, string, func()) { 240 var ( 241 testDirs = []string{`a a`, `b`, `C_`} 242 config = `improbable` 243 ) 244 245 if runtime.GOOS != "windows" { 246 testDirs = append(testDirs, `d\d`) 247 } 248 249 root, err := ioutil.TempDir("", "") 250 require.NoError(t, err, "Failed to create temporary directory") 251 252 cleanup := true 253 defer func() { 254 if cleanup { 255 os.Chdir("..") 256 os.RemoveAll(root) 257 } 258 }() 259 260 assert.Nil(t, err) 261 262 err = os.Chdir(root) 263 require.Nil(t, err) 264 265 for _, dir := range testDirs { 266 err = os.Mkdir(dir, 0750) 267 assert.Nil(t, err) 268 269 err = ioutil.WriteFile( 270 path.Join(dir, config+".toml"), 271 []byte("key = \"value is "+dir+"\"\n"), 272 0640) 273 assert.Nil(t, err) 274 } 275 276 cleanup = false 277 return root, config, func() { 278 os.Chdir("..") 279 os.RemoveAll(root) 280 } 281 } 282 283 // stubs for PFlag Values 284 type stringValue string 285 286 func newStringValue(val string, p *string) *stringValue { 287 *p = val 288 return (*stringValue)(p) 289 } 290 291 func (s *stringValue) Set(val string) error { 292 *s = stringValue(val) 293 return nil 294 } 295 296 func (s *stringValue) Type() string { 297 return "string" 298 } 299 300 func (s *stringValue) String() string { 301 return string(*s) 302 } 303 304 func TestBasics(t *testing.T) { 305 SetConfigFile("/tmp/config.yaml") 306 filename, err := v.getConfigFile() 307 assert.Equal(t, "/tmp/config.yaml", filename) 308 assert.NoError(t, err) 309 } 310 311 func TestSearchInPath_WithoutConfigTypeSet(t *testing.T) { 312 filename := ".dotfilenoext" 313 path := "/tmp" 314 file := filepath.Join(path, filename) 315 SetConfigName(filename) 316 AddConfigPath(path) 317 _, createErr := v.fs.Create(file) 318 defer func() { 319 _ = v.fs.Remove(file) 320 }() 321 assert.NoError(t, createErr) 322 _, err := v.getConfigFile() 323 // unless config type is set, files without extension 324 // are not considered 325 assert.Error(t, err) 326 } 327 328 func TestSearchInPath(t *testing.T) { 329 filename := ".dotfilenoext" 330 path := "/tmp" 331 file := filepath.Join(path, filename) 332 SetConfigName(filename) 333 SetConfigType("yaml") 334 AddConfigPath(path) 335 _, createErr := v.fs.Create(file) 336 defer func() { 337 _ = v.fs.Remove(file) 338 }() 339 assert.NoError(t, createErr) 340 filename, err := v.getConfigFile() 341 assert.Equal(t, file, filename) 342 assert.NoError(t, err) 343 } 344 345 func TestSearchInPath_FilesOnly(t *testing.T) { 346 fs := afero.NewMemMapFs() 347 348 err := fs.Mkdir("/tmp/config", 0777) 349 require.NoError(t, err) 350 351 _, err = fs.Create("/tmp/config/config.yaml") 352 require.NoError(t, err) 353 354 v := New() 355 356 v.SetFs(fs) 357 v.AddConfigPath("/tmp") 358 v.AddConfigPath("/tmp/config") 359 360 filename, err := v.getConfigFile() 361 assert.Equal(t, "/tmp/config/config.yaml", filename) 362 assert.NoError(t, err) 363 } 364 365 func TestDefault(t *testing.T) { 366 SetDefault("age", 45) 367 assert.Equal(t, 45, Get("age")) 368 369 SetDefault("clothing.jacket", "slacks") 370 assert.Equal(t, "slacks", Get("clothing.jacket")) 371 372 SetConfigType("yaml") 373 err := ReadConfig(bytes.NewBuffer(yamlExample)) 374 375 assert.NoError(t, err) 376 assert.Equal(t, "leather", Get("clothing.jacket")) 377 } 378 379 func TestUnmarshaling(t *testing.T) { 380 SetConfigType("yaml") 381 r := bytes.NewReader(yamlExample) 382 383 unmarshalReader(r, v.config) 384 assert.True(t, InConfig("name")) 385 assert.False(t, InConfig("state")) 386 assert.Equal(t, "steve", Get("name")) 387 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) 388 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) 389 assert.Equal(t, 35, Get("age")) 390 } 391 392 func TestUnmarshalExact(t *testing.T) { 393 vip := New() 394 target := &testUnmarshalExtra{} 395 vip.SetConfigType("yaml") 396 r := bytes.NewReader(yamlExampleWithExtras) 397 vip.ReadConfig(r) 398 err := vip.UnmarshalExact(target) 399 if err == nil { 400 t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields") 401 } 402 } 403 404 func TestOverrides(t *testing.T) { 405 Set("age", 40) 406 assert.Equal(t, 40, Get("age")) 407 } 408 409 func TestDefaultPost(t *testing.T) { 410 assert.NotEqual(t, "NYC", Get("state")) 411 SetDefault("state", "NYC") 412 assert.Equal(t, "NYC", Get("state")) 413 } 414 415 func TestAliases(t *testing.T) { 416 RegisterAlias("years", "age") 417 assert.Equal(t, 40, Get("years")) 418 Set("years", 45) 419 assert.Equal(t, 45, Get("age")) 420 } 421 422 func TestAliasInConfigFile(t *testing.T) { 423 // the config file specifies "beard". If we make this an alias for 424 // "hasbeard", we still want the old config file to work with beard. 425 RegisterAlias("beard", "hasbeard") 426 assert.Equal(t, true, Get("hasbeard")) 427 Set("hasbeard", false) 428 assert.Equal(t, false, Get("beard")) 429 } 430 431 func TestYML(t *testing.T) { 432 initYAML() 433 assert.Equal(t, "steve", Get("name")) 434 } 435 436 func TestJSON(t *testing.T) { 437 initJSON() 438 assert.Equal(t, "0001", Get("id")) 439 } 440 441 func TestProperties(t *testing.T) { 442 initProperties() 443 assert.Equal(t, "0001", Get("p_id")) 444 } 445 446 func TestTOML(t *testing.T) { 447 initTOML() 448 assert.Equal(t, "TOML Example", Get("title")) 449 } 450 451 func TestDotEnv(t *testing.T) { 452 initDotEnv() 453 assert.Equal(t, "DotEnv Example", Get("title_dotenv")) 454 } 455 456 func TestHCL(t *testing.T) { 457 initHcl() 458 assert.Equal(t, "0001", Get("id")) 459 assert.Equal(t, 0.55, Get("ppu")) 460 assert.Equal(t, "donut", Get("type")) 461 assert.Equal(t, "Cake", Get("name")) 462 Set("id", "0002") 463 assert.Equal(t, "0002", Get("id")) 464 assert.NotEqual(t, "cronut", Get("type")) 465 } 466 467 func TestIni(t *testing.T) { 468 initIni() 469 assert.Equal(t, "ini", Get("default.name")) 470 } 471 472 func TestRemotePrecedence(t *testing.T) { 473 initJSON() 474 475 remote := bytes.NewReader(remoteExample) 476 assert.Equal(t, "0001", Get("id")) 477 unmarshalReader(remote, v.kvstore) 478 assert.Equal(t, "0001", Get("id")) 479 assert.NotEqual(t, "cronut", Get("type")) 480 assert.Equal(t, "remote", Get("newkey")) 481 Set("newkey", "newvalue") 482 assert.NotEqual(t, "remote", Get("newkey")) 483 assert.Equal(t, "newvalue", Get("newkey")) 484 Set("newkey", "remote") 485 } 486 487 func TestEnv(t *testing.T) { 488 initJSON() 489 490 BindEnv("id") 491 BindEnv("f", "FOOD", "OLD_FOOD") 492 493 testutil.Setenv(t, "ID", "13") 494 testutil.Setenv(t, "FOOD", "apple") 495 testutil.Setenv(t, "OLD_FOOD", "banana") 496 testutil.Setenv(t, "NAME", "crunk") 497 498 assert.Equal(t, "13", Get("id")) 499 assert.Equal(t, "apple", Get("f")) 500 assert.Equal(t, "Cake", Get("name")) 501 502 AutomaticEnv() 503 504 assert.Equal(t, "crunk", Get("name")) 505 } 506 507 func TestMultipleEnv(t *testing.T) { 508 initJSON() 509 510 BindEnv("f", "FOOD", "OLD_FOOD") 511 512 testutil.Setenv(t, "OLD_FOOD", "banana") 513 514 assert.Equal(t, "banana", Get("f")) 515 } 516 517 func TestEmptyEnv(t *testing.T) { 518 initJSON() 519 520 BindEnv("type") // Empty environment variable 521 BindEnv("name") // Bound, but not set environment variable 522 523 testutil.Setenv(t, "TYPE", "") 524 525 assert.Equal(t, "donut", Get("type")) 526 assert.Equal(t, "Cake", Get("name")) 527 } 528 529 func TestEmptyEnv_Allowed(t *testing.T) { 530 initJSON() 531 532 AllowEmptyEnv(true) 533 534 BindEnv("type") // Empty environment variable 535 BindEnv("name") // Bound, but not set environment variable 536 537 testutil.Setenv(t, "TYPE", "") 538 539 assert.Equal(t, "", Get("type")) 540 assert.Equal(t, "Cake", Get("name")) 541 } 542 543 func TestEnvPrefix(t *testing.T) { 544 initJSON() 545 546 SetEnvPrefix("foo") // will be uppercased automatically 547 BindEnv("id") 548 BindEnv("f", "FOOD") // not using prefix 549 550 testutil.Setenv(t, "FOO_ID", "13") 551 testutil.Setenv(t, "FOOD", "apple") 552 testutil.Setenv(t, "FOO_NAME", "crunk") 553 554 assert.Equal(t, "13", Get("id")) 555 assert.Equal(t, "apple", Get("f")) 556 assert.Equal(t, "Cake", Get("name")) 557 558 AutomaticEnv() 559 560 assert.Equal(t, "crunk", Get("name")) 561 } 562 563 func TestAutoEnv(t *testing.T) { 564 Reset() 565 566 AutomaticEnv() 567 568 testutil.Setenv(t, "FOO_BAR", "13") 569 570 assert.Equal(t, "13", Get("foo_bar")) 571 } 572 573 func TestAutoEnvWithPrefix(t *testing.T) { 574 Reset() 575 576 AutomaticEnv() 577 SetEnvPrefix("Baz") 578 579 testutil.Setenv(t, "BAZ_BAR", "13") 580 581 assert.Equal(t, "13", Get("bar")) 582 } 583 584 func TestSetEnvKeyReplacer(t *testing.T) { 585 Reset() 586 587 AutomaticEnv() 588 589 testutil.Setenv(t, "REFRESH_INTERVAL", "30s") 590 591 replacer := strings.NewReplacer("-", "_") 592 SetEnvKeyReplacer(replacer) 593 594 assert.Equal(t, "30s", Get("refresh-interval")) 595 } 596 597 func TestEnvKeyReplacer(t *testing.T) { 598 v := NewWithOptions(EnvKeyReplacer(strings.NewReplacer("-", "_"))) 599 600 v.AutomaticEnv() 601 602 testutil.Setenv(t, "REFRESH_INTERVAL", "30s") 603 604 assert.Equal(t, "30s", v.Get("refresh-interval")) 605 } 606 607 func TestAllKeys(t *testing.T) { 608 initConfigs() 609 610 ks := sort.StringSlice{ 611 "title", 612 "author.bio", 613 "author.e-mail", 614 "author.github", 615 "author.name", 616 "newkey", 617 "owner.organization", 618 "owner.dob", 619 "owner.bio", 620 "name", 621 "beard", 622 "ppu", 623 "batters.batter", 624 "hobbies", 625 "clothing.jacket", 626 "clothing.trousers", 627 "default.import_path", 628 "default.name", 629 "default.version", 630 "clothing.pants.size", 631 "age", 632 "hacker", 633 "id", 634 "type", 635 "eyes", 636 "p_id", 637 "p_ppu", 638 "p_batters.batter.type", 639 "p_type", 640 "p_name", 641 "foos", 642 "title_dotenv", 643 "type_dotenv", 644 "name_dotenv", 645 } 646 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 647 all := map[string]interface{}{ 648 "owner": map[string]interface{}{ 649 "organization": "MongoDB", 650 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 651 "dob": dob, 652 }, 653 "title": "TOML Example", 654 "author": map[string]interface{}{ 655 "e-mail": "fake@localhost", 656 "github": "https://github.com/Unknown", 657 "name": "Unknown", 658 "bio": "Gopher.\nCoding addict.\nGood man.\n", 659 }, 660 "ppu": 0.55, 661 "eyes": "brown", 662 "clothing": map[string]interface{}{ 663 "trousers": "denim", 664 "jacket": "leather", 665 "pants": map[string]interface{}{"size": "large"}, 666 }, 667 "default": map[string]interface{}{ 668 "import_path": "gopkg.in/ini.v1", 669 "name": "ini", 670 "version": "v1", 671 }, 672 "id": "0001", 673 "batters": map[string]interface{}{ 674 "batter": []interface{}{ 675 map[string]interface{}{"type": "Regular"}, 676 map[string]interface{}{"type": "Chocolate"}, 677 map[string]interface{}{"type": "Blueberry"}, 678 map[string]interface{}{"type": "Devil's Food"}, 679 }, 680 }, 681 "hacker": true, 682 "beard": true, 683 "hobbies": []interface{}{ 684 "skateboarding", 685 "snowboarding", 686 "go", 687 }, 688 "age": 35, 689 "type": "donut", 690 "newkey": "remote", 691 "name": "Cake", 692 "p_id": "0001", 693 "p_ppu": "0.55", 694 "p_name": "Cake", 695 "p_batters": map[string]interface{}{ 696 "batter": map[string]interface{}{"type": "Regular"}, 697 }, 698 "p_type": "donut", 699 "foos": []map[string]interface{}{ 700 { 701 "foo": []map[string]interface{}{ 702 {"key": 1}, 703 {"key": 2}, 704 {"key": 3}, 705 {"key": 4}, 706 }, 707 }, 708 }, 709 "title_dotenv": "DotEnv Example", 710 "type_dotenv": "donut", 711 "name_dotenv": "Cake", 712 } 713 714 allkeys := sort.StringSlice(AllKeys()) 715 allkeys.Sort() 716 ks.Sort() 717 718 assert.Equal(t, ks, allkeys) 719 assert.Equal(t, all, AllSettings()) 720 } 721 722 func TestAllKeysWithEnv(t *testing.T) { 723 v := New() 724 725 // bind and define environment variables (including a nested one) 726 v.BindEnv("id") 727 v.BindEnv("foo.bar") 728 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 729 730 testutil.Setenv(t, "ID", "13") 731 testutil.Setenv(t, "FOO_BAR", "baz") 732 733 expectedKeys := sort.StringSlice{"id", "foo.bar"} 734 expectedKeys.Sort() 735 keys := sort.StringSlice(v.AllKeys()) 736 keys.Sort() 737 assert.Equal(t, expectedKeys, keys) 738 } 739 740 func TestAliasesOfAliases(t *testing.T) { 741 Set("Title", "Checking Case") 742 RegisterAlias("Foo", "Bar") 743 RegisterAlias("Bar", "Title") 744 assert.Equal(t, "Checking Case", Get("FOO")) 745 } 746 747 func TestRecursiveAliases(t *testing.T) { 748 RegisterAlias("Baz", "Roo") 749 RegisterAlias("Roo", "baz") 750 } 751 752 func TestUnmarshal(t *testing.T) { 753 SetDefault("port", 1313) 754 Set("name", "Steve") 755 Set("duration", "1s1ms") 756 Set("modes", []int{1, 2, 3}) 757 758 type config struct { 759 Port int 760 Name string 761 Duration time.Duration 762 Modes []int 763 } 764 765 var C config 766 767 err := Unmarshal(&C) 768 if err != nil { 769 t.Fatalf("unable to decode into struct, %v", err) 770 } 771 772 assert.Equal( 773 t, 774 &config{ 775 Name: "Steve", 776 Port: 1313, 777 Duration: time.Second + time.Millisecond, 778 Modes: []int{1, 2, 3}, 779 }, 780 &C, 781 ) 782 783 Set("port", 1234) 784 err = Unmarshal(&C) 785 if err != nil { 786 t.Fatalf("unable to decode into struct, %v", err) 787 } 788 789 assert.Equal( 790 t, 791 &config{ 792 Name: "Steve", 793 Port: 1234, 794 Duration: time.Second + time.Millisecond, 795 Modes: []int{1, 2, 3}, 796 }, 797 &C, 798 ) 799 } 800 801 func TestUnmarshalWithDecoderOptions(t *testing.T) { 802 Set("credentials", "{\"foo\":\"bar\"}") 803 804 opt := DecodeHook(mapstructure.ComposeDecodeHookFunc( 805 mapstructure.StringToTimeDurationHookFunc(), 806 mapstructure.StringToSliceHookFunc(","), 807 // Custom Decode Hook Function 808 func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { 809 if rf != reflect.String || rt != reflect.Map { 810 return data, nil 811 } 812 m := map[string]string{} 813 raw := data.(string) 814 if raw == "" { 815 return m, nil 816 } 817 return m, json.Unmarshal([]byte(raw), &m) 818 }, 819 )) 820 821 type config struct { 822 Credentials map[string]string 823 } 824 825 var C config 826 827 err := Unmarshal(&C, opt) 828 if err != nil { 829 t.Fatalf("unable to decode into struct, %v", err) 830 } 831 832 assert.Equal(t, &config{ 833 Credentials: map[string]string{"foo": "bar"}, 834 }, &C) 835 } 836 837 func TestBindPFlags(t *testing.T) { 838 v := New() // create independent Viper object 839 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 840 841 testValues := map[string]*string{ 842 "host": nil, 843 "port": nil, 844 "endpoint": nil, 845 } 846 847 mutatedTestValues := map[string]string{ 848 "host": "localhost", 849 "port": "6060", 850 "endpoint": "/public", 851 } 852 853 for name := range testValues { 854 testValues[name] = flagSet.String(name, "", "test") 855 } 856 857 err := v.BindPFlags(flagSet) 858 if err != nil { 859 t.Fatalf("error binding flag set, %v", err) 860 } 861 862 flagSet.VisitAll(func(flag *pflag.Flag) { 863 flag.Value.Set(mutatedTestValues[flag.Name]) 864 flag.Changed = true 865 }) 866 867 for name, expected := range mutatedTestValues { 868 assert.Equal(t, expected, v.Get(name)) 869 } 870 } 871 872 // nolint: dupl 873 func TestBindPFlagsStringSlice(t *testing.T) { 874 tests := []struct { 875 Expected []string 876 Value string 877 }{ 878 {[]string{}, ""}, 879 {[]string{"jeden"}, "jeden"}, 880 {[]string{"dwa", "trzy"}, "dwa,trzy"}, 881 {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}, 882 } 883 884 v := New() // create independent Viper object 885 defaultVal := []string{"default"} 886 v.SetDefault("stringslice", defaultVal) 887 888 for _, testValue := range tests { 889 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 890 flagSet.StringSlice("stringslice", testValue.Expected, "test") 891 892 for _, changed := range []bool{true, false} { 893 flagSet.VisitAll(func(f *pflag.Flag) { 894 f.Value.Set(testValue.Value) 895 f.Changed = changed 896 }) 897 898 err := v.BindPFlags(flagSet) 899 if err != nil { 900 t.Fatalf("error binding flag set, %v", err) 901 } 902 903 type TestStr struct { 904 StringSlice []string 905 } 906 val := &TestStr{} 907 if err := v.Unmarshal(val); err != nil { 908 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 909 } 910 if changed { 911 assert.Equal(t, testValue.Expected, val.StringSlice) 912 assert.Equal(t, testValue.Expected, v.Get("stringslice")) 913 } else { 914 assert.Equal(t, defaultVal, val.StringSlice) 915 } 916 } 917 } 918 } 919 920 // nolint: dupl 921 func TestBindPFlagsIntSlice(t *testing.T) { 922 tests := []struct { 923 Expected []int 924 Value string 925 }{ 926 {[]int{}, ""}, 927 {[]int{1}, "1"}, 928 {[]int{2, 3}, "2,3"}, 929 } 930 931 v := New() // create independent Viper object 932 defaultVal := []int{0} 933 v.SetDefault("intslice", defaultVal) 934 935 for _, testValue := range tests { 936 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 937 flagSet.IntSlice("intslice", testValue.Expected, "test") 938 939 for _, changed := range []bool{true, false} { 940 flagSet.VisitAll(func(f *pflag.Flag) { 941 f.Value.Set(testValue.Value) 942 f.Changed = changed 943 }) 944 945 err := v.BindPFlags(flagSet) 946 if err != nil { 947 t.Fatalf("error binding flag set, %v", err) 948 } 949 950 type TestInt struct { 951 IntSlice []int 952 } 953 val := &TestInt{} 954 if err := v.Unmarshal(val); err != nil { 955 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 956 } 957 if changed { 958 assert.Equal(t, testValue.Expected, val.IntSlice) 959 assert.Equal(t, testValue.Expected, v.Get("intslice")) 960 } else { 961 assert.Equal(t, defaultVal, val.IntSlice) 962 } 963 } 964 } 965 } 966 967 func TestBindPFlag(t *testing.T) { 968 testString := "testing" 969 testValue := newStringValue(testString, &testString) 970 971 flag := &pflag.Flag{ 972 Name: "testflag", 973 Value: testValue, 974 Changed: false, 975 } 976 977 BindPFlag("testvalue", flag) 978 979 assert.Equal(t, testString, Get("testvalue")) 980 981 flag.Value.Set("testing_mutate") 982 flag.Changed = true // hack for pflag usage 983 984 assert.Equal(t, "testing_mutate", Get("testvalue")) 985 } 986 987 func TestBindPFlagDetectNilFlag(t *testing.T) { 988 result := BindPFlag("testvalue", nil) 989 assert.Error(t, result) 990 } 991 992 func TestBindPFlagStringToString(t *testing.T) { 993 tests := []struct { 994 Expected map[string]string 995 Value string 996 }{ 997 {map[string]string{}, ""}, 998 {map[string]string{"yo": "hi"}, "yo=hi"}, 999 {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"}, 1000 {map[string]string{"yo": ""}, "yo="}, 1001 {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"}, 1002 } 1003 1004 v := New() // create independent Viper object 1005 defaultVal := map[string]string{} 1006 v.SetDefault("stringtostring", defaultVal) 1007 1008 for _, testValue := range tests { 1009 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1010 flagSet.StringToString("stringtostring", testValue.Expected, "test") 1011 1012 for _, changed := range []bool{true, false} { 1013 flagSet.VisitAll(func(f *pflag.Flag) { 1014 f.Value.Set(testValue.Value) 1015 f.Changed = changed 1016 }) 1017 1018 err := v.BindPFlags(flagSet) 1019 if err != nil { 1020 t.Fatalf("error binding flag set, %v", err) 1021 } 1022 1023 type TestMap struct { 1024 StringToString map[string]string 1025 } 1026 val := &TestMap{} 1027 if err := v.Unmarshal(val); err != nil { 1028 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1029 } 1030 if changed { 1031 assert.Equal(t, testValue.Expected, val.StringToString) 1032 } else { 1033 assert.Equal(t, defaultVal, val.StringToString) 1034 } 1035 } 1036 } 1037 } 1038 1039 func TestBoundCaseSensitivity(t *testing.T) { 1040 assert.Equal(t, "brown", Get("eyes")) 1041 1042 BindEnv("eYEs", "TURTLE_EYES") 1043 1044 testutil.Setenv(t, "TURTLE_EYES", "blue") 1045 1046 assert.Equal(t, "blue", Get("eyes")) 1047 1048 testString := "green" 1049 testValue := newStringValue(testString, &testString) 1050 1051 flag := &pflag.Flag{ 1052 Name: "eyeballs", 1053 Value: testValue, 1054 Changed: true, 1055 } 1056 1057 BindPFlag("eYEs", flag) 1058 assert.Equal(t, "green", Get("eyes")) 1059 } 1060 1061 func TestSizeInBytes(t *testing.T) { 1062 input := map[string]uint{ 1063 "": 0, 1064 "b": 0, 1065 "12 bytes": 0, 1066 "200000000000gb": 0, 1067 "12 b": 12, 1068 "43 MB": 43 * (1 << 20), 1069 "10mb": 10 * (1 << 20), 1070 "1gb": 1 << 30, 1071 } 1072 1073 for str, expected := range input { 1074 assert.Equal(t, expected, parseSizeInBytes(str), str) 1075 } 1076 } 1077 1078 func TestFindsNestedKeys(t *testing.T) { 1079 initConfigs() 1080 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 1081 1082 Set("super", map[string]interface{}{ 1083 "deep": map[string]interface{}{ 1084 "nested": "value", 1085 }, 1086 }) 1087 1088 expected := map[string]interface{}{ 1089 "super": map[string]interface{}{ 1090 "deep": map[string]interface{}{ 1091 "nested": "value", 1092 }, 1093 }, 1094 "super.deep": map[string]interface{}{ 1095 "nested": "value", 1096 }, 1097 "super.deep.nested": "value", 1098 "owner.organization": "MongoDB", 1099 "batters.batter": []interface{}{ 1100 map[string]interface{}{ 1101 "type": "Regular", 1102 }, 1103 map[string]interface{}{ 1104 "type": "Chocolate", 1105 }, 1106 map[string]interface{}{ 1107 "type": "Blueberry", 1108 }, 1109 map[string]interface{}{ 1110 "type": "Devil's Food", 1111 }, 1112 }, 1113 "hobbies": []interface{}{ 1114 "skateboarding", "snowboarding", "go", 1115 }, 1116 "TITLE_DOTENV": "DotEnv Example", 1117 "TYPE_DOTENV": "donut", 1118 "NAME_DOTENV": "Cake", 1119 "title": "TOML Example", 1120 "newkey": "remote", 1121 "batters": map[string]interface{}{ 1122 "batter": []interface{}{ 1123 map[string]interface{}{ 1124 "type": "Regular", 1125 }, 1126 map[string]interface{}{ 1127 "type": "Chocolate", 1128 }, 1129 map[string]interface{}{ 1130 "type": "Blueberry", 1131 }, 1132 map[string]interface{}{ 1133 "type": "Devil's Food", 1134 }, 1135 }, 1136 }, 1137 "eyes": "brown", 1138 "age": 35, 1139 "owner": map[string]interface{}{ 1140 "organization": "MongoDB", 1141 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1142 "dob": dob, 1143 }, 1144 "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1145 "type": "donut", 1146 "id": "0001", 1147 "name": "Cake", 1148 "hacker": true, 1149 "ppu": 0.55, 1150 "clothing": map[string]interface{}{ 1151 "jacket": "leather", 1152 "trousers": "denim", 1153 "pants": map[string]interface{}{ 1154 "size": "large", 1155 }, 1156 }, 1157 "clothing.jacket": "leather", 1158 "clothing.pants.size": "large", 1159 "clothing.trousers": "denim", 1160 "owner.dob": dob, 1161 "beard": true, 1162 "foos": []map[string]interface{}{ 1163 { 1164 "foo": []map[string]interface{}{ 1165 { 1166 "key": 1, 1167 }, 1168 { 1169 "key": 2, 1170 }, 1171 { 1172 "key": 3, 1173 }, 1174 { 1175 "key": 4, 1176 }, 1177 }, 1178 }, 1179 }, 1180 } 1181 1182 for key, expectedValue := range expected { 1183 assert.Equal(t, expectedValue, v.Get(key)) 1184 } 1185 } 1186 1187 func TestReadBufConfig(t *testing.T) { 1188 v := New() 1189 v.SetConfigType("yaml") 1190 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1191 t.Log(v.AllKeys()) 1192 1193 assert.True(t, v.InConfig("name")) 1194 assert.False(t, v.InConfig("state")) 1195 assert.Equal(t, "steve", v.Get("name")) 1196 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 1197 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) 1198 assert.Equal(t, 35, v.Get("age")) 1199 } 1200 1201 func TestIsSet(t *testing.T) { 1202 v := New() 1203 v.SetConfigType("yaml") 1204 1205 /* config and defaults */ 1206 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1207 v.SetDefault("clothing.shoes", "sneakers") 1208 1209 assert.True(t, v.IsSet("clothing")) 1210 assert.True(t, v.IsSet("clothing.jacket")) 1211 assert.False(t, v.IsSet("clothing.jackets")) 1212 assert.True(t, v.IsSet("clothing.shoes")) 1213 1214 /* state change */ 1215 assert.False(t, v.IsSet("helloworld")) 1216 v.Set("helloworld", "fubar") 1217 assert.True(t, v.IsSet("helloworld")) 1218 1219 /* env */ 1220 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 1221 v.BindEnv("eyes") 1222 v.BindEnv("foo") 1223 v.BindEnv("clothing.hat") 1224 v.BindEnv("clothing.hats") 1225 1226 testutil.Setenv(t, "FOO", "bar") 1227 testutil.Setenv(t, "CLOTHING_HAT", "bowler") 1228 1229 assert.True(t, v.IsSet("eyes")) // in the config file 1230 assert.True(t, v.IsSet("foo")) // in the environment 1231 assert.True(t, v.IsSet("clothing.hat")) // in the environment 1232 assert.False(t, v.IsSet("clothing.hats")) // not defined 1233 1234 /* flags */ 1235 flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError) 1236 flagset.Bool("foobaz", false, "foobaz") 1237 flagset.Bool("barbaz", false, "barbaz") 1238 foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz") 1239 v.BindPFlag("foobaz", foobaz) 1240 v.BindPFlag("barbaz", barbaz) 1241 barbaz.Value.Set("true") 1242 barbaz.Changed = true // hack for pflag usage 1243 1244 assert.False(t, v.IsSet("foobaz")) 1245 assert.True(t, v.IsSet("barbaz")) 1246 } 1247 1248 func TestDirsSearch(t *testing.T) { 1249 root, config, cleanup := initDirs(t) 1250 defer cleanup() 1251 1252 v := New() 1253 v.SetConfigName(config) 1254 v.SetDefault(`key`, `default`) 1255 1256 entries, err := ioutil.ReadDir(root) 1257 assert.Nil(t, err) 1258 for _, e := range entries { 1259 if e.IsDir() { 1260 v.AddConfigPath(e.Name()) 1261 } 1262 } 1263 1264 err = v.ReadInConfig() 1265 assert.Nil(t, err) 1266 1267 assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`)) 1268 } 1269 1270 func TestWrongDirsSearchNotFound(t *testing.T) { 1271 _, config, cleanup := initDirs(t) 1272 defer cleanup() 1273 1274 v := New() 1275 v.SetConfigName(config) 1276 v.SetDefault(`key`, `default`) 1277 1278 v.AddConfigPath(`whattayoutalkingbout`) 1279 v.AddConfigPath(`thispathaintthere`) 1280 1281 err := v.ReadInConfig() 1282 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1283 1284 // Even though config did not load and the error might have 1285 // been ignored by the client, the default still loads 1286 assert.Equal(t, `default`, v.GetString(`key`)) 1287 } 1288 1289 func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { 1290 _, config, cleanup := initDirs(t) 1291 defer cleanup() 1292 1293 v := New() 1294 v.SetConfigName(config) 1295 v.SetDefault(`key`, `default`) 1296 1297 v.AddConfigPath(`whattayoutalkingbout`) 1298 v.AddConfigPath(`thispathaintthere`) 1299 1300 err := v.MergeInConfig() 1301 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1302 1303 // Even though config did not load and the error might have 1304 // been ignored by the client, the default still loads 1305 assert.Equal(t, `default`, v.GetString(`key`)) 1306 } 1307 1308 func TestSub(t *testing.T) { 1309 v := New() 1310 v.SetConfigType("yaml") 1311 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1312 1313 subv := v.Sub("clothing") 1314 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) 1315 1316 subv = v.Sub("clothing.pants") 1317 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) 1318 1319 subv = v.Sub("clothing.pants.size") 1320 assert.Equal(t, (*Viper)(nil), subv) 1321 1322 subv = v.Sub("missing.key") 1323 assert.Equal(t, (*Viper)(nil), subv) 1324 } 1325 1326 var hclWriteExpected = []byte(`"foos" = { 1327 "foo" = { 1328 "key" = 1 1329 } 1330 1331 "foo" = { 1332 "key" = 2 1333 } 1334 1335 "foo" = { 1336 "key" = 3 1337 } 1338 1339 "foo" = { 1340 "key" = 4 1341 } 1342 } 1343 1344 "id" = "0001" 1345 1346 "name" = "Cake" 1347 1348 "ppu" = 0.55 1349 1350 "type" = "donut"`) 1351 1352 var jsonWriteExpected = []byte(`{ 1353 "batters": { 1354 "batter": [ 1355 { 1356 "type": "Regular" 1357 }, 1358 { 1359 "type": "Chocolate" 1360 }, 1361 { 1362 "type": "Blueberry" 1363 }, 1364 { 1365 "type": "Devil's Food" 1366 } 1367 ] 1368 }, 1369 "id": "0001", 1370 "name": "Cake", 1371 "ppu": 0.55, 1372 "type": "donut" 1373 }`) 1374 1375 var propertiesWriteExpected = []byte(`p_id = 0001 1376 p_type = donut 1377 p_name = Cake 1378 p_ppu = 0.55 1379 p_batters.batter.type = Regular 1380 `) 1381 1382 var yamlWriteExpected = []byte(`age: 35 1383 beard: true 1384 clothing: 1385 jacket: leather 1386 pants: 1387 size: large 1388 trousers: denim 1389 eyes: brown 1390 hacker: true 1391 hobbies: 1392 - skateboarding 1393 - snowboarding 1394 - go 1395 name: steve 1396 `) 1397 1398 func TestWriteConfig(t *testing.T) { 1399 fs := afero.NewMemMapFs() 1400 testCases := map[string]struct { 1401 configName string 1402 inConfigType string 1403 outConfigType string 1404 fileName string 1405 input []byte 1406 expectedContent []byte 1407 }{ 1408 "hcl with file extension": { 1409 configName: "c", 1410 inConfigType: "hcl", 1411 outConfigType: "hcl", 1412 fileName: "c.hcl", 1413 input: hclExample, 1414 expectedContent: hclWriteExpected, 1415 }, 1416 "hcl without file extension": { 1417 configName: "c", 1418 inConfigType: "hcl", 1419 outConfigType: "hcl", 1420 fileName: "c", 1421 input: hclExample, 1422 expectedContent: hclWriteExpected, 1423 }, 1424 "hcl with file extension and mismatch type": { 1425 configName: "c", 1426 inConfigType: "hcl", 1427 outConfigType: "json", 1428 fileName: "c.hcl", 1429 input: hclExample, 1430 expectedContent: hclWriteExpected, 1431 }, 1432 "json with file extension": { 1433 configName: "c", 1434 inConfigType: "json", 1435 outConfigType: "json", 1436 fileName: "c.json", 1437 input: jsonExample, 1438 expectedContent: jsonWriteExpected, 1439 }, 1440 "json without file extension": { 1441 configName: "c", 1442 inConfigType: "json", 1443 outConfigType: "json", 1444 fileName: "c", 1445 input: jsonExample, 1446 expectedContent: jsonWriteExpected, 1447 }, 1448 "json with file extension and mismatch type": { 1449 configName: "c", 1450 inConfigType: "json", 1451 outConfigType: "hcl", 1452 fileName: "c.json", 1453 input: jsonExample, 1454 expectedContent: jsonWriteExpected, 1455 }, 1456 "properties with file extension": { 1457 configName: "c", 1458 inConfigType: "properties", 1459 outConfigType: "properties", 1460 fileName: "c.properties", 1461 input: propertiesExample, 1462 expectedContent: propertiesWriteExpected, 1463 }, 1464 "properties without file extension": { 1465 configName: "c", 1466 inConfigType: "properties", 1467 outConfigType: "properties", 1468 fileName: "c", 1469 input: propertiesExample, 1470 expectedContent: propertiesWriteExpected, 1471 }, 1472 "yaml with file extension": { 1473 configName: "c", 1474 inConfigType: "yaml", 1475 outConfigType: "yaml", 1476 fileName: "c.yaml", 1477 input: yamlExample, 1478 expectedContent: yamlWriteExpected, 1479 }, 1480 "yaml without file extension": { 1481 configName: "c", 1482 inConfigType: "yaml", 1483 outConfigType: "yaml", 1484 fileName: "c", 1485 input: yamlExample, 1486 expectedContent: yamlWriteExpected, 1487 }, 1488 "yaml with file extension and mismatch type": { 1489 configName: "c", 1490 inConfigType: "yaml", 1491 outConfigType: "json", 1492 fileName: "c.yaml", 1493 input: yamlExample, 1494 expectedContent: yamlWriteExpected, 1495 }, 1496 } 1497 for name, tc := range testCases { 1498 t.Run(name, func(t *testing.T) { 1499 v := New() 1500 v.SetFs(fs) 1501 v.SetConfigName(tc.fileName) 1502 v.SetConfigType(tc.inConfigType) 1503 1504 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1505 if err != nil { 1506 t.Fatal(err) 1507 } 1508 v.SetConfigType(tc.outConfigType) 1509 if err := v.WriteConfigAs(tc.fileName); err != nil { 1510 t.Fatal(err) 1511 } 1512 read, err := afero.ReadFile(fs, tc.fileName) 1513 if err != nil { 1514 t.Fatal(err) 1515 } 1516 assert.Equal(t, tc.expectedContent, read) 1517 }) 1518 } 1519 } 1520 1521 func TestWriteConfigTOML(t *testing.T) { 1522 fs := afero.NewMemMapFs() 1523 1524 testCases := map[string]struct { 1525 configName string 1526 configType string 1527 fileName string 1528 input []byte 1529 }{ 1530 "with file extension": { 1531 configName: "c", 1532 configType: "toml", 1533 fileName: "c.toml", 1534 input: tomlExample, 1535 }, 1536 "without file extension": { 1537 configName: "c", 1538 configType: "toml", 1539 fileName: "c", 1540 input: tomlExample, 1541 }, 1542 } 1543 for name, tc := range testCases { 1544 t.Run(name, func(t *testing.T) { 1545 v := New() 1546 v.SetFs(fs) 1547 v.SetConfigName(tc.configName) 1548 v.SetConfigType(tc.configType) 1549 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1550 if err != nil { 1551 t.Fatal(err) 1552 } 1553 if err := v.WriteConfigAs(tc.fileName); err != nil { 1554 t.Fatal(err) 1555 } 1556 1557 // The TOML String method does not order the contents. 1558 // Therefore, we must read the generated file and compare the data. 1559 v2 := New() 1560 v2.SetFs(fs) 1561 v2.SetConfigName(tc.configName) 1562 v2.SetConfigType(tc.configType) 1563 v2.SetConfigFile(tc.fileName) 1564 err = v2.ReadInConfig() 1565 if err != nil { 1566 t.Fatal(err) 1567 } 1568 1569 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1570 assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) 1571 assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) 1572 assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) 1573 }) 1574 } 1575 } 1576 1577 func TestWriteConfigDotEnv(t *testing.T) { 1578 fs := afero.NewMemMapFs() 1579 testCases := map[string]struct { 1580 configName string 1581 configType string 1582 fileName string 1583 input []byte 1584 }{ 1585 "with file extension": { 1586 configName: "c", 1587 configType: "env", 1588 fileName: "c.env", 1589 input: dotenvExample, 1590 }, 1591 "without file extension": { 1592 configName: "c", 1593 configType: "env", 1594 fileName: "c", 1595 input: dotenvExample, 1596 }, 1597 } 1598 for name, tc := range testCases { 1599 t.Run(name, func(t *testing.T) { 1600 v := New() 1601 v.SetFs(fs) 1602 v.SetConfigName(tc.configName) 1603 v.SetConfigType(tc.configType) 1604 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1605 if err != nil { 1606 t.Fatal(err) 1607 } 1608 if err := v.WriteConfigAs(tc.fileName); err != nil { 1609 t.Fatal(err) 1610 } 1611 1612 // The TOML String method does not order the contents. 1613 // Therefore, we must read the generated file and compare the data. 1614 v2 := New() 1615 v2.SetFs(fs) 1616 v2.SetConfigName(tc.configName) 1617 v2.SetConfigType(tc.configType) 1618 v2.SetConfigFile(tc.fileName) 1619 err = v2.ReadInConfig() 1620 if err != nil { 1621 t.Fatal(err) 1622 } 1623 1624 assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv")) 1625 assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv")) 1626 assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv")) 1627 }) 1628 } 1629 } 1630 1631 func TestSafeWriteConfig(t *testing.T) { 1632 v := New() 1633 fs := afero.NewMemMapFs() 1634 v.SetFs(fs) 1635 v.AddConfigPath("/test") 1636 v.SetConfigName("c") 1637 v.SetConfigType("yaml") 1638 require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample))) 1639 require.NoError(t, v.SafeWriteConfig()) 1640 read, err := afero.ReadFile(fs, "/test/c.yaml") 1641 require.NoError(t, err) 1642 assert.Equal(t, yamlWriteExpected, read) 1643 } 1644 1645 func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) { 1646 v := New() 1647 fs := afero.NewMemMapFs() 1648 v.SetFs(fs) 1649 v.SetConfigName("c") 1650 v.SetConfigType("yaml") 1651 require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'") 1652 } 1653 1654 func TestSafeWriteConfigWithExistingFile(t *testing.T) { 1655 v := New() 1656 fs := afero.NewMemMapFs() 1657 fs.Create("/test/c.yaml") 1658 v.SetFs(fs) 1659 v.AddConfigPath("/test") 1660 v.SetConfigName("c") 1661 v.SetConfigType("yaml") 1662 err := v.SafeWriteConfig() 1663 require.Error(t, err) 1664 _, ok := err.(ConfigFileAlreadyExistsError) 1665 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1666 } 1667 1668 func TestSafeWriteAsConfig(t *testing.T) { 1669 v := New() 1670 fs := afero.NewMemMapFs() 1671 v.SetFs(fs) 1672 err := v.ReadConfig(bytes.NewBuffer(yamlExample)) 1673 if err != nil { 1674 t.Fatal(err) 1675 } 1676 require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml")) 1677 if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil { 1678 t.Fatal(err) 1679 } 1680 } 1681 1682 func TestSafeWriteConfigAsWithExistingFile(t *testing.T) { 1683 v := New() 1684 fs := afero.NewMemMapFs() 1685 fs.Create("/test/c.yaml") 1686 v.SetFs(fs) 1687 err := v.SafeWriteConfigAs("/test/c.yaml") 1688 require.Error(t, err) 1689 _, ok := err.(ConfigFileAlreadyExistsError) 1690 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1691 } 1692 1693 func TestWriteHiddenFile(t *testing.T) { 1694 v := New() 1695 fs := afero.NewMemMapFs() 1696 fs.Create("/test/.config") 1697 v.SetFs(fs) 1698 1699 v.SetConfigName(".config") 1700 v.SetConfigType("yaml") 1701 v.AddConfigPath("/test") 1702 1703 err := v.ReadInConfig() 1704 require.NoError(t, err) 1705 1706 err = v.WriteConfig() 1707 require.NoError(t, err) 1708 } 1709 1710 var yamlMergeExampleTgt = []byte(` 1711 hello: 1712 pop: 37890 1713 largenum: 765432101234567 1714 num2pow63: 9223372036854775808 1715 world: 1716 - us 1717 - uk 1718 - fr 1719 - de 1720 `) 1721 1722 var yamlMergeExampleSrc = []byte(` 1723 hello: 1724 pop: 45000 1725 largenum: 7654321001234567 1726 universe: 1727 - mw 1728 - ad 1729 ints: 1730 - 1 1731 - 2 1732 fu: bar 1733 `) 1734 1735 func TestMergeConfig(t *testing.T) { 1736 v := New() 1737 v.SetConfigType("yml") 1738 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1739 t.Fatal(err) 1740 } 1741 1742 if pop := v.GetInt("hello.pop"); pop != 37890 { 1743 t.Fatalf("pop != 37890, = %d", pop) 1744 } 1745 1746 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1747 t.Fatalf("pop != 37890, = %d", pop) 1748 } 1749 1750 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1751 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1752 } 1753 1754 if pop := v.GetUint("hello.pop"); pop != 37890 { 1755 t.Fatalf("uint pop != 37890, = %d", pop) 1756 } 1757 1758 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1759 t.Fatalf("uint32 pop != 37890, = %d", pop) 1760 } 1761 1762 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1763 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1764 } 1765 1766 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1767 t.Fatalf("len(world) != 4, = %d", len(world)) 1768 } 1769 1770 if fu := v.GetString("fu"); fu != "" { 1771 t.Fatalf("fu != \"\", = %s", fu) 1772 } 1773 1774 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1775 t.Fatal(err) 1776 } 1777 1778 if pop := v.GetInt("hello.pop"); pop != 45000 { 1779 t.Fatalf("pop != 45000, = %d", pop) 1780 } 1781 1782 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1783 t.Fatalf("pop != 45000, = %d", pop) 1784 } 1785 1786 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1787 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1788 } 1789 1790 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1791 t.Fatalf("len(world) != 4, = %d", len(world)) 1792 } 1793 1794 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1795 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1796 } 1797 1798 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1799 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1800 } 1801 1802 if fu := v.GetString("fu"); fu != "bar" { 1803 t.Fatalf("fu != \"bar\", = %s", fu) 1804 } 1805 } 1806 1807 func TestMergeConfigNoMerge(t *testing.T) { 1808 v := New() 1809 v.SetConfigType("yml") 1810 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1811 t.Fatal(err) 1812 } 1813 1814 if pop := v.GetInt("hello.pop"); pop != 37890 { 1815 t.Fatalf("pop != 37890, = %d", pop) 1816 } 1817 1818 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1819 t.Fatalf("len(world) != 4, = %d", len(world)) 1820 } 1821 1822 if fu := v.GetString("fu"); fu != "" { 1823 t.Fatalf("fu != \"\", = %s", fu) 1824 } 1825 1826 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1827 t.Fatal(err) 1828 } 1829 1830 if pop := v.GetInt("hello.pop"); pop != 45000 { 1831 t.Fatalf("pop != 45000, = %d", pop) 1832 } 1833 1834 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 1835 t.Fatalf("len(world) != 0, = %d", len(world)) 1836 } 1837 1838 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1839 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1840 } 1841 1842 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1843 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1844 } 1845 1846 if fu := v.GetString("fu"); fu != "bar" { 1847 t.Fatalf("fu != \"bar\", = %s", fu) 1848 } 1849 } 1850 1851 func TestMergeConfigMap(t *testing.T) { 1852 v := New() 1853 v.SetConfigType("yml") 1854 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1855 t.Fatal(err) 1856 } 1857 1858 assert := func(i int) { 1859 large := v.GetInt64("hello.largenum") 1860 pop := v.GetInt("hello.pop") 1861 if large != 765432101234567 { 1862 t.Fatal("Got large num:", large) 1863 } 1864 1865 if pop != i { 1866 t.Fatal("Got pop:", pop) 1867 } 1868 } 1869 1870 assert(37890) 1871 1872 update := map[string]interface{}{ 1873 "Hello": map[string]interface{}{ 1874 "Pop": 1234, 1875 }, 1876 "World": map[interface{}]interface{}{ 1877 "Rock": 345, 1878 }, 1879 } 1880 1881 if err := v.MergeConfigMap(update); err != nil { 1882 t.Fatal(err) 1883 } 1884 1885 if rock := v.GetInt("world.rock"); rock != 345 { 1886 t.Fatal("Got rock:", rock) 1887 } 1888 1889 assert(1234) 1890 } 1891 1892 func TestUnmarshalingWithAliases(t *testing.T) { 1893 v := New() 1894 v.SetDefault("ID", 1) 1895 v.Set("name", "Steve") 1896 v.Set("lastname", "Owen") 1897 1898 v.RegisterAlias("UserID", "ID") 1899 v.RegisterAlias("Firstname", "name") 1900 v.RegisterAlias("Surname", "lastname") 1901 1902 type config struct { 1903 ID int 1904 FirstName string 1905 Surname string 1906 } 1907 1908 var C config 1909 err := v.Unmarshal(&C) 1910 if err != nil { 1911 t.Fatalf("unable to decode into struct, %v", err) 1912 } 1913 1914 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 1915 } 1916 1917 func TestSetConfigNameClearsFileCache(t *testing.T) { 1918 SetConfigFile("/tmp/config.yaml") 1919 SetConfigName("default") 1920 f, err := v.getConfigFile() 1921 if err == nil { 1922 t.Fatalf("config file cache should have been cleared") 1923 } 1924 assert.Empty(t, f) 1925 } 1926 1927 func TestShadowedNestedValue(t *testing.T) { 1928 config := `name: steve 1929 clothing: 1930 jacket: leather 1931 trousers: denim 1932 pants: 1933 size: large 1934 ` 1935 initConfig("yaml", config) 1936 1937 assert.Equal(t, "steve", GetString("name")) 1938 1939 polyester := "polyester" 1940 SetDefault("clothing.shirt", polyester) 1941 SetDefault("clothing.jacket.price", 100) 1942 1943 assert.Equal(t, "leather", GetString("clothing.jacket")) 1944 assert.Nil(t, Get("clothing.jacket.price")) 1945 assert.Equal(t, polyester, GetString("clothing.shirt")) 1946 1947 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 1948 assert.Equal(t, "leather", clothingSettings["jacket"]) 1949 assert.Equal(t, polyester, clothingSettings["shirt"]) 1950 } 1951 1952 func TestDotParameter(t *testing.T) { 1953 initJSON() 1954 // shoud take precedence over batters defined in jsonExample 1955 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 1956 unmarshalReader(r, v.config) 1957 1958 actual := Get("batters.batter") 1959 expected := []interface{}{map[string]interface{}{"type": "Small"}} 1960 assert.Equal(t, expected, actual) 1961 } 1962 1963 func TestCaseInsensitive(t *testing.T) { 1964 for _, config := range []struct { 1965 typ string 1966 content string 1967 }{ 1968 {"yaml", ` 1969 aBcD: 1 1970 eF: 1971 gH: 2 1972 iJk: 3 1973 Lm: 1974 nO: 4 1975 P: 1976 Q: 5 1977 R: 6 1978 `}, 1979 {"json", `{ 1980 "aBcD": 1, 1981 "eF": { 1982 "iJk": 3, 1983 "Lm": { 1984 "P": { 1985 "Q": 5, 1986 "R": 6 1987 }, 1988 "nO": 4 1989 }, 1990 "gH": 2 1991 } 1992 }`}, 1993 {"toml", `aBcD = 1 1994 [eF] 1995 gH = 2 1996 iJk = 3 1997 [eF.Lm] 1998 nO = 4 1999 [eF.Lm.P] 2000 Q = 5 2001 R = 6 2002 `}, 2003 } { 2004 doTestCaseInsensitive(t, config.typ, config.content) 2005 } 2006 } 2007 2008 func TestCaseInsensitiveSet(t *testing.T) { 2009 Reset() 2010 m1 := map[string]interface{}{ 2011 "Foo": 32, 2012 "Bar": map[interface{}]interface { 2013 }{ 2014 "ABc": "A", 2015 "cDE": "B", 2016 }, 2017 } 2018 2019 m2 := map[string]interface{}{ 2020 "Foo": 52, 2021 "Bar": map[interface{}]interface { 2022 }{ 2023 "bCd": "A", 2024 "eFG": "B", 2025 }, 2026 } 2027 2028 Set("Given1", m1) 2029 Set("Number1", 42) 2030 2031 SetDefault("Given2", m2) 2032 SetDefault("Number2", 52) 2033 2034 // Verify SetDefault 2035 if v := Get("number2"); v != 52 { 2036 t.Fatalf("Expected 52 got %q", v) 2037 } 2038 2039 if v := Get("given2.foo"); v != 52 { 2040 t.Fatalf("Expected 52 got %q", v) 2041 } 2042 2043 if v := Get("given2.bar.bcd"); v != "A" { 2044 t.Fatalf("Expected A got %q", v) 2045 } 2046 2047 if _, ok := m2["Foo"]; !ok { 2048 t.Fatal("Input map changed") 2049 } 2050 2051 // Verify Set 2052 if v := Get("number1"); v != 42 { 2053 t.Fatalf("Expected 42 got %q", v) 2054 } 2055 2056 if v := Get("given1.foo"); v != 32 { 2057 t.Fatalf("Expected 32 got %q", v) 2058 } 2059 2060 if v := Get("given1.bar.abc"); v != "A" { 2061 t.Fatalf("Expected A got %q", v) 2062 } 2063 2064 if _, ok := m1["Foo"]; !ok { 2065 t.Fatal("Input map changed") 2066 } 2067 } 2068 2069 func TestParseNested(t *testing.T) { 2070 type duration struct { 2071 Delay time.Duration 2072 } 2073 2074 type item struct { 2075 Name string 2076 Delay time.Duration 2077 Nested duration 2078 } 2079 2080 config := `[[parent]] 2081 delay="100ms" 2082 [parent.nested] 2083 delay="200ms" 2084 ` 2085 initConfig("toml", config) 2086 2087 var items []item 2088 err := v.UnmarshalKey("parent", &items) 2089 if err != nil { 2090 t.Fatalf("unable to decode into struct, %v", err) 2091 } 2092 2093 assert.Equal(t, 1, len(items)) 2094 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 2095 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 2096 } 2097 2098 func doTestCaseInsensitive(t *testing.T, typ, config string) { 2099 initConfig(typ, config) 2100 Set("RfD", true) 2101 assert.Equal(t, true, Get("rfd")) 2102 assert.Equal(t, true, Get("rFD")) 2103 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 2104 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 2105 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 2106 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 2107 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 2108 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 2109 } 2110 2111 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 2112 watchDir, err := ioutil.TempDir("", "") 2113 require.Nil(t, err) 2114 configFile := path.Join(watchDir, "config.yaml") 2115 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) 2116 require.Nil(t, err) 2117 cleanup := func() { 2118 os.RemoveAll(watchDir) 2119 } 2120 v := New() 2121 v.SetConfigFile(configFile) 2122 err = v.ReadInConfig() 2123 require.Nil(t, err) 2124 require.Equal(t, "bar", v.Get("foo")) 2125 return v, configFile, cleanup 2126 } 2127 2128 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 2129 watchDir, err := ioutil.TempDir("", "") 2130 require.Nil(t, err) 2131 dataDir1 := path.Join(watchDir, "data1") 2132 err = os.Mkdir(dataDir1, 0777) 2133 require.Nil(t, err) 2134 realConfigFile := path.Join(dataDir1, "config.yaml") 2135 t.Logf("Real config file location: %s\n", realConfigFile) 2136 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) 2137 require.Nil(t, err) 2138 cleanup := func() { 2139 os.RemoveAll(watchDir) 2140 } 2141 // now, symlink the tm `data1` dir to `data` in the baseDir 2142 os.Symlink(dataDir1, path.Join(watchDir, "data")) 2143 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 2144 configFile := path.Join(watchDir, "config.yaml") 2145 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 2146 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 2147 // init Viper 2148 v := New() 2149 v.SetConfigFile(configFile) 2150 err = v.ReadInConfig() 2151 require.Nil(t, err) 2152 require.Equal(t, "bar", v.Get("foo")) 2153 return v, watchDir, configFile, cleanup 2154 } 2155 2156 func TestWatchFile(t *testing.T) { 2157 if runtime.GOOS == "linux" { 2158 // TODO(bep) FIX ME 2159 t.Skip("Skip test on Linux ...") 2160 } 2161 2162 t.Run("file content changed", func(t *testing.T) { 2163 // given a `config.yaml` file being watched 2164 v, configFile, cleanup := newViperWithConfigFile(t) 2165 defer cleanup() 2166 _, err := os.Stat(configFile) 2167 require.NoError(t, err) 2168 t.Logf("test config file: %s\n", configFile) 2169 wg := sync.WaitGroup{} 2170 wg.Add(1) 2171 v.OnConfigChange(func(in fsnotify.Event) { 2172 t.Logf("config file changed") 2173 wg.Done() 2174 }) 2175 v.WatchConfig() 2176 // when overwriting the file and waiting for the custom change notification handler to be triggered 2177 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) 2178 wg.Wait() 2179 // then the config value should have changed 2180 require.Nil(t, err) 2181 assert.Equal(t, "baz", v.Get("foo")) 2182 }) 2183 2184 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 2185 // skip if not executed on Linux 2186 if runtime.GOOS != "linux" { 2187 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 2188 } 2189 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 2190 // defer cleanup() 2191 wg := sync.WaitGroup{} 2192 v.WatchConfig() 2193 v.OnConfigChange(func(in fsnotify.Event) { 2194 t.Logf("config file changed") 2195 wg.Done() 2196 }) 2197 wg.Add(1) 2198 // when link to another `config.yaml` file 2199 dataDir2 := path.Join(watchDir, "data2") 2200 err := os.Mkdir(dataDir2, 0777) 2201 require.Nil(t, err) 2202 configFile2 := path.Join(dataDir2, "config.yaml") 2203 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) 2204 require.Nil(t, err) 2205 // change the symlink using the `ln -sfn` command 2206 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 2207 require.Nil(t, err) 2208 wg.Wait() 2209 // then 2210 require.Nil(t, err) 2211 assert.Equal(t, "baz", v.Get("foo")) 2212 }) 2213 } 2214 2215 func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 2216 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 2217 flags.String("foo.bar", "cobra_flag", "") 2218 2219 v := New() 2220 assert.NoError(t, v.BindPFlags(flags)) 2221 2222 config := &struct { 2223 Foo struct { 2224 Bar string 2225 } 2226 }{} 2227 2228 assert.NoError(t, v.Unmarshal(config)) 2229 assert.Equal(t, "cobra_flag", config.Foo.Bar) 2230 } 2231 2232 var yamlExampleWithDot = []byte(`Hacker: true 2233 name: steve 2234 hobbies: 2235 - skateboarding 2236 - snowboarding 2237 - go 2238 clothing: 2239 jacket: leather 2240 trousers: denim 2241 pants: 2242 size: large 2243 age: 35 2244 eyes : brown 2245 beard: true 2246 emails: 2247 steve@hacker.com: 2248 created: 01/02/03 2249 active: true 2250 `) 2251 2252 func TestKeyDelimiter(t *testing.T) { 2253 v := NewWithOptions(KeyDelimiter("::")) 2254 v.SetConfigType("yaml") 2255 r := strings.NewReader(string(yamlExampleWithDot)) 2256 2257 err := v.unmarshalReader(r, v.config) 2258 require.NoError(t, err) 2259 2260 values := map[string]interface{}{ 2261 "image": map[string]interface{}{ 2262 "repository": "someImage", 2263 "tag": "1.0.0", 2264 }, 2265 "ingress": map[string]interface{}{ 2266 "annotations": map[string]interface{}{ 2267 "traefik.frontend.rule.type": "PathPrefix", 2268 "traefik.ingress.kubernetes.io/ssl-redirect": "true", 2269 }, 2270 }, 2271 } 2272 2273 v.SetDefault("charts::values", values) 2274 2275 assert.Equal(t, "leather", v.GetString("clothing::jacket")) 2276 assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created")) 2277 2278 type config struct { 2279 Charts struct { 2280 Values map[string]interface{} 2281 } 2282 } 2283 2284 expected := config{ 2285 Charts: struct { 2286 Values map[string]interface{} 2287 }{ 2288 Values: values, 2289 }, 2290 } 2291 2292 var actual config 2293 2294 assert.NoError(t, v.Unmarshal(&actual)) 2295 2296 assert.Equal(t, expected, actual) 2297 } 2298 2299 var yamlDeepNestedSlices = []byte(`TV: 2300 - title: "The expanse" 2301 seasons: 2302 - first_released: "December 14, 2015" 2303 episodes: 2304 - title: "Dulcinea" 2305 air_date: "December 14, 2015" 2306 - title: "The Big Empty" 2307 air_date: "December 15, 2015" 2308 - title: "Remember the Cant" 2309 air_date: "December 22, 2015" 2310 - first_released: "February 1, 2017" 2311 episodes: 2312 - title: "Safe" 2313 air_date: "February 1, 2017" 2314 - title: "Doors & Corners" 2315 air_date: "February 1, 2017" 2316 - title: "Static" 2317 air_date: "February 8, 2017" 2318 episodes: 2319 - ["Dulcinea", "The Big Empty", "Remember the Cant"] 2320 - ["Safe", "Doors & Corners", "Static"] 2321 `) 2322 2323 func TestSliceIndexAccess(t *testing.T) { 2324 v.SetConfigType("yaml") 2325 r := strings.NewReader(string(yamlDeepNestedSlices)) 2326 2327 err := v.unmarshalReader(r, v.config) 2328 require.NoError(t, err) 2329 2330 assert.Equal(t, "The expanse", v.GetString("tv.0.title")) 2331 assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released")) 2332 assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title")) 2333 assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date")) 2334 2335 // Test for index out of bounds 2336 assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released")) 2337 2338 // Accessing multidimensional arrays 2339 assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2")) 2340 } 2341 2342 func BenchmarkGetBool(b *testing.B) { 2343 key := "BenchmarkGetBool" 2344 v = New() 2345 v.Set(key, true) 2346 2347 for i := 0; i < b.N; i++ { 2348 if !v.GetBool(key) { 2349 b.Fatal("GetBool returned false") 2350 } 2351 } 2352 } 2353 2354 func BenchmarkGet(b *testing.B) { 2355 key := "BenchmarkGet" 2356 v = New() 2357 v.Set(key, true) 2358 2359 for i := 0; i < b.N; i++ { 2360 if !v.Get(key).(bool) { 2361 b.Fatal("Get returned false") 2362 } 2363 } 2364 } 2365 2366 // BenchmarkGetBoolFromMap is the "perfect result" for the above. 2367 func BenchmarkGetBoolFromMap(b *testing.B) { 2368 m := make(map[string]bool) 2369 key := "BenchmarkGetBool" 2370 m[key] = true 2371 2372 for i := 0; i < b.N; i++ { 2373 if !m[key] { 2374 b.Fatal("Map value was false") 2375 } 2376 } 2377 }