Skip to content

Commit 18a9d17

Browse files
committed
move Func, BoolFunc, tests as they require go1.21
Commit 69bc3bd added support for Func() and BoolFunc() to match stdlib. However, the Func method was added in [go1.16.0], and BoolFunc in [go1.21.0], so running the tests on older versions of Go would fail; docker run -it --rm -v ./:/pflag -w /pflag golang:1.21 sh -c 'go test -v ./...' # github.com/spf13/pflag [github.com/spf13/pflag.test] ./bool_func_test.go:86:28: cannot use stdFSet (type *flag.FlagSet) as type BoolFuncFlagSet in argument to runCase: *flag.FlagSet does not implement BoolFuncFlagSet (missing BoolFunc method) ./bool_func_test.go:113:21: undefined: io.Discard ./bool_func_test.go:116:28: cannot use stdFSet (type *flag.FlagSet) as type BoolFuncFlagSet in argument to runCase: *flag.FlagSet does not implement BoolFuncFlagSet (missing BoolFunc method) ./bool_func_test.go:139:7: undefined: errors.Is ./func_test.go:92:28: cannot use stdFSet (type *flag.FlagSet) as type FuncFlagSet in argument to runCase: *flag.FlagSet does not implement FuncFlagSet (missing Func method) ./func_test.go:119:21: undefined: io.Discard ./func_test.go:122:28: cannot use stdFSet (type *flag.FlagSet) as type FuncFlagSet in argument to runCase: *flag.FlagSet does not implement FuncFlagSet (missing Func method) ./func_test.go:145:7: undefined: errors.Is ./func_test.go:145:7: too many errors FAIL github.com/spf13/pflag [build failed] This patch moves the tests to a separate file that is not built for older versions of Go. [go1.16.0]: https://pkg.go.dev/[email protected]#Func [go1.21.0]: https://pkg.go.dev/[email protected]#BoolFunc Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent c5b9e98 commit 18a9d17

File tree

4 files changed

+212
-194
lines changed

4 files changed

+212
-194
lines changed

