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