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: ®, raw: "+testing:custom={hi}", output: CustomType{Value: []string{"hi"}}}.Run) 86 87 It("should parse name-only markers", parseTestCase{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: ®, raw: "+testing:anonymous:literal=foo", output: "foo"}.Run) 91 It("should parse into named-typed values", parseTestCase{reg: ®, raw: "+testing:anonymous:named=foo", output: wrappedMarkerVal("foo")}.Run) 92 It("shouldn't require any argument to an optional-valued marker", parseTestCase{reg: ®, raw: "+testing:anonymousOptional", output: (*int)(nil)}.Run) 93 }) 94 95 It("should parse raw-argument markers", parseTestCase{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: ®, 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: ®, 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: ®, 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: ®, raw: "+testing:allOptional", output: allOptionalStruct{}}.Run) 155 }) 156 157 It("should support markers with multiple segments in the name", parseTestCase{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: ®, raw: "+testing:parent", output: allOptionalStruct{}}.Run) 161 It("should still allow fetching the longer-named one", parseTestCase{reg: ®, raw: "+testing:parent:nested=some string", output: "some string"}.Run) 162 It("should consider anonymously-named ones before considering fields", parseTestCase{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: ®, raw: "+testing:tripleDefined=42", output: 42, target: DescribesPackage}.Run) 167 It("should properly parse the field-level one", parseTestCase{reg: ®, raw: "+testing:tripleDefined=foo", output: "foo", target: DescribesField}.Run) 168 It("should properly parse the type-level one", parseTestCase{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 }