bool_func_go1.21_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
package pflag
5+
6+
import (
7+
"errors"
8+
"flag"
9+
"io"
10+
"strings"
11+
"testing"
12+
)
13+
14+
func TestBoolFuncCompat(t *testing.T) {
15+
// compare behavior with the stdlib 'flag' package
16+
type BoolFuncFlagSet interface {
17+
BoolFunc(name string, usage string, fn func(string) error)
18+
Parse([]string) error
19+
}
20+
21+
unitTestErr := errors.New("unit test error")
22+
runCase := func(f BoolFuncFlagSet, name string, args []string) (values []string, err error) {
23+
fn := func(s string) error {
24+
values = append(values, s)
25+
if s == "err" {
26+
return unitTestErr
27+
}
28+
return nil
29+
}
30+
f.BoolFunc(name, "Callback function", fn)
31+
32+
err = f.Parse(args)
33+
return values, err
34+
}
35+
36+
t.Run("regular parsing", func(t *testing.T) {
37+
flagName := "bflag"
38+
args := []string{"--bflag", "--bflag=false", "--bflag=1", "--bflag=bar", "--bflag="}
39+
40+
// It turns out that, even though the function is called "BoolFunc",
41+
// the standard flag package does not try to parse the value assigned to
42+
// that cli flag as a boolean. The string provided on the command line is
43+
// passed as is to the callback.
44+
// e.g: with "--bflag=not_a_bool" on the command line, the FlagSet does not
45+
// generate an error stating "invalid boolean value", and `fn` will be called
46+
// with "not_a_bool" as an argument.
47+
48+
stdFSet := flag.NewFlagSet("std test", flag.ContinueOnError)
49+
stdValues, err := runCase(stdFSet, flagName, args)
50+
if err != nil {
51+
t.Fatalf("std flag: expected no error, got %v", err)
52+
}
53+
expected := []string{"true", "false", "1", "bar", ""}
54+
if !cmpLists(expected, stdValues) {
55+
t.Fatalf("std flag: expected %v, got %v", expected, stdValues)
56+
}
57+
58+
fset := NewFlagSet("pflag test", ContinueOnError)
59+
pflagValues, err := runCase(fset, flagName, args)
60+
if err != nil {
61+
t.Fatalf("pflag: expected no error, got %v", err)
62+
}
63+
if !cmpLists(stdValues, pflagValues) {
64+
t.Fatalf("pflag: expected %v, got %v", stdValues, pflagValues)
65+
}
66+
})
67+
68+
t.Run("error triggered by callback", func(t *testing.T) {
69+
flagName := "bflag"
70+
args := []string{"--bflag", "--bflag=err", "--bflag=after"}
71+
72+
// test behavior of standard flag.Fset with an error triggered by the callback:
73+
// (note: as can be seen in 'runCase()', if the callback sees "err" as a value
74+
// for the bool flag, it will return an error)
75+
stdFSet := flag.NewFlagSet("std test", flag.ContinueOnError)
76+
stdFSet.SetOutput(io.Discard) // suppress output
77+
78+
// run test case with standard flag.Fset
79+
stdValues, err := runCase(stdFSet, flagName, args)
80+
81+
// double check the standard behavior:
82+
// - .Parse() should return an error, which contains the error message
83+
if err == nil {
84+
t.Fatalf("std flag: expected an error triggered by callback, got no error instead")
85+
}
86+
if !strings.HasSuffix(err.Error(), unitTestErr.Error()) {
87+
t.Fatalf("std flag: expected unittest error, got unexpected error value: %T %v", err, err)
88+
}
89+
// - the function should have been called twice, with the first two values,
90+
// the final "=after" should not be recorded
91+
expected := []string{"true", "err"}
92+
if !cmpLists(expected, stdValues) {
93+
t.Fatalf("std flag: expected %v, got %v", expected, stdValues)
94+
}
95+
96+
// now run the test case on a pflag FlagSet:
97+
fset := NewFlagSet("pflag test", ContinueOnError)
98+
pflagValues, err := runCase(fset, flagName, args)
99+
100+
// check that there is a similar error (note: pflag will _wrap_ the error, while the stdlib
101+
// currently keeps the original message but creates a flat errors.Error)
102+
if !errors.Is(err, unitTestErr) {
103+
t.Fatalf("pflag: got unexpected error value: %T %v", err, err)
104+
}
105+
// the callback should be called the same number of times, with the same values:
106+
if !cmpLists(stdValues, pflagValues) {
107+
t.Fatalf("pflag: expected %v, got %v", stdValues, pflagValues)
108+
}
109+
})
110+
}

bool_func_test.go

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package pflag
22

33
import (
4-
"errors"
5-
"flag"
6-
"io"
74
"strings"
85
"testing"
96
)
@@ -48,104 +45,6 @@ func TestBoolFuncP(t *testing.T) {
4845
}
4946
}
5047

