sigs.k8s.io/controller-runtime@v0.18.2/pkg/internal/testing/process/arguments_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // not quite, but close enough 342 return []string{strings.Join(defs, ",") + "," + strings.Join(a, ",")} 343 } 344 func (a commaArg) Append(vals ...string) Arg { 345 return commaArg(append(a, vals...)) //nolint:unconvert 346 }