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