51-
func TestBoolFuncCompat(t *testing.T) {
52-
// compare behavior with the stdlib 'flag' package
53-
type BoolFuncFlagSet interface {
54-
BoolFunc(name string, usage string, fn func(string) error)
55-
Parse([]string) error
56-
}
57-
58-
unitTestErr := errors.New("unit test error")
59-
runCase := func(f BoolFuncFlagSet, name string, args []string) (values []string, err error) {
60-
fn := func(s string) error {
61-
values = append(values, s)
62-
if s == "err" {
63-
return unitTestErr
64-
}
65-
return nil
66-
}
67-
f.BoolFunc(name, "Callback function", fn)
68-
69-
err = f.Parse(args)
70-
return values, err
71-
}
72-
73-
t.Run("regular parsing", func(t *testing.T) {
74-
flagName := "bflag"
75-
args := []string{"--bflag", "--bflag=false", "--bflag=1", "--bflag=bar", "--bflag="}
76-
77-
// It turns out that, even though the function is called "BoolFunc",
78-
// the standard flag package does not try to parse the value assigned to
79-
// that cli flag as a boolean. The string provided on the command line is
80-
// passed as is to the callback.
81-
// e.g: with "--bflag=not_a_bool" on the command line, the FlagSet does not
82-
// generate an error stating "invalid boolean value", and `fn` will be called
83-
// with "not_a_bool" as an argument.
84-
85-
stdFSet := flag.NewFlagSet("std test", flag.ContinueOnError)
86-
stdValues, err := runCase(stdFSet, flagName, args)
87-
if err != nil {
88-
t.Fatalf("std flag: expected no error, got %v", err)
89-
}
90-
expected := []string{"true", "false", "1", "bar", ""}
91-
if !cmpLists(expected, stdValues) {
92-
t.Fatalf("std flag: expected %v, got %v", expected, stdValues)
93-
}
94-
95-
fset := NewFlagSet("pflag test", ContinueOnError)
96-
pflagValues, err := runCase(fset, flagName, args)
97-
if err != nil {
98-
t.Fatalf("pflag: expected no error, got %v", err)
99-
}
100-
if !cmpLists(stdValues, pflagValues) {
101-
t.Fatalf("pflag: expected %v, got %v", stdValues, pflagValues)
102-
}
103-
})
104-
105-
t.Run("error triggered by callback", func(t *testing.T) {
106-
flagName := "bflag"
107-
args := []string{"--bflag", "--bflag=err", "--bflag=after"}
108-
109-
// test behavior of standard flag.Fset with an error triggered by the callback:
110-
// (note: as can be seen in 'runCase()', if the callback sees "err" as a value
111-
// for the bool flag, it will return an error)
112-
stdFSet := flag.NewFlagSet("std test", flag.ContinueOnError)
113-
stdFSet.SetOutput(io.Discard) // suppress output
114-
115-
// run test case with standard flag.Fset
116-
stdValues, err := runCase(stdFSet, flagName, args)
117-
118-
// double check the standard behavior:
119-
// - .Parse() should return an error, which contains the error message
120-
if err == nil {
121-
t.Fatalf("std flag: expected an error triggered by callback, got no error instead")
122-
}
123-
if !strings.HasSuffix(err.Error(), unitTestErr.Error()) {
124-
t.Fatalf("std flag: expected unittest error, got unexpected error value: %T %v", err, err)
125-
}
126-
// - the function should have been called twice, with the first two values,
127-
// the final "=after" should not be recorded
128-
expected := []string{"true", "err"}
129-
if !cmpLists(expected, stdValues) {
130-
t.Fatalf("std flag: expected %v, got %v", expected, stdValues)
131-
}
132-
133-
// now run the test case on a pflag FlagSet:
134-
fset := NewFlagSet("pflag test", ContinueOnError)
135-
pflagValues, err := runCase(fset, flagName, args)
136-
137-
// check that there is a similar error (note: pflag will _wrap_ the error, while the stdlib
138-
// currently keeps the original message but creates a flat errors.Error)
139-
if !errors.Is(err, unitTestErr) {
140-
t.Fatalf("pflag: got unexpected error value: %T %v", err, err)
141-
}
142-
// the callback should be called the same number of times, with the same values:
143-
if !cmpLists(stdValues, pflagValues) {
144-
t.Fatalf("pflag: expected %v, got %v", stdValues, pflagValues)
145-
}
146-
})
147-
}
148-
14948
func TestBoolFuncUsage(t *testing.T) {
15049
t.Run("regular func flag", func(t *testing.T) {
15150
// regular boolfunc flag:

func_go1.21_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//go:build go1.21
2+
// +build go1.21
3+
4+
package pflag
5+
6+
import (
7+
"errors"
8+
"flag"
9+
"io"
10+
"strings"
11+
"testing"
12+
)
13+
14+
func TestFuncCompat(t *testing.T) {
15+
// compare behavior with the stdlib 'flag' package
16+
type FuncFlagSet interface {
17+
Func(name string, usage string, fn func(string) error)
18+
Parse([]string) error
19+
}
20+
21+
unitTestErr := errors.New("unit test error")
22+
runCase := func(f FuncFlagSet, name string, args []string) (values []string, err error) {
23+
fn := func(s string) error {
24+
values = append(values, s)
25+
if s == "err" {
26+
return unitTestErr
27+
}
28+
return nil
29+
}
30+
f.Func(name, "Callback function", fn)
31+
32+
err = f.Parse(args)
33+
return values, err
34+
}
35+
36+
t.Run("regular parsing", func(t *testing.T) {
37+
flagName := "fnflag"
38+
args := []string{"--fnflag=xx", "--fnflag", "yy", "--fnflag=zz"}
39+
40+
stdFSet := flag.NewFlagSet("std test", flag.ContinueOnError)
41+
stdValues, err := runCase(stdFSet, flagName, args)
42+
if err != nil {
43+
t.Fatalf("std flag: expected no error, got %v", err)
44+
}
45+
expected := []string{"xx", "yy", "zz"}
46+
if !cmpLists(expected, stdValues) {
47+
t.Fatalf("std flag: expected %v, got %v", expected, stdValues)
48+
}
49+
50+
fset := NewFlagSet("pflag test", ContinueOnError)
51+
pflagValues, err := runCase(fset, flagName, args)
52+
if err != nil {
53+
t.Fatalf("pflag: expected no error, got %v", err)
54+
}
55+
if !cmpLists(stdValues, pflagValues) {
56+
t.Fatalf("pflag: expected %v, got %v", stdValues, pflagValues)
57+
}
58+
})
59+
60+
t.Run("error triggered by callback", func(t *testing.T) {
61+
flagName := "fnflag"
62+
args := []string{"--fnflag", "before", "--fnflag", "err", "--fnflag", "after"}
63+
64+
// test behavior of standard flag.Fset with an error triggered by the callback:
65+
// (note: as can be seen in 'runCase()', if the callback sees "err" as a value
66+
// for the flag, it will return an error)
67+
stdFSet := flag.NewFlagSet("std test", flag.ContinueOnError)
68+
stdFSet.SetOutput(io.Discard) // suppress output
69+
70+
// run test case with standard flag.Fset
71+
stdValues, err := runCase(stdFSet, flagName, args)
72+
73+
// double check the standard behavior:
74+
// - .Parse() should return an error, which contains the error message
75+
if err == nil {
76+
t.Fatalf("std flag: expected an error triggered by callback, got no error instead")
77+
}
78+
if !strings.HasSuffix(err.Error(), unitTestErr.Error()) {
79+
t.Fatalf("std flag: expected unittest error, got unexpected error value: %T %v", err, err)
80+
}
81+
// - the function should have been called twice, with the first two values,
82+
// the final "=after" should not be recorded
83+
expected := []string{"before", "err"}
84+
if !cmpLists(expected, stdValues) {
85+
t.Fatalf("std flag: expected %v, got %v", expected, stdValues)
86+
}
87+
88+
// now run the test case on a pflag FlagSet:
89+
fset := NewFlagSet("pflag test", ContinueOnError)
90+
pflagValues, err := runCase(fset, flagName, args)
91+
92+
// check that there is a similar error (note: pflag will _wrap_ the error, while the stdlib
93+
// currently keeps the original message but creates a flat errors.Error)
94+
if !errors.Is(err, unitTestErr) {
95+
t.Fatalf("pflag: got unexpected error value: %T %v", err, err)
96+
}
97+
// the callback should be called the same number of times, with the same values:
98+
if !cmpLists(stdValues, pflagValues) {
99+
t.Fatalf("pflag: expected %v, got %v", stdValues, pflagValues)
100+
}
101+
})
102+
}

0 commit comments

Comments
 (0)