1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package flag_test
6
7import (
8	"bytes"
9	. "flag"
10	"fmt"
11	"internal/testenv"
12	"io"
13	"os"
14	"os/exec"
15	"regexp"
16	"runtime"
17	"slices"
18	"strconv"
19	"strings"
20	"testing"
21	"time"
22)
23
24func boolString(s string) string {
25	if s == "0" {
26		return "false"
27	}
28	return "true"
29}
30
31func TestEverything(t *testing.T) {
32	ResetForTesting(nil)
33	Bool("test_bool", false, "bool value")
34	Int("test_int", 0, "int value")
35	Int64("test_int64", 0, "int64 value")
36	Uint("test_uint", 0, "uint value")
37	Uint64("test_uint64", 0, "uint64 value")
38	String("test_string", "0", "string value")
39	Float64("test_float64", 0, "float64 value")
40	Duration("test_duration", 0, "time.Duration value")
41	Func("test_func", "func value", func(string) error { return nil })
42	BoolFunc("test_boolfunc", "func", func(string) error { return nil })
43
44	m := make(map[string]*Flag)
45	desired := "0"
46	visitor := func(f *Flag) {
47		if len(f.Name) > 5 && f.Name[0:5] == "test_" {
48			m[f.Name] = f
49			ok := false
50			switch {
51			case f.Value.String() == desired:
52				ok = true
53			case f.Name == "test_bool" && f.Value.String() == boolString(desired):
54				ok = true
55			case f.Name == "test_duration" && f.Value.String() == desired+"s":
56				ok = true
57			case f.Name == "test_func" && f.Value.String() == "":
58				ok = true
59			case f.Name == "test_boolfunc" && f.Value.String() == "":
60				ok = true
61			}
62			if !ok {
63				t.Error("Visit: bad value", f.Value.String(), "for", f.Name)
64			}
65		}
66	}
67	VisitAll(visitor)
68	if len(m) != 10 {
69		t.Error("VisitAll misses some flags")
70		for k, v := range m {
71			t.Log(k, *v)
72		}
73	}
74	m = make(map[string]*Flag)
75	Visit(visitor)
76	if len(m) != 0 {
77		t.Errorf("Visit sees unset flags")
78		for k, v := range m {
79			t.Log(k, *v)
80		}
81	}
82	// Now set all flags
83	Set("test_bool", "true")
84	Set("test_int", "1")
85	Set("test_int64", "1")
86	Set("test_uint", "1")
87	Set("test_uint64", "1")
88	Set("test_string", "1")
89	Set("test_float64", "1")
90	Set("test_duration", "1s")
91	Set("test_func", "1")
92	Set("test_boolfunc", "")
93	desired = "1"
94	Visit(visitor)
95	if len(m) != 10 {
96		t.Error("Visit fails after set")
97		for k, v := range m {
98			t.Log(k, *v)
99		}
100	}
101	// Now test they're visited in sort order.
102	var flagNames []string
103	Visit(func(f *Flag) { flagNames = append(flagNames, f.Name) })
104	if !slices.IsSorted(flagNames) {
105		t.Errorf("flag names not sorted: %v", flagNames)
106	}
107}
108
109func TestGet(t *testing.T) {
110	ResetForTesting(nil)
111	Bool("test_bool", true, "bool value")
112	Int("test_int", 1, "int value")
113	Int64("test_int64", 2, "int64 value")
114	Uint("test_uint", 3, "uint value")
115	Uint64("test_uint64", 4, "uint64 value")
116	String("test_string", "5", "string value")
117	Float64("test_float64", 6, "float64 value")
118	Duration("test_duration", 7, "time.Duration value")
119
120	visitor := func(f *Flag) {
121		if len(f.Name) > 5 && f.Name[0:5] == "test_" {
122			g, ok := f.Value.(Getter)
123			if !ok {
124				t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
125				return
126			}
127			switch f.Name {
128			case "test_bool":
129				ok = g.Get() == true
130			case "test_int":
131				ok = g.Get() == int(1)
132			case "test_int64":
133				ok = g.Get() == int64(2)
134			case "test_uint":
135				ok = g.Get() == uint(3)
136			case "test_uint64":
137				ok = g.Get() == uint64(4)
138			case "test_string":
139				ok = g.Get() == "5"
140			case "test_float64":
141				ok = g.Get() == float64(6)
142			case "test_duration":
143				ok = g.Get() == time.Duration(7)
144			}
145			if !ok {
146				t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), f.Name)
147			}
148		}
149	}
150	VisitAll(visitor)
151}
152
153func TestUsage(t *testing.T) {
154	called := false
155	ResetForTesting(func() { called = true })
156	if CommandLine.Parse([]string{"-x"}) == nil {
157		t.Error("parse did not fail for unknown flag")
158	}
159	if !called {
160		t.Error("did not call Usage for unknown flag")
161	}
162}
163
164func testParse(f *FlagSet, t *testing.T) {
165	if f.Parsed() {
166		t.Error("f.Parse() = true before Parse")
167	}
168	boolFlag := f.Bool("bool", false, "bool value")
169	bool2Flag := f.Bool("bool2", false, "bool2 value")
170	intFlag := f.Int("int", 0, "int value")
171	int64Flag := f.Int64("int64", 0, "int64 value")
172	uintFlag := f.Uint("uint", 0, "uint value")
173	uint64Flag := f.Uint64("uint64", 0, "uint64 value")
174	stringFlag := f.String("string", "0", "string value")
175	float64Flag := f.Float64("float64", 0, "float64 value")
176	durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value")
177	extra := "one-extra-argument"
178	args := []string{
179		"-bool",
180		"-bool2=true",
181		"--int", "22",
182		"--int64", "0x23",
183		"-uint", "24",
184		"--uint64", "25",
185		"-string", "hello",
186		"-float64", "2718e28",
187		"-duration", "2m",
188		extra,
189	}
190	if err := f.Parse(args); err != nil {
191		t.Fatal(err)
192	}
193	if !f.Parsed() {
194		t.Error("f.Parse() = false after Parse")
195	}
196	if *boolFlag != true {
197		t.Error("bool flag should be true, is ", *boolFlag)
198	}
199	if *bool2Flag != true {
200		t.Error("bool2 flag should be true, is ", *bool2Flag)
201	}
202	if *intFlag != 22 {
203		t.Error("int flag should be 22, is ", *intFlag)
204	}
205	if *int64Flag != 0x23 {
206		t.Error("int64 flag should be 0x23, is ", *int64Flag)
207	}
208	if *uintFlag != 24 {
209		t.Error("uint flag should be 24, is ", *uintFlag)
210	}
211	if *uint64Flag != 25 {
212		t.Error("uint64 flag should be 25, is ", *uint64Flag)
213	}
214	if *stringFlag != "hello" {
215		t.Error("string flag should be `hello`, is ", *stringFlag)
216	}
217	if *float64Flag != 2718e28 {
218		t.Error("float64 flag should be 2718e28, is ", *float64Flag)
219	}
220	if *durationFlag != 2*time.Minute {
221		t.Error("duration flag should be 2m, is ", *durationFlag)
222	}
223	if len(f.Args()) != 1 {
224		t.Error("expected one argument, got", len(f.Args()))
225	} else if f.Args()[0] != extra {
226		t.Errorf("expected argument %q got %q", extra, f.Args()[0])
227	}
228}
229
230func TestParse(t *testing.T) {
231	ResetForTesting(func() { t.Error("bad parse") })
232	testParse(CommandLine, t)
233}
234
235func TestFlagSetParse(t *testing.T) {
236	testParse(NewFlagSet("test", ContinueOnError), t)
237}
238
239// Declare a user-defined flag type.
240type flagVar []string
241
242func (f *flagVar) String() string {
243	return fmt.Sprint([]string(*f))
244}
245
246func (f *flagVar) Set(value string) error {
247	*f = append(*f, value)
248	return nil
249}
250
251func TestUserDefined(t *testing.T) {
252	var flags FlagSet
253	flags.Init("test", ContinueOnError)
254	flags.SetOutput(io.Discard)
255	var v flagVar
256	flags.Var(&v, "v", "usage")
257	if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
258		t.Error(err)
259	}
260	if len(v) != 3 {
261		t.Fatal("expected 3 args; got ", len(v))
262	}
263	expect := "[1 2 3]"
264	if v.String() != expect {
265		t.Errorf("expected value %q got %q", expect, v.String())
266	}
267}
268
269func TestUserDefinedFunc(t *testing.T) {
270	flags := NewFlagSet("test", ContinueOnError)
271	flags.SetOutput(io.Discard)
272	var ss []string
273	flags.Func("v", "usage", func(s string) error {
274		ss = append(ss, s)
275		return nil
276	})
277	if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
278		t.Error(err)
279	}
280	if len(ss) != 3 {
281		t.Fatal("expected 3 args; got ", len(ss))
282	}
283	expect := "[1 2 3]"
284	if got := fmt.Sprint(ss); got != expect {
285		t.Errorf("expected value %q got %q", expect, got)
286	}
287	// test usage
288	var buf strings.Builder
289	flags.SetOutput(&buf)
290	flags.Parse([]string{"-h"})
291	if usage := buf.String(); !strings.Contains(usage, "usage") {
292		t.Errorf("usage string not included: %q", usage)
293	}
294	// test Func error
295	flags = NewFlagSet("test", ContinueOnError)
296	flags.SetOutput(io.Discard)
297	flags.Func("v", "usage", func(s string) error {
298		return fmt.Errorf("test error")
299	})
300	// flag not set, so no error
301	if err := flags.Parse(nil); err != nil {
302		t.Error(err)
303	}
304	// flag set, expect error
305	if err := flags.Parse([]string{"-v", "1"}); err == nil {
306		t.Error("expected error; got none")
307	} else if errMsg := err.Error(); !strings.Contains(errMsg, "test error") {
308		t.Errorf(`error should contain "test error"; got %q`, errMsg)
309	}
310}
311
312func TestUserDefinedForCommandLine(t *testing.T) {
313	const help = "HELP"
314	var result string
315	ResetForTesting(func() { result = help })
316	Usage()
317	if result != help {
318		t.Fatalf("got %q; expected %q", result, help)
319	}
320}
321
322// Declare a user-defined boolean flag type.
323type boolFlagVar struct {
324	count int
325}
326
327func (b *boolFlagVar) String() string {
328	return fmt.Sprintf("%d", b.count)
329}
330
331func (b *boolFlagVar) Set(value string) error {
332	if value == "true" {
333		b.count++
334	}
335	return nil
336}
337
338func (b *boolFlagVar) IsBoolFlag() bool {
339	return b.count < 4
340}
341
342func TestUserDefinedBool(t *testing.T) {
343	var flags FlagSet
344	flags.Init("test", ContinueOnError)
345	flags.SetOutput(io.Discard)
346	var b boolFlagVar
347	var err error
348	flags.Var(&b, "b", "usage")
349	if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
350		if b.count < 4 {
351			t.Error(err)
352		}
353	}
354
355	if b.count != 4 {
356		t.Errorf("want: %d; got: %d", 4, b.count)
357	}
358
359	if err == nil {
360		t.Error("expected error; got none")
361	}
362}
363
364func TestUserDefinedBoolUsage(t *testing.T) {
365	var flags FlagSet
366	flags.Init("test", ContinueOnError)
367	var buf bytes.Buffer
368	flags.SetOutput(&buf)
369	var b boolFlagVar
370	flags.Var(&b, "b", "X")
371	b.count = 0
372	// b.IsBoolFlag() will return true and usage will look boolean.
373	flags.PrintDefaults()
374	got := buf.String()
375	want := "  -b\tX\n"
376	if got != want {
377		t.Errorf("false: want %q; got %q", want, got)
378	}
379	b.count = 4
380	// b.IsBoolFlag() will return false and usage will look non-boolean.
381	flags.PrintDefaults()
382	got = buf.String()
383	want = "  -b\tX\n  -b value\n    \tX\n"
384	if got != want {
385		t.Errorf("false: want %q; got %q", want, got)
386	}
387}
388
389func TestSetOutput(t *testing.T) {
390	var flags FlagSet
391	var buf strings.Builder
392	flags.SetOutput(&buf)
393	flags.Init("test", ContinueOnError)
394	flags.Parse([]string{"-unknown"})
395	if out := buf.String(); !strings.Contains(out, "-unknown") {
396		t.Logf("expected output mentioning unknown; got %q", out)
397	}
398}
399
400// This tests that one can reset the flags. This still works but not well, and is
401// superseded by FlagSet.
402func TestChangingArgs(t *testing.T) {
403	ResetForTesting(func() { t.Fatal("bad parse") })
404	oldArgs := os.Args
405	defer func() { os.Args = oldArgs }()
406	os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
407	before := Bool("before", false, "")
408	if err := CommandLine.Parse(os.Args[1:]); err != nil {
409		t.Fatal(err)
410	}
411	cmd := Arg(0)
412	os.Args = Args()
413	after := Bool("after", false, "")
414	Parse()
415	args := Args()
416
417	if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
418		t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
419	}
420}
421
422// Test that -help invokes the usage message and returns ErrHelp.
423func TestHelp(t *testing.T) {
424	var helpCalled = false
425	fs := NewFlagSet("help test", ContinueOnError)
426	fs.Usage = func() { helpCalled = true }
427	var flag bool
428	fs.BoolVar(&flag, "flag", false, "regular flag")
429	// Regular flag invocation should work
430	err := fs.Parse([]string{"-flag=true"})
431	if err != nil {
432		t.Fatal("expected no error; got ", err)
433	}
434	if !flag {
435		t.Error("flag was not set by -flag")
436	}
437	if helpCalled {
438		t.Error("help called for regular flag")
439		helpCalled = false // reset for next test
440	}
441	// Help flag should work as expected.
442	err = fs.Parse([]string{"-help"})
443	if err == nil {
444		t.Fatal("error expected")
445	}
446	if err != ErrHelp {
447		t.Fatal("expected ErrHelp; got ", err)
448	}
449	if !helpCalled {
450		t.Fatal("help was not called")
451	}
452	// If we define a help flag, that should override.
453	var help bool
454	fs.BoolVar(&help, "help", false, "help flag")
455	helpCalled = false
456	err = fs.Parse([]string{"-help"})
457	if err != nil {
458		t.Fatal("expected no error for defined -help; got ", err)
459	}
460	if helpCalled {
461		t.Fatal("help was called; should not have been for defined help flag")
462	}
463}
464
465// zeroPanicker is a flag.Value whose String method panics if its dontPanic
466// field is false.
467type zeroPanicker struct {
468	dontPanic bool
469	v         string
470}
471
472func (f *zeroPanicker) Set(s string) error {
473	f.v = s
474	return nil
475}
476
477func (f *zeroPanicker) String() string {
478	if !f.dontPanic {
479		panic("panic!")
480	}
481	return f.v
482}
483
484const defaultOutput = `  -A	for bootstrapping, allow 'any' type
485  -Alongflagname
486    	disable bounds checking
487  -C	a boolean defaulting to true (default true)
488  -D path
489    	set relative path for local imports
490  -E string
491    	issue 23543 (default "0")
492  -F number
493    	a non-zero number (default 2.7)
494  -G float
495    	a float that defaults to zero
496  -M string
497    	a multiline
498    	help
499    	string
500  -N int
501    	a non-zero int (default 27)
502  -O	a flag
503    	multiline help string (default true)
504  -V list
505    	a list of strings (default [a b])
506  -Z int
507    	an int that defaults to zero
508  -ZP0 value
509    	a flag whose String method panics when it is zero
510  -ZP1 value
511    	a flag whose String method panics when it is zero
512  -maxT timeout
513    	set timeout for dial
514
515panic calling String method on zero flag_test.zeroPanicker for flag ZP0: panic!
516panic calling String method on zero flag_test.zeroPanicker for flag ZP1: panic!
517`
518
519func TestPrintDefaults(t *testing.T) {
520	fs := NewFlagSet("print defaults test", ContinueOnError)
521	var buf strings.Builder
522	fs.SetOutput(&buf)
523	fs.Bool("A", false, "for bootstrapping, allow 'any' type")
524	fs.Bool("Alongflagname", false, "disable bounds checking")
525	fs.Bool("C", true, "a boolean defaulting to true")
526	fs.String("D", "", "set relative `path` for local imports")
527	fs.String("E", "0", "issue 23543")
528	fs.Float64("F", 2.7, "a non-zero `number`")
529	fs.Float64("G", 0, "a float that defaults to zero")
530	fs.String("M", "", "a multiline\nhelp\nstring")
531	fs.Int("N", 27, "a non-zero int")
532	fs.Bool("O", true, "a flag\nmultiline help string")
533	fs.Var(&flagVar{"a", "b"}, "V", "a `list` of strings")
534	fs.Int("Z", 0, "an int that defaults to zero")
535	fs.Var(&zeroPanicker{true, ""}, "ZP0", "a flag whose String method panics when it is zero")
536	fs.Var(&zeroPanicker{true, "something"}, "ZP1", "a flag whose String method panics when it is zero")
537	fs.Duration("maxT", 0, "set `timeout` for dial")
538	fs.PrintDefaults()
539	got := buf.String()
540	if got != defaultOutput {
541		t.Errorf("got:\n%q\nwant:\n%q", got, defaultOutput)
542	}
543}
544
545// Issue 19230: validate range of Int and Uint flag values.
546func TestIntFlagOverflow(t *testing.T) {
547	if strconv.IntSize != 32 {
548		return
549	}
550	ResetForTesting(nil)
551	Int("i", 0, "")
552	Uint("u", 0, "")
553	if err := Set("i", "2147483648"); err == nil {
554		t.Error("unexpected success setting Int")
555	}
556	if err := Set("u", "4294967296"); err == nil {
557		t.Error("unexpected success setting Uint")
558	}
559}
560
561// Issue 20998: Usage should respect CommandLine.output.
562func TestUsageOutput(t *testing.T) {
563	ResetForTesting(DefaultUsage)
564	var buf strings.Builder
565	CommandLine.SetOutput(&buf)
566	defer func(old []string) { os.Args = old }(os.Args)
567	os.Args = []string{"app", "-i=1", "-unknown"}
568	Parse()
569	const want = "flag provided but not defined: -i\nUsage of app:\n"
570	if got := buf.String(); got != want {
571		t.Errorf("output = %q; want %q", got, want)
572	}
573}
574
575func TestGetters(t *testing.T) {
576	expectedName := "flag set"
577	expectedErrorHandling := ContinueOnError
578	expectedOutput := io.Writer(os.Stderr)
579	fs := NewFlagSet(expectedName, expectedErrorHandling)
580
581	if fs.Name() != expectedName {
582		t.Errorf("unexpected name: got %s, expected %s", fs.Name(), expectedName)
583	}
584	if fs.ErrorHandling() != expectedErrorHandling {
585		t.Errorf("unexpected ErrorHandling: got %d, expected %d", fs.ErrorHandling(), expectedErrorHandling)
586	}
587	if fs.Output() != expectedOutput {
588		t.Errorf("unexpected output: got %#v, expected %#v", fs.Output(), expectedOutput)
589	}
590
591	expectedName = "gopher"
592	expectedErrorHandling = ExitOnError
593	expectedOutput = os.Stdout
594	fs.Init(expectedName, expectedErrorHandling)
595	fs.SetOutput(expectedOutput)
596
597	if fs.Name() != expectedName {
598		t.Errorf("unexpected name: got %s, expected %s", fs.Name(), expectedName)
599	}
600	if fs.ErrorHandling() != expectedErrorHandling {
601		t.Errorf("unexpected ErrorHandling: got %d, expected %d", fs.ErrorHandling(), expectedErrorHandling)
602	}
603	if fs.Output() != expectedOutput {
604		t.Errorf("unexpected output: got %v, expected %v", fs.Output(), expectedOutput)
605	}
606}
607
608func TestParseError(t *testing.T) {
609	for _, typ := range []string{"bool", "int", "int64", "uint", "uint64", "float64", "duration"} {
610		fs := NewFlagSet("parse error test", ContinueOnError)
611		fs.SetOutput(io.Discard)
612		_ = fs.Bool("bool", false, "")
613		_ = fs.Int("int", 0, "")
614		_ = fs.Int64("int64", 0, "")
615		_ = fs.Uint("uint", 0, "")
616		_ = fs.Uint64("uint64", 0, "")
617		_ = fs.Float64("float64", 0, "")
618		_ = fs.Duration("duration", 0, "")
619		// Strings cannot give errors.
620		args := []string{"-" + typ + "=x"}
621		err := fs.Parse(args) // x is not a valid setting for any flag.
622		if err == nil {
623			t.Errorf("Parse(%q)=%v; expected parse error", args, err)
624			continue
625		}
626		if !strings.Contains(err.Error(), "invalid") || !strings.Contains(err.Error(), "parse error") {
627			t.Errorf("Parse(%q)=%v; expected parse error", args, err)
628		}
629	}
630}
631
632func TestRangeError(t *testing.T) {
633	bad := []string{
634		"-int=123456789012345678901",
635		"-int64=123456789012345678901",
636		"-uint=123456789012345678901",
637		"-uint64=123456789012345678901",
638		"-float64=1e1000",
639	}
640	for _, arg := range bad {
641		fs := NewFlagSet("parse error test", ContinueOnError)
642		fs.SetOutput(io.Discard)
643		_ = fs.Int("int", 0, "")
644		_ = fs.Int64("int64", 0, "")
645		_ = fs.Uint("uint", 0, "")
646		_ = fs.Uint64("uint64", 0, "")
647		_ = fs.Float64("float64", 0, "")
648		// Strings cannot give errors, and bools and durations do not return strconv.NumError.
649		err := fs.Parse([]string{arg})
650		if err == nil {
651			t.Errorf("Parse(%q)=%v; expected range error", arg, err)
652			continue
653		}
654		if !strings.Contains(err.Error(), "invalid") || !strings.Contains(err.Error(), "value out of range") {
655			t.Errorf("Parse(%q)=%v; expected range error", arg, err)
656		}
657	}
658}
659
660func TestExitCode(t *testing.T) {
661	testenv.MustHaveExec(t)
662
663	magic := 123
664	if os.Getenv("GO_CHILD_FLAG") != "" {
665		fs := NewFlagSet("test", ExitOnError)
666		if os.Getenv("GO_CHILD_FLAG_HANDLE") != "" {
667			var b bool
668			fs.BoolVar(&b, os.Getenv("GO_CHILD_FLAG_HANDLE"), false, "")
669		}
670		fs.Parse([]string{os.Getenv("GO_CHILD_FLAG")})
671		os.Exit(magic)
672	}
673
674	tests := []struct {
675		flag       string
676		flagHandle string
677		expectExit int
678	}{
679		{
680			flag:       "-h",
681			expectExit: 0,
682		},
683		{
684			flag:       "-help",
685			expectExit: 0,
686		},
687		{
688			flag:       "-undefined",
689			expectExit: 2,
690		},
691		{
692			flag:       "-h",
693			flagHandle: "h",
694			expectExit: magic,
695		},
696		{
697			flag:       "-help",
698			flagHandle: "help",
699			expectExit: magic,
700		},
701	}
702
703	for _, test := range tests {
704		cmd := exec.Command(os.Args[0], "-test.run=^TestExitCode$")
705		cmd.Env = append(
706			os.Environ(),
707			"GO_CHILD_FLAG="+test.flag,
708			"GO_CHILD_FLAG_HANDLE="+test.flagHandle,
709		)
710		cmd.Run()
711		got := cmd.ProcessState.ExitCode()
712		// ExitCode is either 0 or 1 on Plan 9.
713		if runtime.GOOS == "plan9" && test.expectExit != 0 {
714			test.expectExit = 1
715		}
716		if got != test.expectExit {
717			t.Errorf("unexpected exit code for test case %+v \n: got %d, expect %d",
718				test, got, test.expectExit)
719		}
720	}
721}
722
723func mustPanic(t *testing.T, testName string, expected string, f func()) {
724	t.Helper()
725	defer func() {
726		switch msg := recover().(type) {
727		case nil:
728			t.Errorf("%s\n: expected panic(%q), but did not panic", testName, expected)
729		case string:
730			if ok, _ := regexp.MatchString(expected, msg); !ok {
731				t.Errorf("%s\n: expected panic(%q), but got panic(%q)", testName, expected, msg)
732			}
733		default:
734			t.Errorf("%s\n: expected panic(%q), but got panic(%T%v)", testName, expected, msg, msg)
735		}
736	}()
737	f()
738}
739
740func TestInvalidFlags(t *testing.T) {
741	tests := []struct {
742		flag     string
743		errorMsg string
744	}{
745		{
746			flag:     "-foo",
747			errorMsg: "flag \"-foo\" begins with -",
748		},
749		{
750			flag:     "foo=bar",
751			errorMsg: "flag \"foo=bar\" contains =",
752		},
753	}
754
755	for _, test := range tests {
756		testName := fmt.Sprintf("FlagSet.Var(&v, %q, \"\")", test.flag)
757
758		fs := NewFlagSet("", ContinueOnError)
759		buf := &strings.Builder{}
760		fs.SetOutput(buf)
761
762		mustPanic(t, testName, test.errorMsg, func() {
763			var v flagVar
764			fs.Var(&v, test.flag, "")
765		})
766		if msg := test.errorMsg + "\n"; msg != buf.String() {
767			t.Errorf("%s\n: unexpected output: expected %q, bug got %q", testName, msg, buf)
768		}
769	}
770}
771
772func TestRedefinedFlags(t *testing.T) {
773	tests := []struct {
774		flagSetName string
775		errorMsg    string
776	}{
777		{
778			flagSetName: "",
779			errorMsg:    "flag redefined: foo",
780		},
781		{
782			flagSetName: "fs",
783			errorMsg:    "fs flag redefined: foo",
784		},
785	}
786
787	for _, test := range tests {
788		testName := fmt.Sprintf("flag redefined in FlagSet(%q)", test.flagSetName)
789
790		fs := NewFlagSet(test.flagSetName, ContinueOnError)
791		buf := &strings.Builder{}
792		fs.SetOutput(buf)
793
794		var v flagVar
795		fs.Var(&v, "foo", "")
796
797		mustPanic(t, testName, test.errorMsg, func() {
798			fs.Var(&v, "foo", "")
799		})
800		if msg := test.errorMsg + "\n"; msg != buf.String() {
801			t.Errorf("%s\n: unexpected output: expected %q, bug got %q", testName, msg, buf)
802		}
803	}
804}
805
806func TestUserDefinedBoolFunc(t *testing.T) {
807	flags := NewFlagSet("test", ContinueOnError)
808	flags.SetOutput(io.Discard)
809	var ss []string
810	flags.BoolFunc("v", "usage", func(s string) error {
811		ss = append(ss, s)
812		return nil
813	})
814	if err := flags.Parse([]string{"-v", "", "-v", "1", "-v=2"}); err != nil {
815		t.Error(err)
816	}
817	if len(ss) != 1 {
818		t.Fatalf("got %d args; want 1 arg", len(ss))
819	}
820	want := "[true]"
821	if got := fmt.Sprint(ss); got != want {
822		t.Errorf("got %q; want %q", got, want)
823	}
824	// test usage
825	var buf strings.Builder
826	flags.SetOutput(&buf)
827	flags.Parse([]string{"-h"})
828	if usage := buf.String(); !strings.Contains(usage, "usage") {
829		t.Errorf("usage string not included: %q", usage)
830	}
831	// test BoolFunc error
832	flags = NewFlagSet("test", ContinueOnError)
833	flags.SetOutput(io.Discard)
834	flags.BoolFunc("v", "usage", func(s string) error {
835		return fmt.Errorf("test error")
836	})
837	// flag not set, so no error
838	if err := flags.Parse(nil); err != nil {
839		t.Error(err)
840	}
841	// flag set, expect error
842	if err := flags.Parse([]string{"-v", ""}); err == nil {
843		t.Error("got err == nil; want err != nil")
844	} else if errMsg := err.Error(); !strings.Contains(errMsg, "test error") {
845		t.Errorf(`got %q; error should contain "test error"`, errMsg)
846	}
847}
848
849func TestDefineAfterSet(t *testing.T) {
850	flags := NewFlagSet("test", ContinueOnError)
851	// Set by itself doesn't panic.
852	flags.Set("myFlag", "value")
853
854	// Define-after-set panics.
855	mustPanic(t, "DefineAfterSet", "flag myFlag set at .*/flag_test.go:.* before being defined", func() {
856		_ = flags.String("myFlag", "default", "usage")
857	})
858}
859