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