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