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
6package viper
7
8import (
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
36var yamlExample = []byte(`Hacker: true
37name: steve
38hobbies:
39- skateboarding
40- snowboarding
41- go
42clothing:
43  jacket: leather
44  trousers: denim
45  pants:
46    size: large
47age: 35
48eyes : brown
49beard: true
50`)
51
52var yamlExampleWithExtras = []byte(`Existing: true
53Bogus: true
54`)
55
56type testUnmarshalExtra struct {
57	Existing bool
58}
59
60var tomlExample = []byte(`
61title = "TOML Example"
62
63[owner]
64organization = "MongoDB"
65Bio = "MongoDB Chief Developer Advocate & Hacker at Large"
66dob = 1979-05-27T07:32:00Z # First class dates? Why not?`)
67
68var dotenvExample = []byte(`
69TITLE_DOTENV="DotEnv Example"
70TYPE_DOTENV=donut
71NAME_DOTENV=Cake`)
72
73var 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
88var hclExample = []byte(`
89id = "0001"
90type = "donut"
91name = "Cake"
92ppu = 0.55
93foos {
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
108var propertiesExample = []byte(`
109p_id: 0001
110p_type: donut
111p_name: Cake
112p_ppu: 0.55
113p_batters.batter.type: Regular
114`)
115
116var remoteExample = []byte(`{
117"id":"0002",
118"type":"cronut",
119"newkey":"remote"
120}`)
121
122var iniExample = []byte(`; Package name
123NAME        = ini
124; Package version
125VERSION     = v1
126; Package import path
127IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
128
129# Information about package author
130# Bio can be written in multiple lines.
131[author]
132NAME   = Unknown  ; Succeeding comment
133E-MAIL = fake@localhost
134GITHUB = https://github.com/%(NAME)s
135BIO    = """Gopher.
136Coding addict.
137Good man.
138"""  # Succeeding comment`)
139
140func 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
176func 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
186func initYAML() {
187	initConfig("yaml", string(yamlExample))
188}
189
190func initJSON() {
191	Reset()
192	SetConfigType("json")
193	r := bytes.NewReader(jsonExample)
194
195	unmarshalReader(r, v.config)
196}
197
198func initProperties() {
199	Reset()
200	SetConfigType("properties")
201	r := bytes.NewReader(propertiesExample)
202
203	unmarshalReader(r, v.config)
204}
205
206func initTOML() {
207	Reset()
208	SetConfigType("toml")
209	r := bytes.NewReader(tomlExample)
210
211	unmarshalReader(r, v.config)
212}
213
214func initDotEnv() {
215	Reset()
216	SetConfigType("env")
217	r := bytes.NewReader(dotenvExample)
218
219	unmarshalReader(r, v.config)
220}
221
222func initHcl() {
223	Reset()
224	SetConfigType("hcl")
225	r := bytes.NewReader(hclExample)
226
227	unmarshalReader(r, v.config)
228}
229
230func initIni() {
231	Reset()
232	SetConfigType("ini")
233	r := bytes.NewReader(iniExample)
234
235	unmarshalReader(r, v.config)
236}
237
238// make directories for testing
239func 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
284type stringValue string
285
286func newStringValue(val string, p *string) *stringValue {
287	*p = val
288	return (*stringValue)(p)
289}
290
291func (s *stringValue) Set(val string) error {
292	*s = stringValue(val)
293	return nil
294}
295
296func (s *stringValue) Type() string {
297	return "string"
298}
299
300func (s *stringValue) String() string {
301	return string(*s)
302}
303
304func 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
311func 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
328func 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
345func 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
365func 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
379func 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
392func 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
404func TestOverrides(t *testing.T) {
405	Set("age", 40)
406	assert.Equal(t, 40, Get("age"))
407}
408
409func TestDefaultPost(t *testing.T) {
410	assert.NotEqual(t, "NYC", Get("state"))
411	SetDefault("state", "NYC")
412	assert.Equal(t, "NYC", Get("state"))
413}
414
415func 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
422func 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
431func TestYML(t *testing.T) {
432	initYAML()
433	assert.Equal(t, "steve", Get("name"))
434}
435
436func TestJSON(t *testing.T) {
437	initJSON()
438	assert.Equal(t, "0001", Get("id"))
439}
440
441func TestProperties(t *testing.T) {
442	initProperties()
443	assert.Equal(t, "0001", Get("p_id"))
444}
445
446func TestTOML(t *testing.T) {
447	initTOML()
448	assert.Equal(t, "TOML Example", Get("title"))
449}
450
451func TestDotEnv(t *testing.T) {
452	initDotEnv()
453	assert.Equal(t, "DotEnv Example", Get("title_dotenv"))
454}
455
456func 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
467func TestIni(t *testing.T) {
468	initIni()
469	assert.Equal(t, "ini", Get("default.name"))
470}
471
472func 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
487func 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
507func 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
517func 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
529func 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
543func 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
563func 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
573func 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
584func 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
597func 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
607func 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
722func 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
740func 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
747func TestRecursiveAliases(t *testing.T) {
748	RegisterAlias("Baz", "Roo")
749	RegisterAlias("Roo", "baz")
750}
751
752func 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
801func 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
837func 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
873func 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
921func TestBindPFlagsStringArray(t *testing.T) {
922	tests := []struct {
923		Expected []string
924		Value    string
925	}{
926		{[]string{}, ""},
927		{[]string{"jeden"}, "jeden"},
928		{[]string{"dwa,trzy"}, "dwa,trzy"},
929		{[]string{"cztery,\"piec , szesc\""}, "cztery,\"piec , szesc\""},
930	}
931
932	v := New() // create independent Viper object
933	defaultVal := []string{"default"}
934	v.SetDefault("stringarray", defaultVal)
935
936	for _, testValue := range tests {
937		flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
938		flagSet.StringArray("stringarray", testValue.Expected, "test")
939
940		for _, changed := range []bool{true, false} {
941			flagSet.VisitAll(func(f *pflag.Flag) {
942				f.Value.Set(testValue.Value)
943				f.Changed = changed
944			})
945
946			err := v.BindPFlags(flagSet)
947			if err != nil {
948				t.Fatalf("error binding flag set, %v", err)
949			}
950
951			type TestStr struct {
952				StringArray []string
953			}
954			val := &TestStr{}
955			if err := v.Unmarshal(val); err != nil {
956				t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
957			}
958			if changed {
959				assert.Equal(t, testValue.Expected, val.StringArray)
960				assert.Equal(t, testValue.Expected, v.Get("stringarray"))
961			} else {
962				assert.Equal(t, defaultVal, val.StringArray)
963			}
964		}
965	}
966}
967
968// nolint: dupl
969func TestBindPFlagsIntSlice(t *testing.T) {
970	tests := []struct {
971		Expected []int
972		Value    string
973	}{
974		{[]int{}, ""},
975		{[]int{1}, "1"},
976		{[]int{2, 3}, "2,3"},
977	}
978
979	v := New() // create independent Viper object
980	defaultVal := []int{0}
981	v.SetDefault("intslice", defaultVal)
982
983	for _, testValue := range tests {
984		flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
985		flagSet.IntSlice("intslice", testValue.Expected, "test")
986
987		for _, changed := range []bool{true, false} {
988			flagSet.VisitAll(func(f *pflag.Flag) {
989				f.Value.Set(testValue.Value)
990				f.Changed = changed
991			})
992
993			err := v.BindPFlags(flagSet)
994			if err != nil {
995				t.Fatalf("error binding flag set, %v", err)
996			}
997
998			type TestInt struct {
999				IntSlice []int
1000			}
1001			val := &TestInt{}
1002			if err := v.Unmarshal(val); err != nil {
1003				t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
1004			}
1005			if changed {
1006				assert.Equal(t, testValue.Expected, val.IntSlice)
1007				assert.Equal(t, testValue.Expected, v.Get("intslice"))
1008			} else {
1009				assert.Equal(t, defaultVal, val.IntSlice)
1010			}
1011		}
1012	}
1013}
1014
1015func TestBindPFlag(t *testing.T) {
1016	testString := "testing"
1017	testValue := newStringValue(testString, &testString)
1018
1019	flag := &pflag.Flag{
1020		Name:    "testflag",
1021		Value:   testValue,
1022		Changed: false,
1023	}
1024
1025	BindPFlag("testvalue", flag)
1026
1027	assert.Equal(t, testString, Get("testvalue"))
1028
1029	flag.Value.Set("testing_mutate")
1030	flag.Changed = true // hack for pflag usage
1031
1032	assert.Equal(t, "testing_mutate", Get("testvalue"))
1033}
1034
1035func TestBindPFlagDetectNilFlag(t *testing.T) {
1036	result := BindPFlag("testvalue", nil)
1037	assert.Error(t, result)
1038}
1039
1040func TestBindPFlagStringToString(t *testing.T) {
1041	tests := []struct {
1042		Expected map[string]string
1043		Value    string
1044	}{
1045		{map[string]string{}, ""},
1046		{map[string]string{"yo": "hi"}, "yo=hi"},
1047		{map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"},
1048		{map[string]string{"yo": ""}, "yo="},
1049		{map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"},
1050	}
1051
1052	v := New() // create independent Viper object
1053	defaultVal := map[string]string{}
1054	v.SetDefault("stringtostring", defaultVal)
1055
1056	for _, testValue := range tests {
1057		flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
1058		flagSet.StringToString("stringtostring", testValue.Expected, "test")
1059
1060		for _, changed := range []bool{true, false} {
1061			flagSet.VisitAll(func(f *pflag.Flag) {
1062				f.Value.Set(testValue.Value)
1063				f.Changed = changed
1064			})
1065
1066			err := v.BindPFlags(flagSet)
1067			if err != nil {
1068				t.Fatalf("error binding flag set, %v", err)
1069			}
1070
1071			type TestMap struct {
1072				StringToString map[string]string
1073			}
1074			val := &TestMap{}
1075			if err := v.Unmarshal(val); err != nil {
1076				t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
1077			}
1078			if changed {
1079				assert.Equal(t, testValue.Expected, val.StringToString)
1080			} else {
1081				assert.Equal(t, defaultVal, val.StringToString)
1082			}
1083		}
1084	}
1085}
1086
1087func TestBoundCaseSensitivity(t *testing.T) {
1088	assert.Equal(t, "brown", Get("eyes"))
1089
1090	BindEnv("eYEs", "TURTLE_EYES")
1091
1092	testutil.Setenv(t, "TURTLE_EYES", "blue")
1093
1094	assert.Equal(t, "blue", Get("eyes"))
1095
1096	testString := "green"
1097	testValue := newStringValue(testString, &testString)
1098
1099	flag := &pflag.Flag{
1100		Name:    "eyeballs",
1101		Value:   testValue,
1102		Changed: true,
1103	}
1104
1105	BindPFlag("eYEs", flag)
1106	assert.Equal(t, "green", Get("eyes"))
1107}
1108
1109func TestSizeInBytes(t *testing.T) {
1110	input := map[string]uint{
1111		"":               0,
1112		"b":              0,
1113		"12 bytes":       0,
1114		"200000000000gb": 0,
1115		"12 b":           12,
1116		"43 MB":          43 * (1 << 20),
1117		"10mb":           10 * (1 << 20),
1118		"1gb":            1 << 30,
1119	}
1120
1121	for str, expected := range input {
1122		assert.Equal(t, expected, parseSizeInBytes(str), str)
1123	}
1124}
1125
1126func TestFindsNestedKeys(t *testing.T) {
1127	initConfigs()
1128	dob, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
1129
1130	Set("super", map[string]interface{}{
1131		"deep": map[string]interface{}{
1132			"nested": "value",
1133		},
1134	})
1135
1136	expected := map[string]interface{}{
1137		"super": map[string]interface{}{
1138			"deep": map[string]interface{}{
1139				"nested": "value",
1140			},
1141		},
1142		"super.deep": map[string]interface{}{
1143			"nested": "value",
1144		},
1145		"super.deep.nested":  "value",
1146		"owner.organization": "MongoDB",
1147		"batters.batter": []interface{}{
1148			map[string]interface{}{
1149				"type": "Regular",
1150			},
1151			map[string]interface{}{
1152				"type": "Chocolate",
1153			},
1154			map[string]interface{}{
1155				"type": "Blueberry",
1156			},
1157			map[string]interface{}{
1158				"type": "Devil's Food",
1159			},
1160		},
1161		"hobbies": []interface{}{
1162			"skateboarding", "snowboarding", "go",
1163		},
1164		"TITLE_DOTENV": "DotEnv Example",
1165		"TYPE_DOTENV":  "donut",
1166		"NAME_DOTENV":  "Cake",
1167		"title":        "TOML Example",
1168		"newkey":       "remote",
1169		"batters": map[string]interface{}{
1170			"batter": []interface{}{
1171				map[string]interface{}{
1172					"type": "Regular",
1173				},
1174				map[string]interface{}{
1175					"type": "Chocolate",
1176				},
1177				map[string]interface{}{
1178					"type": "Blueberry",
1179				},
1180				map[string]interface{}{
1181					"type": "Devil's Food",
1182				},
1183			},
1184		},
1185		"eyes": "brown",
1186		"age":  35,
1187		"owner": map[string]interface{}{
1188			"organization": "MongoDB",
1189			"bio":          "MongoDB Chief Developer Advocate & Hacker at Large",
1190			"dob":          dob,
1191		},
1192		"owner.bio": "MongoDB Chief Developer Advocate & Hacker at Large",
1193		"type":      "donut",
1194		"id":        "0001",
1195		"name":      "Cake",
1196		"hacker":    true,
1197		"ppu":       0.55,
1198		"clothing": map[string]interface{}{
1199			"jacket":   "leather",
1200			"trousers": "denim",
1201			"pants": map[string]interface{}{
1202				"size": "large",
1203			},
1204		},
1205		"clothing.jacket":     "leather",
1206		"clothing.pants.size": "large",
1207		"clothing.trousers":   "denim",
1208		"owner.dob":           dob,
1209		"beard":               true,
1210		"foos": []map[string]interface{}{
1211			{
1212				"foo": []map[string]interface{}{
1213					{
1214						"key": 1,
1215					},
1216					{
1217						"key": 2,
1218					},
1219					{
1220						"key": 3,
1221					},
1222					{
1223						"key": 4,
1224					},
1225				},
1226			},
1227		},
1228	}
1229
1230	for key, expectedValue := range expected {
1231		assert.Equal(t, expectedValue, v.Get(key))
1232	}
1233}
1234
1235func TestReadBufConfig(t *testing.T) {
1236	v := New()
1237	v.SetConfigType("yaml")
1238	v.ReadConfig(bytes.NewBuffer(yamlExample))
1239	t.Log(v.AllKeys())
1240
1241	assert.True(t, v.InConfig("name"))
1242	assert.False(t, v.InConfig("state"))
1243	assert.Equal(t, "steve", v.Get("name"))
1244	assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies"))
1245	assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing"))
1246	assert.Equal(t, 35, v.Get("age"))
1247}
1248
1249func TestIsSet(t *testing.T) {
1250	v := New()
1251	v.SetConfigType("yaml")
1252
1253	/* config and defaults */
1254	v.ReadConfig(bytes.NewBuffer(yamlExample))
1255	v.SetDefault("clothing.shoes", "sneakers")
1256
1257	assert.True(t, v.IsSet("clothing"))
1258	assert.True(t, v.IsSet("clothing.jacket"))
1259	assert.False(t, v.IsSet("clothing.jackets"))
1260	assert.True(t, v.IsSet("clothing.shoes"))
1261
1262	/* state change */
1263	assert.False(t, v.IsSet("helloworld"))
1264	v.Set("helloworld", "fubar")
1265	assert.True(t, v.IsSet("helloworld"))
1266
1267	/* env */
1268	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
1269	v.BindEnv("eyes")
1270	v.BindEnv("foo")
1271	v.BindEnv("clothing.hat")
1272	v.BindEnv("clothing.hats")
1273
1274	testutil.Setenv(t, "FOO", "bar")
1275	testutil.Setenv(t, "CLOTHING_HAT", "bowler")
1276
1277	assert.True(t, v.IsSet("eyes"))           // in the config file
1278	assert.True(t, v.IsSet("foo"))            // in the environment
1279	assert.True(t, v.IsSet("clothing.hat"))   // in the environment
1280	assert.False(t, v.IsSet("clothing.hats")) // not defined
1281
1282	/* flags */
1283	flagset := pflag.NewFlagSet("testisset", pflag.ContinueOnError)
1284	flagset.Bool("foobaz", false, "foobaz")
1285	flagset.Bool("barbaz", false, "barbaz")
1286	foobaz, barbaz := flagset.Lookup("foobaz"), flagset.Lookup("barbaz")
1287	v.BindPFlag("foobaz", foobaz)
1288	v.BindPFlag("barbaz", barbaz)
1289	barbaz.Value.Set("true")
1290	barbaz.Changed = true // hack for pflag usage
1291
1292	assert.False(t, v.IsSet("foobaz"))
1293	assert.True(t, v.IsSet("barbaz"))
1294}
1295
1296func TestDirsSearch(t *testing.T) {
1297	root, config, cleanup := initDirs(t)
1298	defer cleanup()
1299
1300	v := New()
1301	v.SetConfigName(config)
1302	v.SetDefault(`key`, `default`)
1303
1304	entries, err := ioutil.ReadDir(root)
1305	assert.Nil(t, err)
1306	for _, e := range entries {
1307		if e.IsDir() {
1308			v.AddConfigPath(e.Name())
1309		}
1310	}
1311
1312	err = v.ReadInConfig()
1313	assert.Nil(t, err)
1314
1315	assert.Equal(t, `value is `+filepath.Base(v.configPaths[0]), v.GetString(`key`))
1316}
1317
1318func TestWrongDirsSearchNotFound(t *testing.T) {
1319	_, config, cleanup := initDirs(t)
1320	defer cleanup()
1321
1322	v := New()
1323	v.SetConfigName(config)
1324	v.SetDefault(`key`, `default`)
1325
1326	v.AddConfigPath(`whattayoutalkingbout`)
1327	v.AddConfigPath(`thispathaintthere`)
1328
1329	err := v.ReadInConfig()
1330	assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err))
1331
1332	// Even though config did not load and the error might have
1333	// been ignored by the client, the default still loads
1334	assert.Equal(t, `default`, v.GetString(`key`))
1335}
1336
1337func TestWrongDirsSearchNotFoundForMerge(t *testing.T) {
1338	_, config, cleanup := initDirs(t)
1339	defer cleanup()
1340
1341	v := New()
1342	v.SetConfigName(config)
1343	v.SetDefault(`key`, `default`)
1344
1345	v.AddConfigPath(`whattayoutalkingbout`)
1346	v.AddConfigPath(`thispathaintthere`)
1347
1348	err := v.MergeInConfig()
1349	assert.Equal(t, reflect.TypeOf(ConfigFileNotFoundError{"", ""}), reflect.TypeOf(err))
1350
1351	// Even though config did not load and the error might have
1352	// been ignored by the client, the default still loads
1353	assert.Equal(t, `default`, v.GetString(`key`))
1354}
1355
1356func TestSub(t *testing.T) {
1357	v := New()
1358	v.SetConfigType("yaml")
1359	v.ReadConfig(bytes.NewBuffer(yamlExample))
1360
1361	subv := v.Sub("clothing")
1362	assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("pants.size"))
1363
1364	subv = v.Sub("clothing.pants")
1365	assert.Equal(t, v.Get("clothing.pants.size"), subv.Get("size"))
1366
1367	subv = v.Sub("clothing.pants.size")
1368	assert.Equal(t, (*Viper)(nil), subv)
1369
1370	subv = v.Sub("missing.key")
1371	assert.Equal(t, (*Viper)(nil), subv)
1372}
1373
1374var hclWriteExpected = []byte(`"foos" = {
1375  "foo" = {
1376    "key" = 1
1377  }
1378
1379  "foo" = {
1380    "key" = 2
1381  }
1382
1383  "foo" = {
1384    "key" = 3
1385  }
1386
1387  "foo" = {
1388    "key" = 4
1389  }
1390}
1391
1392"id" = "0001"
1393
1394"name" = "Cake"
1395
1396"ppu" = 0.55
1397
1398"type" = "donut"`)
1399
1400var jsonWriteExpected = []byte(`{
1401  "batters": {
1402    "batter": [
1403      {
1404        "type": "Regular"
1405      },
1406      {
1407        "type": "Chocolate"
1408      },
1409      {
1410        "type": "Blueberry"
1411      },
1412      {
1413        "type": "Devil's Food"
1414      }
1415    ]
1416  },
1417  "id": "0001",
1418  "name": "Cake",
1419  "ppu": 0.55,
1420  "type": "donut"
1421}`)
1422
1423var propertiesWriteExpected = []byte(`p_id = 0001
1424p_type = donut
1425p_name = Cake
1426p_ppu = 0.55
1427p_batters.batter.type = Regular
1428`)
1429
1430var yamlWriteExpected = []byte(`age: 35
1431beard: true
1432clothing:
1433  jacket: leather
1434  pants:
1435    size: large
1436  trousers: denim
1437eyes: brown
1438hacker: true
1439hobbies:
1440- skateboarding
1441- snowboarding
1442- go
1443name: steve
1444`)
1445
1446func TestWriteConfig(t *testing.T) {
1447	fs := afero.NewMemMapFs()
1448	testCases := map[string]struct {
1449		configName      string
1450		inConfigType    string
1451		outConfigType   string
1452		fileName        string
1453		input           []byte
1454		expectedContent []byte
1455	}{
1456		"hcl with file extension": {
1457			configName:      "c",
1458			inConfigType:    "hcl",
1459			outConfigType:   "hcl",
1460			fileName:        "c.hcl",
1461			input:           hclExample,
1462			expectedContent: hclWriteExpected,
1463		},
1464		"hcl without file extension": {
1465			configName:      "c",
1466			inConfigType:    "hcl",
1467			outConfigType:   "hcl",
1468			fileName:        "c",
1469			input:           hclExample,
1470			expectedContent: hclWriteExpected,
1471		},
1472		"hcl with file extension and mismatch type": {
1473			configName:      "c",
1474			inConfigType:    "hcl",
1475			outConfigType:   "json",
1476			fileName:        "c.hcl",
1477			input:           hclExample,
1478			expectedContent: hclWriteExpected,
1479		},
1480		"json with file extension": {
1481			configName:      "c",
1482			inConfigType:    "json",
1483			outConfigType:   "json",
1484			fileName:        "c.json",
1485			input:           jsonExample,
1486			expectedContent: jsonWriteExpected,
1487		},
1488		"json without file extension": {
1489			configName:      "c",
1490			inConfigType:    "json",
1491			outConfigType:   "json",
1492			fileName:        "c",
1493			input:           jsonExample,
1494			expectedContent: jsonWriteExpected,
1495		},
1496		"json with file extension and mismatch type": {
1497			configName:      "c",
1498			inConfigType:    "json",
1499			outConfigType:   "hcl",
1500			fileName:        "c.json",
1501			input:           jsonExample,
1502			expectedContent: jsonWriteExpected,
1503		},
1504		"properties with file extension": {
1505			configName:      "c",
1506			inConfigType:    "properties",
1507			outConfigType:   "properties",
1508			fileName:        "c.properties",
1509			input:           propertiesExample,
1510			expectedContent: propertiesWriteExpected,
1511		},
1512		"properties without file extension": {
1513			configName:      "c",
1514			inConfigType:    "properties",
1515			outConfigType:   "properties",
1516			fileName:        "c",
1517			input:           propertiesExample,
1518			expectedContent: propertiesWriteExpected,
1519		},
1520		"yaml with file extension": {
1521			configName:      "c",
1522			inConfigType:    "yaml",
1523			outConfigType:   "yaml",
1524			fileName:        "c.yaml",
1525			input:           yamlExample,
1526			expectedContent: yamlWriteExpected,
1527		},
1528		"yaml without file extension": {
1529			configName:      "c",
1530			inConfigType:    "yaml",
1531			outConfigType:   "yaml",
1532			fileName:        "c",
1533			input:           yamlExample,
1534			expectedContent: yamlWriteExpected,
1535		},
1536		"yaml with file extension and mismatch type": {
1537			configName:      "c",
1538			inConfigType:    "yaml",
1539			outConfigType:   "json",
1540			fileName:        "c.yaml",
1541			input:           yamlExample,
1542			expectedContent: yamlWriteExpected,
1543		},
1544	}
1545	for name, tc := range testCases {
1546		t.Run(name, func(t *testing.T) {
1547			v := New()
1548			v.SetFs(fs)
1549			v.SetConfigName(tc.fileName)
1550			v.SetConfigType(tc.inConfigType)
1551
1552			err := v.ReadConfig(bytes.NewBuffer(tc.input))
1553			if err != nil {
1554				t.Fatal(err)
1555			}
1556			v.SetConfigType(tc.outConfigType)
1557			if err := v.WriteConfigAs(tc.fileName); err != nil {
1558				t.Fatal(err)
1559			}
1560			read, err := afero.ReadFile(fs, tc.fileName)
1561			if err != nil {
1562				t.Fatal(err)
1563			}
1564			assert.Equal(t, tc.expectedContent, read)
1565		})
1566	}
1567}
1568
1569func TestWriteConfigTOML(t *testing.T) {
1570	fs := afero.NewMemMapFs()
1571
1572	testCases := map[string]struct {
1573		configName string
1574		configType string
1575		fileName   string
1576		input      []byte
1577	}{
1578		"with file extension": {
1579			configName: "c",
1580			configType: "toml",
1581			fileName:   "c.toml",
1582			input:      tomlExample,
1583		},
1584		"without file extension": {
1585			configName: "c",
1586			configType: "toml",
1587			fileName:   "c",
1588			input:      tomlExample,
1589		},
1590	}
1591	for name, tc := range testCases {
1592		t.Run(name, func(t *testing.T) {
1593			v := New()
1594			v.SetFs(fs)
1595			v.SetConfigName(tc.configName)
1596			v.SetConfigType(tc.configType)
1597			err := v.ReadConfig(bytes.NewBuffer(tc.input))
1598			if err != nil {
1599				t.Fatal(err)
1600			}
1601			if err := v.WriteConfigAs(tc.fileName); err != nil {
1602				t.Fatal(err)
1603			}
1604
1605			// The TOML String method does not order the contents.
1606			// Therefore, we must read the generated file and compare the data.
1607			v2 := New()
1608			v2.SetFs(fs)
1609			v2.SetConfigName(tc.configName)
1610			v2.SetConfigType(tc.configType)
1611			v2.SetConfigFile(tc.fileName)
1612			err = v2.ReadInConfig()
1613			if err != nil {
1614				t.Fatal(err)
1615			}
1616
1617			assert.Equal(t, v.GetString("title"), v2.GetString("title"))
1618			assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
1619			assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
1620			assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
1621		})
1622	}
1623}
1624
1625func TestWriteConfigDotEnv(t *testing.T) {
1626	fs := afero.NewMemMapFs()
1627	testCases := map[string]struct {
1628		configName string
1629		configType string
1630		fileName   string
1631		input      []byte
1632	}{
1633		"with file extension": {
1634			configName: "c",
1635			configType: "env",
1636			fileName:   "c.env",
1637			input:      dotenvExample,
1638		},
1639		"without file extension": {
1640			configName: "c",
1641			configType: "env",
1642			fileName:   "c",
1643			input:      dotenvExample,
1644		},
1645	}
1646	for name, tc := range testCases {
1647		t.Run(name, func(t *testing.T) {
1648			v := New()
1649			v.SetFs(fs)
1650			v.SetConfigName(tc.configName)
1651			v.SetConfigType(tc.configType)
1652			err := v.ReadConfig(bytes.NewBuffer(tc.input))
1653			if err != nil {
1654				t.Fatal(err)
1655			}
1656			if err := v.WriteConfigAs(tc.fileName); err != nil {
1657				t.Fatal(err)
1658			}
1659
1660			// The TOML String method does not order the contents.
1661			// Therefore, we must read the generated file and compare the data.
1662			v2 := New()
1663			v2.SetFs(fs)
1664			v2.SetConfigName(tc.configName)
1665			v2.SetConfigType(tc.configType)
1666			v2.SetConfigFile(tc.fileName)
1667			err = v2.ReadInConfig()
1668			if err != nil {
1669				t.Fatal(err)
1670			}
1671
1672			assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv"))
1673			assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv"))
1674			assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv"))
1675		})
1676	}
1677}
1678
1679func TestSafeWriteConfig(t *testing.T) {
1680	v := New()
1681	fs := afero.NewMemMapFs()
1682	v.SetFs(fs)
1683	v.AddConfigPath("/test")
1684	v.SetConfigName("c")
1685	v.SetConfigType("yaml")
1686	require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample)))
1687	require.NoError(t, v.SafeWriteConfig())
1688	read, err := afero.ReadFile(fs, "/test/c.yaml")
1689	require.NoError(t, err)
1690	assert.Equal(t, yamlWriteExpected, read)
1691}
1692
1693func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) {
1694	v := New()
1695	fs := afero.NewMemMapFs()
1696	v.SetFs(fs)
1697	v.SetConfigName("c")
1698	v.SetConfigType("yaml")
1699	require.EqualError(t, v.SafeWriteConfig(), "missing configuration for 'configPath'")
1700}
1701
1702func TestSafeWriteConfigWithExistingFile(t *testing.T) {
1703	v := New()
1704	fs := afero.NewMemMapFs()
1705	fs.Create("/test/c.yaml")
1706	v.SetFs(fs)
1707	v.AddConfigPath("/test")
1708	v.SetConfigName("c")
1709	v.SetConfigType("yaml")
1710	err := v.SafeWriteConfig()
1711	require.Error(t, err)
1712	_, ok := err.(ConfigFileAlreadyExistsError)
1713	assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
1714}
1715
1716func TestSafeWriteAsConfig(t *testing.T) {
1717	v := New()
1718	fs := afero.NewMemMapFs()
1719	v.SetFs(fs)
1720	err := v.ReadConfig(bytes.NewBuffer(yamlExample))
1721	if err != nil {
1722		t.Fatal(err)
1723	}
1724	require.NoError(t, v.SafeWriteConfigAs("/test/c.yaml"))
1725	if _, err = afero.ReadFile(fs, "/test/c.yaml"); err != nil {
1726		t.Fatal(err)
1727	}
1728}
1729
1730func TestSafeWriteConfigAsWithExistingFile(t *testing.T) {
1731	v := New()
1732	fs := afero.NewMemMapFs()
1733	fs.Create("/test/c.yaml")
1734	v.SetFs(fs)
1735	err := v.SafeWriteConfigAs("/test/c.yaml")
1736	require.Error(t, err)
1737	_, ok := err.(ConfigFileAlreadyExistsError)
1738	assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
1739}
1740
1741var yamlMergeExampleTgt = []byte(`
1742hello:
1743    pop: 37890
1744    largenum: 765432101234567
1745    num2pow63: 9223372036854775808
1746    universe: null
1747    world:
1748    - us
1749    - uk
1750    - fr
1751    - de
1752`)
1753
1754var yamlMergeExampleSrc = []byte(`
1755hello:
1756    pop: 45000
1757    largenum: 7654321001234567
1758    universe:
1759    - mw
1760    - ad
1761    ints:
1762    - 1
1763    - 2
1764fu: bar
1765`)
1766
1767func TestMergeConfig(t *testing.T) {
1768	v := New()
1769	v.SetConfigType("yml")
1770	if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil {
1771		t.Fatal(err)
1772	}
1773
1774	if pop := v.GetInt("hello.pop"); pop != 37890 {
1775		t.Fatalf("pop != 37890, = %d", pop)
1776	}
1777
1778	if pop := v.GetInt32("hello.pop"); pop != int32(37890) {
1779		t.Fatalf("pop != 37890, = %d", pop)
1780	}
1781
1782	if pop := v.GetInt64("hello.largenum"); pop != int64(765432101234567) {
1783		t.Fatalf("int64 largenum != 765432101234567, = %d", pop)
1784	}
1785
1786	if pop := v.GetUint("hello.pop"); pop != 37890 {
1787		t.Fatalf("uint pop != 37890, = %d", pop)
1788	}
1789
1790	if pop := v.GetUint32("hello.pop"); pop != 37890 {
1791		t.Fatalf("uint32 pop != 37890, = %d", pop)
1792	}
1793
1794	if pop := v.GetUint64("hello.num2pow63"); pop != 9223372036854775808 {
1795		t.Fatalf("uint64 num2pow63 != 9223372036854775808, = %d", pop)
1796	}
1797
1798	if world := v.GetStringSlice("hello.world"); len(world) != 4 {
1799		t.Fatalf("len(world) != 4, = %d", len(world))
1800	}
1801
1802	if fu := v.GetString("fu"); fu != "" {
1803		t.Fatalf("fu != \"\", = %s", fu)
1804	}
1805
1806	if err := v.MergeConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil {
1807		t.Fatal(err)
1808	}
1809
1810	if pop := v.GetInt("hello.pop"); pop != 45000 {
1811		t.Fatalf("pop != 45000, = %d", pop)
1812	}
1813
1814	if pop := v.GetInt32("hello.pop"); pop != int32(45000) {
1815		t.Fatalf("pop != 45000, = %d", pop)
1816	}
1817
1818	if pop := v.GetInt64("hello.largenum"); pop != int64(7654321001234567) {
1819		t.Fatalf("int64 largenum != 7654321001234567, = %d", pop)
1820	}
1821
1822	if world := v.GetStringSlice("hello.world"); len(world) != 4 {
1823		t.Fatalf("len(world) != 4, = %d", len(world))
1824	}
1825
1826	if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 {
1827		t.Fatalf("len(universe) != 2, = %d", len(universe))
1828	}
1829
1830	if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
1831		t.Fatalf("len(ints) != 2, = %d", len(ints))
1832	}
1833
1834	if fu := v.GetString("fu"); fu != "bar" {
1835		t.Fatalf("fu != \"bar\", = %s", fu)
1836	}
1837}
1838
1839func TestMergeConfigNoMerge(t *testing.T) {
1840	v := New()
1841	v.SetConfigType("yml")
1842	if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil {
1843		t.Fatal(err)
1844	}
1845
1846	if pop := v.GetInt("hello.pop"); pop != 37890 {
1847		t.Fatalf("pop != 37890, = %d", pop)
1848	}
1849
1850	if world := v.GetStringSlice("hello.world"); len(world) != 4 {
1851		t.Fatalf("len(world) != 4, = %d", len(world))
1852	}
1853
1854	if fu := v.GetString("fu"); fu != "" {
1855		t.Fatalf("fu != \"\", = %s", fu)
1856	}
1857
1858	if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleSrc)); err != nil {
1859		t.Fatal(err)
1860	}
1861
1862	if pop := v.GetInt("hello.pop"); pop != 45000 {
1863		t.Fatalf("pop != 45000, = %d", pop)
1864	}
1865
1866	if world := v.GetStringSlice("hello.world"); len(world) != 0 {
1867		t.Fatalf("len(world) != 0, = %d", len(world))
1868	}
1869
1870	if universe := v.GetStringSlice("hello.universe"); len(universe) != 2 {
1871		t.Fatalf("len(universe) != 2, = %d", len(universe))
1872	}
1873
1874	if ints := v.GetIntSlice("hello.ints"); len(ints) != 2 {
1875		t.Fatalf("len(ints) != 2, = %d", len(ints))
1876	}
1877
1878	if fu := v.GetString("fu"); fu != "bar" {
1879		t.Fatalf("fu != \"bar\", = %s", fu)
1880	}
1881}
1882
1883func TestMergeConfigMap(t *testing.T) {
1884	v := New()
1885	v.SetConfigType("yml")
1886	if err := v.ReadConfig(bytes.NewBuffer(yamlMergeExampleTgt)); err != nil {
1887		t.Fatal(err)
1888	}
1889
1890	assert := func(i int) {
1891		large := v.GetInt64("hello.largenum")
1892		pop := v.GetInt("hello.pop")
1893		if large != 765432101234567 {
1894			t.Fatal("Got large num:", large)
1895		}
1896
1897		if pop != i {
1898			t.Fatal("Got pop:", pop)
1899		}
1900	}
1901
1902	assert(37890)
1903
1904	update := map[string]interface{}{
1905		"Hello": map[string]interface{}{
1906			"Pop": 1234,
1907		},
1908		"World": map[interface{}]interface{}{
1909			"Rock": 345,
1910		},
1911	}
1912
1913	if err := v.MergeConfigMap(update); err != nil {
1914		t.Fatal(err)
1915	}
1916
1917	if rock := v.GetInt("world.rock"); rock != 345 {
1918		t.Fatal("Got rock:", rock)
1919	}
1920
1921	assert(1234)
1922}
1923
1924func TestUnmarshalingWithAliases(t *testing.T) {
1925	v := New()
1926	v.SetDefault("ID", 1)
1927	v.Set("name", "Steve")
1928	v.Set("lastname", "Owen")
1929
1930	v.RegisterAlias("UserID", "ID")
1931	v.RegisterAlias("Firstname", "name")
1932	v.RegisterAlias("Surname", "lastname")
1933
1934	type config struct {
1935		ID        int
1936		FirstName string
1937		Surname   string
1938	}
1939
1940	var C config
1941	err := v.Unmarshal(&C)
1942	if err != nil {
1943		t.Fatalf("unable to decode into struct, %v", err)
1944	}
1945
1946	assert.Equal(t, &config{ID: 1, FirstName: "Steve", Surname: "Owen"}, &C)
1947}
1948
1949func TestSetConfigNameClearsFileCache(t *testing.T) {
1950	SetConfigFile("/tmp/config.yaml")
1951	SetConfigName("default")
1952	f, err := v.getConfigFile()
1953	if err == nil {
1954		t.Fatalf("config file cache should have been cleared")
1955	}
1956	assert.Empty(t, f)
1957}
1958
1959func TestShadowedNestedValue(t *testing.T) {
1960	config := `name: steve
1961clothing:
1962  jacket: leather
1963  trousers: denim
1964  pants:
1965    size: large
1966`
1967	initConfig("yaml", config)
1968
1969	assert.Equal(t, "steve", GetString("name"))
1970
1971	polyester := "polyester"
1972	SetDefault("clothing.shirt", polyester)
1973	SetDefault("clothing.jacket.price", 100)
1974
1975	assert.Equal(t, "leather", GetString("clothing.jacket"))
1976	assert.Nil(t, Get("clothing.jacket.price"))
1977	assert.Equal(t, polyester, GetString("clothing.shirt"))
1978
1979	clothingSettings := AllSettings()["clothing"].(map[string]interface{})
1980	assert.Equal(t, "leather", clothingSettings["jacket"])
1981	assert.Equal(t, polyester, clothingSettings["shirt"])
1982}
1983
1984func TestDotParameter(t *testing.T) {
1985	initJSON()
1986	// shoud take precedence over batters defined in jsonExample
1987	r := bytes.NewReader([]byte(`{ "batters.batter": [ { "type": "Small" } ] }`))
1988	unmarshalReader(r, v.config)
1989
1990	actual := Get("batters.batter")
1991	expected := []interface{}{map[string]interface{}{"type": "Small"}}
1992	assert.Equal(t, expected, actual)
1993}
1994
1995func TestCaseInsensitive(t *testing.T) {
1996	for _, config := range []struct {
1997		typ     string
1998		content string
1999	}{
2000		{"yaml", `
2001aBcD: 1
2002eF:
2003  gH: 2
2004  iJk: 3
2005  Lm:
2006    nO: 4
2007    P:
2008      Q: 5
2009      R: 6
2010`},
2011		{"json", `{
2012  "aBcD": 1,
2013  "eF": {
2014    "iJk": 3,
2015    "Lm": {
2016      "P": {
2017        "Q": 5,
2018        "R": 6
2019      },
2020      "nO": 4
2021    },
2022    "gH": 2
2023  }
2024}`},
2025		{"toml", `aBcD = 1
2026[eF]
2027gH = 2
2028iJk = 3
2029[eF.Lm]
2030nO = 4
2031[eF.Lm.P]
2032Q = 5
2033R = 6
2034`},
2035	} {
2036		doTestCaseInsensitive(t, config.typ, config.content)
2037	}
2038}
2039
2040func TestCaseInsensitiveSet(t *testing.T) {
2041	Reset()
2042	m1 := map[string]interface{}{
2043		"Foo": 32,
2044		"Bar": map[interface{}]interface{}{
2045			"ABc": "A",
2046			"cDE": "B",
2047		},
2048	}
2049
2050	m2 := map[string]interface{}{
2051		"Foo": 52,
2052		"Bar": map[interface{}]interface{}{
2053			"bCd": "A",
2054			"eFG": "B",
2055		},
2056	}
2057
2058	Set("Given1", m1)
2059	Set("Number1", 42)
2060
2061	SetDefault("Given2", m2)
2062	SetDefault("Number2", 52)
2063
2064	// Verify SetDefault
2065	if v := Get("number2"); v != 52 {
2066		t.Fatalf("Expected 52 got %q", v)
2067	}
2068
2069	if v := Get("given2.foo"); v != 52 {
2070		t.Fatalf("Expected 52 got %q", v)
2071	}
2072
2073	if v := Get("given2.bar.bcd"); v != "A" {
2074		t.Fatalf("Expected A got %q", v)
2075	}
2076
2077	if _, ok := m2["Foo"]; !ok {
2078		t.Fatal("Input map changed")
2079	}
2080
2081	// Verify Set
2082	if v := Get("number1"); v != 42 {
2083		t.Fatalf("Expected 42 got %q", v)
2084	}
2085
2086	if v := Get("given1.foo"); v != 32 {
2087		t.Fatalf("Expected 32 got %q", v)
2088	}
2089
2090	if v := Get("given1.bar.abc"); v != "A" {
2091		t.Fatalf("Expected A got %q", v)
2092	}
2093
2094	if _, ok := m1["Foo"]; !ok {
2095		t.Fatal("Input map changed")
2096	}
2097}
2098
2099func TestParseNested(t *testing.T) {
2100	type duration struct {
2101		Delay time.Duration
2102	}
2103
2104	type item struct {
2105		Name   string
2106		Delay  time.Duration
2107		Nested duration
2108	}
2109
2110	config := `[[parent]]
2111	delay="100ms"
2112	[parent.nested]
2113	delay="200ms"
2114`
2115	initConfig("toml", config)
2116
2117	var items []item
2118	err := v.UnmarshalKey("parent", &items)
2119	if err != nil {
2120		t.Fatalf("unable to decode into struct, %v", err)
2121	}
2122
2123	assert.Equal(t, 1, len(items))
2124	assert.Equal(t, 100*time.Millisecond, items[0].Delay)
2125	assert.Equal(t, 200*time.Millisecond, items[0].Nested.Delay)
2126}
2127
2128func doTestCaseInsensitive(t *testing.T, typ, config string) {
2129	initConfig(typ, config)
2130	Set("RfD", true)
2131	assert.Equal(t, true, Get("rfd"))
2132	assert.Equal(t, true, Get("rFD"))
2133	assert.Equal(t, 1, cast.ToInt(Get("abcd")))
2134	assert.Equal(t, 1, cast.ToInt(Get("Abcd")))
2135	assert.Equal(t, 2, cast.ToInt(Get("ef.gh")))
2136	assert.Equal(t, 3, cast.ToInt(Get("ef.ijk")))
2137	assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no")))
2138	assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q")))
2139}
2140
2141func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
2142	watchDir, err := ioutil.TempDir("", "")
2143	require.Nil(t, err)
2144	configFile := path.Join(watchDir, "config.yaml")
2145	err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640)
2146	require.Nil(t, err)
2147	cleanup := func() {
2148		os.RemoveAll(watchDir)
2149	}
2150	v := New()
2151	v.SetConfigFile(configFile)
2152	err = v.ReadInConfig()
2153	require.Nil(t, err)
2154	require.Equal(t, "bar", v.Get("foo"))
2155	return v, configFile, cleanup
2156}
2157
2158func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func()) {
2159	watchDir, err := ioutil.TempDir("", "")
2160	require.Nil(t, err)
2161	dataDir1 := path.Join(watchDir, "data1")
2162	err = os.Mkdir(dataDir1, 0777)
2163	require.Nil(t, err)
2164	realConfigFile := path.Join(dataDir1, "config.yaml")
2165	t.Logf("Real config file location: %s\n", realConfigFile)
2166	err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640)
2167	require.Nil(t, err)
2168	cleanup := func() {
2169		os.RemoveAll(watchDir)
2170	}
2171	// now, symlink the tm `data1` dir to `data` in the baseDir
2172	os.Symlink(dataDir1, path.Join(watchDir, "data"))
2173	// and link the `<watchdir>/datadir1/config.yaml` to `<watchdir>/config.yaml`
2174	configFile := path.Join(watchDir, "config.yaml")
2175	os.Symlink(path.Join(watchDir, "data", "config.yaml"), configFile)
2176	t.Logf("Config file location: %s\n", path.Join(watchDir, "config.yaml"))
2177	// init Viper
2178	v := New()
2179	v.SetConfigFile(configFile)
2180	err = v.ReadInConfig()
2181	require.Nil(t, err)
2182	require.Equal(t, "bar", v.Get("foo"))
2183	return v, watchDir, configFile, cleanup
2184}
2185
2186func TestWatchFile(t *testing.T) {
2187	if runtime.GOOS == "linux" {
2188		// TODO(bep) FIX ME
2189		t.Skip("Skip test on Linux ...")
2190	}
2191
2192	t.Run("file content changed", func(t *testing.T) {
2193		// given a `config.yaml` file being watched
2194		v, configFile, cleanup := newViperWithConfigFile(t)
2195		defer cleanup()
2196		_, err := os.Stat(configFile)
2197		require.NoError(t, err)
2198		t.Logf("test config file: %s\n", configFile)
2199		wg := sync.WaitGroup{}
2200		wg.Add(1)
2201		v.OnConfigChange(func(in fsnotify.Event) {
2202			t.Logf("config file changed")
2203			wg.Done()
2204		})
2205		v.WatchConfig()
2206		// when overwriting the file and waiting for the custom change notification handler to be triggered
2207		err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640)
2208		wg.Wait()
2209		// then the config value should have changed
2210		require.Nil(t, err)
2211		assert.Equal(t, "baz", v.Get("foo"))
2212	})
2213
2214	t.Run("link to real file changed (à la Kubernetes)", func(t *testing.T) {
2215		// skip if not executed on Linux
2216		if runtime.GOOS != "linux" {
2217			t.Skipf("Skipping test as symlink replacements don't work on non-linux environment...")
2218		}
2219		v, watchDir, _, _ := newViperWithSymlinkedConfigFile(t)
2220		// defer cleanup()
2221		wg := sync.WaitGroup{}
2222		v.WatchConfig()
2223		v.OnConfigChange(func(in fsnotify.Event) {
2224			t.Logf("config file changed")
2225			wg.Done()
2226		})
2227		wg.Add(1)
2228		// when link to another `config.yaml` file
2229		dataDir2 := path.Join(watchDir, "data2")
2230		err := os.Mkdir(dataDir2, 0777)
2231		require.Nil(t, err)
2232		configFile2 := path.Join(dataDir2, "config.yaml")
2233		err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640)
2234		require.Nil(t, err)
2235		// change the symlink using the `ln -sfn` command
2236		err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run()
2237		require.Nil(t, err)
2238		wg.Wait()
2239		// then
2240		require.Nil(t, err)
2241		assert.Equal(t, "baz", v.Get("foo"))
2242	})
2243}
2244
2245func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
2246	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
2247	flags.String("foo.bar", "cobra_flag", "")
2248
2249	v := New()
2250	assert.NoError(t, v.BindPFlags(flags))
2251
2252	config := &struct {
2253		Foo struct {
2254			Bar string
2255		}
2256	}{}
2257
2258	assert.NoError(t, v.Unmarshal(config))
2259	assert.Equal(t, "cobra_flag", config.Foo.Bar)
2260}
2261
2262var yamlExampleWithDot = []byte(`Hacker: true
2263name: steve
2264hobbies:
2265  - skateboarding
2266  - snowboarding
2267  - go
2268clothing:
2269  jacket: leather
2270  trousers: denim
2271  pants:
2272    size: large
2273age: 35
2274eyes : brown
2275beard: true
2276emails:
2277  steve@hacker.com:
2278    created: 01/02/03
2279    active: true
2280`)
2281
2282func TestKeyDelimiter(t *testing.T) {
2283	v := NewWithOptions(KeyDelimiter("::"))
2284	v.SetConfigType("yaml")
2285	r := strings.NewReader(string(yamlExampleWithDot))
2286
2287	err := v.unmarshalReader(r, v.config)
2288	require.NoError(t, err)
2289
2290	values := map[string]interface{}{
2291		"image": map[string]interface{}{
2292			"repository": "someImage",
2293			"tag":        "1.0.0",
2294		},
2295		"ingress": map[string]interface{}{
2296			"annotations": map[string]interface{}{
2297				"traefik.frontend.rule.type":                 "PathPrefix",
2298				"traefik.ingress.kubernetes.io/ssl-redirect": "true",
2299			},
2300		},
2301	}
2302
2303	v.SetDefault("charts::values", values)
2304
2305	assert.Equal(t, "leather", v.GetString("clothing::jacket"))
2306	assert.Equal(t, "01/02/03", v.GetString("emails::steve@hacker.com::created"))
2307
2308	type config struct {
2309		Charts struct {
2310			Values map[string]interface{}
2311		}
2312	}
2313
2314	expected := config{
2315		Charts: struct {
2316			Values map[string]interface{}
2317		}{
2318			Values: values,
2319		},
2320	}
2321
2322	var actual config
2323
2324	assert.NoError(t, v.Unmarshal(&actual))
2325
2326	assert.Equal(t, expected, actual)
2327}
2328
2329var yamlDeepNestedSlices = []byte(`TV:
2330- title: "The expanse"
2331  seasons:
2332  - first_released: "December 14, 2015"
2333    episodes:
2334    - title: "Dulcinea"
2335      air_date: "December 14, 2015"
2336    - title: "The Big Empty"
2337      air_date: "December 15, 2015"
2338    - title: "Remember the Cant"
2339      air_date: "December 22, 2015"
2340  - first_released: "February 1, 2017"
2341    episodes:
2342    - title: "Safe"
2343      air_date: "February 1, 2017"
2344    - title: "Doors & Corners"
2345      air_date: "February 1, 2017"
2346    - title: "Static"
2347      air_date: "February 8, 2017"
2348  episodes:
2349    - ["Dulcinea", "The Big Empty", "Remember the Cant"]
2350    - ["Safe", "Doors & Corners", "Static"]
2351`)
2352
2353func TestSliceIndexAccess(t *testing.T) {
2354	v.SetConfigType("yaml")
2355	r := strings.NewReader(string(yamlDeepNestedSlices))
2356
2357	err := v.unmarshalReader(r, v.config)
2358	require.NoError(t, err)
2359
2360	assert.Equal(t, "The expanse", v.GetString("tv.0.title"))
2361	assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released"))
2362	assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title"))
2363	assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date"))
2364
2365	// Test for index out of bounds
2366	assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released"))
2367
2368	// Accessing multidimensional arrays
2369	assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2"))
2370}
2371
2372func BenchmarkGetBool(b *testing.B) {
2373	key := "BenchmarkGetBool"
2374	v = New()
2375	v.Set(key, true)
2376
2377	for i := 0; i < b.N; i++ {
2378		if !v.GetBool(key) {
2379			b.Fatal("GetBool returned false")
2380		}
2381	}
2382}
2383
2384func BenchmarkGet(b *testing.B) {
2385	key := "BenchmarkGet"
2386	v = New()
2387	v.Set(key, true)
2388
2389	for i := 0; i < b.N; i++ {
2390		if !v.Get(key).(bool) {
2391			b.Fatal("Get returned false")
2392		}
2393	}
2394}
2395
2396// BenchmarkGetBoolFromMap is the "perfect result" for the above.
2397func BenchmarkGetBoolFromMap(b *testing.B) {
2398	m := make(map[string]bool)
2399	key := "BenchmarkGetBool"
2400	m[key] = true
2401
2402	for i := 0; i < b.N; i++ {
2403		if !m[key] {
2404			b.Fatal("Map value was false")
2405		}
2406	}
2407}
2408