1
16
17 package process_test
18
19 import (
20 "net/url"
21 "strings"
22
23 . "github.com/onsi/ginkgo/v2"
24 . "github.com/onsi/gomega"
25
26 . "sigs.k8s.io/controller-runtime/pkg/internal/testing/process"
27 )
28
29 var _ = Describe("Arguments Templates", func() {
30 It("templates URLs", func() {
31 templates := []string{
32 "plain URL: {{ .SomeURL }}",
33 "method on URL: {{ .SomeURL.Hostname }}",
34 "empty URL: {{ .EmptyURL }}",
35 "handled empty URL: {{- if .EmptyURL }}{{ .EmptyURL }}{{ end }}",
36 }
37 data := struct {
38 SomeURL *url.URL
39 EmptyURL *url.URL
40 }{
41 &url.URL{Scheme: "https", Host: "the.host.name:3456"},
42 nil,
43 }
44
45 out, err := RenderTemplates(templates, data)
46 Expect(err).NotTo(HaveOccurred())
47 Expect(out).To(BeEquivalentTo([]string{
48 "plain URL: https://the.host.name:3456",
49 "method on URL: the.host.name",
50 "empty URL: <nil>",
51 "handled empty URL:",
52 }))
53 })
54
55 It("templates strings", func() {
56 templates := []string{
57 "a string: {{ .SomeString }}",
58 "empty string: {{- .EmptyString }}",
59 }
60 data := struct {
61 SomeString string
62 EmptyString string
63 }{
64 "this is some random string",
65 "",
66 }
67
68 out, err := RenderTemplates(templates, data)
69 Expect(err).NotTo(HaveOccurred())
70 Expect(out).To(BeEquivalentTo([]string{
71 "a string: this is some random string",
72 "empty string:",
73 }))
74 })
75
76 It("has no access to unexported fields", func() {
77 templates := []string{
78 "this is just a string",
79 "this blows up {{ .test }}",
80 }
81 data := struct{ test string }{"ooops private"}
82
83 out, err := RenderTemplates(templates, data)
84 Expect(out).To(BeEmpty())
85 Expect(err).To(MatchError(
86 ContainSubstring("is an unexported field of struct"),
87 ))
88 })
89
90 It("errors when field cannot be found", func() {
91 templates := []string{"this does {{ .NotExist }}"}
92 data := struct{ Unused string }{"unused"}
93
94 out, err := RenderTemplates(templates, data)
95 Expect(out).To(BeEmpty())
96 Expect(err).To(MatchError(
97 ContainSubstring("can't evaluate field"),
98 ))
99 })
100
101 Context("when joining with structured Arguments", func() {
102 var (
103 args *Arguments
104 templ = []string{
105 "--cheese=parmesean",
106 "-om",
107 "nom nom nom",
108 "--sharpness={{ .sharpness }}",
109 }
110 data = TemplateDefaults{
111 Data: map[string]string{"sharpness": "extra"},
112 Defaults: map[string][]string{
113 "cracker": {"ritz"},
114 "pickle": {"kosher-dill"},
115 },
116 MinimalDefaults: map[string][]string{
117 "pickle": {"kosher-dill"},
118 },
119 }
120 )
121 BeforeEach(func() {
122 args = EmptyArguments()
123 })
124
125 Context("when a template is given", func() {
126 It("should use minimal defaults", func() {
127 all, _, err := TemplateAndArguments(templ, args, data)
128 Expect(err).NotTo(HaveOccurred())
129 Expect(all).To(SatisfyAll(
130 Not(ContainElement("--cracker=ritz")),
131 ContainElement("--pickle=kosher-dill"),
132 ))
133 })
134
135 It("should render the template against the data", func() {
136 all, _, err := TemplateAndArguments(templ, args, data)
137 Expect(err).NotTo(HaveOccurred())
138 Expect(all).To(ContainElements(
139 "--sharpness=extra",
140 ))
141 })
142
143 It("should append the rendered template to structured arguments", func() {
144 args.Append("cheese", "cheddar")
145
146 all, _, err := TemplateAndArguments(templ, args, data)
147 Expect(err).NotTo(HaveOccurred())
148 Expect(all).To(Equal([]string{
149 "--cheese=cheddar",
150 "--cheese=parmesean",
151 "--pickle=kosher-dill",
152 "--sharpness=extra",
153 "-om",
154 "nom nom nom",
155 }))
156 })
157
158 It("should indicate which arguments were not able to be converted to structured flags", func() {
159 _, rest, err := TemplateAndArguments(templ, args, data)
160 Expect(err).NotTo(HaveOccurred())
161 Expect(rest).To(Equal([]string{"-om", "nom nom nom"}))
162
163 })
164 })
165
166 Context("when no template is given", func() {
167 It("should render the structured arguments with the given defaults", func() {
168 args.
169 Append("cheese", "cheddar", "parmesean").
170 Append("cracker", "triscuit")
171
172 Expect(TemplateAndArguments(nil, args, data)).To(Equal([]string{
173 "--cheese=cheddar",
174 "--cheese=parmesean",
175 "--cracker=ritz",
176 "--cracker=triscuit",
177 "--pickle=kosher-dill",
178 }))
179 })
180 })
181 })
182
183 Context("when converting to structured Arguments", func() {
184 var args *Arguments
185 BeforeEach(func() {
186 args = EmptyArguments()
187 })
188
189 It("should skip arguments that don't start with `--`", func() {
190 rest := SliceToArguments([]string{"-first", "second", "--foo=bar"}, args)
191 Expect(rest).To(Equal([]string{"-first", "second"}))
192 Expect(args.AsStrings(nil)).To(Equal([]string{"--foo=bar"}))
193 })
194
195 It("should skip arguments that don't contain an `=` because they're ambiguous", func() {
196 rest := SliceToArguments([]string{"--first", "--second", "--foo=bar"}, args)
197 Expect(rest).To(Equal([]string{"--first", "--second"}))
198 Expect(args.AsStrings(nil)).To(Equal([]string{"--foo=bar"}))
199 })
200
201 It("should stop at the flag terminator (`--`)", func() {
202 rest := SliceToArguments([]string{"--first", "--second", "--", "--foo=bar"}, args)
203 Expect(rest).To(Equal([]string{"--first", "--second", "--", "--foo=bar"}))
204 Expect(args.AsStrings(nil)).To(BeEmpty())
205 })
206
207 It("should split --foo=bar into Append(foo, bar)", func() {
208 rest := SliceToArguments([]string{"--foo=bar1", "--foo=bar2"}, args)
209 Expect(rest).To(BeEmpty())
210 Expect(args.Get("foo").Get(nil)).To(Equal([]string{"bar1", "bar2"}))
211 })
212
213 It("should split --foo=bar=baz into Append(foo, bar=baz)", func() {
214 rest := SliceToArguments([]string{"--vmodule=file.go=3", "--vmodule=other.go=4"}, args)
215 Expect(rest).To(BeEmpty())
216 Expect(args.Get("vmodule").Get(nil)).To(Equal([]string{"file.go=3", "other.go=4"}))
217 })
218
219 It("should append to existing arguments", func() {
220 args.Append("foo", "barA")
221 rest := SliceToArguments([]string{"--foo=bar1", "--foo=bar2"}, args)
222 Expect(rest).To(BeEmpty())
223 Expect(args.Get("foo").Get([]string{"barI"})).To(Equal([]string{"barI", "barA", "bar1", "bar2"}))
224 })
225 })
226 })
227
228 var _ = Describe("Arguments", func() {
229 Context("when appending", func() {
230 It("should copy from defaults when appending for the first time", func() {
231 args := EmptyArguments().
232 Append("some-key", "val3")
233 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"val1", "val2", "val3"}))
234 })
235
236 It("should not copy from defaults if the flag has been disabled previously", func() {
237 args := EmptyArguments().
238 Disable("some-key").
239 Append("some-key", "val3")
240 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"val3"}))
241 })
242
243 It("should only copy defaults the first time", func() {
244 args := EmptyArguments().
245 Append("some-key", "val3", "val4").
246 Append("some-key", "val5")
247 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"val1", "val2", "val3", "val4", "val5"}))
248 })
249
250 It("should not copy from defaults if the flag has been previously overridden", func() {
251 args := EmptyArguments().
252 Set("some-key", "vala").
253 Append("some-key", "valb", "valc")
254 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"vala", "valb", "valc"}))
255 })
256
257 Context("when explicitly overriding defaults", func() {
258 It("should not copy from defaults, but should append to previous calls", func() {
259 args := EmptyArguments().
260 AppendNoDefaults("some-key", "vala").
261 AppendNoDefaults("some-key", "valb", "valc")
262 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"vala", "valb", "valc"}))
263 })
264
265 It("should not copy from defaults, but should respect previous appends' copies", func() {
266 args := EmptyArguments().
267 Append("some-key", "vala").
268 AppendNoDefaults("some-key", "valb", "valc")
269 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"val1", "val2", "vala", "valb", "valc"}))
270 })
271
272 It("should not copy from defaults if the flag has been previously appended to ignoring defaults", func() {
273 args := EmptyArguments().
274 AppendNoDefaults("some-key", "vala").
275 Append("some-key", "valb", "valc")
276 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"vala", "valb", "valc"}))
277 })
278 })
279 })
280
281 It("should ignore defaults when overriding", func() {
282 args := EmptyArguments().
283 Set("some-key", "vala")
284 Expect(args.Get("some-key").Get([]string{"val1", "val2"})).To(Equal([]string{"vala"}))
285 })
286
287 It("should allow directly setting the argument value for custom argument types", func() {
288 args := EmptyArguments().
289 SetRaw("custom-key", commaArg{"val3"}).
290 Append("custom-key", "val4")
291 Expect(args.Get("custom-key").Get([]string{"val1", "val2"})).To(Equal([]string{"val1,val2,val3,val4"}))
292 })
293
294 Context("when rendering flags", func() {
295 It("should not render defaults for disabled flags", func() {
296 defs := map[string][]string{
297 "some-key": {"val1", "val2"},
298 "other-key": {"val"},
299 }
300 args := EmptyArguments().
301 Disable("some-key")
302 Expect(args.AsStrings(defs)).To(ConsistOf("--other-key=val"))
303 })
304
305 It("should render name-only flags as --key", func() {
306 args := EmptyArguments().
307 Enable("some-key")
308 Expect(args.AsStrings(nil)).To(ConsistOf("--some-key"))
309 })
310
311 It("should render multiple values as --key=val1, --key=val2", func() {
312 args := EmptyArguments().
313 Append("some-key", "val1", "val2").
314 Append("other-key", "vala", "valb")
315 Expect(args.AsStrings(nil)).To(ConsistOf("--other-key=valb", "--other-key=vala", "--some-key=val1", "--some-key=val2"))
316 })
317
318 It("should read from defaults if the user hasn't set a value for a flag", func() {
319 defs := map[string][]string{
320 "some-key": {"val1", "val2"},
321 }
322 args := EmptyArguments().
323 Append("other-key", "vala", "valb")
324 Expect(args.AsStrings(defs)).To(ConsistOf("--other-key=valb", "--other-key=vala", "--some-key=val1", "--some-key=val2"))
325 })
326
327 It("should not render defaults if the user has set a value for a flag", func() {
328 defs := map[string][]string{
329 "some-key": {"val1", "val2"},
330 }
331 args := EmptyArguments().
332 Set("some-key", "vala")
333 Expect(args.AsStrings(defs)).To(ConsistOf("--some-key=vala"))
334 })
335 })
336 })
337
338 type commaArg []string
339
340 func (a commaArg) Get(defs []string) []string {
341
342 return []string{strings.Join(defs, ",") + "," + strings.Join(a, ",")}
343 }
344 func (a commaArg) Append(vals ...string) Arg {
345 return commaArg(append(a, vals...))
346 }
347
View as plain text