github.com/nsherman-spoton/viper@v1.15.3/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/nsherman-spoton/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 Set("baz", "bat") 896 RegisterAlias("Baz", "Roo") 897 RegisterAlias("Roo", "baz") 898 assert.Equal(t, "bat", Get("Baz")) 899 } 900 901 func TestUnmarshal(t *testing.T) { 902 SetDefault("port", 1313) 903 Set("name", "Steve") 904 Set("duration", "1s1ms") 905 Set("modes", []int{1, 2, 3}) 906 907 type config struct { 908 Port int 909 Name string 910 Duration time.Duration 911 Modes []int 912 } 913 914 var C config 915 916 err := Unmarshal(&C) 917 if err != nil { 918 t.Fatalf("unable to decode into struct, %v", err) 919 } 920 921 assert.Equal( 922 t, 923 &config{ 924 Name: "Steve", 925 Port: 1313, 926 Duration: time.Second + time.Millisecond, 927 Modes: []int{1, 2, 3}, 928 }, 929 &C, 930 ) 931 932 Set("port", 1234) 933 err = Unmarshal(&C) 934 if err != nil { 935 t.Fatalf("unable to decode into struct, %v", err) 936 } 937 938 assert.Equal( 939 t, 940 &config{ 941 Name: "Steve", 942 Port: 1234, 943 Duration: time.Second + time.Millisecond, 944 Modes: []int{1, 2, 3}, 945 }, 946 &C, 947 ) 948 } 949 950 func TestUnmarshalWithDecoderOptions(t *testing.T) { 951 Set("credentials", "{\"foo\":\"bar\"}") 952 953 opt := DecodeHook(mapstructure.ComposeDecodeHookFunc( 954 mapstructure.StringToTimeDurationHookFunc(), 955 mapstructure.StringToSliceHookFunc(","), 956 // Custom Decode Hook Function 957 func(rf reflect.Kind, rt reflect.Kind, data interface{}) (interface{}, error) { 958 if rf != reflect.String || rt != reflect.Map { 959 return data, nil 960 } 961 m := map[string]string{} 962 raw := data.(string) 963 if raw == "" { 964 return m, nil 965 } 966 return m, json.Unmarshal([]byte(raw), &m) 967 }, 968 )) 969 970 type config struct { 971 Credentials map[string]string 972 } 973 974 var C config 975 976 err := Unmarshal(&C, opt) 977 if err != nil { 978 t.Fatalf("unable to decode into struct, %v", err) 979 } 980 981 assert.Equal(t, &config{ 982 Credentials: map[string]string{"foo": "bar"}, 983 }, &C) 984 } 985 986 func TestBindPFlags(t *testing.T) { 987 v := New() // create independent Viper object 988 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 989 990 testValues := map[string]*string{ 991 "host": nil, 992 "port": nil, 993 "endpoint": nil, 994 } 995 996 mutatedTestValues := map[string]string{ 997 "host": "localhost", 998 "port": "6060", 999 "endpoint": "/public", 1000 } 1001 1002 for name := range testValues { 1003 testValues[name] = flagSet.String(name, "", "test") 1004 } 1005 1006 err := v.BindPFlags(flagSet) 1007 if err != nil { 1008 t.Fatalf("error binding flag set, %v", err) 1009 } 1010 1011 flagSet.VisitAll(func(flag *pflag.Flag) { 1012 flag.Value.Set(mutatedTestValues[flag.Name]) 1013 flag.Changed = true 1014 }) 1015 1016 for name, expected := range mutatedTestValues { 1017 assert.Equal(t, expected, v.Get(name)) 1018 } 1019 } 1020 1021 //nolint:dupl 1022 func TestBindPFlagsStringSlice(t *testing.T) { 1023 tests := []struct { 1024 Expected []string 1025 Value string 1026 }{ 1027 {[]string{}, ""}, 1028 {[]string{"jeden"}, "jeden"}, 1029 {[]string{"dwa", "trzy"}, "dwa,trzy"}, 1030 {[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""}, 1031 } 1032 1033 v := New() // create independent Viper object 1034 defaultVal := []string{"default"} 1035 v.SetDefault("stringslice", defaultVal) 1036 1037 for _, testValue := range tests { 1038 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1039 flagSet.StringSlice("stringslice", testValue.Expected, "test") 1040 1041 for _, changed := range []bool{true, false} { 1042 flagSet.VisitAll(func(f *pflag.Flag) { 1043 f.Value.Set(testValue.Value) 1044 f.Changed = changed 1045 }) 1046 1047 err := v.BindPFlags(flagSet) 1048 if err != nil { 1049 t.Fatalf("error binding flag set, %v", err) 1050 } 1051 1052 type TestStr struct { 1053 StringSlice []string 1054 } 1055 val := &TestStr{} 1056 if err := v.Unmarshal(val); err != nil { 1057 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1058 } 1059 if changed { 1060 assert.Equal(t, testValue.Expected, val.StringSlice) 1061 assert.Equal(t, testValue.Expected, v.Get("stringslice")) 1062 } else { 1063 assert.Equal(t, defaultVal, val.StringSlice) 1064 } 1065 } 1066 } 1067 } 1068 1069 //nolint:dupl 1070 func TestBindPFlagsStringArray(t *testing.T) { 1071 tests := []struct { 1072 Expected []string 1073 Value string 1074 }{ 1075 {[]string{}, ""}, 1076 {[]string{"jeden"}, "jeden"}, 1077 {[]string{"dwa,trzy"}, "dwa,trzy"}, 1078 {[]string{"cztery,\"piec , szesc\""}, "cztery,\"piec , szesc\""}, 1079 } 1080 1081 v := New() // create independent Viper object 1082 defaultVal := []string{"default"} 1083 v.SetDefault("stringarray", defaultVal) 1084 1085 for _, testValue := range tests { 1086 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1087 flagSet.StringArray("stringarray", testValue.Expected, "test") 1088 1089 for _, changed := range []bool{true, false} { 1090 flagSet.VisitAll(func(f *pflag.Flag) { 1091 f.Value.Set(testValue.Value) 1092 f.Changed = changed 1093 }) 1094 1095 err := v.BindPFlags(flagSet) 1096 if err != nil { 1097 t.Fatalf("error binding flag set, %v", err) 1098 } 1099 1100 type TestStr struct { 1101 StringArray []string 1102 } 1103 val := &TestStr{} 1104 if err := v.Unmarshal(val); err != nil { 1105 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1106 } 1107 if changed { 1108 assert.Equal(t, testValue.Expected, val.StringArray) 1109 assert.Equal(t, testValue.Expected, v.Get("stringarray")) 1110 } else { 1111 assert.Equal(t, defaultVal, val.StringArray) 1112 } 1113 } 1114 } 1115 } 1116 1117 func TestSliceFlagsReturnCorrectType(t *testing.T) { 1118 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1119 flagSet.IntSlice("int", []int{1, 2}, "") 1120 flagSet.StringSlice("str", []string{"3", "4"}, "") 1121 flagSet.DurationSlice("duration", []time.Duration{5 * time.Second}, "") 1122 1123 v := New() 1124 v.BindPFlags(flagSet) 1125 1126 all := v.AllSettings() 1127 1128 if _, ok := all["int"].([]int); !ok { 1129 t.Errorf("unexpected type %T expected []int", all["int"]) 1130 } 1131 if _, ok := all["str"].([]string); !ok { 1132 t.Errorf("unexpected type %T expected []string", all["str"]) 1133 } 1134 if _, ok := all["duration"].([]time.Duration); !ok { 1135 t.Errorf("unexpected type %T expected []time.Duration", all["duration"]) 1136 } 1137 } 1138 1139 //nolint:dupl 1140 func TestBindPFlagsIntSlice(t *testing.T) { 1141 tests := []struct { 1142 Expected []int 1143 Value string 1144 }{ 1145 {[]int{}, ""}, 1146 {[]int{1}, "1"}, 1147 {[]int{2, 3}, "2,3"}, 1148 } 1149 1150 v := New() // create independent Viper object 1151 defaultVal := []int{0} 1152 v.SetDefault("intslice", defaultVal) 1153 1154 for _, testValue := range tests { 1155 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1156 flagSet.IntSlice("intslice", testValue.Expected, "test") 1157 1158 for _, changed := range []bool{true, false} { 1159 flagSet.VisitAll(func(f *pflag.Flag) { 1160 f.Value.Set(testValue.Value) 1161 f.Changed = changed 1162 }) 1163 1164 err := v.BindPFlags(flagSet) 1165 if err != nil { 1166 t.Fatalf("error binding flag set, %v", err) 1167 } 1168 1169 type TestInt struct { 1170 IntSlice []int 1171 } 1172 val := &TestInt{} 1173 if err := v.Unmarshal(val); err != nil { 1174 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1175 } 1176 if changed { 1177 assert.Equal(t, testValue.Expected, val.IntSlice) 1178 assert.Equal(t, testValue.Expected, v.Get("intslice")) 1179 } else { 1180 assert.Equal(t, defaultVal, val.IntSlice) 1181 } 1182 } 1183 } 1184 } 1185 1186 func TestBindPFlag(t *testing.T) { 1187 testString := "testing" 1188 testValue := newStringValue(testString, &testString) 1189 1190 flag := &pflag.Flag{ 1191 Name: "testflag", 1192 Value: testValue, 1193 Changed: false, 1194 } 1195 1196 BindPFlag("testvalue", flag) 1197 1198 assert.Equal(t, testString, Get("testvalue")) 1199 1200 flag.Value.Set("testing_mutate") 1201 flag.Changed = true // hack for pflag usage 1202 1203 assert.Equal(t, "testing_mutate", Get("testvalue")) 1204 } 1205 1206 func TestBindPFlagDetectNilFlag(t *testing.T) { 1207 result := BindPFlag("testvalue", nil) 1208 assert.Error(t, result) 1209 } 1210 1211 func TestBindPFlagStringToString(t *testing.T) { 1212 tests := []struct { 1213 Expected map[string]string 1214 Value string 1215 }{ 1216 {map[string]string{}, ""}, 1217 {map[string]string{"yo": "hi"}, "yo=hi"}, 1218 {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"}, 1219 {map[string]string{"yo": ""}, "yo="}, 1220 {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"}, 1221 } 1222 1223 v := New() // create independent Viper object 1224 defaultVal := map[string]string{} 1225 v.SetDefault("stringtostring", defaultVal) 1226 1227 for _, testValue := range tests { 1228 flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) 1229 flagSet.StringToString("stringtostring", testValue.Expected, "test") 1230 1231 for _, changed := range []bool{true, false} { 1232 flagSet.VisitAll(func(f *pflag.Flag) { 1233 f.Value.Set(testValue.Value) 1234 f.Changed = changed 1235 }) 1236 1237 err := v.BindPFlags(flagSet) 1238 if err != nil { 1239 t.Fatalf("error binding flag set, %v", err) 1240 } 1241 1242 type TestMap struct { 1243 StringToString map[string]string 1244 } 1245 val := &TestMap{} 1246 if err := v.Unmarshal(val); err != nil { 1247 t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err) 1248 } 1249 if changed { 1250 assert.Equal(t, testValue.Expected, val.StringToString) 1251 } else { 1252 assert.Equal(t, defaultVal, val.StringToString) 1253 } 1254 } 1255 } 1256 } 1257 1258 func TestBoundCaseSensitivity(t *testing.T) { 1259 assert.Equal(t, "brown", Get("eyes")) 1260 1261 BindEnv("eYEs", "TURTLE_EYES") 1262 1263 testutil.Setenv(t, "TURTLE_EYES", "blue") 1264 1265 assert.Equal(t, "blue", Get("eyes")) 1266 1267 testString := "green" 1268 testValue := newStringValue(testString, &testString) 1269 1270 flag := &pflag.Flag{ 1271 Name: "eyeballs", 1272 Value: testValue, 1273 Changed: true, 1274 } 1275 1276 BindPFlag("eYEs", flag) 1277 assert.Equal(t, "green", Get("eyes")) 1278 } 1279 1280 func TestSizeInBytes(t *testing.T) { 1281 input := map[string]uint{ 1282 "": 0, 1283 "b": 0, 1284 "12 bytes": 0, 1285 "200000000000gb": 0, 1286 "12 b": 12, 1287 "43 MB": 43 * (1 << 20), 1288 "10mb": 10 * (1 << 20), 1289 "1gb": 1 << 30, 1290 } 1291 1292 for str, expected := range input { 1293 assert.Equal(t, expected, parseSizeInBytes(str), str) 1294 } 1295 } 1296 1297 func TestFindsNestedKeys(t *testing.T) { 1298 initConfigs() 1299 dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 1300 1301 Set("super", map[string]interface{}{ 1302 "deep": map[string]interface{}{ 1303 "nested": "value", 1304 }, 1305 }) 1306 1307 expected := map[string]interface{}{ 1308 "super": map[string]interface{}{ 1309 "deep": map[string]interface{}{ 1310 "nested": "value", 1311 }, 1312 }, 1313 "super.deep": map[string]interface{}{ 1314 "nested": "value", 1315 }, 1316 "super.deep.nested": "value", 1317 "owner.organization": "MongoDB", 1318 "batters.batter": []interface{}{ 1319 map[string]interface{}{ 1320 "type": "Regular", 1321 }, 1322 map[string]interface{}{ 1323 "type": "Chocolate", 1324 }, 1325 map[string]interface{}{ 1326 "type": "Blueberry", 1327 }, 1328 map[string]interface{}{ 1329 "type": "Devil's Food", 1330 }, 1331 }, 1332 "hobbies": []interface{}{ 1333 "skateboarding", "snowboarding", "go", 1334 }, 1335 "TITLE_DOTENV": "DotEnv Example", 1336 "TYPE_DOTENV": "donut", 1337 "NAME_DOTENV": "Cake", 1338 "title": "TOML Example", 1339 "newkey": "remote", 1340 "batters": map[string]interface{}{ 1341 "batter": []interface{}{ 1342 map[string]interface{}{ 1343 "type": "Regular", 1344 }, 1345 map[string]interface{}{ 1346 "type": "Chocolate", 1347 }, 1348 map[string]interface{}{ 1349 "type": "Blueberry", 1350 }, 1351 map[string]interface{}{ 1352 "type": "Devil's Food", 1353 }, 1354 }, 1355 }, 1356 "eyes": "brown", 1357 "age": 35, 1358 "owner": map[string]interface{}{ 1359 "organization": "MongoDB", 1360 "bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1361 "dob": dob, 1362 }, 1363 "owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large", 1364 "type": "donut", 1365 "id": "0001", 1366 "name": "Cake", 1367 "hacker": true, 1368 "ppu": 0.55, 1369 "clothing": map[string]interface{}{ 1370 "jacket": "leather", 1371 "trousers": "denim", 1372 "pants": map[string]interface{}{ 1373 "size": "large", 1374 }, 1375 }, 1376 "clothing.jacket": "leather", 1377 "clothing.pants.size": "large", 1378 "clothing.trousers": "denim", 1379 "owner.dob": dob, 1380 "beard": true, 1381 "foos": []map[string]interface{}{ 1382 { 1383 "foo": []map[string]interface{}{ 1384 { 1385 "key": 1, 1386 }, 1387 { 1388 "key": 2, 1389 }, 1390 { 1391 "key": 3, 1392 }, 1393 { 1394 "key": 4, 1395 }, 1396 }, 1397 }, 1398 }, 1399 } 1400 1401 for key, expectedValue := range expected { 1402 assert.Equal(t, expectedValue, v.Get(key)) 1403 } 1404 } 1405 1406 func TestReadBufConfig(t *testing.T) { 1407 v := New() 1408 v.SetConfigType("yaml") 1409 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1410 t.Log(v.AllKeys()) 1411 1412 assert.True(t, v.InConfig("name")) 1413 assert.True(t, v.InConfig("clothing.jacket")) 1414 assert.False(t, v.InConfig("state")) 1415 assert.False(t, v.InConfig("clothing.hat")) 1416 assert.Equal(t, "steve", v.Get("name")) 1417 assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies")) 1418 assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing")) 1419 assert.Equal(t, 35, v.Get("age")) 1420 } 1421 1422 func TestIsSet(t *testing.T) { 1423 v := New() 1424 v.SetConfigType("yaml") 1425 1426 /* config and defaults */ 1427 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1428 v.SetDefault("clothing.shoes", "sneakers") 1429 1430 assert.True(t, v.IsSet("clothing")) 1431 assert.True(t, v.IsSet("clothing.jacket")) 1432 assert.False(t, v.IsSet("clothing.jackets")) 1433 assert.True(t, v.IsSet("clothing.shoes")) 1434 1435 /* state change */ 1436 assert.False(t, v.IsSet("helloworld")) 1437 v.Set("helloworld", "fubar") 1438 assert.True(t, v.IsSet("helloworld")) 1439 1440 /* env */ 1441 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 1442 v.BindEnv("eyes") 1443 v.BindEnv("foo") 1444 v.BindEnv("clothing.hat") 1445 v.BindEnv("clothing.hats") 1446 1447 testutil.Setenv(t, "FOO", "bar") 1448 testutil.Setenv(t, "CLOTHING_HAT", "bowler") 1449 1450 assert.True(t, v.IsSet("eyes")) // in the config file 1451 assert.True(t, v.IsSet("foo")) // in the environment 1452 assert.True(t, v.IsSet("clothing.hat")) // in the environment 1453 assert.False(t, v.IsSet("clothing.hats")) // not defined 1454 1455 /* flags */ 1456 flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError) 1457 flagset.Bool("foobaz", false, "foobaz") 1458 flagset.Bool("barbaz", false, "barbaz") 1459 foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz") 1460 v.BindPFlag("foobaz", foobaz) 1461 v.BindPFlag("barbaz", barbaz) 1462 barbaz.Value.Set("true") 1463 barbaz.Changed = true // hack for pflag usage 1464 1465 assert.False(t, v.IsSet("foobaz")) 1466 assert.True(t, v.IsSet("barbaz")) 1467 } 1468 1469 func TestDirsSearch(t *testing.T) { 1470 root, config, cleanup := initDirs(t) 1471 defer cleanup() 1472 1473 v := New() 1474 v.SetConfigName(config) 1475 v.SetDefault(`key`, `default`) 1476 1477 entries, err := ioutil.ReadDir(root) 1478 assert.Nil(t, err) 1479 for _, e := range entries { 1480 if e.IsDir() { 1481 v.AddConfigPath(e.Name()) 1482 } 1483 } 1484 1485 err = v.ReadInConfig() 1486 assert.Nil(t, err) 1487 1488 assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`)) 1489 } 1490 1491 func TestWrongDirsSearchNotFound(t *testing.T) { 1492 _, config, cleanup := initDirs(t) 1493 defer cleanup() 1494 1495 v := New() 1496 v.SetConfigName(config) 1497 v.SetDefault(`key`, `default`) 1498 1499 v.AddConfigPath(`whattayoutalkingbout`) 1500 v.AddConfigPath(`thispathaintthere`) 1501 1502 err := v.ReadInConfig() 1503 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1504 1505 // Even though config did not load and the error might have 1506 // been ignored by the client, the default still loads 1507 assert.Equal(t, `default`, v.GetString(`key`)) 1508 } 1509 1510 func TestWrongDirsSearchNotFoundForMerge(t *testing.T) { 1511 _, config, cleanup := initDirs(t) 1512 defer cleanup() 1513 1514 v := New() 1515 v.SetConfigName(config) 1516 v.SetDefault(`key`, `default`) 1517 1518 v.AddConfigPath(`whattayoutalkingbout`) 1519 v.AddConfigPath(`thispathaintthere`) 1520 1521 err := v.MergeInConfig() 1522 assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err)) 1523 1524 // Even though config did not load and the error might have 1525 // been ignored by the client, the default still loads 1526 assert.Equal(t, `default`, v.GetString(`key`)) 1527 } 1528 1529 func TestSub(t *testing.T) { 1530 v := New() 1531 v.SetConfigType("yaml") 1532 v.ReadConfig(bytes.NewBuffer(yamlExample)) 1533 1534 subv := v.Sub("clothing") 1535 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size")) 1536 1537 subv = v.Sub("clothing.pants") 1538 assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size")) 1539 1540 subv = v.Sub("clothing.pants.size") 1541 assert.Equal(t, (*Viper)(nil), subv) 1542 1543 subv = v.Sub("missing.key") 1544 assert.Equal(t, (*Viper)(nil), subv) 1545 1546 subv = v.Sub("clothing") 1547 assert.Equal(t, subv.parents[0], "clothing") 1548 1549 subv = v.Sub("clothing").Sub("pants") 1550 assert.Equal(t, len(subv.parents), 2) 1551 assert.Equal(t, subv.parents[0], "clothing") 1552 assert.Equal(t, subv.parents[1], "pants") 1553 } 1554 1555 var hclWriteExpected = []byte(`"foos" = { 1556 "foo" = { 1557 "key" = 1 1558 } 1559 1560 "foo" = { 1561 "key" = 2 1562 } 1563 1564 "foo" = { 1565 "key" = 3 1566 } 1567 1568 "foo" = { 1569 "key" = 4 1570 } 1571 } 1572 1573 "id" = "0001" 1574 1575 "name" = "Cake" 1576 1577 "ppu" = 0.55 1578 1579 "type" = "donut"`) 1580 1581 var jsonWriteExpected = []byte(`{ 1582 "batters": { 1583 "batter": [ 1584 { 1585 "type": "Regular" 1586 }, 1587 { 1588 "type": "Chocolate" 1589 }, 1590 { 1591 "type": "Blueberry" 1592 }, 1593 { 1594 "type": "Devil's Food" 1595 } 1596 ] 1597 }, 1598 "id": "0001", 1599 "name": "Cake", 1600 "ppu": 0.55, 1601 "type": "donut" 1602 }`) 1603 1604 var propertiesWriteExpected = []byte(`p_id = 0001 1605 p_type = donut 1606 p_name = Cake 1607 p_ppu = 0.55 1608 p_batters.batter.type = Regular 1609 `) 1610 1611 // var yamlWriteExpected = []byte(`age: 35 1612 // beard: true 1613 // clothing: 1614 // jacket: leather 1615 // pants: 1616 // size: large 1617 // trousers: denim 1618 // eyes: brown 1619 // hacker: true 1620 // hobbies: 1621 // - skateboarding 1622 // - snowboarding 1623 // - go 1624 // name: steve 1625 // `) 1626 1627 func TestWriteConfig(t *testing.T) { 1628 fs := afero.NewMemMapFs() 1629 testCases := map[string]struct { 1630 configName string 1631 inConfigType string 1632 outConfigType string 1633 fileName string 1634 input []byte 1635 expectedContent []byte 1636 }{ 1637 "hcl with file extension": { 1638 configName: "c", 1639 inConfigType: "hcl", 1640 outConfigType: "hcl", 1641 fileName: "c.hcl", 1642 input: hclExample, 1643 expectedContent: hclWriteExpected, 1644 }, 1645 "hcl without file extension": { 1646 configName: "c", 1647 inConfigType: "hcl", 1648 outConfigType: "hcl", 1649 fileName: "c", 1650 input: hclExample, 1651 expectedContent: hclWriteExpected, 1652 }, 1653 "hcl with file extension and mismatch type": { 1654 configName: "c", 1655 inConfigType: "hcl", 1656 outConfigType: "json", 1657 fileName: "c.hcl", 1658 input: hclExample, 1659 expectedContent: hclWriteExpected, 1660 }, 1661 "json with file extension": { 1662 configName: "c", 1663 inConfigType: "json", 1664 outConfigType: "json", 1665 fileName: "c.json", 1666 input: jsonExample, 1667 expectedContent: jsonWriteExpected, 1668 }, 1669 "json without file extension": { 1670 configName: "c", 1671 inConfigType: "json", 1672 outConfigType: "json", 1673 fileName: "c", 1674 input: jsonExample, 1675 expectedContent: jsonWriteExpected, 1676 }, 1677 "json with file extension and mismatch type": { 1678 configName: "c", 1679 inConfigType: "json", 1680 outConfigType: "hcl", 1681 fileName: "c.json", 1682 input: jsonExample, 1683 expectedContent: jsonWriteExpected, 1684 }, 1685 "properties with file extension": { 1686 configName: "c", 1687 inConfigType: "properties", 1688 outConfigType: "properties", 1689 fileName: "c.properties", 1690 input: propertiesExample, 1691 expectedContent: propertiesWriteExpected, 1692 }, 1693 "properties without file extension": { 1694 configName: "c", 1695 inConfigType: "properties", 1696 outConfigType: "properties", 1697 fileName: "c", 1698 input: propertiesExample, 1699 expectedContent: propertiesWriteExpected, 1700 }, 1701 "yaml with file extension": { 1702 configName: "c", 1703 inConfigType: "yaml", 1704 outConfigType: "yaml", 1705 fileName: "c.yaml", 1706 input: yamlExample, 1707 expectedContent: yamlWriteExpected, 1708 }, 1709 "yaml without file extension": { 1710 configName: "c", 1711 inConfigType: "yaml", 1712 outConfigType: "yaml", 1713 fileName: "c", 1714 input: yamlExample, 1715 expectedContent: yamlWriteExpected, 1716 }, 1717 "yaml with file extension and mismatch type": { 1718 configName: "c", 1719 inConfigType: "yaml", 1720 outConfigType: "json", 1721 fileName: "c.yaml", 1722 input: yamlExample, 1723 expectedContent: yamlWriteExpected, 1724 }, 1725 } 1726 for name, tc := range testCases { 1727 t.Run(name, func(t *testing.T) { 1728 v := New() 1729 v.SetFs(fs) 1730 v.SetConfigName(tc.fileName) 1731 v.SetConfigType(tc.inConfigType) 1732 1733 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1734 if err != nil { 1735 t.Fatal(err) 1736 } 1737 v.SetConfigType(tc.outConfigType) 1738 if err := v.WriteConfigAs(tc.fileName); err != nil { 1739 t.Fatal(err) 1740 } 1741 read, err := afero.ReadFile(fs, tc.fileName) 1742 if err != nil { 1743 t.Fatal(err) 1744 } 1745 assert.Equal(t, tc.expectedContent, read) 1746 }) 1747 } 1748 } 1749 1750 func TestWriteConfigTOML(t *testing.T) { 1751 fs := afero.NewMemMapFs() 1752 1753 testCases := map[string]struct { 1754 configName string 1755 configType string 1756 fileName string 1757 input []byte 1758 }{ 1759 "with file extension": { 1760 configName: "c", 1761 configType: "toml", 1762 fileName: "c.toml", 1763 input: tomlExample, 1764 }, 1765 "without file extension": { 1766 configName: "c", 1767 configType: "toml", 1768 fileName: "c", 1769 input: tomlExample, 1770 }, 1771 } 1772 for name, tc := range testCases { 1773 t.Run(name, func(t *testing.T) { 1774 v := New() 1775 v.SetFs(fs) 1776 v.SetConfigName(tc.configName) 1777 v.SetConfigType(tc.configType) 1778 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1779 if err != nil { 1780 t.Fatal(err) 1781 } 1782 if err := v.WriteConfigAs(tc.fileName); err != nil { 1783 t.Fatal(err) 1784 } 1785 1786 // The TOML String method does not order the contents. 1787 // Therefore, we must read the generated file and compare the data. 1788 v2 := New() 1789 v2.SetFs(fs) 1790 v2.SetConfigName(tc.configName) 1791 v2.SetConfigType(tc.configType) 1792 v2.SetConfigFile(tc.fileName) 1793 err = v2.ReadInConfig() 1794 if err != nil { 1795 t.Fatal(err) 1796 } 1797 1798 assert.Equal(t, v.GetString("title"), v2.GetString("title")) 1799 assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio")) 1800 assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob")) 1801 assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization")) 1802 }) 1803 } 1804 } 1805 1806 func TestWriteConfigDotEnv(t *testing.T) { 1807 fs := afero.NewMemMapFs() 1808 testCases := map[string]struct { 1809 configName string 1810 configType string 1811 fileName string 1812 input []byte 1813 }{ 1814 "with file extension": { 1815 configName: "c", 1816 configType: "env", 1817 fileName: "c.env", 1818 input: dotenvExample, 1819 }, 1820 "without file extension": { 1821 configName: "c", 1822 configType: "env", 1823 fileName: "c", 1824 input: dotenvExample, 1825 }, 1826 } 1827 for name, tc := range testCases { 1828 t.Run(name, func(t *testing.T) { 1829 v := New() 1830 v.SetFs(fs) 1831 v.SetConfigName(tc.configName) 1832 v.SetConfigType(tc.configType) 1833 err := v.ReadConfig(bytes.NewBuffer(tc.input)) 1834 if err != nil { 1835 t.Fatal(err) 1836 } 1837 if err := v.WriteConfigAs(tc.fileName); err != nil { 1838 t.Fatal(err) 1839 } 1840 1841 // The TOML String method does not order the contents. 1842 // Therefore, we must read the generated file and compare the data. 1843 v2 := New() 1844 v2.SetFs(fs) 1845 v2.SetConfigName(tc.configName) 1846 v2.SetConfigType(tc.configType) 1847 v2.SetConfigFile(tc.fileName) 1848 err = v2.ReadInConfig() 1849 if err != nil { 1850 t.Fatal(err) 1851 } 1852 1853 assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv")) 1854 assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv")) 1855 assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv")) 1856 }) 1857 } 1858 } 1859 1860 func TestSafeWriteConfig(t *testing.T) { 1861 v := New() 1862 fs := afero.NewMemMapFs() 1863 v.SetFs(fs) 1864 v.AddConfigPath("/test") 1865 v.SetConfigName("c") 1866 v.SetConfigType("yaml") 1867 require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample))) 1868 require.NoError(t, v.SafeWriteConfig()) 1869 read, err := afero.ReadFile(fs, testutil.AbsFilePath(t, "/test/c.yaml")) 1870 require.NoError(t, err) 1871 assert.Equal(t, yamlWriteExpected, read) 1872 } 1873 1874 func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) { 1875 v := New() 1876 fs := afero.NewMemMapFs() 1877 v.SetFs(fs) 1878 v.SetConfigName("c") 1879 v.SetConfigType("yaml") 1880 require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'") 1881 } 1882 1883 func TestSafeWriteConfigWithExistingFile(t *testing.T) { 1884 v := New() 1885 fs := afero.NewMemMapFs() 1886 fs.Create(testutil.AbsFilePath(t, "/test/c.yaml")) 1887 v.SetFs(fs) 1888 v.AddConfigPath("/test") 1889 v.SetConfigName("c") 1890 v.SetConfigType("yaml") 1891 err := v.SafeWriteConfig() 1892 require.Error(t, err) 1893 _, ok := err.(ConfigFileAlreadyExistsError) 1894 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1895 } 1896 1897 func TestSafeWriteAsConfig(t *testing.T) { 1898 v := New() 1899 fs := afero.NewMemMapFs() 1900 v.SetFs(fs) 1901 err := v.ReadConfig(bytes.NewBuffer(yamlExample)) 1902 if err != nil { 1903 t.Fatal(err) 1904 } 1905 require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml")) 1906 if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil { 1907 t.Fatal(err) 1908 } 1909 } 1910 1911 func TestSafeWriteConfigAsWithExistingFile(t *testing.T) { 1912 v := New() 1913 fs := afero.NewMemMapFs() 1914 fs.Create("/test/c.yaml") 1915 v.SetFs(fs) 1916 err := v.SafeWriteConfigAs("/test/c.yaml") 1917 require.Error(t, err) 1918 _, ok := err.(ConfigFileAlreadyExistsError) 1919 assert.True(t, ok, "Expected ConfigFileAlreadyExistsError") 1920 } 1921 1922 func TestWriteHiddenFile(t *testing.T) { 1923 v := New() 1924 fs := afero.NewMemMapFs() 1925 fs.Create(testutil.AbsFilePath(t, "/test/.config")) 1926 v.SetFs(fs) 1927 1928 v.SetConfigName(".config") 1929 v.SetConfigType("yaml") 1930 v.AddConfigPath("/test") 1931 1932 err := v.ReadInConfig() 1933 require.NoError(t, err) 1934 1935 err = v.WriteConfig() 1936 require.NoError(t, err) 1937 } 1938 1939 var yamlMergeExampleTgt = []byte(` 1940 hello: 1941 pop: 37890 1942 largenum: 765432101234567 1943 num2pow63: 9223372036854775808 1944 universe: null 1945 world: 1946 - us 1947 - uk 1948 - fr 1949 - de 1950 `) 1951 1952 var yamlMergeExampleSrc = []byte(` 1953 hello: 1954 pop: 45000 1955 largenum: 7654321001234567 1956 universe: 1957 - mw 1958 - ad 1959 ints: 1960 - 1 1961 - 2 1962 fu: bar 1963 `) 1964 1965 var jsonMergeExampleTgt = []byte(` 1966 { 1967 "hello": { 1968 "foo": null, 1969 "pop": 123456 1970 } 1971 } 1972 `) 1973 1974 var jsonMergeExampleSrc = []byte(` 1975 { 1976 "hello": { 1977 "foo": "foo str", 1978 "pop": "pop str" 1979 } 1980 } 1981 `) 1982 1983 func TestMergeConfig(t *testing.T) { 1984 v := New() 1985 v.SetConfigType("yml") 1986 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 1987 t.Fatal(err) 1988 } 1989 1990 if pop := v.GetInt("hello.pop"); pop != 37890 { 1991 t.Fatalf("pop != 37890, = %d", pop) 1992 } 1993 1994 if pop := v.GetInt32("hello.pop"); pop != int32(37890) { 1995 t.Fatalf("pop != 37890, = %d", pop) 1996 } 1997 1998 if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) { 1999 t.Fatalf("int64 largenum != 765432101234567, = %d", pop) 2000 } 2001 2002 if pop := v.GetUint("hello.pop"); pop != 37890 { 2003 t.Fatalf("uint pop != 37890, = %d", pop) 2004 } 2005 2006 if pop := v.GetUint16("hello.pop"); pop != uint16(37890) { 2007 t.Fatalf("uint pop != 37890, = %d", pop) 2008 } 2009 2010 if pop := v.GetUint32("hello.pop"); pop != 37890 { 2011 t.Fatalf("uint32 pop != 37890, = %d", pop) 2012 } 2013 2014 if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 { 2015 t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %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 fu := v.GetString("fu"); fu != "" { 2023 t.Fatalf("fu != \"\", = %s", fu) 2024 } 2025 2026 if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 2027 t.Fatal(err) 2028 } 2029 2030 if pop := v.GetInt("hello.pop"); pop != 45000 { 2031 t.Fatalf("pop != 45000, = %d", pop) 2032 } 2033 2034 if pop := v.GetInt32("hello.pop"); pop != int32(45000) { 2035 t.Fatalf("pop != 45000, = %d", pop) 2036 } 2037 2038 if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) { 2039 t.Fatalf("int64 largenum != 7654321001234567, = %d", pop) 2040 } 2041 2042 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 2043 t.Fatalf("len(world) != 4, = %d", len(world)) 2044 } 2045 2046 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 2047 t.Fatalf("len(universe) != 2, = %d", len(universe)) 2048 } 2049 2050 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 2051 t.Fatalf("len(ints) != 2, = %d", len(ints)) 2052 } 2053 2054 if fu := v.GetString("fu"); fu != "bar" { 2055 t.Fatalf("fu != \"bar\", = %s", fu) 2056 } 2057 } 2058 2059 func TestMergeConfigOverrideType(t *testing.T) { 2060 v := New() 2061 v.SetConfigType("json") 2062 if err := v.ReadConfig(bytes.NewBuffer(jsonMergeExampleTgt)); err != nil { 2063 t.Fatal(err) 2064 } 2065 2066 if err := v.MergeConfig(bytes.NewBuffer(jsonMergeExampleSrc)); err != nil { 2067 t.Fatal(err) 2068 } 2069 2070 if pop := v.GetString("hello.pop"); pop != "pop str" { 2071 t.Fatalf("pop != \"pop str\", = %s", pop) 2072 } 2073 2074 if foo := v.GetString("hello.foo"); foo != "foo str" { 2075 t.Fatalf("foo != \"foo str\", = %s", foo) 2076 } 2077 } 2078 2079 func TestMergeConfigNoMerge(t *testing.T) { 2080 v := New() 2081 v.SetConfigType("yml") 2082 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 2083 t.Fatal(err) 2084 } 2085 2086 if pop := v.GetInt("hello.pop"); pop != 37890 { 2087 t.Fatalf("pop != 37890, = %d", pop) 2088 } 2089 2090 if world := v.GetStringSlice("hello.world"); len(world) != 4 { 2091 t.Fatalf("len(world) != 4, = %d", len(world)) 2092 } 2093 2094 if fu := v.GetString("fu"); fu != "" { 2095 t.Fatalf("fu != \"\", = %s", fu) 2096 } 2097 2098 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil { 2099 t.Fatal(err) 2100 } 2101 2102 if pop := v.GetInt("hello.pop"); pop != 45000 { 2103 t.Fatalf("pop != 45000, = %d", pop) 2104 } 2105 2106 if world := v.GetStringSlice("hello.world"); len(world) != 0 { 2107 t.Fatalf("len(world) != 0, = %d", len(world)) 2108 } 2109 2110 if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 { 2111 t.Fatalf("len(universe) != 2, = %d", len(universe)) 2112 } 2113 2114 if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 { 2115 t.Fatalf("len(ints) != 2, = %d", len(ints)) 2116 } 2117 2118 if fu := v.GetString("fu"); fu != "bar" { 2119 t.Fatalf("fu != \"bar\", = %s", fu) 2120 } 2121 } 2122 2123 func TestMergeConfigMap(t *testing.T) { 2124 v := New() 2125 v.SetConfigType("yml") 2126 if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil { 2127 t.Fatal(err) 2128 } 2129 2130 assert := func(i int) { 2131 large := v.GetInt64("hello.largenum") 2132 pop := v.GetInt("hello.pop") 2133 if large != 765432101234567 { 2134 t.Fatal("Got large num:", large) 2135 } 2136 2137 if pop != i { 2138 t.Fatal("Got pop:", pop) 2139 } 2140 } 2141 2142 assert(37890) 2143 2144 update := map[string]interface{}{ 2145 "Hello": map[string]interface{}{ 2146 "Pop": 1234, 2147 }, 2148 "World": map[interface{}]interface{}{ 2149 "Rock": 345, 2150 }, 2151 } 2152 2153 if err := v.MergeConfigMap(update); err != nil { 2154 t.Fatal(err) 2155 } 2156 2157 if rock := v.GetInt("world.rock"); rock != 345 { 2158 t.Fatal("Got rock:", rock) 2159 } 2160 2161 assert(1234) 2162 } 2163 2164 func TestUnmarshalingWithAliases(t *testing.T) { 2165 v := New() 2166 v.SetDefault("ID", 1) 2167 v.Set("name", "Steve") 2168 v.Set("lastname", "Owen") 2169 2170 v.RegisterAlias("UserID", "ID") 2171 v.RegisterAlias("Firstname", "name") 2172 v.RegisterAlias("Surname", "lastname") 2173 2174 type config struct { 2175 ID int 2176 FirstName string 2177 Surname string 2178 } 2179 2180 var C config 2181 err := v.Unmarshal(&C) 2182 if err != nil { 2183 t.Fatalf("unable to decode into struct, %v", err) 2184 } 2185 2186 assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C) 2187 } 2188 2189 func TestSetConfigNameClearsFileCache(t *testing.T) { 2190 SetConfigFile("/tmp/config.yaml") 2191 SetConfigName("default") 2192 f, err := v.getConfigFile() 2193 if err == nil { 2194 t.Fatalf("config file cache should have been cleared") 2195 } 2196 assert.Empty(t, f) 2197 } 2198 2199 func TestShadowedNestedValue(t *testing.T) { 2200 config := `name: steve 2201 clothing: 2202 jacket: leather 2203 trousers: denim 2204 pants: 2205 size: large 2206 ` 2207 initConfig("yaml", config) 2208 2209 assert.Equal(t, "steve", GetString("name")) 2210 2211 polyester := "polyester" 2212 SetDefault("clothing.shirt", polyester) 2213 SetDefault("clothing.jacket.price", 100) 2214 2215 assert.Equal(t, "leather", GetString("clothing.jacket")) 2216 assert.Nil(t, Get("clothing.jacket.price")) 2217 assert.Equal(t, polyester, GetString("clothing.shirt")) 2218 2219 clothingSettings := AllSettings()["clothing"].(map[string]interface{}) 2220 assert.Equal(t, "leather", clothingSettings["jacket"]) 2221 assert.Equal(t, polyester, clothingSettings["shirt"]) 2222 } 2223 2224 func TestDotParameter(t *testing.T) { 2225 initJSON() 2226 // shoud take precedence over batters defined in jsonExample 2227 r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`)) 2228 unmarshalReader(r, v.config) 2229 2230 actual := Get("batters.batter") 2231 expected := []interface{}{map[string]interface{}{"type": "Small"}} 2232 assert.Equal(t, expected, actual) 2233 } 2234 2235 func TestCaseInsensitive(t *testing.T) { 2236 for _, config := range []struct { 2237 typ string 2238 content string 2239 }{ 2240 {"yaml", ` 2241 aBcD: 1 2242 eF: 2243 gH: 2 2244 iJk: 3 2245 Lm: 2246 nO: 4 2247 P: 2248 Q: 5 2249 R: 6 2250 `}, 2251 {"json", `{ 2252 "aBcD": 1, 2253 "eF": { 2254 "iJk": 3, 2255 "Lm": { 2256 "P": { 2257 "Q": 5, 2258 "R": 6 2259 }, 2260 "nO": 4 2261 }, 2262 "gH": 2 2263 } 2264 }`}, 2265 {"toml", `aBcD = 1 2266 [eF] 2267 gH = 2 2268 iJk = 3 2269 [eF.Lm] 2270 nO = 4 2271 [eF.Lm.P] 2272 Q = 5 2273 R = 6 2274 `}, 2275 } { 2276 doTestCaseInsensitive(t, config.typ, config.content) 2277 } 2278 } 2279 2280 func TestCaseInsensitiveSet(t *testing.T) { 2281 Reset() 2282 m1 := map[string]interface{}{ 2283 "Foo": 32, 2284 "Bar": map[interface{}]interface{}{ 2285 "ABc": "A", 2286 "cDE": "B", 2287 }, 2288 } 2289 2290 m2 := map[string]interface{}{ 2291 "Foo": 52, 2292 "Bar": map[interface{}]interface{}{ 2293 "bCd": "A", 2294 "eFG": "B", 2295 }, 2296 } 2297 2298 Set("Given1", m1) 2299 Set("Number1", 42) 2300 2301 SetDefault("Given2", m2) 2302 SetDefault("Number2", 52) 2303 2304 // Verify SetDefault 2305 if v := Get("number2"); v != 52 { 2306 t.Fatalf("Expected 52 got %q", v) 2307 } 2308 2309 if v := Get("given2.foo"); v != 52 { 2310 t.Fatalf("Expected 52 got %q", v) 2311 } 2312 2313 if v := Get("given2.bar.bcd"); v != "A" { 2314 t.Fatalf("Expected A got %q", v) 2315 } 2316 2317 if _, ok := m2["Foo"]; !ok { 2318 t.Fatal("Input map changed") 2319 } 2320 2321 // Verify Set 2322 if v := Get("number1"); v != 42 { 2323 t.Fatalf("Expected 42 got %q", v) 2324 } 2325 2326 if v := Get("given1.foo"); v != 32 { 2327 t.Fatalf("Expected 32 got %q", v) 2328 } 2329 2330 if v := Get("given1.bar.abc"); v != "A" { 2331 t.Fatalf("Expected A got %q", v) 2332 } 2333 2334 if _, ok := m1["Foo"]; !ok { 2335 t.Fatal("Input map changed") 2336 } 2337 } 2338 2339 func TestParseNested(t *testing.T) { 2340 type duration struct { 2341 Delay time.Duration 2342 } 2343 2344 type item struct { 2345 Name string 2346 Delay time.Duration 2347 Nested duration 2348 } 2349 2350 config := `[[parent]] 2351 delay="100ms" 2352 [parent.nested] 2353 delay="200ms" 2354 ` 2355 initConfig("toml", config) 2356 2357 var items []item 2358 err := v.UnmarshalKey("parent", &items) 2359 if err != nil { 2360 t.Fatalf("unable to decode into struct, %v", err) 2361 } 2362 2363 assert.Equal(t, 1, len(items)) 2364 assert.Equal(t, 100*time.Millisecond, items[0].Delay) 2365 assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay) 2366 } 2367 2368 func doTestCaseInsensitive(t *testing.T, typ, config string) { 2369 initConfig(typ, config) 2370 Set("RfD", true) 2371 assert.Equal(t, true, Get("rfd")) 2372 assert.Equal(t, true, Get("rFD")) 2373 assert.Equal(t, 1, cast.ToInt(Get("abcd"))) 2374 assert.Equal(t, 1, cast.ToInt(Get("Abcd"))) 2375 assert.Equal(t, 2, cast.ToInt(Get("ef.gh"))) 2376 assert.Equal(t, 3, cast.ToInt(Get("ef.ijk"))) 2377 assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no"))) 2378 assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q"))) 2379 } 2380 2381 func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) { 2382 watchDir, err := ioutil.TempDir("", "") 2383 require.Nil(t, err) 2384 configFile := path.Join(watchDir, "config.yaml") 2385 err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0o640) 2386 require.Nil(t, err) 2387 cleanup := func() { 2388 os.RemoveAll(watchDir) 2389 } 2390 v := New() 2391 v.SetConfigFile(configFile) 2392 err = v.ReadInConfig() 2393 require.Nil(t, err) 2394 require.Equal(t, "bar", v.Get("foo")) 2395 return v, configFile, cleanup 2396 } 2397 2398 func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) { 2399 watchDir, err := ioutil.TempDir("", "") 2400 require.Nil(t, err) 2401 dataDir1 := path.Join(watchDir, "data1") 2402 err = os.Mkdir(dataDir1, 0o777) 2403 require.Nil(t, err) 2404 realConfigFile := path.Join(dataDir1, "config.yaml") 2405 t.Logf("Real config file location: %s\n", realConfigFile) 2406 err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640) 2407 require.Nil(t, err) 2408 cleanup := func() { 2409 os.RemoveAll(watchDir) 2410 } 2411 // now, symlink the tm `data1` dir to `data` in the baseDir 2412 os.Symlink(dataDir1, path.Join(watchDir, "data")) 2413 // and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml` 2414 configFile := path.Join(watchDir, "config.yaml") 2415 os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile) 2416 t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml")) 2417 // init Viper 2418 v := New() 2419 v.SetConfigFile(configFile) 2420 err = v.ReadInConfig() 2421 require.Nil(t, err) 2422 require.Equal(t, "bar", v.Get("foo")) 2423 return v, watchDir, configFile, cleanup 2424 } 2425 2426 func TestWatchFile(t *testing.T) { 2427 if runtime.GOOS == "linux" { 2428 // TODO(bep) FIX ME 2429 t.Skip("Skip test on Linux ...") 2430 } 2431 2432 t.Run("file content changed", func(t *testing.T) { 2433 // given a `config.yaml` file being watched 2434 v, configFile, cleanup := newViperWithConfigFile(t) 2435 defer cleanup() 2436 _, err := os.Stat(configFile) 2437 require.NoError(t, err) 2438 t.Logf("test config file: %s\n", configFile) 2439 wg := sync.WaitGroup{} 2440 wg.Add(1) 2441 var wgDoneOnce sync.Once // OnConfigChange is called twice on Windows 2442 v.OnConfigChange(func(in fsnotify.Event) { 2443 t.Logf("config file changed") 2444 wgDoneOnce.Do(func() { 2445 wg.Done() 2446 }) 2447 }) 2448 v.WatchConfig() 2449 // when overwriting the file and waiting for the custom change notification handler to be triggered 2450 err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0o640) 2451 wg.Wait() 2452 // then the config value should have changed 2453 require.Nil(t, err) 2454 assert.Equal(t, "baz", v.Get("foo")) 2455 }) 2456 2457 t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) { 2458 // skip if not executed on Linux 2459 if runtime.GOOS != "linux" { 2460 t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...") 2461 } 2462 v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t) 2463 // defer cleanup() 2464 wg := sync.WaitGroup{} 2465 v.WatchConfig() 2466 v.OnConfigChange(func(in fsnotify.Event) { 2467 t.Logf("config file changed") 2468 wg.Done() 2469 }) 2470 wg.Add(1) 2471 // when link to another `config.yaml` file 2472 dataDir2 := path.Join(watchDir, "data2") 2473 err := os.Mkdir(dataDir2, 0o777) 2474 require.Nil(t, err) 2475 configFile2 := path.Join(dataDir2, "config.yaml") 2476 err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0o640) 2477 require.Nil(t, err) 2478 // change the symlink using the `ln -sfn` command 2479 err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 2480 require.Nil(t, err) 2481 wg.Wait() 2482 // then 2483 require.Nil(t, err) 2484 assert.Equal(t, "baz", v.Get("foo")) 2485 }) 2486 } 2487 2488 func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) { 2489 flags := pflag.NewFlagSet("test", pflag.ContinueOnError) 2490 flags.String("foo.bar", "cobra_flag", "") 2491 2492 v := New() 2493 assert.NoError(t, v.BindPFlags(flags)) 2494 2495 config := &struct { 2496 Foo struct { 2497 Bar string 2498 } 2499 }{} 2500 2501 assert.NoError(t, v.Unmarshal(config)) 2502 assert.Equal(t, "cobra_flag", config.Foo.Bar) 2503 } 2504 2505 // var yamlExampleWithDot = []byte(`Hacker: true 2506 // name: steve 2507 // hobbies: 2508 // - skateboarding 2509 // - snowboarding 2510 // - go 2511 // clothing: 2512 // jacket: leather 2513 // trousers: denim 2514 // pants: 2515 // size: large 2516 // age: 35 2517 // eyes : brown 2518 // beard: true 2519 // emails: 2520 // steve@hacker.com: 2521 // created: 01/02/03 2522 // active: true 2523 // `) 2524 2525 func TestKeyDelimiter(t *testing.T) { 2526 v := NewWithOptions(KeyDelimiter("::")) 2527 v.SetConfigType("yaml") 2528 r := strings.NewReader(string(yamlExampleWithDot)) 2529 2530 err := v.unmarshalReader(r, v.config) 2531 require.NoError(t, err) 2532 2533 values := map[string]interface{}{ 2534 "image": map[string]interface{}{ 2535 "repository": "someImage", 2536 "tag": "1.0.0", 2537 }, 2538 "ingress": map[string]interface{}{ 2539 "annotations": map[string]interface{}{ 2540 "traefik.frontend.rule.type": "PathPrefix", 2541 "traefik.ingress.kubernetes.io/ssl-redirect": "true", 2542 }, 2543 }, 2544 } 2545 2546 v.SetDefault("charts::values", values) 2547 2548 assert.Equal(t, "leather", v.GetString("clothing::jacket")) 2549 assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created")) 2550 2551 type config struct { 2552 Charts struct { 2553 Values map[string]interface{} 2554 } 2555 } 2556 2557 expected := config{ 2558 Charts: struct { 2559 Values map[string]interface{} 2560 }{ 2561 Values: values, 2562 }, 2563 } 2564 2565 var actual config 2566 2567 assert.NoError(t, v.Unmarshal(&actual)) 2568 2569 assert.Equal(t, expected, actual) 2570 } 2571 2572 var yamlDeepNestedSlices = []byte(`TV: 2573 - title: "The Expanse" 2574 title_i18n: 2575 USA: "The Expanse" 2576 Japan: "エクスパンス -巨獣めざめる-" 2577 seasons: 2578 - first_released: "December 14, 2015" 2579 episodes: 2580 - title: "Dulcinea" 2581 air_date: "December 14, 2015" 2582 - title: "The Big Empty" 2583 air_date: "December 15, 2015" 2584 - title: "Remember the Cant" 2585 air_date: "December 22, 2015" 2586 - first_released: "February 1, 2017" 2587 episodes: 2588 - title: "Safe" 2589 air_date: "February 1, 2017" 2590 - title: "Doors & Corners" 2591 air_date: "February 1, 2017" 2592 - title: "Static" 2593 air_date: "February 8, 2017" 2594 episodes: 2595 - ["Dulcinea", "The Big Empty", "Remember the Cant"] 2596 - ["Safe", "Doors & Corners", "Static"] 2597 `) 2598 2599 func TestSliceIndexAccess(t *testing.T) { 2600 v.SetConfigType("yaml") 2601 r := strings.NewReader(string(yamlDeepNestedSlices)) 2602 2603 err := v.unmarshalReader(r, v.config) 2604 require.NoError(t, err) 2605 2606 assert.Equal(t, "The Expanse", v.GetString("tv.0.title")) 2607 assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released")) 2608 assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title")) 2609 assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date")) 2610 2611 // Test nested keys with capital letters 2612 assert.Equal(t, "The Expanse", v.GetString("tv.0.title_i18n.USA")) 2613 assert.Equal(t, "エクスパンス -巨獣めざめる-", v.GetString("tv.0.title_i18n.Japan")) 2614 2615 // Test for index out of bounds 2616 assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released")) 2617 2618 // Accessing multidimensional arrays 2619 assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2")) 2620 } 2621 2622 func BenchmarkGetBool(b *testing.B) { 2623 key := "BenchmarkGetBool" 2624 v = New() 2625 v.Set(key, true) 2626 2627 for i := 0; i < b.N; i++ { 2628 if !v.GetBool(key) { 2629 b.Fatal("GetBool returned false") 2630 } 2631 } 2632 } 2633 2634 func BenchmarkGet(b *testing.B) { 2635 key := "BenchmarkGet" 2636 v = New() 2637 v.Set(key, true) 2638 2639 for i := 0; i < b.N; i++ { 2640 if !v.Get(key).(bool) { 2641 b.Fatal("Get returned false") 2642 } 2643 } 2644 } 2645 2646 // BenchmarkGetBoolFromMap is the "perfect result" for the above. 2647 func BenchmarkGetBoolFromMap(b *testing.B) { 2648 m := make(map[string]bool) 2649 key := "BenchmarkGetBool" 2650 m[key] = true 2651 2652 for i := 0; i < b.N; i++ { 2653 if !m[key] { 2654 b.Fatal("Map value was false") 2655 } 2656 } 2657 } 2658 2659 // Skip some tests on Windows that kept failing when Windows was added to the CI as a target. 2660 func skipWindows(t *testing.T) { 2661 if runtime.GOOS == "windows" { 2662 t.Skip("Skip test on Windows") 2663 } 2664 }