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