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