github.com/itlabers/viper@v1.7.1-0.20200610115111-a6072d2e9820/viper_test.go (about) 1 // Copyright © 2014 Steve Francia <spf@spf13.com>. 2 // 3 // Use of this source code is governed by an MIT-style 4 // license that can be found in the LICENSE file. 5 6 package viper 7 8 import ( 9 "bytes" 10 "encoding/json" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 17 "reflect" 18 "runtime" 19 "sort" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/fsnotify/fsnotify" 26 "github.com/mitchellh/mapstructure" 27 "github.com/spf13/afero" 28 "github.com/spf13/cast" 29 30 "github.com/spf13/pflag" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 var yamlExample = []byte(`Hacker: true 36 name: steve 37 hobbies: 38 - skateboarding 39 - snowboarding 40 - go 41 clothing: 42 jacket: leather 43 trousers: denim 44 pants: 45 size: large 46 age: 35 47 eyes : brown 48 beard: true 49 `) 50 51 var yamlExampleWithExtras = []byte(`Existing: true 52 Bogus: true 53 `) 54 55 type testUnmarshalExtra struct { 56 Existing bool 57 } 58 59 var tomlExample = []byte(` 60 title = "TOML Example" 61 62 [owner] 63 organization = "MongoDB" 64 Bio = "MongoDB Chief Developer Advocate & Hacker at Large" 65 dob = 1979-05-27T07:32:00Z # First class dates? Why not?`) 66 67 var dotenvExample = []byte(` 68 TITLE_DOTENV="DotEnv Example" 69 TYPE_DOTENV=donut 70 NAME_DOTENV=Cake`) 71 72 var jsonExample = []byte(`{ 73 "id": "0001", 74 "type": "donut", 75 "name": "Cake", 76 "ppu": 0.55, 77 "batters": { 78 "batter": [ 79 { "type": "Regular" }, 80 { "type": "Chocolate" }, 81 { "type": "Blueberry" }, 82 { "type": "Devil's Food" } 83 ] 84 } 85 }`) 86 87 var hclExample = []byte(` 88 id = "0001" 89 type = "donut" 90 name = "Cake" 91 ppu = 0.55 92 foos { 93 foo { 94 key = 1 95 } 96 foo { 97 key = 2 98 } 99 foo { 100 key = 3 101 } 102 foo { 103 key = 4 104 } 105 }`) 106 107 var propertiesExample = []byte(` 108 p_id: 0001 109 p_type: donut 110 p_name: Cake 111 p_ppu: 0.55 112 p_batters.batter.type: Regular 113 `) 114 115 var remoteExample = []byte(`{ 116 "id":"0002", 117 "type":"cronut", 118 "newkey":"remote" 119 }`) 120 121 var iniExample = []byte(`; Package name 122 NAME = ini 123 ; Package version 124 VERSION = v1 125 ; Package import path 126 IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s 127 128 # Information about package author 129 # Bio can be written in multiple lines. 130 [author] 131 NAME = Unknown ; Succeeding comment 132 E-MAIL = fake@localhost 133 GITHUB = https://github.com/%(NAME)s 134 BIO = """Gopher. 135 Coding addict. 136 Good man. 137 """ # Succeeding comment`) 138 139 func initConfigs() { 140 Reset() 141 var r io.Reader 142 SetConfigType("yaml") 143 r = bytes.NewReader(yamlExample) 144 unmarshalReader(r, v.config) 145 146 SetConfigType("json") 147 r = bytes.NewReader(jsonExample) 148 unmarshalReader(r, v.config) 149 150 SetConfigType("hcl") 151 r = bytes.NewReader(hclExample) 152 unmarshalReader(r, v.config) 153 154 SetConfigType("properties") 155 r = bytes.NewReader(propertiesExample) 156 unmarshalReader(r, v.config) 157 158 SetConfigType("toml") 159 r = bytes.NewReader(tomlExample) 160 unmarshalReader(r, v.config) 161 162 SetConfigType("env") 163 r = bytes.NewReader(dotenvExample) 164 unmarshalReader(r, v.config) 165 166 SetConfigType("json") 167 remote := bytes.NewReader(remoteExample) 168 unmarshalReader(remote, v.kvstore) 169 170 SetConfigType("ini") 171 r = bytes.NewReader(iniExample) 172 unmarshalReader(r, v.config) 173 } 174 175 func initConfig(typ, config string) { 176 Reset() 177 SetConfigType(typ) 178 r := strings.NewReader(config) 179 180 if err := unmarshalReader(r, v.config); err != nil { 181 panic(err) 182 } 183 } 184 185 func initYAML() { 186 initConfig("yaml", string(yamlExample)) 187 } 188 189 func initJSON() { 190 Reset() 191 SetConfigType("json") 192 r := bytes.NewReader(jsonExample) 193 194 unmarshalReader(r, v.config) 195 } 196 197 func initProperties() { 198 Reset() 199 SetConfigType("properties") 200 r := bytes.NewReader(propertiesExample) 201 202 unmarshalReader(r, v.config) 203 } 204 205 func initTOML() { 206 Reset() 207 SetConfigType("toml") 208 r := bytes.NewReader(tomlExample) 209 210 unmarshalReader(r, v.config) 211 } 212 213 func initDotEnv() { 214 Reset() 215 SetConfigType("env") 216 r := bytes.NewReader(dotenvExample) 217 218 unmarshalReader(r, v.config) 219 } 220 221 func initHcl() { 222 Reset() 223 SetConfigType("hcl") 224 r := bytes.NewReader(hclExample) 225 226 unmarshalReader(r, v.config) 227 } 228 229 func initIni() { 230 Reset() 231 SetConfigType("ini") 232 r := bytes.NewReader(iniExample) 233 234 unmarshalReader(r, v.config) 235 } 236 237 // make directories for testing 238 func initDirs(t *testing.T) (string, string, func()) { 239 var ( 240 testDirs = []string{`a a`, `b`, `C_`} 241 config = `improbable` 242 ) 243 244 if runtime.GOOS != "windows" { 245 testDirs = append(testDirs, `d\d`) 246 } 247 248 root, err := ioutil.TempDir("", "") 249 require.NoError(t, err, "Failed to create temporary directory") 250 251 cleanup := true 252 defer func() { 253 if cleanup { 254 os.Chdir("..") 255 os.RemoveAll(root) 256 } 257 }() 258 259 assert.Nil(t, err) 260 261 err = os.Chdir(root) 262 require.Nil(t, err) 263 264 for _, dir := range testDirs { 265 err = os.Mkdir(dir, 0750) 266 assert.Nil(t, err) 267 268 err = ioutil.WriteFile( 269 path.Join(dir, config+".toml"), 270 []byte("key = \"value is "+dir+"\"\n"), 271 0640) 272 assert.Nil(t, err) 273 } 274 275 cleanup = false 276 return root, config, func() { 277 os.Chdir("..") 278 os.RemoveAll(root) 279 } 280 } 281 282 // stubs for PFlag Values 283 type stringValue string 284 285 func newStringValue(val string, p *string) *stringValue { 286 *p = val 287 return (*stringValue)(p) 288 } 289 290 func (s *stringValue) Set(val string) error { 291 *s = stringValue(val) 292 return nil 293 } 294 295 func (s *stringValue) Type() string { 296 return "string" 297 } 298 299 func (s *stringValue) String() string { 300 return string(*s) 301 } 302 303 func TestBasics(t *testing.T) { 304 SetConfigFile("/tmp/config.yaml") 305 filename, err := v.getConfigFile() 306 assert.Equal(t, "/tmp/config.yaml", filename) 307 assert.NoError(t, err) 308 } 309 310 func TestSearchInPath_WithoutConfigTypeSet(t *testing.T) { 311 filename := ".dotfilenoext" 312 path := "/tmp" 313 file := filepath.Join(path, filename) 314 SetConfigName(filename) 315 AddConfigPath(path) 316 _, createErr := v.fs.Create(file) 317 defer func() { 318 _ = v.fs.Remove(file) 319 }() 320 assert.NoError(t, createErr) 321 _, err := v.getConfigFile() 322 // unless config type is set, files without extension 323 // are not considered 324 assert.Error(t, err) 325 } 326 327 func TestSearchInPath(t *testing.T) { 328 filename := ".dotfilenoext" 329 path := "/tmp" 330 file := filepath.Join(path, filename) 331 SetConfigName(filename) 332 SetConfigType("yaml") 333 AddConfigPath(path) 334 _, createErr := v.fs.Create(file) 335 defer func() { 336 _ = v.fs.Remove(file) 337 }() 338 assert.NoError(t, createErr) 339 filename, err := v.getConfigFile() 340 assert.Equal(t, file, filename) 341 assert.NoError(t, err) 342 } 343 344 func TestSearchInPath_FilesOnly(t *testing.T) { 345 fs := afero.NewMemMapFs() 346 347 err := fs.Mkdir("/tmp/config", 0777) 348 require.NoError(t, err) 349 350 _, err = fs.Create("/tmp/config/config.yaml") 351 require.NoError(t, err) 352 353 v := New() 354 355 v.SetFs(fs) 356 v.AddConfigPath("/tmp") 357 v.AddConfigPath("/tmp/config") 358 359 filename, err := v.getConfigFile() 360 assert.Equal(t, "/tmp/config/config.yaml", filename) 361 assert.NoError(t, err) 362 } 363 364 func TestDefault(t *testing.T) { 365 SetDefault("age", 45) 366 assert.Equal(t, 45, Get("age")) 367 368 SetDefault("clothing.jacket", "slacks") 369 assert.Equal(t, "slacks", Get("clothing.jacket")) 370 371 SetConfigType("yaml") 372 err := ReadConfig(bytes.NewBuffer(yamlExample)) 373 374 assert.NoError(t, err) 375 assert.Equal(t, "leather", Get("clothing.jacket")) 376 } 377 378 func TestUnmarshaling(t *testing.T) { 379 SetConfigType("yaml") 380 r := bytes.NewReader(yamlExample) 381 382 unmarshalReader(r, v.config) 383 assert.True(t, InConfig("name")) 384 assert.False(t, InConfig("state")) 385 assert.Equal(t, "steve", Get("name")) 386 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies")) 387 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing")) 388 assert.Equal(t, 35, Get("age")) 389 } 390 391 func TestUnmarshalExact(t *testing.T) { 392 vip := New() 393 target := &testUnmarshalExtra{} 394 vip.SetConfigType("yaml") 395 r := bytes.NewReader(yamlExampleWithExtras) 396 vip.ReadConfig(r) 397 err := vip.UnmarshalExact(target) 398 if err == nil { 399 t.Fatal("UnmarshalExact should error when populating a struct from a conf that contains unused fields") 400 } 401 } 402 403 func TestOverrides(t *testing.T) { 404 Set("age", 40) 405 assert.Equal(t, 40, Get("age")) 406 } 407 408 func TestDefaultPost(t *testing.T) { 409 assert.NotEqual(t, "NYC", Get("state")) 410 SetDefault("state", "NYC") 411 assert.Equal(t, "NYC", Get("state")) 412 } 413 414 func TestAliases(t *testing.T) { 415 RegisterAlias("years", "age") 416 assert.Equal(t, 40, Get("years")) 417 Set("years", 45) 418 assert.Equal(t, 45, Get("age")) 419 } 420 421 func TestAliasInConfigFile(t *testing.T) { 422 // the config file specifies "beard". If we make this an alias for 423 // "hasbeard", we still want the old config file to work with beard. 424 RegisterAlias("beard", "hasbeard") 425 assert.Equal(t, true, Get("hasbeard")) 426 Set("hasbeard", false) 427 assert.Equal(t, false, Get("beard")) 428 } 429 430 func TestYML(t *testing.T) { 431 initYAML() 432 assert.Equal(t, "steve", Get("name")) 433 } 434 435 func TestJSON(t *testing.T) { 436 initJSON() 437 assert.Equal(t, "0001", Get("id")) 438 } 439 440 func TestProperties(t *testing.T) { 441 initProperties() 442 assert.Equal(t, "0001", Get("p_id")) 443 } 444 445 func TestTOML(t *testing.T) { 446 initTOML() 447 assert.Equal(t, "TOML Example", Get("title")) 448 } 449 450 func TestDotEnv(t *testing.T) { 451 initDotEnv() 452 assert.Equal(t, "DotEnv Example", Get("title_dotenv")) 453 } 454 455 func TestHCL(t *testing.T) { 456 initHcl() 457 assert.Equal(t, "0001", Get("id")) 458 assert.Equal(t, 0.55, Get("ppu")) 459 assert.Equal(t, "donut", Get("type")) 460 assert.Equal(t, "Cake", Get("name")) 461 Set("id", "0002") 462 assert.Equal(t, "0002", Get("id")) 463 assert.NotEqual(t, "cronut", Get("type")) 464 } 465 466 func TestIni(t *testing.T) { 467 initIni() 468 assert.Equal(t, "ini", Get("default.name")) 469 } 470 471 func TestRemotePrecedence(t *testing.T) { 472 initJSON() 473 474 remote := bytes.NewReader(remoteExample) 475 assert.Equal(t, "0001", Get("id")) 476 unmarshalReader(remote, v.kvstore) 477 assert.Equal(t, "0001", Get("id")) 478 assert.NotEqual(t, "cronut", Get("type")) 479 assert.Equal(t, "remote", Get("newkey")) 480 Set("newkey", "newvalue") 481 assert.NotEqual(t, "remote", Get("newkey")) 482 assert.Equal(t, "newvalue", Get("newkey")) 483 Set("newkey", "remote") 484 } 485 486 func TestEnv(t *testing.T) { 487 initJSON() 488 489 BindEnv("id") 490 BindEnv("f", "FOOD") 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 505 func 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 522 func 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 541 func 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 561 func 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 569 func 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 578 func 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 590 func 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 599 func 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 713 func 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 730 func 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 737 func TestRecursiveAliases(t *testing.T) { 738 RegisterAlias("Baz", "Roo") 739 RegisterAlias("Roo", "baz") 740 } 741 742 func 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 791 func 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 827 func 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 862 func 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 908 func 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 953 func 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 973 func 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 1020 func 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 1041 func 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 1058 func 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 1165 func 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 1179 func 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 1225 func 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 1247 func 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 1266 func 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 1285 func 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 1303 var 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 1329 var 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 1352 var propertiesWriteExpected = []byte(`p_id = 0001 1353 p_type = donut 1354 p_name = Cake 1355 p_ppu = 0.55 1356 p_batters.batter.type = Regular 1357 `) 1358 1359 var yamlWriteExpected = []byte(`age: 35 1360 beard: true 1361 clothing: 1362 jacket: leather 1363 pants: 1364 size: large 1365 trousers: denim 1366 eyes: brown 1367 hacker: true 1368 hobbies: 1369 - skateboarding 1370 - snowboarding 1371 - go 1372 name: steve 1373 `) 1374 1375 func 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 1498 func 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 1554 func 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 1608 func 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 1622 func 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 1631 func 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 1645 func 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 1659 func 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 1670 var yamlMergeExampleTgt = []byte(` 1671 hello: 1672 pop: 37890 1673 largenum: 765432101234567 1674 num2pow63: 9223372036854775808 1675 world: 1676 - us 1677 - uk 1678 - fr 1679 - de 1680 `) 1681 1682 var yamlMergeExampleSrc = []byte(` 1683 hello: 1684 pop: 45000 1685 largenum: 7654321001234567 1686 universe: 1687 - mw 1688 - ad 1689 ints: 1690 - 1 1691 - 2 1692 fu: bar 1693 `) 1694 1695 func 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 1767 func 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 1811 func 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 1852 func 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 1877 func 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 1887 func TestShadowedNestedValue(t *testing.T) { 1888 config := `name: steve 1889 clothing: 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 1912 func 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 1923 func TestCaseInsensitive(t *testing.T) { 1924 for _, config := range []struct { 1925 typ string 1926 content string 1927 }{ 1928 {"yaml", ` 1929 aBcD: 1 1930 eF: 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] 1955 gH = 2 1956 iJk = 3 1957 [eF.Lm] 1958 nO = 4 1959 [eF.Lm.P] 1960 Q = 5 1961 R = 6 1962 `}, 1963 } { 1964 doTestCaseInsensitive(t, config.typ, config.content) 1965 } 1966 } 1967 1968 func 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 2027 func 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 2056 func 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 2069 func 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 2086 func 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 2114 func 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 2173 func 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 2190 var yamlExampleWithDot = []byte(`Hacker: true 2191 name: steve 2192 hobbies: 2193 - skateboarding 2194 - snowboarding 2195 - go 2196 clothing: 2197 jacket: leather 2198 trousers: denim 2199 pants: 2200 size: large 2201 age: 35 2202 eyes : brown 2203 beard: true 2204 emails: 2205 steve@hacker.com: 2206 created: 01/02/03 2207 active: true 2208 `) 2209 2210 func 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 2257 func 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 2269 func 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. 2282 func 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 }