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