1 package config
2
3 import (
4 "bytes"
5 "flag"
6 "testing"
7 "time"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11 )
12
13 func TestInt(t *testing.T) {
14 flags := flag.NewFlagSet("test", 0)
15 var context struct {
16 Number int `default:"5" usage:"some number"`
17 }
18 require.NotPanics(t, func() {
19 AddOptionsToSet(flags, &context, "")
20 })
21 require.Equal(t, []simpleFlag{
22 {
23 name: "number",
24 usage: "some number",
25 defValue: "5",
26 }},
27 allFlags(flags))
28 assert.Equal(t, 5, context.Number)
29 }
30
31 func TestLower(t *testing.T) {
32 flags := flag.NewFlagSet("test", 0)
33 var context struct {
34 Ahem string
35 MixedCase string
36 }
37 require.NotPanics(t, func() {
38 AddOptionsToSet(flags, &context, "")
39 })
40 require.Equal(t, []simpleFlag{
41 {
42 name: "ahem",
43 },
44 {
45 name: "mixedcase",
46 },
47 },
48 allFlags(flags))
49 }
50
51 func TestPrefix(t *testing.T) {
52 flags := flag.NewFlagSet("test", 0)
53 var context struct {
54 Number int `usage:"some number"`
55 }
56 require.NotPanics(t, func() {
57 AddOptionsToSet(flags, &context, "some.prefix")
58 })
59 require.Equal(t, []simpleFlag{
60 {
61 name: "some.prefix.number",
62 usage: "some number",
63 defValue: "0",
64 }},
65 allFlags(flags))
66 }
67
68 func TestRecursion(t *testing.T) {
69 flags := flag.NewFlagSet("test", 0)
70 type Nested struct {
71 Number1 int `usage:"embedded number"`
72 }
73 var context struct {
74 Nested
75 A struct {
76 B struct {
77 C struct {
78 Number2 int `usage:"some number"`
79 }
80 }
81 }
82 }
83 require.NotPanics(t, func() {
84 AddOptionsToSet(flags, &context, "")
85 })
86 require.Equal(t, []simpleFlag{
87 {
88 name: "a.b.c.number2",
89 usage: "some number",
90 defValue: "0",
91 },
92 {
93 name: "number1",
94 usage: "embedded number",
95 defValue: "0",
96 },
97 },
98 allFlags(flags))
99 }
100
101 func TestPanics(t *testing.T) {
102 flags := flag.NewFlagSet("test", 0)
103 assert.PanicsWithValue(t, `invalid default "a" for int entry prefix.number: strconv.Atoi: parsing "a": invalid syntax`, func() {
104 var context struct {
105 Number int `default:"a"`
106 }
107 AddOptionsToSet(flags, &context, "prefix")
108 })
109
110 assert.PanicsWithValue(t, `invalid default "10000000000000000000" for int entry prefix.number: strconv.Atoi: parsing "10000000000000000000": value out of range`, func() {
111 var context struct {
112 Number int `default:"10000000000000000000"`
113 }
114 AddOptionsToSet(flags, &context, "prefix")
115 })
116
117 assert.PanicsWithValue(t, `options parameter without a type - nil?!`, func() {
118 AddOptionsToSet(flags, nil, "")
119 })
120
121 assert.PanicsWithValue(t, `need a pointer to a struct, got instead: *int`, func() {
122 number := 0
123 AddOptionsToSet(flags, &number, "")
124 })
125
126 assert.PanicsWithValue(t, `struct entry "prefix.number" not exported`, func() {
127 var context struct {
128 number int
129 }
130 AddOptionsToSet(flags, &context, "prefix")
131 })
132
133 assert.PanicsWithValue(t, `unsupported struct entry type "prefix.somenumber": config.MyInt`, func() {
134 type MyInt int
135 var context struct {
136 SomeNumber MyInt
137 }
138 AddOptionsToSet(flags, &context, "prefix")
139 })
140 }
141
142 type TypesTestCase struct {
143 name string
144 copyFlags bool
145 }
146
147 func TestTypes(t *testing.T) {
148 testcases := []TypesTestCase{
149 {name: "directly"},
150 {name: "CopyFlags", copyFlags: true},
151 }
152
153 for _, testcase := range testcases {
154 testTypes(t, testcase)
155 }
156 }
157
158 func testTypes(t *testing.T, testcase TypesTestCase) {
159 flags := flag.NewFlagSet("test", 0)
160 type Context struct {
161 Bool bool `default:"true"`
162 Duration time.Duration `default:"1ms"`
163 Float64 float64 `default:"1.23456789"`
164 String string `default:"hello world"`
165 Int int `default:"-1" usage:"some number"`
166 Int64 int64 `default:"-1234567890123456789"`
167 Uint uint `default:"1"`
168 Uint64 uint64 `default:"1234567890123456789"`
169 }
170 var context Context
171 require.NotPanics(t, func() {
172 AddOptionsToSet(flags, &context, "")
173 })
174
175 if testcase.copyFlags {
176 original := bytes.Buffer{}
177 flags.SetOutput(&original)
178 flags.PrintDefaults()
179
180 flags2 := flag.NewFlagSet("test", 0)
181 CopyFlags(flags, flags2)
182 flags = flags2
183
184 copier := bytes.Buffer{}
185 flags.SetOutput(&copier)
186 flags.PrintDefaults()
187 assert.Equal(t, original.String(), copier.String(), testcase.name+": help messages equal")
188 assert.Contains(t, copier.String(), "some number", testcase.name+": copied help message contains defaults")
189 }
190
191 require.Equal(t, []simpleFlag{
192 {
193 name: "bool",
194 defValue: "true",
195 isBool: true,
196 },
197 {
198 name: "duration",
199 defValue: "1ms",
200 },
201 {
202 name: "float64",
203 defValue: "1.23456789",
204 },
205 {
206 name: "int",
207 usage: "some number",
208 defValue: "-1",
209 },
210 {
211 name: "int64",
212 defValue: "-1234567890123456789",
213 },
214 {
215 name: "string",
216 defValue: "hello world",
217 },
218 {
219 name: "uint",
220 defValue: "1",
221 },
222 {
223 name: "uint64",
224 defValue: "1234567890123456789",
225 },
226 },
227 allFlags(flags), testcase.name)
228 assert.Equal(t,
229 Context{true, time.Millisecond, 1.23456789, "hello world",
230 -1, -1234567890123456789, 1, 1234567890123456789,
231 },
232 context,
233 "default values must match")
234 require.NoError(t, flags.Parse([]string{
235 "-int", "-2",
236 "-int64", "-9123456789012345678",
237 "-uint", "2",
238 "-uint64", "9123456789012345678",
239 "-string", "pong",
240 "-float64", "-1.23456789",
241 "-bool=false",
242 "-duration=1s",
243 }), testcase.name)
244 assert.Equal(t,
245 Context{false, time.Second, -1.23456789, "pong",
246 -2, -9123456789012345678, 2, 9123456789012345678,
247 },
248 context,
249 testcase.name+": parsed values must match")
250 }
251
252 func allFlags(fs *flag.FlagSet) []simpleFlag {
253 var flags []simpleFlag
254 fs.VisitAll(func(f *flag.Flag) {
255 s := simpleFlag{
256 name: f.Name,
257 usage: f.Usage,
258 defValue: f.DefValue,
259 }
260 type boolFlag interface {
261 flag.Value
262 IsBoolFlag() bool
263 }
264 if fv, ok := f.Value.(boolFlag); ok && fv.IsBoolFlag() {
265 s.isBool = true
266 }
267 flags = append(flags, s)
268 })
269 return flags
270 }
271
272 type simpleFlag struct {
273 name string
274 usage string
275 defValue string
276 isBool bool
277 }
278
View as plain text