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