1package flags
2
3import (
4	"bytes"
5	"fmt"
6	"io/ioutil"
7	"os"
8	"reflect"
9	"strings"
10	"testing"
11)
12
13func TestWriteIni(t *testing.T) {
14	oldEnv := EnvSnapshot()
15	defer oldEnv.Restore()
16	os.Setenv("ENV_DEFAULT", "env-def")
17
18	var tests = []struct {
19		args     []string
20		options  IniOptions
21		expected string
22	}{
23		{
24			[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
25			IniDefault,
26			`[Application Options]
27; Show verbose debug information
28verbose = true
29verbose = true
30
31; Test env-default1 value
32EnvDefault1 = env-def
33
34; Test env-default2 value
35EnvDefault2 = env-def
36
37[Other Options]
38; A map from string to int
39int-map = a:2
40int-map = b:3
41
42`,
43		},
44		{
45			[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "3.14", "command"},
46			IniDefault | IniIncludeDefaults,
47			`[Application Options]
48; Show verbose debug information
49verbose = true
50verbose = true
51
52; A slice of pointers to string
53; PtrSlice =
54
55EmptyDescription = false
56
57; Test default value
58Default = "Some\nvalue"
59
60; Test default array value
61DefaultArray = Some value
62DefaultArray = "Other\tvalue"
63
64; Testdefault map value
65DefaultMap = another:value
66DefaultMap = some:value
67
68; Test env-default1 value
69EnvDefault1 = env-def
70
71; Test env-default2 value
72EnvDefault2 = env-def
73
74; Option with named argument
75OptionWithArgName =
76
77; Option with choices
78OptionWithChoices =
79
80; Option only available in ini
81only-ini =
82
83[Other Options]
84; A slice of strings
85StringSlice = some
86StringSlice = value
87
88; A map from string to int
89int-map = a:2
90int-map = b:3
91
92[Subgroup]
93; This is a subgroup option
94Opt =
95
96; Not hidden inside group
97NotHiddenInsideGroup =
98
99[Subsubgroup]
100; This is a subsubgroup option
101Opt =
102
103[command]
104; Use for extra verbosity
105; ExtraVerbose =
106
107`,
108		},
109		{
110			[]string{"filename", "0", "3.14", "command"},
111			IniDefault | IniIncludeDefaults | IniCommentDefaults,
112			`[Application Options]
113; Show verbose debug information
114; verbose =
115
116; A slice of pointers to string
117; PtrSlice =
118
119; EmptyDescription = false
120
121; Test default value
122; Default = "Some\nvalue"
123
124; Test default array value
125; DefaultArray = Some value
126; DefaultArray = "Other\tvalue"
127
128; Testdefault map value
129; DefaultMap = another:value
130; DefaultMap = some:value
131
132; Test env-default1 value
133EnvDefault1 = env-def
134
135; Test env-default2 value
136EnvDefault2 = env-def
137
138; Option with named argument
139; OptionWithArgName =
140
141; Option with choices
142; OptionWithChoices =
143
144; Option only available in ini
145; only-ini =
146
147[Other Options]
148; A slice of strings
149; StringSlice = some
150; StringSlice = value
151
152; A map from string to int
153; int-map = a:1
154
155[Subgroup]
156; This is a subgroup option
157; Opt =
158
159; Not hidden inside group
160; NotHiddenInsideGroup =
161
162[Subsubgroup]
163; This is a subsubgroup option
164; Opt =
165
166[command]
167; Use for extra verbosity
168; ExtraVerbose =
169
170`,
171		},
172		{
173			[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "3.14", "command"},
174			IniDefault | IniIncludeDefaults | IniCommentDefaults,
175			`[Application Options]
176; Show verbose debug information
177; verbose =
178
179; A slice of pointers to string
180; PtrSlice =
181
182; EmptyDescription = false
183
184; Test default value
185Default = New value
186
187; Test default array value
188DefaultArray = New value
189
190; Testdefault map value
191DefaultMap = new:value
192
193; Test env-default1 value
194EnvDefault1 = env-def
195
196; Test env-default2 value
197EnvDefault2 = env-def
198
199; Option with named argument
200; OptionWithArgName =
201
202; Option with choices
203; OptionWithChoices =
204
205; Option only available in ini
206; only-ini =
207
208[Other Options]
209; A slice of strings
210; StringSlice = some
211; StringSlice = value
212
213; A map from string to int
214; int-map = a:1
215
216[Subgroup]
217; This is a subgroup option
218; Opt =
219
220; Not hidden inside group
221; NotHiddenInsideGroup =
222
223[Subsubgroup]
224; This is a subsubgroup option
225; Opt =
226
227[command]
228; Use for extra verbosity
229; ExtraVerbose =
230
231`,
232		},
233	}
234
235	for _, test := range tests {
236		var opts helpOptions
237
238		p := NewNamedParser("TestIni", Default)
239		p.AddGroup("Application Options", "The application options", &opts)
240
241		_, err := p.ParseArgs(test.args)
242
243		if err != nil {
244			t.Fatalf("Unexpected error: %v", err)
245		}
246
247		inip := NewIniParser(p)
248
249		var b bytes.Buffer
250		inip.Write(&b, test.options)
251
252		got := b.String()
253		expected := test.expected
254
255		msg := fmt.Sprintf("with arguments %+v and ini options %b", test.args, test.options)
256		assertDiff(t, got, expected, msg)
257	}
258}
259
260func TestReadIni_flagEquivalent(t *testing.T) {
261	type options struct {
262		Opt1 bool `long:"opt1"`
263
264		Group1 struct {
265			Opt2 bool `long:"opt2"`
266		} `group:"group1"`
267
268		Group2 struct {
269			Opt3 bool `long:"opt3"`
270		} `group:"group2" namespace:"ns1"`
271
272		Cmd1 struct {
273			Opt4 bool `long:"opt4"`
274			Opt5 bool `long:"foo.opt5"`
275
276			Group1 struct {
277				Opt6 bool `long:"opt6"`
278				Opt7 bool `long:"foo.opt7"`
279			} `group:"group1"`
280
281			Group2 struct {
282				Opt8 bool `long:"opt8"`
283			} `group:"group2" namespace:"ns1"`
284		} `command:"cmd1"`
285	}
286
287	a := `
288opt1=true
289
290[group1]
291opt2=true
292
293[group2]
294ns1.opt3=true
295
296[cmd1]
297opt4=true
298foo.opt5=true
299
300[cmd1.group1]
301opt6=true
302foo.opt7=true
303
304[cmd1.group2]
305ns1.opt8=true
306`
307	b := `
308opt1=true
309opt2=true
310ns1.opt3=true
311
312[cmd1]
313opt4=true
314foo.opt5=true
315opt6=true
316foo.opt7=true
317ns1.opt8=true
318`
319
320	parse := func(readIni string) (opts options, writeIni string) {
321		p := NewNamedParser("TestIni", Default)
322		p.AddGroup("Application Options", "The application options", &opts)
323
324		inip := NewIniParser(p)
325		err := inip.Parse(strings.NewReader(readIni))
326
327		if err != nil {
328			t.Fatalf("Unexpected error: %s\n\nFile:\n%s", err, readIni)
329		}
330
331		var b bytes.Buffer
332		inip.Write(&b, Default)
333
334		return opts, b.String()
335	}
336
337	aOpt, aIni := parse(a)
338	bOpt, bIni := parse(b)
339
340	assertDiff(t, aIni, bIni, "")
341	if !reflect.DeepEqual(aOpt, bOpt) {
342		t.Errorf("not equal")
343	}
344}
345
346func TestReadIni(t *testing.T) {
347	var opts helpOptions
348
349	p := NewNamedParser("TestIni", Default)
350	p.AddGroup("Application Options", "The application options", &opts)
351
352	inip := NewIniParser(p)
353
354	inic := `
355; Show verbose debug information
356verbose = true
357verbose = true
358
359DefaultMap = another:"value\n1"
360DefaultMap = some:value 2
361
362[Application Options]
363; A slice of pointers to string
364; PtrSlice =
365
366; Test default value
367Default = "New\nvalue"
368
369; Test env-default1 value
370EnvDefault1 = New value
371
372[Other Options]
373# A slice of strings
374StringSlice = "some\nvalue"
375StringSlice = another value
376
377; A map from string to int
378int-map = a:2
379int-map = b:3
380
381`
382
383	b := strings.NewReader(inic)
384	err := inip.Parse(b)
385
386	if err != nil {
387		t.Fatalf("Unexpected error: %s", err)
388	}
389
390	assertBoolArray(t, opts.Verbose, []bool{true, true})
391
392	if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) {
393		t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap)
394	}
395
396	assertString(t, opts.Default, "New\nvalue")
397
398	assertString(t, opts.EnvDefault1, "New value")
399
400	assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"})
401
402	if v, ok := opts.Other.IntMap["a"]; !ok {
403		t.Errorf("Expected \"a\" in Other.IntMap")
404	} else if v != 2 {
405		t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v)
406	}
407
408	if v, ok := opts.Other.IntMap["b"]; !ok {
409		t.Errorf("Expected \"b\" in Other.IntMap")
410	} else if v != 3 {
411		t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v)
412	}
413}
414
415func TestReadAndWriteIni(t *testing.T) {
416	var tests = []struct {
417		options IniOptions
418		read    string
419		write   string
420	}{
421		{
422			IniIncludeComments,
423			`[Application Options]
424; Show verbose debug information
425verbose = true
426verbose = true
427
428; Test default value
429Default = "quote me"
430
431; Test default array value
432DefaultArray = 1
433DefaultArray = "2"
434DefaultArray = 3
435
436; Testdefault map value
437; DefaultMap =
438
439; Test env-default1 value
440EnvDefault1 = env-def
441
442; Test env-default2 value
443EnvDefault2 = env-def
444
445[Other Options]
446; A slice of strings
447; StringSlice =
448
449; A map from string to int
450int-map = a:2
451int-map = b:"3"
452
453`,
454			`[Application Options]
455; Show verbose debug information
456verbose = true
457verbose = true
458
459; Test default value
460Default = "quote me"
461
462; Test default array value
463DefaultArray = 1
464DefaultArray = 2
465DefaultArray = 3
466
467; Testdefault map value
468; DefaultMap =
469
470; Test env-default1 value
471EnvDefault1 = env-def
472
473; Test env-default2 value
474EnvDefault2 = env-def
475
476[Other Options]
477; A slice of strings
478; StringSlice =
479
480; A map from string to int
481int-map = a:2
482int-map = b:3
483
484`,
485		},
486		{
487			IniIncludeComments,
488			`[Application Options]
489; Show verbose debug information
490verbose = true
491verbose = true
492
493; Test default value
494Default = "quote me"
495
496; Test default array value
497DefaultArray = "1"
498DefaultArray = "2"
499DefaultArray = "3"
500
501; Testdefault map value
502; DefaultMap =
503
504; Test env-default1 value
505EnvDefault1 = env-def
506
507; Test env-default2 value
508EnvDefault2 = env-def
509
510[Other Options]
511; A slice of strings
512; StringSlice =
513
514; A map from string to int
515int-map = a:"2"
516int-map = b:"3"
517
518`,
519			`[Application Options]
520; Show verbose debug information
521verbose = true
522verbose = true
523
524; Test default value
525Default = "quote me"
526
527; Test default array value
528DefaultArray = "1"
529DefaultArray = "2"
530DefaultArray = "3"
531
532; Testdefault map value
533; DefaultMap =
534
535; Test env-default1 value
536EnvDefault1 = env-def
537
538; Test env-default2 value
539EnvDefault2 = env-def
540
541[Other Options]
542; A slice of strings
543; StringSlice =
544
545; A map from string to int
546int-map = a:"2"
547int-map = b:"3"
548
549`,
550		},
551	}
552
553	for _, test := range tests {
554		var opts helpOptions
555
556		p := NewNamedParser("TestIni", Default)
557		p.AddGroup("Application Options", "The application options", &opts)
558
559		inip := NewIniParser(p)
560
561		read := strings.NewReader(test.read)
562		err := inip.Parse(read)
563		if err != nil {
564			t.Fatalf("Unexpected error: %s", err)
565		}
566
567		var write bytes.Buffer
568		inip.Write(&write, test.options)
569
570		got := write.String()
571
572		msg := fmt.Sprintf("with ini options %b", test.options)
573		assertDiff(t, got, test.write, msg)
574	}
575}
576
577func TestReadIniWrongQuoting(t *testing.T) {
578	var tests = []struct {
579		iniFile    string
580		lineNumber uint
581	}{
582		{
583			iniFile:    `Default = "New\nvalue`,
584			lineNumber: 1,
585		},
586		{
587			iniFile:    `StringSlice = "New\nvalue`,
588			lineNumber: 1,
589		},
590		{
591			iniFile: `StringSlice = "New\nvalue"
592			StringSlice = "Second\nvalue`,
593			lineNumber: 2,
594		},
595		{
596			iniFile:    `DefaultMap = some:"value`,
597			lineNumber: 1,
598		},
599		{
600			iniFile: `DefaultMap = some:value
601			DefaultMap = another:"value`,
602			lineNumber: 2,
603		},
604	}
605
606	for _, test := range tests {
607		var opts helpOptions
608
609		p := NewNamedParser("TestIni", Default)
610		p.AddGroup("Application Options", "The application options", &opts)
611
612		inip := NewIniParser(p)
613
614		inic := test.iniFile
615
616		b := strings.NewReader(inic)
617		err := inip.Parse(b)
618
619		if err == nil {
620			t.Fatalf("Expect error")
621		}
622
623		iniError := err.(*IniError)
624
625		if iniError.LineNumber != test.lineNumber {
626			t.Fatalf("Expect error on line %d", test.lineNumber)
627		}
628	}
629}
630
631func TestIniCommands(t *testing.T) {
632	var opts struct {
633		Value string `short:"v" long:"value"`
634
635		Add struct {
636			Name int `short:"n" long:"name" ini-name:"AliasName"`
637
638			Other struct {
639				O string `short:"o" long:"other"`
640			} `group:"Other Options"`
641		} `command:"add"`
642	}
643
644	p := NewNamedParser("TestIni", Default)
645	p.AddGroup("Application Options", "The application options", &opts)
646
647	inip := NewIniParser(p)
648
649	inic := `[Application Options]
650value = some value
651
652[add]
653AliasName = 5
654
655[add.Other Options]
656other = subgroup
657
658`
659
660	b := strings.NewReader(inic)
661	err := inip.Parse(b)
662
663	if err != nil {
664		t.Fatalf("Unexpected error: %s", err)
665	}
666
667	assertString(t, opts.Value, "some value")
668
669	if opts.Add.Name != 5 {
670		t.Errorf("Expected opts.Add.Name to be 5, but got %v", opts.Add.Name)
671	}
672
673	assertString(t, opts.Add.Other.O, "subgroup")
674
675	// Test writing it back
676	buf := &bytes.Buffer{}
677
678	inip.Write(buf, IniDefault)
679
680	assertDiff(t, buf.String(), inic, "ini contents")
681}
682
683func TestIniNoIni(t *testing.T) {
684	var opts struct {
685		NoValue string `short:"n" long:"novalue" no-ini:"yes"`
686		Value   string `short:"v" long:"value"`
687	}
688
689	p := NewNamedParser("TestIni", Default)
690	p.AddGroup("Application Options", "The application options", &opts)
691
692	inip := NewIniParser(p)
693
694	// read INI
695	inic := `[Application Options]
696novalue = some value
697value = some other value
698`
699
700	b := strings.NewReader(inic)
701	err := inip.Parse(b)
702
703	if err == nil {
704		t.Fatalf("Expected error")
705	}
706
707	iniError := err.(*IniError)
708
709	if v := uint(2); iniError.LineNumber != v {
710		t.Errorf("Expected opts.Add.Name to be %d, but got %d", v, iniError.LineNumber)
711	}
712
713	if v := "unknown option: novalue"; iniError.Message != v {
714		t.Errorf("Expected opts.Add.Name to be %s, but got %s", v, iniError.Message)
715	}
716
717	// write INI
718	opts.NoValue = "some value"
719	opts.Value = "some other value"
720
721	file, err := ioutil.TempFile("", "")
722	if err != nil {
723		t.Fatalf("Cannot create temporary file: %s", err)
724	}
725	defer os.Remove(file.Name())
726
727	err = inip.WriteFile(file.Name(), IniIncludeDefaults)
728	if err != nil {
729		t.Fatalf("Could not write ini file: %s", err)
730	}
731
732	found, err := ioutil.ReadFile(file.Name())
733	if err != nil {
734		t.Fatalf("Could not read written ini file: %s", err)
735	}
736
737	expected := "[Application Options]\nValue = some other value\n\n"
738
739	assertDiff(t, string(found), expected, "ini content")
740}
741
742func TestIniParse(t *testing.T) {
743	file, err := ioutil.TempFile("", "")
744	if err != nil {
745		t.Fatalf("Cannot create temporary file: %s", err)
746	}
747	defer os.Remove(file.Name())
748
749	_, err = file.WriteString("value = 123")
750	if err != nil {
751		t.Fatalf("Cannot write to temporary file: %s", err)
752	}
753
754	file.Close()
755
756	var opts struct {
757		Value int `long:"value"`
758	}
759
760	err = IniParse(file.Name(), &opts)
761	if err != nil {
762		t.Fatalf("Could not parse ini: %s", err)
763	}
764
765	if opts.Value != 123 {
766		t.Fatalf("Expected Value to be \"123\" but was \"%d\"", opts.Value)
767	}
768}
769
770func TestIniCliOverrides(t *testing.T) {
771	file, err := ioutil.TempFile("", "")
772
773	if err != nil {
774		t.Fatalf("Cannot create temporary file: %s", err)
775	}
776
777	defer os.Remove(file.Name())
778
779	_, err = file.WriteString("values = 123\n")
780	_, err = file.WriteString("values = 456\n")
781
782	if err != nil {
783		t.Fatalf("Cannot write to temporary file: %s", err)
784	}
785
786	file.Close()
787
788	var opts struct {
789		Values []int `long:"values"`
790	}
791
792	p := NewParser(&opts, Default)
793	err = NewIniParser(p).ParseFile(file.Name())
794
795	if err != nil {
796		t.Fatalf("Could not parse ini: %s", err)
797	}
798
799	_, err = p.ParseArgs([]string{"--values", "111", "--values", "222"})
800
801	if err != nil {
802		t.Fatalf("Failed to parse arguments: %s", err)
803	}
804
805	if len(opts.Values) != 2 {
806		t.Fatalf("Expected Values to contain two elements, but got %d", len(opts.Values))
807	}
808
809	if opts.Values[0] != 111 {
810		t.Fatalf("Expected Values[0] to be 111, but got '%d'", opts.Values[0])
811	}
812
813	if opts.Values[1] != 222 {
814		t.Fatalf("Expected Values[1] to be 222, but got '%d'", opts.Values[1])
815	}
816}
817
818func TestIniOverrides(t *testing.T) {
819	file, err := ioutil.TempFile("", "")
820
821	if err != nil {
822		t.Fatalf("Cannot create temporary file: %s", err)
823	}
824
825	defer os.Remove(file.Name())
826
827	_, err = file.WriteString("value-with-default = \"ini-value\"\n")
828	_, err = file.WriteString("value-with-default-override-cli = \"ini-value\"\n")
829
830	if err != nil {
831		t.Fatalf("Cannot write to temporary file: %s", err)
832	}
833
834	file.Close()
835
836	var opts struct {
837		ValueWithDefault            string `long:"value-with-default" default:"value"`
838		ValueWithDefaultOverrideCli string `long:"value-with-default-override-cli" default:"value"`
839	}
840
841	p := NewParser(&opts, Default)
842	err = NewIniParser(p).ParseFile(file.Name())
843
844	if err != nil {
845		t.Fatalf("Could not parse ini: %s", err)
846	}
847
848	_, err = p.ParseArgs([]string{"--value-with-default-override-cli", "cli-value"})
849
850	if err != nil {
851		t.Fatalf("Failed to parse arguments: %s", err)
852	}
853
854	assertString(t, opts.ValueWithDefault, "ini-value")
855	assertString(t, opts.ValueWithDefaultOverrideCli, "cli-value")
856}
857
858func TestIniRequired(t *testing.T) {
859	var opts struct {
860		Required string               `short:"r" required:"yes" description:"required"`
861		Config   func(s string) error `long:"config" default:"no-ini-file" no-ini:"true"`
862	}
863
864	p := NewParser(&opts, Default)
865
866	opts.Config = func(s string) error {
867		inip := NewIniParser(p)
868		inip.ParseAsDefaults = true
869		return inip.Parse(strings.NewReader("Required = ini-value\n"))
870	}
871
872	_, err := p.ParseArgs([]string{"-r", "cli-value"})
873
874	if err != nil {
875		t.Fatalf("Failed to parse arguments: %s", err)
876	}
877
878	assertString(t, opts.Required, "cli-value")
879}
880
881func TestWriteFile(t *testing.T) {
882	file, err := ioutil.TempFile("", "")
883	if err != nil {
884		t.Fatalf("Cannot create temporary file: %s", err)
885	}
886	defer os.Remove(file.Name())
887
888	var opts struct {
889		Value int `long:"value"`
890	}
891
892	opts.Value = 123
893
894	p := NewParser(&opts, Default)
895	ini := NewIniParser(p)
896
897	err = ini.WriteFile(file.Name(), IniIncludeDefaults)
898	if err != nil {
899		t.Fatalf("Could not write ini file: %s", err)
900	}
901
902	found, err := ioutil.ReadFile(file.Name())
903	if err != nil {
904		t.Fatalf("Could not read written ini file: %s", err)
905	}
906
907	expected := "[Application Options]\nValue = 123\n\n"
908
909	assertDiff(t, string(found), expected, "ini content")
910}
911
912func TestOverwriteRequiredOptions(t *testing.T) {
913	var tests = []struct {
914		args     []string
915		expected []string
916	}{
917		{
918			args: []string{"--value", "from CLI"},
919			expected: []string{
920				"from CLI",
921				"from default",
922			},
923		},
924		{
925			args: []string{"--value", "from CLI", "--default", "from CLI"},
926			expected: []string{
927				"from CLI",
928				"from CLI",
929			},
930		},
931		{
932			args: []string{"--config", "no file name"},
933			expected: []string{
934				"from INI",
935				"from INI",
936			},
937		},
938		{
939			args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name"},
940			expected: []string{
941				"from INI",
942				"from INI",
943			},
944		},
945		{
946			args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name", "--value", "from CLI after", "--default", "from CLI after"},
947			expected: []string{
948				"from CLI after",
949				"from CLI after",
950			},
951		},
952	}
953
954	for _, test := range tests {
955		var opts struct {
956			Config  func(s string) error `long:"config" no-ini:"true"`
957			Value   string               `long:"value" required:"true"`
958			Default string               `long:"default" required:"true" default:"from default"`
959		}
960
961		p := NewParser(&opts, Default)
962
963		opts.Config = func(s string) error {
964			ini := NewIniParser(p)
965
966			return ini.Parse(bytes.NewBufferString("value = from INI\ndefault = from INI"))
967		}
968
969		_, err := p.ParseArgs(test.args)
970		if err != nil {
971			t.Fatalf("Unexpected error %s with args %+v", err, test.args)
972		}
973
974		if opts.Value != test.expected[0] {
975			t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected[0], opts.Value, test.args)
976		}
977
978		if opts.Default != test.expected[1] {
979			t.Fatalf("Expected Default to be \"%s\" but was \"%s\" with args %+v", test.expected[1], opts.Default, test.args)
980		}
981	}
982}
983
984func TestIniOverwriteOptions(t *testing.T) {
985	var tests = []struct {
986		args     []string
987		expected string
988		toggled  bool
989	}{
990		{
991			args:     []string{},
992			expected: "from default",
993		},
994		{
995			args:     []string{"--value", "from CLI"},
996			expected: "from CLI",
997		},
998		{
999			args:     []string{"--config", "no file name"},
1000			expected: "from INI",
1001			toggled:  true,
1002		},
1003		{
1004			args:     []string{"--value", "from CLI before", "--config", "no file name"},
1005			expected: "from CLI before",
1006			toggled:  true,
1007		},
1008		{
1009			args:     []string{"--config", "no file name", "--value", "from CLI after"},
1010			expected: "from CLI after",
1011			toggled:  true,
1012		},
1013		{
1014			args:     []string{"--toggle"},
1015			toggled:  true,
1016			expected: "from default",
1017		},
1018	}
1019
1020	for _, test := range tests {
1021		var opts struct {
1022			Config string `long:"config" no-ini:"true"`
1023			Value  string `long:"value" default:"from default"`
1024			Toggle bool   `long:"toggle"`
1025		}
1026
1027		p := NewParser(&opts, Default)
1028
1029		_, err := p.ParseArgs(test.args)
1030		if err != nil {
1031			t.Fatalf("Unexpected error %s with args %+v", err, test.args)
1032		}
1033
1034		if opts.Config != "" {
1035			inip := NewIniParser(p)
1036			inip.ParseAsDefaults = true
1037
1038			err = inip.Parse(bytes.NewBufferString("value = from INI\ntoggle = true"))
1039			if err != nil {
1040				t.Fatalf("Unexpected error %s with args %+v", err, test.args)
1041			}
1042		}
1043
1044		if opts.Value != test.expected {
1045			t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected, opts.Value, test.args)
1046		}
1047
1048		if opts.Toggle != test.toggled {
1049			t.Fatalf("Expected Toggle to be \"%v\" but was \"%v\" with args %+v", test.toggled, opts.Toggle, test.args)
1050		}
1051
1052	}
1053}
1054