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