...

Source file src/sigs.k8s.io/controller-runtime/pkg/internal/testing/process/arguments_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/internal/testing/process

     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  }
   347  

View as plain text