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