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