github.com/Zyvik/viper@v1.7.2-0.20210523130016-5b1eb90a6789/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/spf13/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 var yamlMergeExampleTgt = []byte(` 1694 hello: 1695 pop: 37890 1696 largenum: 765432101234567 1697 num2pow63: 9223372036854775808 1698 universe: null 1699 world: 1700 - us 1701 - uk 1702 - fr 1703 - de 1704 `) 1705 1706 var yamlMergeExampleSrc = []byte(` 1707 hello: 1708 pop: 45000 1709 largenum: 7654321001234567 1710 universe: 1711 - mw 1712 - ad 1713 ints: 1714 - 1 1715 - 2 1716 fu: bar 1717 `) 1718 1719 func TestMergeConfig(t *testing.T) { 1720 v := New() 1721 v.SetConfigType("yml") 1722 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1723 t.Fatal(err) 1724 } 1725 1726 if pop := v.GetInt("hello.pop"); pop != 37890 { 1727 t.Fatalf("pop != 37890, = %d", pop) 1728 } 1729 1730 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1731 t.Fatalf("pop != 37890, = %d", pop) 1732 } 1733 1734 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1735 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1736 } 1737 1738 if pop := v.GetUint("hello.pop"); pop != 37890 { 1739 t.Fatalf("uint pop != 37890, = %d", pop) 1740 } 1741 1742 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1743 t.Fatalf("uint32 pop != 37890, = %d", pop) 1744 } 1745 1746 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1747 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1748 } 1749 1750 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1751 t.Fatalf("len(world) != 4, = %d", len(world)) 1752 } 1753 1754 if fu := v.GetString("fu"); fu != "" { 1755 t.Fatalf("fu != \"\", = %s", fu) 1756 } 1757 1758 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1759 t.Fatal(err) 1760 } 1761 1762 if pop := v.GetInt("hello.pop"); pop != 45000 { 1763 t.Fatalf("pop != 45000, = %d", pop) 1764 } 1765 1766 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1767 t.Fatalf("pop != 45000, = %d", pop) 1768 } 1769 1770 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1771 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1772 } 1773 1774 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1775 t.Fatalf("len(world) != 4, = %d", len(world)) 1776 } 1777 1778 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1779 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1780 } 1781 1782 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1783 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1784 } 1785 1786 if fu := v.GetString("fu"); fu != "bar" { 1787 t.Fatalf("fu != \"bar\", = %s", fu) 1788 } 1789 } 1790 1791 func TestMergeConfigNoMerge(t *testing.T) { 1792 v := New() 1793 v.SetConfigType("yml") 1794 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1795 t.Fatal(err) 1796 } 1797 1798 if pop := v.GetInt("hello.pop"); pop != 37890 { 1799 t.Fatalf("pop != 37890, = %d", pop) 1800 } 1801 1802 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1803 t.Fatalf("len(world) != 4, = %d", len(world)) 1804 } 1805 1806 if fu := v.GetString("fu"); fu != "" { 1807 t.Fatalf("fu != \"\", = %s", fu) 1808 } 1809 1810 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1811 t.Fatal(err) 1812 } 1813 1814 if pop := v.GetInt("hello.pop"); pop != 45000 { 1815 t.Fatalf("pop != 45000, = %d", pop) 1816 } 1817 1818 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 1819 t.Fatalf("len(world) != 0, = %d", len(world)) 1820 } 1821 1822 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1823 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1824 } 1825 1826 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1827 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1828 } 1829 1830 if fu := v.GetString("fu"); fu != "bar" { 1831 t.Fatalf("fu != \"bar\", = %s", fu) 1832 } 1833 } 1834 1835 func TestMergeConfigMap(t *testing.T) { 1836 v := New() 1837 v.SetConfigType("yml") 1838 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1839 t.Fatal(err) 1840 } 1841 1842 assert := func(i int) { 1843 large := v.GetInt64("hello.largenum") 1844 pop := v.GetInt("hello.pop") 1845 if large != 765432101234567 { 1846 t.Fatal("Got large num:", large) 1847 } 1848 1849 if pop != i { 1850 t.Fatal("Got pop:", pop) 1851 } 1852 } 1853 1854 assert(37890) 1855 1856 update := map[string]interface{}{ 1857 "Hello": map[string]interface{}{ 1858 "Pop": 1234, 1859 }, 1860 "World": map[interface{}]interface{}{ 1861 "Rock": 345, 1862 }, 1863 } 1864 1865 if err := v.MergeConfigMap(update); err != nil { 1866 t.Fatal(err) 1867 } 1868 1869 if rock := v.GetInt("world.rock"); rock != 345 { 1870 t.Fatal("Got rock:", rock) 1871 } 1872 1873 assert(1234) 1874 } 1875 1876 func TestUnmarshalingWithAliases(t *testing.T) { 1877 v := New() 1878 v.SetDefault("ID", 1) 1879 v.Set("name", "Steve") 1880 v.Set("lastname", "Owen") 1881 1882 v.RegisterAlias("UserID", "ID") 1883 v.RegisterAlias("Firstname", "name") 1884 v.RegisterAlias("Surname", "lastname") 1885 1886 type config struct { 1887 ID int 1888 FirstName string 1889 Surname string 1890 } 1891 1892 var C config 1893 err := v.Unmarshal(&C) 1894 if err != nil { 1895 t.Fatalf("unable to decode into struct, %v", err) 1896 } 1897 1898 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 1899 } 1900 1901 func TestSetConfigNameClearsFileCache(t *testing.T) { 1902 SetConfigFile("/tmp/config.yaml") 1903 SetConfigName("default") 1904 f, err := v.getConfigFile() 1905 if err == nil { 1906 t.Fatalf("config file cache should have been cleared") 1907 } 1908 assert.Empty(t, f) 1909 } 1910 1911 func TestShadowedNestedValue(t *testing.T) { 1912 config := `name: steve 1913 clothing: 1914 jacket: leather 1915 trousers: denim 1916 pants: 1917 size: large 1918 ` 1919 initConfig("yaml", config) 1920 1921 assert.Equal(t, "steve", GetString("name")) 1922 1923 polyester := "polyester" 1924 SetDefault("clothing.shirt", polyester) 1925 SetDefault("clothing.jacket.price", 100) 1926 1927 assert.Equal(t, "leather", GetString("clothing.jacket")) 1928 assert.Nil(t, Get("clothing.jacket.price")) 1929 assert.Equal(t, polyester, GetString("clothing.shirt")) 1930 1931 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 1932 assert.Equal(t, "leather", clothingSettings["jacket"]) 1933 assert.Equal(t, polyester, clothingSettings["shirt"]) 1934 } 1935 1936 func TestDotParameter(t *testing.T) { 1937 initJSON() 1938 // shoud take precedence over batters defined in jsonExample 1939 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 1940 unmarshalReader(r, v.config) 1941 1942 actual := Get("batters.batter") 1943 expected := []interface{}{map[string]interface{}{"type": "Small"}} 1944 assert.Equal(t, expected, actual) 1945 } 1946 1947 func TestCaseInsensitive(t *testing.T) { 1948 for _, config := range []struct { 1949 typ string 1950 content string 1951 }{ 1952 {"yaml", ` 1953 aBcD: 1 1954 eF: 1955 gH: 2 1956 iJk: 3 1957 Lm: 1958 nO: 4 1959 P: 1960 Q: 5 1961 R: 6 1962 `}, 1963 {"json", `{ 1964 "aBcD": 1, 1965 "eF": { 1966 "iJk": 3, 1967 "Lm": { 1968 "P": { 1969 "Q": 5, 1970 "R": 6 1971 }, 1972 "nO": 4 1973 }, 1974 "gH": 2 1975 } 1976 }`}, 1977 {"toml", `aBcD = 1 1978 [eF] 1979 gH = 2 1980 iJk = 3 1981 [eF.Lm] 1982 nO = 4 1983 [eF.Lm.P] 1984 Q = 5 1985 R = 6 1986 `}, 1987 } { 1988 doTestCaseInsensitive(t, config.typ, config.content) 1989 } 1990 } 1991 1992 func TestCaseInsensitiveSet(t *testing.T) { 1993 Reset() 1994 m1 := map[string]interface{}{ 1995 "Foo": 32, 1996 "Bar": map[interface{}]interface { 1997 }{ 1998 "ABc": "A", 1999 "cDE": "B", 2000 }, 2001 } 2002 2003 m2 := map[string]interface{}{ 2004 "Foo": 52, 2005 "Bar": map[interface{}]interface { 2006 }{ 2007 "bCd": "A", 2008 "eFG": "B", 2009 }, 2010 } 2011 2012 Set("Given1", m1) 2013 Set("Number1", 42) 2014 2015 SetDefault("Given2", m2) 2016 SetDefault("Number2", 52) 2017 2018 // Verify SetDefault 2019 if v := Get("number2"); v != 52 { 2020 t.Fatalf("Expected 52 got %q", v) 2021 } 2022 2023 if v := Get("given2.foo"); v != 52 { 2024 t.Fatalf("Expected 52 got %q", v) 2025 } 2026 2027 if v := Get("given2.bar.bcd"); v != "A" { 2028 t.Fatalf("Expected A got %q", v) 2029 } 2030 2031 if _, ok := m2["Foo"]; !ok { 2032 t.Fatal("Input map changed") 2033 } 2034 2035 // Verify Set 2036 if v := Get("number1"); v != 42 { 2037 t.Fatalf("Expected 42 got %q", v) 2038 } 2039 2040 if v := Get("given1.foo"); v != 32 { 2041 t.Fatalf("Expected 32 got %q", v) 2042 } 2043 2044 if v := Get("given1.bar.abc"); v != "A" { 2045 t.Fatalf("Expected A got %q", v) 2046 } 2047 2048 if _, ok := m1["Foo"]; !ok { 2049 t.Fatal("Input map changed") 2050 } 2051 } 2052 2053 func TestParseNested(t *testing.T) { 2054 type duration struct { 2055 Delay time.Duration 2056 } 2057 2058 type item struct { 2059 Name string 2060 Delay time.Duration 2061 Nested duration 2062 } 2063 2064 config := `[[parent]] 2065 delay="100ms" 2066 [parent.nested] 2067 delay="200ms" 2068 ` 2069 initConfig("toml", config) 2070 2071 var items []item 2072 err := v.UnmarshalKey("parent", &items) 2073 if err != nil { 2074 t.Fatalf("unable to decode into struct, %v", err) 2075 } 2076 2077 assert.Equal(t, 1, len(items)) 2078 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 2079 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 2080 } 2081 2082 func doTestCaseInsensitive(t *testing.T, typ, config string) { 2083 initConfig(typ, config) 2084 Set("RfD", true) 2085 assert.Equal(t, true, Get("rfd")) 2086 assert.Equal(t, true, Get("rFD")) 2087 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 2088 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 2089 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 2090 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 2091 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 2092 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 2093 } 2094 2095 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 2096 watchDir, err := ioutil.TempDir("", "") 2097 require.Nil(t, err) 2098 configFile := path.Join(watchDir, "config.yaml") 2099 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) 2100 require.Nil(t, err) 2101 cleanup := func() { 2102 os.RemoveAll(watchDir) 2103 } 2104 v := New() 2105 v.SetConfigFile(configFile) 2106 err = v.ReadInConfig() 2107 require.Nil(t, err) 2108 require.Equal(t, "bar", v.Get("foo")) 2109 return v, configFile, cleanup 2110 } 2111 2112 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 2113 watchDir, err := ioutil.TempDir("", "") 2114 require.Nil(t, err) 2115 dataDir1 := path.Join(watchDir, "data1") 2116 err = os.Mkdir(dataDir1, 0777) 2117 require.Nil(t, err) 2118 realConfigFile := path.Join(dataDir1, "config.yaml") 2119 t.Logf("Real config file location: %s\n", realConfigFile) 2120 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) 2121 require.Nil(t, err) 2122 cleanup := func() { 2123 os.RemoveAll(watchDir) 2124 } 2125 // now, symlink the tm `data1` dir to `data` in the baseDir 2126 os.Symlink(dataDir1, path.Join(watchDir, "data")) 2127 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 2128 configFile := path.Join(watchDir, "config.yaml") 2129 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 2130 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 2131 // init Viper 2132 v := New() 2133 v.SetConfigFile(configFile) 2134 err = v.ReadInConfig() 2135 require.Nil(t, err) 2136 require.Equal(t, "bar", v.Get("foo")) 2137 return v, watchDir, configFile, cleanup 2138 } 2139 2140 func TestWatchFile(t *testing.T) { 2141 if runtime.GOOS == "linux" { 2142 // TODO(bep) FIX ME 2143 t.Skip("Skip test on Linux ...") 2144 } 2145 2146 t.Run("file content changed", func(t *testing.T) { 2147 // given a `config.yaml` file being watched 2148 v, configFile, cleanup := newViperWithConfigFile(t) 2149 defer cleanup() 2150 _, err := os.Stat(configFile) 2151 require.NoError(t, err) 2152 t.Logf("test config file: %s\n", configFile) 2153 wg := sync.WaitGroup{} 2154 wg.Add(1) 2155 v.OnConfigChange(func(in fsnotify.Event) { 2156 t.Logf("config file changed") 2157 wg.Done() 2158 }) 2159 v.WatchConfig() 2160 // when overwriting the file and waiting for the custom change notification handler to be triggered 2161 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) 2162 wg.Wait() 2163 // then the config value should have changed 2164 require.Nil(t, err) 2165 assert.Equal(t, "baz", v.Get("foo")) 2166 }) 2167 2168 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 2169 // skip if not executed on Linux 2170 if runtime.GOOS != "linux" { 2171 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 2172 } 2173 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 2174 // defer cleanup() 2175 wg := sync.WaitGroup{} 2176 v.WatchConfig() 2177 v.OnConfigChange(func(in fsnotify.Event) { 2178 t.Logf("config file changed") 2179 wg.Done() 2180 }) 2181 wg.Add(1) 2182 // when link to another `config.yaml` file 2183 dataDir2 := path.Join(watchDir, "data2") 2184 err := os.Mkdir(dataDir2, 0777) 2185 require.Nil(t, err) 2186 configFile2 := path.Join(dataDir2, "config.yaml") 2187 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) 2188 require.Nil(t, err) 2189 // change the symlink using the `ln -sfn` command 2190 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 2191 require.Nil(t, err) 2192 wg.Wait() 2193 // then 2194 require.Nil(t, err) 2195 assert.Equal(t, "baz", v.Get("foo")) 2196 }) 2197 } 2198 2199 func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 2200 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 2201 flags.String("foo.bar", "cobra_flag", "") 2202 2203 v := New() 2204 assert.NoError(t, v.BindPFlags(flags)) 2205 2206 config := &struct { 2207 Foo struct { 2208 Bar string 2209 } 2210 }{} 2211 2212 assert.NoError(t, v.Unmarshal(config)) 2213 assert.Equal(t, "cobra_flag", config.Foo.Bar) 2214 } 2215 2216 var yamlExampleWithDot = []byte(`Hacker: true 2217 name: steve 2218 hobbies: 2219 - skateboarding 2220 - snowboarding 2221 - go 2222 clothing: 2223 jacket: leather 2224 trousers: denim 2225 pants: 2226 size: large 2227 age: 35 2228 eyes : brown 2229 beard: true 2230 emails: 2231 steve@hacker.com: 2232 created: 01/02/03 2233 active: true 2234 `) 2235 2236 func TestKeyDelimiter(t *testing.T) { 2237 v := NewWithOptions(KeyDelimiter("::")) 2238 v.SetConfigType("yaml") 2239 r := strings.NewReader(string(yamlExampleWithDot)) 2240 2241 err := v.unmarshalReader(r, v.config) 2242 require.NoError(t, err) 2243 2244 values := map[string]interface{}{ 2245 "image": map[string]interface{}{ 2246 "repository": "someImage", 2247 "tag": "1.0.0", 2248 }, 2249 "ingress": map[string]interface{}{ 2250 "annotations": map[string]interface{}{ 2251 "traefik.frontend.rule.type": "PathPrefix", 2252 "traefik.ingress.kubernetes.io/ssl-redirect": "true", 2253 }, 2254 }, 2255 } 2256 2257 v.SetDefault("charts::values", values) 2258 2259 assert.Equal(t, "leather", v.GetString("clothing::jacket")) 2260 assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created")) 2261 2262 type config struct { 2263 Charts struct { 2264 Values map[string]interface{} 2265 } 2266 } 2267 2268 expected := config{ 2269 Charts: struct { 2270 Values map[string]interface{} 2271 }{ 2272 Values: values, 2273 }, 2274 } 2275 2276 var actual config 2277 2278 assert.NoError(t, v.Unmarshal(&actual)) 2279 2280 assert.Equal(t, expected, actual) 2281 } 2282 2283 var yamlDeepNestedSlices = []byte(`TV: 2284 - title: "The expanse" 2285 seasons: 2286 - first_released: "December 14, 2015" 2287 episodes: 2288 - title: "Dulcinea" 2289 air_date: "December 14, 2015" 2290 - title: "The Big Empty" 2291 air_date: "December 15, 2015" 2292 - title: "Remember the Cant" 2293 air_date: "December 22, 2015" 2294 - first_released: "February 1, 2017" 2295 episodes: 2296 - title: "Safe" 2297 air_date: "February 1, 2017" 2298 - title: "Doors & Corners" 2299 air_date: "February 1, 2017" 2300 - title: "Static" 2301 air_date: "February 8, 2017" 2302 episodes: 2303 - ["Dulcinea", "The Big Empty", "Remember the Cant"] 2304 - ["Safe", "Doors & Corners", "Static"] 2305 `) 2306 2307 func TestSliceIndexAccess(t *testing.T) { 2308 v.SetConfigType("yaml") 2309 r := strings.NewReader(string(yamlDeepNestedSlices)) 2310 2311 err := v.unmarshalReader(r, v.config) 2312 require.NoError(t, err) 2313 2314 assert.Equal(t, "The expanse", v.GetString("tv.0.title")) 2315 assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released")) 2316 assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title")) 2317 assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date")) 2318 2319 // Test for index out of bounds 2320 assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released")) 2321 2322 // Accessing multidimensional arrays 2323 assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2")) 2324 } 2325 2326 func BenchmarkGetBool(b *testing.B) { 2327 key := "BenchmarkGetBool" 2328 v = New() 2329 v.Set(key, true) 2330 2331 for i := 0; i < b.N; i++ { 2332 if !v.GetBool(key) { 2333 b.Fatal("GetBool returned false") 2334 } 2335 } 2336 } 2337 2338 func BenchmarkGet(b *testing.B) { 2339 key := "BenchmarkGet" 2340 v = New() 2341 v.Set(key, true) 2342 2343 for i := 0; i < b.N; i++ { 2344 if !v.Get(key).(bool) { 2345 b.Fatal("Get returned false") 2346 } 2347 } 2348 } 2349 2350 // BenchmarkGetBoolFromMap is the "perfect result" for the above. 2351 func BenchmarkGetBoolFromMap(b *testing.B) { 2352 m := make(map[string]bool) 2353 key := "BenchmarkGetBool" 2354 m[key] = true 2355 2356 for i := 0; i < b.N; i++ { 2357 if !m[key] { 2358 b.Fatal("Map value was false") 2359 } 2360 } 2361 }