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