github.com/waynz0r/controller-tools@v0.4.1-0.20200916220028-16254aeef2d7/pkg/markers/parse_test.go (about)

     1  /*
     2  Copyright 2019 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 markers_test
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"reflect"
    23  	sc "text/scanner"
    24  
    25  	. "github.com/onsi/ginkgo"
    26  	. "github.com/onsi/gomega"
    27  
    28  	. "sigs.k8s.io/controller-tools/pkg/markers"
    29  )
    30  
    31  func mustDefine(reg *Registry, name string, target TargetType, obj interface{}) {
    32  	Expect(reg.Define(name, target, obj)).To(Succeed())
    33  }
    34  
    35  type wrappedMarkerVal string
    36  type multiFieldStruct struct {
    37  	Str          string
    38  	Int          int
    39  	Bool         bool
    40  	Any          interface{}
    41  	PtrOpt       *string
    42  	NormalOpt    string `marker:",optional"`
    43  	DiffNamed    string `marker:"other"`
    44  	BothTags     string `marker:"both,optional"`
    45  	Slice        []int
    46  	SliceOfSlice [][]int
    47  }
    48  
    49  type allOptionalStruct struct {
    50  	OptStr string `marker:",optional"`
    51  	OptInt *int
    52  }
    53  
    54  type CustomType struct {
    55  	Value interface{}
    56  }
    57  
    58  var _ = Describe("Parsing", func() {
    59  	var reg *Registry
    60  
    61  	Context("of full markers", func() {
    62  		BeforeEach(func() {
    63  			reg = &Registry{}
    64  
    65  			mustDefine(reg, "testing:empty", DescribesPackage, struct{}{})
    66  			mustDefine(reg, "testing:anonymous:literal", DescribesPackage, "")
    67  			mustDefine(reg, "testing:anonymous:named", DescribesPackage, wrappedMarkerVal(""))
    68  			mustDefine(reg, "testing:raw", DescribesPackage, RawArguments(""))
    69  			mustDefine(reg, "testing:multiField", DescribesPackage, multiFieldStruct{})
    70  			mustDefine(reg, "testing:allOptional", DescribesPackage, allOptionalStruct{})
    71  			mustDefine(reg, "testing:anonymousOptional", DescribesPackage, (*int)(nil))
    72  			mustDefine(reg, "testing:multi:segment", DescribesPackage, 0)
    73  			mustDefine(reg, "testing:parent", DescribesPackage, allOptionalStruct{})
    74  			mustDefine(reg, "testing:parent:nested", DescribesPackage, "")
    75  			mustDefine(reg, "testing:tripleDefined", DescribesPackage, 0)
    76  			mustDefine(reg, "testing:tripleDefined", DescribesField, "")
    77  			mustDefine(reg, "testing:tripleDefined", DescribesType, false)
    78  
    79  			defn, err := MakeAnyTypeDefinition("testing:custom", DescribesPackage, CustomType{})
    80  			Expect(err).NotTo(HaveOccurred())
    81  
    82  			Expect(reg.Register(defn)).To(Succeed())
    83  		})
    84  
    85  		It("should work with fiddled field names", parseTestCase{reg: &reg, raw: "+testing:custom={hi}", output: CustomType{Value: []string{"hi"}}}.Run)
    86  
    87  		It("should parse name-only markers", parseTestCase{reg: &reg, raw: "+testing:empty", output: struct{}{}}.Run)
    88  
    89  		Context("when parsing anonymous markers", func() {
    90  			It("should parse into literal-typed values", parseTestCase{reg: &reg, raw: "+testing:anonymous:literal=foo", output: "foo"}.Run)
    91  			It("should parse into named-typed values", parseTestCase{reg: &reg, raw: "+testing:anonymous:named=foo", output: wrappedMarkerVal("foo")}.Run)
    92  			It("shouldn't require any argument to an optional-valued marker", parseTestCase{reg: &reg, raw: "+testing:anonymousOptional", output: (*int)(nil)}.Run)
    93  		})
    94  
    95  		It("should parse raw-argument markers", parseTestCase{reg: &reg, raw: "+testing:raw=this;totally,doesn't;get,parsed", output: RawArguments("this;totally,doesn't;get,parsed")}.Run)
    96  
    97  		Context("when parsing multi-field markers", func() {
    98  			definitelyAString := "optional string"
    99  			It("should support parsing multiple fields with the appropriate names", parseTestCase{
   100  				reg: &reg,
   101  				raw: `+testing:multiField:str=some str,int=42,bool=true,any=21,ptrOpt="optional string",normalOpt="other string",other="yet another",both="and one more",slice=99;104;101;101;115;101,sliceOfSlice={{1,1},{2,3},{5,8}}`,
   102  				output: multiFieldStruct{
   103  					Str:          "some str",
   104  					Bool:         true,
   105  					Any:          21,
   106  					PtrOpt:       &definitelyAString,
   107  					NormalOpt:    "other string",
   108  					DiffNamed:    "yet another",
   109  					BothTags:     "and one more",
   110  					Slice:        []int{99, 104, 101, 101, 115, 101},
   111  					SliceOfSlice: [][]int{{1, 1}, {2, 3}, {5, 8}},
   112  					Int:          42,
   113  				},
   114  			}.Run)
   115  
   116  			It("shouldn't matter what order the fields are specified in", parseTestCase{
   117  				reg: &reg,
   118  				// just some random order with everything out of order
   119  				raw: `+testing:multiField:int=42,str=some str,any=21,bool=true,any=21,sliceOfSlice={{1,1},{2,3},{5,8}},slice=99;104;101;101;115;101,other="yet another"`,
   120  				output: multiFieldStruct{
   121  					Str:          "some str",
   122  					Bool:         true,
   123  					Any:          21,
   124  					DiffNamed:    "yet another",
   125  					Slice:        []int{99, 104, 101, 101, 115, 101},
   126  					SliceOfSlice: [][]int{{1, 1}, {2, 3}, {5, 8}},
   127  					Int:          42,
   128  				},
   129  			}.Run)
   130  
   131  			It("should support leaving out optional fields", parseTestCase{
   132  				reg: &reg,
   133  				raw: `+testing:multiField:str=some str,bool=true,any=21,other="yet another",slice=99;104;101;101;115;101,sliceOfSlice={{1,1},{2,3},{5,8}},int=42`,
   134  				output: multiFieldStruct{
   135  					Str:          "some str",
   136  					Bool:         true,
   137  					Any:          21,
   138  					DiffNamed:    "yet another",
   139  					Slice:        []int{99, 104, 101, 101, 115, 101},
   140  					SliceOfSlice: [][]int{{1, 1}, {2, 3}, {5, 8}},
   141  					Int:          42,
   142  				},
   143  			}.Run)
   144  			It("should error out for missing values", func() {
   145  				By("looking up the marker definition")
   146  				raw := "+testing:multiField:str=`hi`"
   147  				defn := reg.Lookup(raw, DescribesPackage)
   148  				Expect(defn).NotTo(BeNil())
   149  
   150  				By("trying to parse the marker")
   151  				_, err := defn.Parse(raw)
   152  				Expect(err).To(HaveOccurred())
   153  			})
   154  			It("shouldn't require any arguments to an optional-valued marker", parseTestCase{reg: &reg, raw: "+testing:allOptional", output: allOptionalStruct{}}.Run)
   155  		})
   156  
   157  		It("should support markers with multiple segments in the name", parseTestCase{reg: &reg, raw: "+testing:multi:segment=42", output: 42}.Run)
   158  
   159  		Context("when dealing with disambiguating anonymous markers", func() {
   160  			It("should favor the shorter-named one", parseTestCase{reg: &reg, raw: "+testing:parent", output: allOptionalStruct{}}.Run)
   161  			It("should still allow fetching the longer-named one", parseTestCase{reg: &reg, raw: "+testing:parent:nested=some string", output: "some string"}.Run)
   162  			It("should consider anonymously-named ones before considering fields", parseTestCase{reg: &reg, raw: "+testing:parent:optStr=other string", output: allOptionalStruct{OptStr: "other string"}}.Run)
   163  		})
   164  
   165  		Context("when dealing with markers describing multiple things", func() {
   166  			It("should properly parse the package-level one", parseTestCase{reg: &reg, raw: "+testing:tripleDefined=42", output: 42, target: DescribesPackage}.Run)
   167  			It("should properly parse the field-level one", parseTestCase{reg: &reg, raw: "+testing:tripleDefined=foo", output: "foo", target: DescribesField}.Run)
   168  			It("should properly parse the type-level one", parseTestCase{reg: &reg, raw: "+testing:tripleDefined=true", output: true, target: DescribesType}.Run)
   169  		})
   170  	})
   171  
   172  	Context("of individual arguments", func() {
   173  		It("should support bare strings", argParseTestCase{arg: Argument{Type: StringType}, raw: `some string here!`, output: "some string here!"}.Run)
   174  		It("should support double-quoted strings", argParseTestCase{arg: Argument{Type: StringType}, raw: `"some; string, \nhere"`, output: "some; string, \nhere"}.Run)
   175  		It("should support raw strings", argParseTestCase{arg: Argument{Type: StringType}, raw: "`some; string, \\nhere`", output: `some; string, \nhere`}.Run)
   176  		It("should support integers", argParseTestCase{arg: Argument{Type: IntType}, raw: "42", output: 42}.Run)
   177  		It("should support negative integers", argParseTestCase{arg: Argument{Type: IntType}, raw: "-42", output: -42}.Run)
   178  		It("should support false booleans", argParseTestCase{arg: Argument{Type: BoolType}, raw: "false", output: false}.Run)
   179  		It("should support true booleans", argParseTestCase{arg: Argument{Type: BoolType}, raw: "true", output: true}.Run)
   180  
   181  		sliceOSlice := Argument{Type: SliceType, ItemType: &Argument{Type: SliceType, ItemType: &Argument{Type: IntType}}}
   182  		sliceOSliceOut := [][]int{{1, 1}, {2, 3}, {5, 8}}
   183  
   184  		It("should support bare slices", argParseTestCase{arg: Argument{Type: SliceType, ItemType: &Argument{Type: StringType}}, raw: "hi;hello;hey y'all", output: []string{"hi", "hello", "hey y'all"}}.Run)
   185  		It("should support delimitted slices", argParseTestCase{arg: Argument{Type: SliceType, ItemType: &Argument{Type: StringType}}, raw: "{hi,hello,hey y'all}", output: []string{"hi", "hello", "hey y'all"}}.Run)
   186  		It("should support delimitted slices of bare slices", argParseTestCase{arg: sliceOSlice, raw: "{1;1,2;3,5;8}", output: sliceOSliceOut}.Run)
   187  		It("should support delimitted slices of delimitted slices", argParseTestCase{arg: sliceOSlice, raw: "{{1,1},{2,3},{5,8}}", output: sliceOSliceOut}.Run)
   188  
   189  		It("should support maps", argParseTestCase{arg: Argument{Type: MapType, ItemType: &Argument{Type: StringType}}, raw: "{formal: hello, `informal`: `hi!`}", output: map[string]string{"formal": "hello", "informal": "hi!"}}.Run)
   190  
   191  		Context("with any value", func() {
   192  			anyArg := Argument{Type: AnyType}
   193  			It("should support bare strings", argParseTestCase{arg: anyArg, raw: `some string here!`, output: "some string here!"}.Run)
   194  			It("should support double-quoted strings", argParseTestCase{arg: anyArg, raw: `"some; string, \nhere"`, output: "some; string, \nhere"}.Run)
   195  			It("should support raw strings", argParseTestCase{arg: anyArg, raw: "`some; string, \\nhere`", output: `some; string, \nhere`}.Run)
   196  			It("should support integers", argParseTestCase{arg: anyArg, raw: "42", output: 42}.Run)
   197  			It("should support negative integers", argParseTestCase{arg: anyArg, raw: "-42", output: -42}.Run)
   198  			It("should support false booleans", argParseTestCase{arg: anyArg, raw: "false", output: false}.Run)
   199  			It("should support true booleans", argParseTestCase{arg: anyArg, raw: "true", output: true}.Run)
   200  
   201  			sliceOSliceOut := [][]int{{1, 1}, {2, 3}, {5, 8}}
   202  
   203  			It("should support bare slices", argParseTestCase{arg: anyArg, raw: "hi;hello;hey y'all", output: []string{"hi", "hello", "hey y'all"}}.Run)
   204  			It("should support delimitted slices", argParseTestCase{arg: anyArg, raw: "{hi,hello,hey y'all}", output: []string{"hi", "hello", "hey y'all"}}.Run)
   205  			It("should support delimitted slices of bare slices", argParseTestCase{arg: anyArg, raw: "{1;1,2;3,5;8}", output: sliceOSliceOut}.Run)
   206  			It("should support delimitted slices of delimitted slices", argParseTestCase{arg: anyArg, raw: "{{1,1},{2,3},{5,8}}", output: sliceOSliceOut}.Run)
   207  
   208  			complexMap := map[string]interface{}{
   209  				"text":     "abc",
   210  				"len":      3,
   211  				"as bytes": []int{97, 98, 99},
   212  				"props": map[string]interface{}{
   213  					"encoding": "ascii",
   214  					"nullsafe": true,
   215  					"tags":     []string{"triple", "in a row"},
   216  				},
   217  			}
   218  			It("should support non-uniform, nested maps (and always guess as such)", argParseTestCase{arg: anyArg, raw: `{text: "abc", len: 3, "as bytes": 97;98;99, props: {encoding: ascii, nullsafe: true, tags: {"triple", "in a row"}}}`, output: complexMap}.Run)
   219  		})
   220  	})
   221  })
   222  
   223  type parseTestCase struct {
   224  	reg    **Registry
   225  	raw    string
   226  	output interface{}
   227  	target TargetType // NB(directxman12): iota is DescribesPackage
   228  }
   229  
   230  func (tc parseTestCase) Run() {
   231  	reg := *tc.reg
   232  	By("looking up the marker definition")
   233  	defn := reg.Lookup(tc.raw, tc.target)
   234  	Expect(defn).NotTo(BeNil())
   235  
   236  	By("parsing the marker")
   237  	outVal, err := defn.Parse(tc.raw)
   238  	Expect(err).NotTo(HaveOccurred())
   239  
   240  	By("checking for the expected output")
   241  	Expect(outVal).To(Equal(tc.output))
   242  }
   243  
   244  type argParseTestCase struct {
   245  	arg    Argument
   246  	raw    string
   247  	output interface{}
   248  }
   249  
   250  func (tc argParseTestCase) Run() {
   251  	scanner := sc.Scanner{}
   252  	scanner.Init(bytes.NewBufferString(tc.raw))
   253  	scanner.Mode = sc.ScanIdents | sc.ScanInts | sc.ScanStrings | sc.ScanRawStrings | sc.SkipComments
   254  	scanner.Error = func(scanner *sc.Scanner, msg string) {
   255  		Fail(fmt.Sprintf("%s (at %s)", msg, scanner.Position))
   256  	}
   257  
   258  	var actualOut reflect.Value
   259  	if tc.arg.Type == AnyType {
   260  		actualOut = reflect.Indirect(reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()))
   261  	} else {
   262  		actualOut = reflect.Indirect(reflect.New(reflect.TypeOf(tc.output)))
   263  	}
   264  
   265  	By("parsing the raw argument")
   266  	tc.arg.Parse(&scanner, tc.raw, actualOut)
   267  
   268  	By("checking that it equals the expected output")
   269  	Expect(actualOut.Interface()).To(Equal(tc.output))
   270  }