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 6package viper 7 8import ( 9 "bytes" 10 "encoding/json" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 17 "reflect" 18 "runtime" 19 "sort" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/fsnotify/fsnotify" 26 "github.com/mitchellh/mapstructure" 27 "github.com/spf13/afero" 28 "github.com/spf13/cast" 29 "github.com/spf13/pflag" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 33 "github.com/spf13/viper/internal/testutil" 34) 35 36var yamlExample = []byte(`Hacker: true 37name: steve 38hobbies: 39- skateboarding 40- snowboarding 41- go 42clothing: 43 jacket: leather 44 trousers: denim 45 pants: 46 size: large 47age: 35 48eyes : brown 49beard: true 50`) 51 52var yamlExampleWithExtras = []byte(`Existing: true 53Bogus: true 54`) 55 56type testUnmarshalExtra struct { 57 Existing bool 58} 59 60var tomlExample = []byte(` 61title = "TOML Example" 62 63[owner] 64organization = "MongoDB" 65Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 66dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 67 68var dotenvExample = []byte(` 69TITLE_DOTENV="DotEnv Example" 70TYPE_DOTENV=donut 71NAME_DOTENV=Cake`) 72 73var 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 88var hclExample = []byte(` 89id = "0001" 90type = "donut" 91name = "Cake" 92ppu = 0.55 93foos { 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 108var propertiesExample = []byte(` 109p_id: 0001 110p_type: donut 111p_name: Cake 112p_ppu: 0.55 113p_batters.batter.type: Regular 114`) 115 116var remoteExample = []byte(`{ 117"id":"0002", 118"type":"cronut", 119"newkey":"remote" 120}`) 121 122var iniExample = []byte(`; Package name 123NAME = ini 124; Package version 125VERSION = v1 126; Package import path 127IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s 128 129# Information about package author 130# Bio can be written in multiple lines. 131[author] 132NAME = Unknown ; Succeeding comment 133E-MAIL = fake@localhost 134GITHUB = https://github.com/%(NAME)s 135BIO = """Gopher. 136Coding addict. 137Good man. 138""" # Succeeding comment`) 139 140func 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 176func 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 186func initYAML() { 187 initConfig("yaml", string(yamlExample)) 188} 189 190func initJSON() { 191 Reset() 192 SetConfigType("json") 193 r := bytes.NewReader(jsonExample) 194 195 unmarshalReader(r, v.config) 196} 197 198func initProperties() { 199 Reset() 200 SetConfigType("properties") 201 r := bytes.NewReader(propertiesExample) 202 203 unmarshalReader(r, v.config) 204} 205 206func initTOML() { 207 Reset() 208 SetConfigType("toml") 209 r := bytes.NewReader(tomlExample) 210 211 unmarshalReader(r, v.config) 212} 213 214func initDotEnv() { 215 Reset() 216 SetConfigType("env") 217 r := bytes.NewReader(dotenvExample) 218 219 unmarshalReader(r, v.config) 220} 221 222func initHcl() { 223 Reset() 224 SetConfigType("hcl") 225 r := bytes.NewReader(hclExample) 226 227 unmarshalReader(r, v.config) 228} 229 230func initIni() { 231 Reset() 232 SetConfigType("ini") 233 r := bytes.NewReader(iniExample) 234 235 unmarshalReader(r, v.config) 236} 237 238// make directories for testing 239func 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 284type stringValue string 285 286func newStringValue(val string, p *string) *stringValue { 287 *p = val 288 return (*stringValue)(p) 289} 290 291func (s *stringValue) Set(val string) error { 292 *s = stringValue(val) 293 return nil 294} 295 296func (s *stringValue) Type() string { 297 return "string" 298} 299 300func (s *stringValue) String() string { 301 return string(*s) 302} 303 304func 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 311func 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 328func 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 345func 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 365func 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 379func 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 392func 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 404func TestOverrides(t *testing.T) { 405 Set("age", 40) 406 assert.Equal(t, 40, Get("age")) 407} 408 409func TestDefaultPost(t *testing.T) { 410 assert.NotEqual(t, "NYC", Get("state")) 411 SetDefault("state", "NYC") 412 assert.Equal(t, "NYC", Get("state")) 413} 414 415func 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 422func 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 431func TestYML(t *testing.T) { 432 initYAML() 433 assert.Equal(t, "steve", Get("name")) 434} 435 436func TestJSON(t *testing.T) { 437 initJSON() 438 assert.Equal(t, "0001", Get("id")) 439} 440 441func TestProperties(t *testing.T) { 442 initProperties() 443 assert.Equal(t, "0001", Get("p_id")) 444} 445 446func TestTOML(t *testing.T) { 447 initTOML() 448 assert.Equal(t, "TOML Example", Get("title")) 449} 450 451func TestDotEnv(t *testing.T) { 452 initDotEnv() 453 assert.Equal(t, "DotEnv Example", Get("title_dotenv")) 454} 455 456func 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 467func TestIni(t *testing.T) { 468 initIni() 469 assert.Equal(t, "ini", Get("default.name")) 470} 471 472func 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 487func 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 507func 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 517func 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 529func 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 543func 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 563func 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 573func 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 584func 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 597func 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 607func 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 722func 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 740func 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 747func TestRecursiveAliases(t *testing.T) { 748 RegisterAlias("Baz", "Roo") 749 RegisterAlias("Roo", "baz") 750} 751 752func 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 801func 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 837func 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 873func 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 921func TestBindPFlagsStringArray(t *testing.T) { 922 tests := []struct { 923 Expected []string 924 Value string 925 }{ 926 {[]string{}, ""}, 927 {[]string{"jeden"}, "jeden"}, 928 {[]string{"dwa,trzy"}, "dwa,trzy"}, 929 {[]string{"cztery,\"piec , szesc\""}, "cztery,\"piec , szesc\""}, 930 } 931 932 v := New() // create independent Viper object 933 defaultVal := []string{"default"} 934 v.SetDefault("stringarray", defaultVal) 935 936 for _, testValue := range tests { 937 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 938 flagSet.StringArray("stringarray", testValue.Expected, "test") 939 940 for _, changed := range []bool{true, false} { 941 flagSet.VisitAll(func(f *pflag.Flag) { 942 f.Value.Set(testValue.Value) 943 f.Changed = changed 944 }) 945 946 err := v.BindPFlags(flagSet) 947 if err != nil { 948 t.Fatalf("error binding flag set, %v", err) 949 } 950 951 type TestStr struct { 952 StringArray []string 953 } 954 val := &TestStr{} 955 if err := v.Unmarshal(val); err != nil { 956 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 957 } 958 if changed { 959 assert.Equal(t, testValue.Expected, val.StringArray) 960 assert.Equal(t, testValue.Expected, v.Get("stringarray")) 961 } else { 962 assert.Equal(t, defaultVal, val.StringArray) 963 } 964 } 965 } 966} 967 968// nolint: dupl 969func TestBindPFlagsIntSlice(t *testing.T) { 970 tests := []struct { 971 Expected []int 972 Value string 973 }{ 974 {[]int{}, ""}, 975 {[]int{1}, "1"}, 976 {[]int{2, 3}, "2,3"}, 977 } 978 979 v := New() // create independent Viper object 980 defaultVal := []int{0} 981 v.SetDefault("intslice", defaultVal) 982 983 for _, testValue := range tests { 984 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 985 flagSet.IntSlice("intslice", testValue.Expected, "test") 986 987 for _, changed := range []bool{true, false} { 988 flagSet.VisitAll(func(f *pflag.Flag) { 989 f.Value.Set(testValue.Value) 990 f.Changed = changed 991 }) 992 993 err := v.BindPFlags(flagSet) 994 if err != nil { 995 t.Fatalf("error binding flag set, %v", err) 996 } 997 998 type TestInt struct { 999 IntSlice []int 1000 } 1001 val := &TestInt{} 1002 if err := v.Unmarshal(val); err != nil { 1003 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1004 } 1005 if changed { 1006 assert.Equal(t, testValue.Expected, val.IntSlice) 1007 assert.Equal(t, testValue.Expected, v.Get("intslice")) 1008 } else { 1009 assert.Equal(t, defaultVal, val.IntSlice) 1010 } 1011 } 1012 } 1013} 1014 1015func TestBindPFlag(t *testing.T) { 1016 testString := "testing" 1017 testValue := newStringValue(testString, &testString) 1018 1019 flag := &pflag.Flag{ 1020 Name: "testflag", 1021 Value: testValue, 1022 Changed: false, 1023 } 1024 1025 BindPFlag("testvalue", flag) 1026 1027 assert.Equal(t, testString, Get("testvalue")) 1028 1029 flag.Value.Set("testing_mutate") 1030 flag.Changed = true // hack for pflag usage 1031 1032 assert.Equal(t, "testing_mutate", Get("testvalue")) 1033} 1034 1035func TestBindPFlagDetectNilFlag(t *testing.T) { 1036 result := BindPFlag("testvalue", nil) 1037 assert.Error(t, result) 1038} 1039 1040func TestBindPFlagStringToString(t *testing.T) { 1041 tests := []struct { 1042 Expected map[string]string 1043 Value string 1044 }{ 1045 {map[string]string{}, ""}, 1046 {map[string]string{"yo": "hi"}, "yo=hi"}, 1047 {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"}, 1048 {map[string]string{"yo": ""}, "yo="}, 1049 {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"}, 1050 } 1051 1052 v := New() // create independent Viper object 1053 defaultVal := map[string]string{} 1054 v.SetDefault("stringtostring", defaultVal) 1055 1056 for _, testValue := range tests { 1057 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1058 flagSet.StringToString("stringtostring", testValue.Expected, "test") 1059 1060 for _, changed := range []bool{true, false} { 1061 flagSet.VisitAll(func(f *pflag.Flag) { 1062 f.Value.Set(testValue.Value) 1063 f.Changed = changed 1064 }) 1065 1066 err := v.BindPFlags(flagSet) 1067 if err != nil { 1068 t.Fatalf("error binding flag set, %v", err) 1069 } 1070 1071 type TestMap struct { 1072 StringToString map[string]string 1073 } 1074 val := &TestMap{} 1075 if err := v.Unmarshal(val); err != nil { 1076 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1077 } 1078 if changed { 1079 assert.Equal(t, testValue.Expected, val.StringToString) 1080 } else { 1081 assert.Equal(t, defaultVal, val.StringToString) 1082 } 1083 } 1084 } 1085} 1086 1087func TestBoundCaseSensitivity(t *testing.T) { 1088 assert.Equal(t, "brown", Get("eyes")) 1089 1090 BindEnv("eYEs", "TURTLE_EYES") 1091 1092 testutil.Setenv(t, "TURTLE_EYES", "blue") 1093 1094 assert.Equal(t, "blue", Get("eyes")) 1095 1096 testString := "green" 1097 testValue := newStringValue(testString, &testString) 1098 1099 flag := &pflag.Flag{ 1100 Name: "eyeballs", 1101 Value: testValue, 1102 Changed: true, 1103 } 1104 1105 BindPFlag("eYEs", flag) 1106 assert.Equal(t, "green", Get("eyes")) 1107} 1108 1109func TestSizeInBytes(t *testing.T) { 1110 input := map[string]uint{ 1111 "": 0, 1112 "b": 0, 1113 "12 bytes": 0, 1114 "200000000000gb": 0, 1115 "12 b": 12, 1116 "43 MB": 43 * (1 << 20), 1117 "10mb": 10 * (1 << 20), 1118 "1gb": 1 << 30, 1119 } 1120 1121 for str, expected := range input { 1122 assert.Equal(t, expected, parseSizeInBytes(str), str) 1123 } 1124} 1125 1126func TestFindsNestedKeys(t *testing.T) { 1127 initConfigs() 1128 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 1129 1130 Set("super", map[string]interface{}{ 1131 "deep": map[string]interface{}{ 1132 "nested": "value", 1133 }, 1134 }) 1135 1136 expected := map[string]interface{}{ 1137 "super": map[string]interface{}{ 1138 "deep": map[string]interface{}{ 1139 "nested": "value", 1140 }, 1141 }, 1142 "super.deep": map[string]interface{}{ 1143 "nested": "value", 1144 }, 1145 "super.deep.nested": "value", 1146 "owner.organization": "MongoDB", 1147 "batters.batter": []interface{}{ 1148 map[string]interface{}{ 1149 "type": "Regular", 1150 }, 1151 map[string]interface{}{ 1152 "type": "Chocolate", 1153 }, 1154 map[string]interface{}{ 1155 "type": "Blueberry", 1156 }, 1157 map[string]interface{}{ 1158 "type": "Devil's Food", 1159 }, 1160 }, 1161 "hobbies": []interface{}{ 1162 "skateboarding", "snowboarding", "go", 1163 }, 1164 "TITLE_DOTENV": "DotEnv Example", 1165 "TYPE_DOTENV": "donut", 1166 "NAME_DOTENV": "Cake", 1167 "title": "TOML Example", 1168 "newkey": "remote", 1169 "batters": map[string]interface{}{ 1170 "batter": []interface{}{ 1171 map[string]interface{}{ 1172 "type": "Regular", 1173 }, 1174 map[string]interface{}{ 1175 "type": "Chocolate", 1176 }, 1177 map[string]interface{}{ 1178 "type": "Blueberry", 1179 }, 1180 map[string]interface{}{ 1181 "type": "Devil's Food", 1182 }, 1183 }, 1184 }, 1185 "eyes": "brown", 1186 "age": 35, 1187 "owner": map[string]interface{}{ 1188 "organization": "MongoDB", 1189 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1190 "dob": dob, 1191 }, 1192 "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1193 "type": "donut", 1194 "id": "0001", 1195 "name": "Cake", 1196 "hacker": true, 1197 "ppu": 0.55, 1198 "clothing": map[string]interface{}{ 1199 "jacket": "leather", 1200 "trousers": "denim", 1201 "pants": map[string]interface{}{ 1202 "size": "large", 1203 }, 1204 }, 1205 "clothing.jacket": "leather", 1206 "clothing.pants.size": "large", 1207 "clothing.trousers": "denim", 1208 "owner.dob": dob, 1209 "beard": true, 1210 "foos": []map[string]interface{}{ 1211 { 1212 "foo": []map[string]interface{}{ 1213 { 1214 "key": 1, 1215 }, 1216 { 1217 "key": 2, 1218 }, 1219 { 1220 "key": 3, 1221 }, 1222 { 1223 "key": 4, 1224 }, 1225 }, 1226 }, 1227 }, 1228 } 1229 1230 for key, expectedValue := range expected { 1231 assert.Equal(t, expectedValue, v.Get(key)) 1232 } 1233} 1234 1235func TestReadBufConfig(t *testing.T) { 1236 v := New() 1237 v.SetConfigType("yaml") 1238 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1239 t.Log(v.AllKeys()) 1240 1241 assert.True(t, v.InConfig("name")) 1242 assert.False(t, v.InConfig("state")) 1243 assert.Equal(t, "steve", v.Get("name")) 1244 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 1245 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) 1246 assert.Equal(t, 35, v.Get("age")) 1247} 1248 1249func TestIsSet(t *testing.T) { 1250 v := New() 1251 v.SetConfigType("yaml") 1252 1253 /* config and defaults */ 1254 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1255 v.SetDefault("clothing.shoes", "sneakers") 1256 1257 assert.True(t, v.IsSet("clothing")) 1258 assert.True(t, v.IsSet("clothing.jacket")) 1259 assert.False(t, v.IsSet("clothing.jackets")) 1260 assert.True(t, v.IsSet("clothing.shoes")) 1261 1262 /* state change */ 1263 assert.False(t, v.IsSet("helloworld")) 1264 v.Set("helloworld", "fubar") 1265 assert.True(t, v.IsSet("helloworld")) 1266 1267 /* env */ 1268 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 1269 v.BindEnv("eyes") 1270 v.BindEnv("foo") 1271 v.BindEnv("clothing.hat") 1272 v.BindEnv("clothing.hats") 1273 1274 testutil.Setenv(t, "FOO", "bar") 1275 testutil.Setenv(t, "CLOTHING_HAT", "bowler") 1276 1277 assert.True(t, v.IsSet("eyes")) // in the config file 1278 assert.True(t, v.IsSet("foo")) // in the environment 1279 assert.True(t, v.IsSet("clothing.hat")) // in the environment 1280 assert.False(t, v.IsSet("clothing.hats")) // not defined 1281 1282 /* flags */ 1283 flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError) 1284 flagset.Bool("foobaz", false, "foobaz") 1285 flagset.Bool("barbaz", false, "barbaz") 1286 foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz") 1287 v.BindPFlag("foobaz", foobaz) 1288 v.BindPFlag("barbaz", barbaz) 1289 barbaz.Value.Set("true") 1290 barbaz.Changed = true // hack for pflag usage 1291 1292 assert.False(t, v.IsSet("foobaz")) 1293 assert.True(t, v.IsSet("barbaz")) 1294} 1295 1296func TestDirsSearch(t *testing.T) { 1297 root, config, cleanup := initDirs(t) 1298 defer cleanup() 1299 1300 v := New() 1301 v.SetConfigName(config) 1302 v.SetDefault(`key`, `default`) 1303 1304 entries, err := ioutil.ReadDir(root) 1305 assert.Nil(t, err) 1306 for _, e := range entries { 1307 if e.IsDir() { 1308 v.AddConfigPath(e.Name()) 1309 } 1310 } 1311 1312 err = v.ReadInConfig() 1313 assert.Nil(t, err) 1314 1315 assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`)) 1316} 1317 1318func TestWrongDirsSearchNotFound(t *testing.T) { 1319 _, config, cleanup := initDirs(t) 1320 defer cleanup() 1321 1322 v := New() 1323 v.SetConfigName(config) 1324 v.SetDefault(`key`, `default`) 1325 1326 v.AddConfigPath(`whattayoutalkingbout`) 1327 v.AddConfigPath(`thispathaintthere`) 1328 1329 err := v.ReadInConfig() 1330 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1331 1332 // Even though config did not load and the error might have 1333 // been ignored by the client, the default still loads 1334 assert.Equal(t, `default`, v.GetString(`key`)) 1335} 1336 1337func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { 1338 _, config, cleanup := initDirs(t) 1339 defer cleanup() 1340 1341 v := New() 1342 v.SetConfigName(config) 1343 v.SetDefault(`key`, `default`) 1344 1345 v.AddConfigPath(`whattayoutalkingbout`) 1346 v.AddConfigPath(`thispathaintthere`) 1347 1348 err := v.MergeInConfig() 1349 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1350 1351 // Even though config did not load and the error might have 1352 // been ignored by the client, the default still loads 1353 assert.Equal(t, `default`, v.GetString(`key`)) 1354} 1355 1356func TestSub(t *testing.T) { 1357 v := New() 1358 v.SetConfigType("yaml") 1359 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1360 1361 subv := v.Sub("clothing") 1362 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) 1363 1364 subv = v.Sub("clothing.pants") 1365 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) 1366 1367 subv = v.Sub("clothing.pants.size") 1368 assert.Equal(t, (*Viper)(nil), subv) 1369 1370 subv = v.Sub("missing.key") 1371 assert.Equal(t, (*Viper)(nil), subv) 1372} 1373 1374var hclWriteExpected = []byte(`"foos" = { 1375 "foo" = { 1376 "key" = 1 1377 } 1378 1379 "foo" = { 1380 "key" = 2 1381 } 1382 1383 "foo" = { 1384 "key" = 3 1385 } 1386 1387 "foo" = { 1388 "key" = 4 1389 } 1390} 1391 1392"id" = "0001" 1393 1394"name" = "Cake" 1395 1396"ppu" = 0.55 1397 1398"type" = "donut"`) 1399 1400var jsonWriteExpected = []byte(`{ 1401 "batters": { 1402 "batter": [ 1403 { 1404 "type": "Regular" 1405 }, 1406 { 1407 "type": "Chocolate" 1408 }, 1409 { 1410 "type": "Blueberry" 1411 }, 1412 { 1413 "type": "Devil's Food" 1414 } 1415 ] 1416 }, 1417 "id": "0001", 1418 "name": "Cake", 1419 "ppu": 0.55, 1420 "type": "donut" 1421}`) 1422 1423var propertiesWriteExpected = []byte(`p_id = 0001 1424p_type = donut 1425p_name = Cake 1426p_ppu = 0.55 1427p_batters.batter.type = Regular 1428`) 1429 1430var yamlWriteExpected = []byte(`age: 35 1431beard: true 1432clothing: 1433 jacket: leather 1434 pants: 1435 size: large 1436 trousers: denim 1437eyes: brown 1438hacker: true 1439hobbies: 1440- skateboarding 1441- snowboarding 1442- go 1443name: steve 1444`) 1445 1446func TestWriteConfig(t *testing.T) { 1447 fs := afero.NewMemMapFs() 1448 testCases := map[string]struct { 1449 configName string 1450 inConfigType string 1451 outConfigType string 1452 fileName string 1453 input []byte 1454 expectedContent []byte 1455 }{ 1456 "hcl with file extension": { 1457 configName: "c", 1458 inConfigType: "hcl", 1459 outConfigType: "hcl", 1460 fileName: "c.hcl", 1461 input: hclExample, 1462 expectedContent: hclWriteExpected, 1463 }, 1464 "hcl without file extension": { 1465 configName: "c", 1466 inConfigType: "hcl", 1467 outConfigType: "hcl", 1468 fileName: "c", 1469 input: hclExample, 1470 expectedContent: hclWriteExpected, 1471 }, 1472 "hcl with file extension and mismatch type": { 1473 configName: "c", 1474 inConfigType: "hcl", 1475 outConfigType: "json", 1476 fileName: "c.hcl", 1477 input: hclExample, 1478 expectedContent: hclWriteExpected, 1479 }, 1480 "json with file extension": { 1481 configName: "c", 1482 inConfigType: "json", 1483 outConfigType: "json", 1484 fileName: "c.json", 1485 input: jsonExample, 1486 expectedContent: jsonWriteExpected, 1487 }, 1488 "json without file extension": { 1489 configName: "c", 1490 inConfigType: "json", 1491 outConfigType: "json", 1492 fileName: "c", 1493 input: jsonExample, 1494 expectedContent: jsonWriteExpected, 1495 }, 1496 "json with file extension and mismatch type": { 1497 configName: "c", 1498 inConfigType: "json", 1499 outConfigType: "hcl", 1500 fileName: "c.json", 1501 input: jsonExample, 1502 expectedContent: jsonWriteExpected, 1503 }, 1504 "properties with file extension": { 1505 configName: "c", 1506 inConfigType: "properties", 1507 outConfigType: "properties", 1508 fileName: "c.properties", 1509 input: propertiesExample, 1510 expectedContent: propertiesWriteExpected, 1511 }, 1512 "properties without file extension": { 1513 configName: "c", 1514 inConfigType: "properties", 1515 outConfigType: "properties", 1516 fileName: "c", 1517 input: propertiesExample, 1518 expectedContent: propertiesWriteExpected, 1519 }, 1520 "yaml with file extension": { 1521 configName: "c", 1522 inConfigType: "yaml", 1523 outConfigType: "yaml", 1524 fileName: "c.yaml", 1525 input: yamlExample, 1526 expectedContent: yamlWriteExpected, 1527 }, 1528 "yaml without file extension": { 1529 configName: "c", 1530 inConfigType: "yaml", 1531 outConfigType: "yaml", 1532 fileName: "c", 1533 input: yamlExample, 1534 expectedContent: yamlWriteExpected, 1535 }, 1536 "yaml with file extension and mismatch type": { 1537 configName: "c", 1538 inConfigType: "yaml", 1539 outConfigType: "json", 1540 fileName: "c.yaml", 1541 input: yamlExample, 1542 expectedContent: yamlWriteExpected, 1543 }, 1544 } 1545 for name, tc := range testCases { 1546 t.Run(name, func(t *testing.T) { 1547 v := New() 1548 v.SetFs(fs) 1549 v.SetConfigName(tc.fileName) 1550 v.SetConfigType(tc.inConfigType) 1551 1552 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1553 if err != nil { 1554 t.Fatal(err) 1555 } 1556 v.SetConfigType(tc.outConfigType) 1557 if err := v.WriteConfigAs(tc.fileName); err != nil { 1558 t.Fatal(err) 1559 } 1560 read, err := afero.ReadFile(fs, tc.fileName) 1561 if err != nil { 1562 t.Fatal(err) 1563 } 1564 assert.Equal(t, tc.expectedContent, read) 1565 }) 1566 } 1567} 1568 1569func TestWriteConfigTOML(t *testing.T) { 1570 fs := afero.NewMemMapFs() 1571 1572 testCases := map[string]struct { 1573 configName string 1574 configType string 1575 fileName string 1576 input []byte 1577 }{ 1578 "with file extension": { 1579 configName: "c", 1580 configType: "toml", 1581 fileName: "c.toml", 1582 input: tomlExample, 1583 }, 1584 "without file extension": { 1585 configName: "c", 1586 configType: "toml", 1587 fileName: "c", 1588 input: tomlExample, 1589 }, 1590 } 1591 for name, tc := range testCases { 1592 t.Run(name, func(t *testing.T) { 1593 v := New() 1594 v.SetFs(fs) 1595 v.SetConfigName(tc.configName) 1596 v.SetConfigType(tc.configType) 1597 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1598 if err != nil { 1599 t.Fatal(err) 1600 } 1601 if err := v.WriteConfigAs(tc.fileName); err != nil { 1602 t.Fatal(err) 1603 } 1604 1605 // The TOML String method does not order the contents. 1606 // Therefore, we must read the generated file and compare the data. 1607 v2 := New() 1608 v2.SetFs(fs) 1609 v2.SetConfigName(tc.configName) 1610 v2.SetConfigType(tc.configType) 1611 v2.SetConfigFile(tc.fileName) 1612 err = v2.ReadInConfig() 1613 if err != nil { 1614 t.Fatal(err) 1615 } 1616 1617 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1618 assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) 1619 assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) 1620 assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) 1621 }) 1622 } 1623} 1624 1625func TestWriteConfigDotEnv(t *testing.T) { 1626 fs := afero.NewMemMapFs() 1627 testCases := map[string]struct { 1628 configName string 1629 configType string 1630 fileName string 1631 input []byte 1632 }{ 1633 "with file extension": { 1634 configName: "c", 1635 configType: "env", 1636 fileName: "c.env", 1637 input: dotenvExample, 1638 }, 1639 "without file extension": { 1640 configName: "c", 1641 configType: "env", 1642 fileName: "c", 1643 input: dotenvExample, 1644 }, 1645 } 1646 for name, tc := range testCases { 1647 t.Run(name, func(t *testing.T) { 1648 v := New() 1649 v.SetFs(fs) 1650 v.SetConfigName(tc.configName) 1651 v.SetConfigType(tc.configType) 1652 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1653 if err != nil { 1654 t.Fatal(err) 1655 } 1656 if err := v.WriteConfigAs(tc.fileName); err != nil { 1657 t.Fatal(err) 1658 } 1659 1660 // The TOML String method does not order the contents. 1661 // Therefore, we must read the generated file and compare the data. 1662 v2 := New() 1663 v2.SetFs(fs) 1664 v2.SetConfigName(tc.configName) 1665 v2.SetConfigType(tc.configType) 1666 v2.SetConfigFile(tc.fileName) 1667 err = v2.ReadInConfig() 1668 if err != nil { 1669 t.Fatal(err) 1670 } 1671 1672 assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv")) 1673 assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv")) 1674 assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv")) 1675 }) 1676 } 1677} 1678 1679func TestSafeWriteConfig(t *testing.T) { 1680 v := New() 1681 fs := afero.NewMemMapFs() 1682 v.SetFs(fs) 1683 v.AddConfigPath("/test") 1684 v.SetConfigName("c") 1685 v.SetConfigType("yaml") 1686 require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample))) 1687 require.NoError(t, v.SafeWriteConfig()) 1688 read, err := afero.ReadFile(fs, "/test/c.yaml") 1689 require.NoError(t, err) 1690 assert.Equal(t, yamlWriteExpected, read) 1691} 1692 1693func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) { 1694 v := New() 1695 fs := afero.NewMemMapFs() 1696 v.SetFs(fs) 1697 v.SetConfigName("c") 1698 v.SetConfigType("yaml") 1699 require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'") 1700} 1701 1702func TestSafeWriteConfigWithExistingFile(t *testing.T) { 1703 v := New() 1704 fs := afero.NewMemMapFs() 1705 fs.Create("/test/c.yaml") 1706 v.SetFs(fs) 1707 v.AddConfigPath("/test") 1708 v.SetConfigName("c") 1709 v.SetConfigType("yaml") 1710 err := v.SafeWriteConfig() 1711 require.Error(t, err) 1712 _, ok := err.(ConfigFileAlreadyExistsError) 1713 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1714} 1715 1716func TestSafeWriteAsConfig(t *testing.T) { 1717 v := New() 1718 fs := afero.NewMemMapFs() 1719 v.SetFs(fs) 1720 err := v.ReadConfig(bytes.NewBuffer(yamlExample)) 1721 if err != nil { 1722 t.Fatal(err) 1723 } 1724 require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml")) 1725 if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil { 1726 t.Fatal(err) 1727 } 1728} 1729 1730func TestSafeWriteConfigAsWithExistingFile(t *testing.T) { 1731 v := New() 1732 fs := afero.NewMemMapFs() 1733 fs.Create("/test/c.yaml") 1734 v.SetFs(fs) 1735 err := v.SafeWriteConfigAs("/test/c.yaml") 1736 require.Error(t, err) 1737 _, ok := err.(ConfigFileAlreadyExistsError) 1738 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1739} 1740 1741var yamlMergeExampleTgt = []byte(` 1742hello: 1743 pop: 37890 1744 largenum: 765432101234567 1745 num2pow63: 9223372036854775808 1746 universe: null 1747 world: 1748 - us 1749 - uk 1750 - fr 1751 - de 1752`) 1753 1754var yamlMergeExampleSrc = []byte(` 1755hello: 1756 pop: 45000 1757 largenum: 7654321001234567 1758 universe: 1759 - mw 1760 - ad 1761 ints: 1762 - 1 1763 - 2 1764fu: bar 1765`) 1766 1767func TestMergeConfig(t *testing.T) { 1768 v := New() 1769 v.SetConfigType("yml") 1770 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1771 t.Fatal(err) 1772 } 1773 1774 if pop := v.GetInt("hello.pop"); pop != 37890 { 1775 t.Fatalf("pop != 37890, = %d", pop) 1776 } 1777 1778 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1779 t.Fatalf("pop != 37890, = %d", pop) 1780 } 1781 1782 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1783 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 1784 } 1785 1786 if pop := v.GetUint("hello.pop"); pop != 37890 { 1787 t.Fatalf("uint pop != 37890, = %d", pop) 1788 } 1789 1790 if pop := v.GetUint32("hello.pop"); pop != 37890 { 1791 t.Fatalf("uint32 pop != 37890, = %d", pop) 1792 } 1793 1794 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 1795 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop) 1796 } 1797 1798 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1799 t.Fatalf("len(world) != 4, = %d", len(world)) 1800 } 1801 1802 if fu := v.GetString("fu"); fu != "" { 1803 t.Fatalf("fu != \"\", = %s", fu) 1804 } 1805 1806 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1807 t.Fatal(err) 1808 } 1809 1810 if pop := v.GetInt("hello.pop"); pop != 45000 { 1811 t.Fatalf("pop != 45000, = %d", pop) 1812 } 1813 1814 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 1815 t.Fatalf("pop != 45000, = %d", pop) 1816 } 1817 1818 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 1819 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 1820 } 1821 1822 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1823 t.Fatalf("len(world) != 4, = %d", len(world)) 1824 } 1825 1826 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1827 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1828 } 1829 1830 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1831 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1832 } 1833 1834 if fu := v.GetString("fu"); fu != "bar" { 1835 t.Fatalf("fu != \"bar\", = %s", fu) 1836 } 1837} 1838 1839func TestMergeConfigNoMerge(t *testing.T) { 1840 v := New() 1841 v.SetConfigType("yml") 1842 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1843 t.Fatal(err) 1844 } 1845 1846 if pop := v.GetInt("hello.pop"); pop != 37890 { 1847 t.Fatalf("pop != 37890, = %d", pop) 1848 } 1849 1850 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 1851 t.Fatalf("len(world) != 4, = %d", len(world)) 1852 } 1853 1854 if fu := v.GetString("fu"); fu != "" { 1855 t.Fatalf("fu != \"\", = %s", fu) 1856 } 1857 1858 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 1859 t.Fatal(err) 1860 } 1861 1862 if pop := v.GetInt("hello.pop"); pop != 45000 { 1863 t.Fatalf("pop != 45000, = %d", pop) 1864 } 1865 1866 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 1867 t.Fatalf("len(world) != 0, = %d", len(world)) 1868 } 1869 1870 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 1871 t.Fatalf("len(universe) != 2, = %d", len(universe)) 1872 } 1873 1874 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 1875 t.Fatalf("len(ints) != 2, = %d", len(ints)) 1876 } 1877 1878 if fu := v.GetString("fu"); fu != "bar" { 1879 t.Fatalf("fu != \"bar\", = %s", fu) 1880 } 1881} 1882 1883func TestMergeConfigMap(t *testing.T) { 1884 v := New() 1885 v.SetConfigType("yml") 1886 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1887 t.Fatal(err) 1888 } 1889 1890 assert := func(i int) { 1891 large := v.GetInt64("hello.largenum") 1892 pop := v.GetInt("hello.pop") 1893 if large != 765432101234567 { 1894 t.Fatal("Got large num:", large) 1895 } 1896 1897 if pop != i { 1898 t.Fatal("Got pop:", pop) 1899 } 1900 } 1901 1902 assert(37890) 1903 1904 update := map[string]interface{}{ 1905 "Hello": map[string]interface{}{ 1906 "Pop": 1234, 1907 }, 1908 "World": map[interface{}]interface{}{ 1909 "Rock": 345, 1910 }, 1911 } 1912 1913 if err := v.MergeConfigMap(update); err != nil { 1914 t.Fatal(err) 1915 } 1916 1917 if rock := v.GetInt("world.rock"); rock != 345 { 1918 t.Fatal("Got rock:", rock) 1919 } 1920 1921 assert(1234) 1922} 1923 1924func TestUnmarshalingWithAliases(t *testing.T) { 1925 v := New() 1926 v.SetDefault("ID", 1) 1927 v.Set("name", "Steve") 1928 v.Set("lastname", "Owen") 1929 1930 v.RegisterAlias("UserID", "ID") 1931 v.RegisterAlias("Firstname", "name") 1932 v.RegisterAlias("Surname", "lastname") 1933 1934 type config struct { 1935 ID int 1936 FirstName string 1937 Surname string 1938 } 1939 1940 var C config 1941 err := v.Unmarshal(&C) 1942 if err != nil { 1943 t.Fatalf("unable to decode into struct, %v", err) 1944 } 1945 1946 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 1947} 1948 1949func TestSetConfigNameClearsFileCache(t *testing.T) { 1950 SetConfigFile("/tmp/config.yaml") 1951 SetConfigName("default") 1952 f, err := v.getConfigFile() 1953 if err == nil { 1954 t.Fatalf("config file cache should have been cleared") 1955 } 1956 assert.Empty(t, f) 1957} 1958 1959func TestShadowedNestedValue(t *testing.T) { 1960 config := `name: steve 1961clothing: 1962 jacket: leather 1963 trousers: denim 1964 pants: 1965 size: large 1966` 1967 initConfig("yaml", config) 1968 1969 assert.Equal(t, "steve", GetString("name")) 1970 1971 polyester := "polyester" 1972 SetDefault("clothing.shirt", polyester) 1973 SetDefault("clothing.jacket.price", 100) 1974 1975 assert.Equal(t, "leather", GetString("clothing.jacket")) 1976 assert.Nil(t, Get("clothing.jacket.price")) 1977 assert.Equal(t, polyester, GetString("clothing.shirt")) 1978 1979 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 1980 assert.Equal(t, "leather", clothingSettings["jacket"]) 1981 assert.Equal(t, polyester, clothingSettings["shirt"]) 1982} 1983 1984func TestDotParameter(t *testing.T) { 1985 initJSON() 1986 // shoud take precedence over batters defined in jsonExample 1987 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 1988 unmarshalReader(r, v.config) 1989 1990 actual := Get("batters.batter") 1991 expected := []interface{}{map[string]interface{}{"type": "Small"}} 1992 assert.Equal(t, expected, actual) 1993} 1994 1995func TestCaseInsensitive(t *testing.T) { 1996 for _, config := range []struct { 1997 typ string 1998 content string 1999 }{ 2000 {"yaml", ` 2001aBcD: 1 2002eF: 2003 gH: 2 2004 iJk: 3 2005 Lm: 2006 nO: 4 2007 P: 2008 Q: 5 2009 R: 6 2010`}, 2011 {"json", `{ 2012 "aBcD": 1, 2013 "eF": { 2014 "iJk": 3, 2015 "Lm": { 2016 "P": { 2017 "Q": 5, 2018 "R": 6 2019 }, 2020 "nO": 4 2021 }, 2022 "gH": 2 2023 } 2024}`}, 2025 {"toml", `aBcD = 1 2026[eF] 2027gH = 2 2028iJk = 3 2029[eF.Lm] 2030nO = 4 2031[eF.Lm.P] 2032Q = 5 2033R = 6 2034`}, 2035 } { 2036 doTestCaseInsensitive(t, config.typ, config.content) 2037 } 2038} 2039 2040func TestCaseInsensitiveSet(t *testing.T) { 2041 Reset() 2042 m1 := map[string]interface{}{ 2043 "Foo": 32, 2044 "Bar": map[interface{}]interface{}{ 2045 "ABc": "A", 2046 "cDE": "B", 2047 }, 2048 } 2049 2050 m2 := map[string]interface{}{ 2051 "Foo": 52, 2052 "Bar": map[interface{}]interface{}{ 2053 "bCd": "A", 2054 "eFG": "B", 2055 }, 2056 } 2057 2058 Set("Given1", m1) 2059 Set("Number1", 42) 2060 2061 SetDefault("Given2", m2) 2062 SetDefault("Number2", 52) 2063 2064 // Verify SetDefault 2065 if v := Get("number2"); v != 52 { 2066 t.Fatalf("Expected 52 got %q", v) 2067 } 2068 2069 if v := Get("given2.foo"); v != 52 { 2070 t.Fatalf("Expected 52 got %q", v) 2071 } 2072 2073 if v := Get("given2.bar.bcd"); v != "A" { 2074 t.Fatalf("Expected A got %q", v) 2075 } 2076 2077 if _, ok := m2["Foo"]; !ok { 2078 t.Fatal("Input map changed") 2079 } 2080 2081 // Verify Set 2082 if v := Get("number1"); v != 42 { 2083 t.Fatalf("Expected 42 got %q", v) 2084 } 2085 2086 if v := Get("given1.foo"); v != 32 { 2087 t.Fatalf("Expected 32 got %q", v) 2088 } 2089 2090 if v := Get("given1.bar.abc"); v != "A" { 2091 t.Fatalf("Expected A got %q", v) 2092 } 2093 2094 if _, ok := m1["Foo"]; !ok { 2095 t.Fatal("Input map changed") 2096 } 2097} 2098 2099func TestParseNested(t *testing.T) { 2100 type duration struct { 2101 Delay time.Duration 2102 } 2103 2104 type item struct { 2105 Name string 2106 Delay time.Duration 2107 Nested duration 2108 } 2109 2110 config := `[[parent]] 2111 delay="100ms" 2112 [parent.nested] 2113 delay="200ms" 2114` 2115 initConfig("toml", config) 2116 2117 var items []item 2118 err := v.UnmarshalKey("parent", &items) 2119 if err != nil { 2120 t.Fatalf("unable to decode into struct, %v", err) 2121 } 2122 2123 assert.Equal(t, 1, len(items)) 2124 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 2125 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 2126} 2127 2128func doTestCaseInsensitive(t *testing.T, typ, config string) { 2129 initConfig(typ, config) 2130 Set("RfD", true) 2131 assert.Equal(t, true, Get("rfd")) 2132 assert.Equal(t, true, Get("rFD")) 2133 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 2134 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 2135 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 2136 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 2137 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 2138 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 2139} 2140 2141func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 2142 watchDir, err := ioutil.TempDir("", "") 2143 require.Nil(t, err) 2144 configFile := path.Join(watchDir, "config.yaml") 2145 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640) 2146 require.Nil(t, err) 2147 cleanup := func() { 2148 os.RemoveAll(watchDir) 2149 } 2150 v := New() 2151 v.SetConfigFile(configFile) 2152 err = v.ReadInConfig() 2153 require.Nil(t, err) 2154 require.Equal(t, "bar", v.Get("foo")) 2155 return v, configFile, cleanup 2156} 2157 2158func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 2159 watchDir, err := ioutil.TempDir("", "") 2160 require.Nil(t, err) 2161 dataDir1 := path.Join(watchDir, "data1") 2162 err = os.Mkdir(dataDir1, 0777) 2163 require.Nil(t, err) 2164 realConfigFile := path.Join(dataDir1, "config.yaml") 2165 t.Logf("Real config file location: %s\n", realConfigFile) 2166 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640) 2167 require.Nil(t, err) 2168 cleanup := func() { 2169 os.RemoveAll(watchDir) 2170 } 2171 // now, symlink the tm `data1` dir to `data` in the baseDir 2172 os.Symlink(dataDir1, path.Join(watchDir, "data")) 2173 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 2174 configFile := path.Join(watchDir, "config.yaml") 2175 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 2176 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 2177 // init Viper 2178 v := New() 2179 v.SetConfigFile(configFile) 2180 err = v.ReadInConfig() 2181 require.Nil(t, err) 2182 require.Equal(t, "bar", v.Get("foo")) 2183 return v, watchDir, configFile, cleanup 2184} 2185 2186func TestWatchFile(t *testing.T) { 2187 if runtime.GOOS == "linux" { 2188 // TODO(bep) FIX ME 2189 t.Skip("Skip test on Linux ...") 2190 } 2191 2192 t.Run("file content changed", func(t *testing.T) { 2193 // given a `config.yaml` file being watched 2194 v, configFile, cleanup := newViperWithConfigFile(t) 2195 defer cleanup() 2196 _, err := os.Stat(configFile) 2197 require.NoError(t, err) 2198 t.Logf("test config file: %s\n", configFile) 2199 wg := sync.WaitGroup{} 2200 wg.Add(1) 2201 v.OnConfigChange(func(in fsnotify.Event) { 2202 t.Logf("config file changed") 2203 wg.Done() 2204 }) 2205 v.WatchConfig() 2206 // when overwriting the file and waiting for the custom change notification handler to be triggered 2207 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640) 2208 wg.Wait() 2209 // then the config value should have changed 2210 require.Nil(t, err) 2211 assert.Equal(t, "baz", v.Get("foo")) 2212 }) 2213 2214 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 2215 // skip if not executed on Linux 2216 if runtime.GOOS != "linux" { 2217 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 2218 } 2219 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 2220 // defer cleanup() 2221 wg := sync.WaitGroup{} 2222 v.WatchConfig() 2223 v.OnConfigChange(func(in fsnotify.Event) { 2224 t.Logf("config file changed") 2225 wg.Done() 2226 }) 2227 wg.Add(1) 2228 // when link to another `config.yaml` file 2229 dataDir2 := path.Join(watchDir, "data2") 2230 err := os.Mkdir(dataDir2, 0777) 2231 require.Nil(t, err) 2232 configFile2 := path.Join(dataDir2, "config.yaml") 2233 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640) 2234 require.Nil(t, err) 2235 // change the symlink using the `ln -sfn` command 2236 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 2237 require.Nil(t, err) 2238 wg.Wait() 2239 // then 2240 require.Nil(t, err) 2241 assert.Equal(t, "baz", v.Get("foo")) 2242 }) 2243} 2244 2245func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 2246 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 2247 flags.String("foo.bar", "cobra_flag", "") 2248 2249 v := New() 2250 assert.NoError(t, v.BindPFlags(flags)) 2251 2252 config := &struct { 2253 Foo struct { 2254 Bar string 2255 } 2256 }{} 2257 2258 assert.NoError(t, v.Unmarshal(config)) 2259 assert.Equal(t, "cobra_flag", config.Foo.Bar) 2260} 2261 2262var yamlExampleWithDot = []byte(`Hacker: true 2263name: steve 2264hobbies: 2265 - skateboarding 2266 - snowboarding 2267 - go 2268clothing: 2269 jacket: leather 2270 trousers: denim 2271 pants: 2272 size: large 2273age: 35 2274eyes : brown 2275beard: true 2276emails: 2277 steve@hacker.com: 2278 created: 01/02/03 2279 active: true 2280`) 2281 2282func TestKeyDelimiter(t *testing.T) { 2283 v := NewWithOptions(KeyDelimiter("::")) 2284 v.SetConfigType("yaml") 2285 r := strings.NewReader(string(yamlExampleWithDot)) 2286 2287 err := v.unmarshalReader(r, v.config) 2288 require.NoError(t, err) 2289 2290 values := map[string]interface{}{ 2291 "image": map[string]interface{}{ 2292 "repository": "someImage", 2293 "tag": "1.0.0", 2294 }, 2295 "ingress": map[string]interface{}{ 2296 "annotations": map[string]interface{}{ 2297 "traefik.frontend.rule.type": "PathPrefix", 2298 "traefik.ingress.kubernetes.io/ssl-redirect": "true", 2299 }, 2300 }, 2301 } 2302 2303 v.SetDefault("charts::values", values) 2304 2305 assert.Equal(t, "leather", v.GetString("clothing::jacket")) 2306 assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created")) 2307 2308 type config struct { 2309 Charts struct { 2310 Values map[string]interface{} 2311 } 2312 } 2313 2314 expected := config{ 2315 Charts: struct { 2316 Values map[string]interface{} 2317 }{ 2318 Values: values, 2319 }, 2320 } 2321 2322 var actual config 2323 2324 assert.NoError(t, v.Unmarshal(&actual)) 2325 2326 assert.Equal(t, expected, actual) 2327} 2328 2329var yamlDeepNestedSlices = []byte(`TV: 2330- title: "The expanse" 2331 seasons: 2332 - first_released: "December 14, 2015" 2333 episodes: 2334 - title: "Dulcinea" 2335 air_date: "December 14, 2015" 2336 - title: "The Big Empty" 2337 air_date: "December 15, 2015" 2338 - title: "Remember the Cant" 2339 air_date: "December 22, 2015" 2340 - first_released: "February 1, 2017" 2341 episodes: 2342 - title: "Safe" 2343 air_date: "February 1, 2017" 2344 - title: "Doors & Corners" 2345 air_date: "February 1, 2017" 2346 - title: "Static" 2347 air_date: "February 8, 2017" 2348 episodes: 2349 - ["Dulcinea", "The Big Empty", "Remember the Cant"] 2350 - ["Safe", "Doors & Corners", "Static"] 2351`) 2352 2353func TestSliceIndexAccess(t *testing.T) { 2354 v.SetConfigType("yaml") 2355 r := strings.NewReader(string(yamlDeepNestedSlices)) 2356 2357 err := v.unmarshalReader(r, v.config) 2358 require.NoError(t, err) 2359 2360 assert.Equal(t, "The expanse", v.GetString("tv.0.title")) 2361 assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released")) 2362 assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title")) 2363 assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date")) 2364 2365 // Test for index out of bounds 2366 assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released")) 2367 2368 // Accessing multidimensional arrays 2369 assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2")) 2370} 2371 2372func BenchmarkGetBool(b *testing.B) { 2373 key := "BenchmarkGetBool" 2374 v = New() 2375 v.Set(key, true) 2376 2377 for i := 0; i < b.N; i++ { 2378 if !v.GetBool(key) { 2379 b.Fatal("GetBool returned false") 2380 } 2381 } 2382} 2383 2384func BenchmarkGet(b *testing.B) { 2385 key := "BenchmarkGet" 2386 v = New() 2387 v.Set(key, true) 2388 2389 for i := 0; i < b.N; i++ { 2390 if !v.Get(key).(bool) { 2391 b.Fatal("Get returned false") 2392 } 2393 } 2394} 2395 2396// BenchmarkGetBoolFromMap is the "perfect result" for the above. 2397func BenchmarkGetBoolFromMap(b *testing.B) { 2398 m := make(map[string]bool) 2399 key := "BenchmarkGetBool" 2400 m[key] = true 2401 2402 for i := 0; i < b.N; i++ { 2403 if !m[key] { 2404 b.Fatal("Map value was false") 2405 } 2406 } 2407} 2408