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: ®, 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 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 }