github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/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 bare strings containing number-ish values 1", argParseTestCase{arg: Argument{Type: StringType}, raw: `aa 0Baaa aaa`, output: "aa 0Baaa aaa"}.Run)
   175  		It("should support bare strings containing number-ish values 2", argParseTestCase{arg: Argument{Type: StringType}, raw: `/tmp/tmp-CHECKCRD-0B7LDeoZta`, output: "/tmp/tmp-CHECKCRD-0B7LDeoZta"}.Run)
   176  		It("should support bare strings containing number-ish values 3", argParseTestCase{arg: Argument{Type: StringType}, raw: `.0B7LDeoZt`, output: ".0B7LDeoZt"}.Run)
   177  		It("should support double-quoted strings", argParseTestCase{arg: Argument{Type: StringType}, raw: `"some; string, \nhere"`, output: "some; string, \nhere"}.Run)
   178  		It("should support raw strings", argParseTestCase{arg: Argument{Type: StringType}, raw: "`some; string, \\nhere`", output: `some; string, \nhere`}.Run)
   179  		It("should support integers", argParseTestCase{arg: Argument{Type: IntType}, raw: "42", output: 42}.Run)
   180  		It("should support negative integers", argParseTestCase{arg: Argument{Type: IntType}, raw: "-42", output: -42}.Run)
   181  		It("should support false booleans", argParseTestCase{arg: Argument{Type: BoolType}, raw: "false", output: false}.Run)
   182  		It("should support true booleans", argParseTestCase{arg: Argument{Type: BoolType}, raw: "true", output: true}.Run)
   183  
   184  		sliceOSlice := Argument{Type: SliceType, ItemType: &Argument{Type: SliceType, ItemType: &Argument{Type: IntType}}}
   185  		sliceOSliceOut := [][]int{{1, 1}, {2, 3}, {5, 8}}
   186  
   187  		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)
   188  		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)
   189  		It("should support delimitted slices of bare slices", argParseTestCase{arg: sliceOSlice, raw: "{1;1,2;3,5;8}", output: sliceOSliceOut}.Run)
   190  		It("should support delimitted slices of delimitted slices", argParseTestCase{arg: sliceOSlice, raw: "{{1,1},{2,3},{5,8}}", output: sliceOSliceOut}.Run)
   191  
   192  		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)
   193  
   194  		Context("with any value", func() {
   195  			anyArg := Argument{Type: AnyType}
   196  			It("should support bare strings", argParseTestCase{arg: anyArg, raw: `some string here!`, output: "some string here!"}.Run)
   197  			It("should support double-quoted strings", argParseTestCase{arg: anyArg, raw: `"some; string, \nhere"`, output: "some; string, \nhere"}.Run)
   198  			It("should support raw strings", argParseTestCase{arg: anyArg, raw: "`some; string, \\nhere`", output: `some; string, \nhere`}.Run)
   199  			It("should support integers", argParseTestCase{arg: anyArg, raw: "42", output: 42}.Run)
   200  			It("should support negative integers", argParseTestCase{arg: anyArg, raw: "-42", output: -42}.Run)
   201  			It("should support false booleans", argParseTestCase{arg: anyArg, raw: "false", output: false}.Run)
   202  			It("should support true booleans", argParseTestCase{arg: anyArg, raw: "true", output: true}.Run)
   203  
   204  			sliceOSliceOut := [][]int{{1, 1}, {2, 3}, {5, 8}}
   205  
   206  			It("should support bare slices", argParseTestCase{arg: anyArg, raw: "hi;hello;hey y'all", output: []string{"hi", "hello", "hey y'all"}}.Run)
   207  			It("should support delimitted slices", argParseTestCase{arg: anyArg, raw: "{hi,hello,hey y'all}", output: []string{"hi", "hello", "hey y'all"}}.Run)
   208  			It("should support delimitted slices of bare slices", argParseTestCase{arg: anyArg, raw: "{1;1,2;3,5;8}", output: sliceOSliceOut}.Run)
   209  			It("should support delimitted slices of delimitted slices", argParseTestCase{arg: anyArg, raw: "{{1,1},{2,3},{5,8}}", output: sliceOSliceOut}.Run)
   210  
   211  			complexMap := map[string]interface{}{
   212  				"text":     "abc",
   213  				"len":      3,
   214  				"as bytes": []int{97, 98, 99},
   215  				"props": map[string]interface{}{
   216  					"encoding": "ascii",
   217  					"nullsafe": true,
   218  					"tags":     []string{"triple", "in a row"},
   219  				},
   220  			}
   221  			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)
   222  		})
   223  	})
   224  })
   225  
   226  type parseTestCase struct {
   227  	reg    **Registry
   228  	raw    string
   229  	output interface{}
   230  	target TargetType // NB(directxman12): iota is DescribesPackage
   231  }
   232  
   233  func (tc parseTestCase) Run() {
   234  	reg := *tc.reg
   235  	By("looking up the marker definition")
   236  	defn := reg.Lookup(tc.raw, tc.target)
   237  	Expect(defn).NotTo(BeNil())
   238  
   239  	By("parsing the marker")
   240  	outVal, err := defn.Parse(tc.raw)
   241  	Expect(err).NotTo(HaveOccurred())
   242  
   243  	By("checking for the expected output")
   244  	Expect(outVal).To(Equal(tc.output))
   245  }
   246  
   247  type argParseTestCase struct {
   248  	arg    Argument
   249  	raw    string
   250  	output interface{}
   251  }
   252  
   253  func (tc argParseTestCase) Run() {
   254  	scanner := sc.Scanner{}
   255  	scanner.Init(bytes.NewBufferString(tc.raw))
   256  	scanner.Mode = sc.ScanIdents | sc.ScanInts | sc.ScanStrings | sc.ScanRawStrings | sc.SkipComments
   257  	scanner.Error = func(scanner *sc.Scanner, msg string) {
   258  		Fail(fmt.Sprintf("%s (at %s)", msg, scanner.Position))
   259  	}
   260  
   261  	var actualOut reflect.Value
   262  	if tc.arg.Type == AnyType {
   263  		actualOut = reflect.Indirect(reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()))
   264  	} else {
   265  		actualOut = reflect.Indirect(reflect.New(reflect.TypeOf(tc.output)))
   266  	}
   267  
   268  	By("parsing the raw argument")
   269  	tc.arg.Parse(&scanner, tc.raw, actualOut)
   270  
   271  	By("checking that it equals the expected output")
   272  	Expect(actualOut.Interface()).To(Equal(tc.output))
   273  }