1package flags
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"reflect"
8	"runtime"
9	"strconv"
10	"strings"
11	"testing"
12	"time"
13)
14
15type defaultOptions struct {
16	Int        int `long:"i"`
17	IntDefault int `long:"id" default:"1"`
18
19	Float64        float64 `long:"f"`
20	Float64Default float64 `long:"fd" default:"-3.14"`
21
22	NumericFlag bool `short:"3"`
23
24	String            string `long:"str"`
25	StringDefault     string `long:"strd" default:"abc"`
26	StringNotUnquoted string `long:"strnot" unquote:"false"`
27
28	Time        time.Duration `long:"t"`
29	TimeDefault time.Duration `long:"td" default:"1m"`
30
31	Map        map[string]int `long:"m"`
32	MapDefault map[string]int `long:"md" default:"a:1"`
33
34	Slice        []int `long:"s"`
35	SliceDefault []int `long:"sd" default:"1" default:"2"`
36}
37
38func TestDefaults(t *testing.T) {
39	var tests = []struct {
40		msg      string
41		args     []string
42		expected defaultOptions
43	}{
44		{
45			msg:  "no arguments, expecting default values",
46			args: []string{},
47			expected: defaultOptions{
48				Int:        0,
49				IntDefault: 1,
50
51				Float64:        0.0,
52				Float64Default: -3.14,
53
54				NumericFlag: false,
55
56				String:        "",
57				StringDefault: "abc",
58
59				Time:        0,
60				TimeDefault: time.Minute,
61
62				Map:        map[string]int{},
63				MapDefault: map[string]int{"a": 1},
64
65				Slice:        []int{},
66				SliceDefault: []int{1, 2},
67			},
68		},
69		{
70			msg:  "non-zero value arguments, expecting overwritten arguments",
71			args: []string{"--i=3", "--id=3", "--f=-2.71", "--fd=2.71", "-3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"},
72			expected: defaultOptions{
73				Int:        3,
74				IntDefault: 3,
75
76				Float64:        -2.71,
77				Float64Default: 2.71,
78
79				NumericFlag: true,
80
81				String:        "def",
82				StringDefault: "def",
83
84				Time:        3 * time.Millisecond,
85				TimeDefault: 3 * time.Millisecond,
86
87				Map:        map[string]int{"c": 3},
88				MapDefault: map[string]int{"c": 3},
89
90				Slice:        []int{3},
91				SliceDefault: []int{3},
92			},
93		},
94		{
95			msg:  "zero value arguments, expecting overwritten arguments",
96			args: []string{"--i=0", "--id=0", "--f=0", "--fd=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"},
97			expected: defaultOptions{
98				Int:        0,
99				IntDefault: 0,
100
101				Float64:        0,
102				Float64Default: 0,
103
104				String:        "",
105				StringDefault: "",
106
107				Time:        0,
108				TimeDefault: 0,
109
110				Map:        map[string]int{"": 0},
111				MapDefault: map[string]int{"": 0},
112
113				Slice:        []int{0},
114				SliceDefault: []int{0},
115			},
116		},
117	}
118
119	for _, test := range tests {
120		var opts defaultOptions
121
122		_, err := ParseArgs(&opts, test.args)
123		if err != nil {
124			t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
125		}
126
127		if opts.Slice == nil {
128			opts.Slice = []int{}
129		}
130
131		if !reflect.DeepEqual(opts, test.expected) {
132			t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
133		}
134	}
135}
136
137func TestNoDefaultsForBools(t *testing.T) {
138	var opts struct {
139		DefaultBool bool `short:"d" default:"true"`
140	}
141
142	if runtime.GOOS == "windows" {
143		assertParseFail(t, ErrInvalidTag, "boolean flag `/d' may not have default values, they always default to `false' and can only be turned on", &opts)
144	} else {
145		assertParseFail(t, ErrInvalidTag, "boolean flag `-d' may not have default values, they always default to `false' and can only be turned on", &opts)
146	}
147}
148
149func TestUnquoting(t *testing.T) {
150	var tests = []struct {
151		arg   string
152		err   error
153		value string
154	}{
155		{
156			arg:   "\"abc",
157			err:   strconv.ErrSyntax,
158			value: "",
159		},
160		{
161			arg:   "\"\"abc\"",
162			err:   strconv.ErrSyntax,
163			value: "",
164		},
165		{
166			arg:   "\"abc\"",
167			err:   nil,
168			value: "abc",
169		},
170		{
171			arg:   "\"\\\"abc\\\"\"",
172			err:   nil,
173			value: "\"abc\"",
174		},
175		{
176			arg:   "\"\\\"abc\"",
177			err:   nil,
178			value: "\"abc",
179		},
180	}
181
182	for _, test := range tests {
183		var opts defaultOptions
184
185		for _, delimiter := range []bool{false, true} {
186			p := NewParser(&opts, None)
187
188			var err error
189			if delimiter {
190				_, err = p.ParseArgs([]string{"--str=" + test.arg, "--strnot=" + test.arg})
191			} else {
192				_, err = p.ParseArgs([]string{"--str", test.arg, "--strnot", test.arg})
193			}
194
195			if test.err == nil {
196				if err != nil {
197					t.Fatalf("Expected no error but got: %v", err)
198				}
199
200				if test.value != opts.String {
201					t.Fatalf("Expected String to be %q but got %q", test.value, opts.String)
202				}
203				if q := strconv.Quote(test.value); q != opts.StringNotUnquoted {
204					t.Fatalf("Expected StringDefault to be %q but got %q", q, opts.StringNotUnquoted)
205				}
206			} else {
207				if err == nil {
208					t.Fatalf("Expected error")
209				} else if e, ok := err.(*Error); ok {
210					if strings.HasPrefix(e.Message, test.err.Error()) {
211						t.Fatalf("Expected error message to end with %q but got %v", test.err.Error(), e.Message)
212					}
213				}
214			}
215		}
216	}
217}
218
219// EnvRestorer keeps a copy of a set of env variables and can restore the env from them
220type EnvRestorer struct {
221	env map[string]string
222}
223
224func (r *EnvRestorer) Restore() {
225	os.Clearenv()
226
227	for k, v := range r.env {
228		os.Setenv(k, v)
229	}
230}
231
232// EnvSnapshot returns a snapshot of the currently set env variables
233func EnvSnapshot() *EnvRestorer {
234	r := EnvRestorer{make(map[string]string)}
235
236	for _, kv := range os.Environ() {
237		parts := strings.SplitN(kv, "=", 2)
238
239		if len(parts) != 2 {
240			panic("got a weird env variable: " + kv)
241		}
242
243		r.env[parts[0]] = parts[1]
244	}
245
246	return &r
247}
248
249type envNestedOptions struct {
250	Foo string `long:"foo" default:"z" env:"FOO"`
251}
252
253type envDefaultOptions struct {
254	Int    int              `long:"i" default:"1" env:"TEST_I"`
255	Time   time.Duration    `long:"t" default:"1m" env:"TEST_T"`
256	Map    map[string]int   `long:"m" default:"a:1" env:"TEST_M" env-delim:";"`
257	Slice  []int            `long:"s" default:"1" default:"2" env:"TEST_S"  env-delim:","`
258	Nested envNestedOptions `group:"nested" namespace:"nested" env-namespace:"NESTED"`
259}
260
261func TestEnvDefaults(t *testing.T) {
262	var tests = []struct {
263		msg         string
264		args        []string
265		expected    envDefaultOptions
266		expectedErr string
267		env         map[string]string
268	}{
269		{
270			msg:  "no arguments, no env, expecting default values",
271			args: []string{},
272			expected: envDefaultOptions{
273				Int:   1,
274				Time:  time.Minute,
275				Map:   map[string]int{"a": 1},
276				Slice: []int{1, 2},
277				Nested: envNestedOptions{
278					Foo: "z",
279				},
280			},
281		},
282		{
283			msg:  "no arguments, env defaults, expecting env default values",
284			args: []string{},
285			expected: envDefaultOptions{
286				Int:   2,
287				Time:  2 * time.Minute,
288				Map:   map[string]int{"a": 2, "b": 3},
289				Slice: []int{4, 5, 6},
290				Nested: envNestedOptions{
291					Foo: "a",
292				},
293			},
294			env: map[string]string{
295				"TEST_I":     "2",
296				"TEST_T":     "2m",
297				"TEST_M":     "a:2;b:3",
298				"TEST_S":     "4,5,6",
299				"NESTED_FOO": "a",
300			},
301		},
302		{
303			msg:         "no arguments, malformed env defaults, expecting parse error",
304			args:        []string{},
305			expectedErr: `parsing "two": invalid syntax`,
306			env: map[string]string{
307				"TEST_I": "two",
308			},
309		},
310		{
311			msg:  "non-zero value arguments, expecting overwritten arguments",
312			args: []string{"--i=3", "--t=3ms", "--m=c:3", "--s=3", "--nested.foo=\"p\""},
313			expected: envDefaultOptions{
314				Int:   3,
315				Time:  3 * time.Millisecond,
316				Map:   map[string]int{"c": 3},
317				Slice: []int{3},
318				Nested: envNestedOptions{
319					Foo: "p",
320				},
321			},
322			env: map[string]string{
323				"TEST_I":     "2",
324				"TEST_T":     "2m",
325				"TEST_M":     "a:2;b:3",
326				"TEST_S":     "4,5,6",
327				"NESTED_FOO": "a",
328			},
329		},
330		{
331			msg:  "zero value arguments, expecting overwritten arguments",
332			args: []string{"--i=0", "--t=0ms", "--m=:0", "--s=0", "--nested.foo=\"\""},
333			expected: envDefaultOptions{
334				Int:   0,
335				Time:  0,
336				Map:   map[string]int{"": 0},
337				Slice: []int{0},
338				Nested: envNestedOptions{
339					Foo: "",
340				},
341			},
342			env: map[string]string{
343				"TEST_I":     "2",
344				"TEST_T":     "2m",
345				"TEST_M":     "a:2;b:3",
346				"TEST_S":     "4,5,6",
347				"NESTED_FOO": "a",
348			},
349		},
350	}
351
352	oldEnv := EnvSnapshot()
353	defer oldEnv.Restore()
354
355	for _, test := range tests {
356		var opts envDefaultOptions
357		oldEnv.Restore()
358		for envKey, envValue := range test.env {
359			os.Setenv(envKey, envValue)
360		}
361		_, err := NewParser(&opts, None).ParseArgs(test.args)
362		if test.expectedErr != "" {
363			if err == nil {
364				t.Errorf("%s:\nExpected error containing substring %q", test.msg, test.expectedErr)
365			} else if !strings.Contains(err.Error(), test.expectedErr) {
366				t.Errorf("%s:\nExpected error %q to contain substring %q", test.msg, err, test.expectedErr)
367			}
368		} else {
369			if err != nil {
370				t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
371			}
372
373			if opts.Slice == nil {
374				opts.Slice = []int{}
375			}
376
377			if !reflect.DeepEqual(opts, test.expected) {
378				t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
379			}
380		}
381	}
382}
383
384type CustomFlag struct {
385	Value string
386}
387
388func (c *CustomFlag) UnmarshalFlag(s string) error {
389	c.Value = s
390	return nil
391}
392
393func (c *CustomFlag) IsValidValue(s string) error {
394	if !(s == "-1" || s == "-foo") {
395		return errors.New("invalid flag value")
396	}
397	return nil
398}
399
400func TestOptionAsArgument(t *testing.T) {
401	var tests = []struct {
402		args        []string
403		expectError bool
404		errType     ErrorType
405		errMsg      string
406		rest        []string
407	}{
408		{
409			// short option must not be accepted as argument
410			args:        []string{"--string-slice", "foobar", "--string-slice", "-o"},
411			expectError: true,
412			errType:     ErrExpectedArgument,
413			errMsg:      "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-o'",
414		},
415		{
416			// long option must not be accepted as argument
417			args:        []string{"--string-slice", "foobar", "--string-slice", "--other-option"},
418			expectError: true,
419			errType:     ErrExpectedArgument,
420			errMsg:      "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `--other-option'",
421		},
422		{
423			// long option must not be accepted as argument
424			args:        []string{"--string-slice", "--"},
425			expectError: true,
426			errType:     ErrExpectedArgument,
427			errMsg:      "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got double dash `--'",
428		},
429		{
430			// quoted and appended option should be accepted as argument (even if it looks like an option)
431			args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""},
432		},
433		{
434			// Accept any single character arguments including '-'
435			args: []string{"--string-slice", "-"},
436		},
437		{
438			// Do not accept arguments which start with '-' even if the next character is a digit
439			args:        []string{"--string-slice", "-3.14"},
440			expectError: true,
441			errType:     ErrExpectedArgument,
442			errMsg:      "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-3.14'",
443		},
444		{
445			// Do not accept arguments which start with '-' if the next character is not a digit
446			args:        []string{"--string-slice", "-character"},
447			expectError: true,
448			errType:     ErrExpectedArgument,
449			errMsg:      "expected argument for flag `" + defaultLongOptDelimiter + "string-slice', but got option `-character'",
450		},
451		{
452			args: []string{"-o", "-", "-"},
453			rest: []string{"-", "-"},
454		},
455		{
456			// Accept arguments which start with '-' if the next character is a digit
457			args: []string{"--int-slice", "-3"},
458		},
459		{
460			// Accept arguments which start with '-' if the next character is a digit
461			args: []string{"--int16", "-3"},
462		},
463		{
464			// Accept arguments which start with '-' if the next character is a digit
465			args: []string{"--float32", "-3.2"},
466		},
467		{
468			// Accept arguments which start with '-' if the next character is a digit
469			args: []string{"--float32ptr", "-3.2"},
470		},
471		{
472			// Accept arguments for values that pass the IsValidValue fuction for value validators
473			args: []string{"--custom-flag", "-foo"},
474		},
475		{
476			// Accept arguments for values that pass the IsValidValue fuction for value validators
477			args: []string{"--custom-flag", "-1"},
478		},
479		{
480			// Rejects arguments for values that fail the IsValidValue fuction for value validators
481			args:        []string{"--custom-flag", "-2"},
482			expectError: true,
483			errType:     ErrExpectedArgument,
484			errMsg:      "invalid flag value",
485		},
486	}
487
488	var opts struct {
489		StringSlice []string   `long:"string-slice"`
490		IntSlice    []int      `long:"int-slice"`
491		Int16       int16      `long:"int16"`
492		Float32     float32    `long:"float32"`
493		Float32Ptr  *float32   `long:"float32ptr"`
494		OtherOption bool       `long:"other-option" short:"o"`
495		Custom      CustomFlag `long:"custom-flag" short:"c"`
496	}
497
498	for _, test := range tests {
499		if test.expectError {
500			assertParseFail(t, test.errType, test.errMsg, &opts, test.args...)
501		} else {
502			args := assertParseSuccess(t, &opts, test.args...)
503
504			assertStringArray(t, args, test.rest)
505		}
506	}
507}
508
509func TestUnknownFlagHandler(t *testing.T) {
510
511	var opts struct {
512		Flag1 string `long:"flag1"`
513		Flag2 string `long:"flag2"`
514	}
515
516	p := NewParser(&opts, None)
517
518	var unknownFlag1 string
519	var unknownFlag2 bool
520	var unknownFlag3 string
521
522	// Set up a callback to intercept unknown options during parsing
523	p.UnknownOptionHandler = func(option string, arg SplitArgument, args []string) ([]string, error) {
524		if option == "unknownFlag1" {
525			if argValue, ok := arg.Value(); ok {
526				unknownFlag1 = argValue
527				return args, nil
528			}
529			// consume a value from remaining args list
530			unknownFlag1 = args[0]
531			return args[1:], nil
532		} else if option == "unknownFlag2" {
533			// treat this one as a bool switch, don't consume any args
534			unknownFlag2 = true
535			return args, nil
536		} else if option == "unknownFlag3" {
537			if argValue, ok := arg.Value(); ok {
538				unknownFlag3 = argValue
539				return args, nil
540			}
541			// consume a value from remaining args list
542			unknownFlag3 = args[0]
543			return args[1:], nil
544		}
545
546		return args, fmt.Errorf("Unknown flag: %v", option)
547	}
548
549	// Parse args containing some unknown flags, verify that
550	// our callback can handle all of them
551	_, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--unknownFlag3=baz", "--flag2=foo"})
552
553	if err != nil {
554		assertErrorf(t, "Parser returned unexpected error %v", err)
555	}
556
557	assertString(t, opts.Flag1, "stuff")
558	assertString(t, opts.Flag2, "foo")
559	assertString(t, unknownFlag1, "blah")
560	assertString(t, unknownFlag3, "baz")
561
562	if !unknownFlag2 {
563		assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2)
564	}
565
566	// Parse args with unknown flags that callback doesn't handle, verify it returns error
567	_, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"})
568
569	if err == nil {
570		assertErrorf(t, "Parser should have returned error, but returned nil")
571	}
572}
573
574func TestChoices(t *testing.T) {
575	var opts struct {
576		Choice string `long:"choose" choice:"v1" choice:"v2"`
577	}
578
579	assertParseFail(t, ErrInvalidChoice, "Invalid value `invalid' for option `"+defaultLongOptDelimiter+"choose'. Allowed values are: v1 or v2", &opts, "--choose", "invalid")
580	assertParseSuccess(t, &opts, "--choose", "v2")
581	assertString(t, opts.Choice, "v2")
582}
583
584func TestEmbedded(t *testing.T) {
585	type embedded struct {
586		V bool `short:"v"`
587	}
588	var opts struct {
589		embedded
590	}
591
592	assertParseSuccess(t, &opts, "-v")
593
594	if !opts.V {
595		t.Errorf("Expected V to be true")
596	}
597}
598
599type command struct {
600}
601
602func (c *command) Execute(args []string) error {
603	return nil
604}
605
606func TestCommandHandlerNoCommand(t *testing.T) {
607	var opts = struct {
608		Value bool `short:"v"`
609	}{}
610
611	parser := NewParser(&opts, Default&^PrintErrors)
612
613	var executedCommand Commander
614	var executedArgs []string
615
616	executed := false
617
618	parser.CommandHandler = func(command Commander, args []string) error {
619		executed = true
620
621		executedCommand = command
622		executedArgs = args
623
624		return nil
625	}
626
627	_, err := parser.ParseArgs([]string{"arg1", "arg2"})
628
629	if err != nil {
630		t.Fatalf("Unexpected parse error: %s", err)
631	}
632
633	if !executed {
634		t.Errorf("Expected command handler to be executed")
635	}
636
637	if executedCommand != nil {
638		t.Errorf("Did not exect an executed command")
639	}
640
641	assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
642}
643
644func TestCommandHandler(t *testing.T) {
645	var opts = struct {
646		Value bool `short:"v"`
647
648		Command command `command:"cmd"`
649	}{}
650
651	parser := NewParser(&opts, Default&^PrintErrors)
652
653	var executedCommand Commander
654	var executedArgs []string
655
656	executed := false
657
658	parser.CommandHandler = func(command Commander, args []string) error {
659		executed = true
660
661		executedCommand = command
662		executedArgs = args
663
664		return nil
665	}
666
667	_, err := parser.ParseArgs([]string{"cmd", "arg1", "arg2"})
668
669	if err != nil {
670		t.Fatalf("Unexpected parse error: %s", err)
671	}
672
673	if !executed {
674		t.Errorf("Expected command handler to be executed")
675	}
676
677	if executedCommand == nil {
678		t.Errorf("Expected command handler to be executed")
679	}
680
681	assertStringArray(t, executedArgs, []string{"arg1", "arg2"})
682}
683