go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/selector/parse_test.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package selector 9 10 import ( 11 "fmt" 12 "testing" 13 14 . "go.charczuk.com/sdk/assert" 15 ) 16 17 func Test_MustParse(t *testing.T) { 18 ItsEqual(t, "x == a", MustParse("x==a").String()) 19 20 var err error 21 func() { 22 defer func() { 23 if r := recover(); r != nil { 24 err = fmt.Errorf("%v", r) 25 } 26 }() 27 MustParse("x!!") 28 }() 29 ItsNotNil(t, err) 30 } 31 32 func Test_Parse_invalid(t *testing.T) { 33 testBadStrings := []string{ 34 "x=a||y=b", 35 "x==a==b", 36 "!x=a", 37 "x<a", 38 "x>1", 39 "x>1,z<5", 40 "x=", 41 "x= ", 42 "x=,z= ", 43 "x= ,z= ", 44 "foo == bar foo", 45 } 46 var err error 47 for _, str := range testBadStrings { 48 _, err = Parse(str) 49 ItsNotNil(t, err, str) 50 } 51 } 52 53 func Test_Parse_semiValid(t *testing.T) { 54 testGoodStrings := []string{ 55 "", 56 "x=a,y=b,z=c", 57 "x!=a,y=b", 58 "x", 59 "!x", 60 "!x,y", 61 "!x,y,z", 62 "!x,y,!z", 63 "!x,y,z,a", 64 "!x,y,z,!a", 65 } 66 67 var err error 68 for _, str := range testGoodStrings { 69 _, err = Parse(str) 70 ItsNil(t, err, str) 71 } 72 } 73 74 func Test_Parse_trailingComma(t *testing.T) { 75 testBadStrings := []string{ 76 ",", 77 "x,", 78 "!x,", 79 "foo==bar,", 80 "foo!=bar,", 81 "foo in (bar,baz),", 82 "foo not in (bar,baz),", 83 } 84 85 var err error 86 for _, str := range testBadStrings { 87 _, err = Parse(str) 88 ItsNotNil(t, err, str) 89 } 90 } 91 92 func Test_Parse_equals(t *testing.T) { 93 valid := Labels{ 94 "foo": "bar", 95 "moo": "lar", 96 } 97 invalid := Labels{ 98 "zoo": "mar", 99 "moo": "lar", 100 } 101 102 selector, err := Parse("foo == bar") 103 ItsNil(t, err) 104 ItsEqual(t, true, selector.Matches(valid)) 105 ItsEqual(t, false, selector.Matches(invalid)) 106 } 107 108 func Test_Parse_notEquals(t *testing.T) { 109 valid := Labels{ 110 "foo": "far", 111 "moo": "lar", 112 } 113 invalidPresent := Labels{ 114 "foo": "bar", 115 "moo": "lar", 116 } 117 invalidMissing := Labels{ 118 "zoo": "mar", 119 "moo": "lar", 120 } 121 122 selector, err := Parse("foo != bar") 123 ItsNil(t, err) 124 ItsEqual(t, true, selector.Matches(valid)) 125 ItsEqual(t, true, selector.Matches(invalidMissing)) 126 ItsEqual(t, false, selector.Matches(invalidPresent)) 127 } 128 129 func Test_Parse_In(t *testing.T) { 130 valid := Labels{ 131 "foo": "far", 132 "moo": "lar", 133 } 134 valid2 := Labels{ 135 "foo": "bar", 136 "moo": "lar", 137 } 138 invalid := Labels{ 139 "foo": "mar", 140 "moo": "lar", 141 } 142 invalidMissing := Labels{ 143 "zoo": "mar", 144 "moo": "lar", 145 } 146 147 selector, err := Parse("foo in (bar,far)") 148 ItsNil(t, err) 149 ItsEqual(t, true, selector.Matches(valid), selector.String()) 150 ItsEqual(t, true, selector.Matches(valid2)) 151 ItsEqual(t, false, selector.Matches(invalidMissing)) 152 ItsEqual(t, false, selector.Matches(invalid), selector.String()) 153 } 154 155 func Test_Parse_group(t *testing.T) { 156 valid := Labels{ 157 "zoo": "mar", 158 "moo": "lar", 159 "thing": "map", 160 } 161 invalid := Labels{ 162 "zoo": "mar", 163 "moo": "something", 164 "thing": "map", 165 } 166 invalid2 := Labels{ 167 "zoo": "mar", 168 "moo": "lar", 169 "!thing": "map", 170 } 171 selector, err := Parse("zoo=mar, moo=lar, thing") 172 ItsNil(t, err) 173 ItsEqual(t, true, selector.Matches(valid)) 174 ItsEqual(t, false, selector.Matches(invalid)) 175 ItsEqual(t, false, selector.Matches(invalid2)) 176 177 complicated, err := Parse("zoo in (mar,lar,dar),moo,!thingy") 178 ItsNil(t, err) 179 ItsNotNil(t, complicated) 180 ItsEqual(t, true, complicated.Matches(valid)) 181 } 182 183 func Test_Parse_groupComplicated(t *testing.T) { 184 valid := Labels{ 185 "zoo": "mar", 186 "moo": "lar", 187 "thing": "map", 188 } 189 complicated, err := Parse("zoo in (mar,lar,dar),moo,thing == map,!thingy") 190 ItsNil(t, err) 191 ItsNotNil(t, complicated) 192 ItsEqual(t, true, complicated.Matches(valid)) 193 } 194 195 func Test_Parse_docsExample(t *testing.T) { 196 sel, err := Parse("x in (foo,,baz),y,z notin ()") 197 ItsNil(t, err) 198 ItsNotNil(t, sel) 199 } 200 201 func Test_Parse_subdomainKey(t *testing.T) { 202 sel, err := Parse("example.com/failure-domain == primary") 203 ItsNil(t, err) 204 ItsNotNil(t, sel) 205 ItsEqual(t, "example.com/failure-domain == primary", sel.String()) 206 ItsEqual(t, true, sel.Matches(map[string]string{ 207 "bar": "foo", 208 "example.com/failure-domain": "primary", 209 "foo": "bar", 210 })) 211 } 212 213 func Test_Parse_equalsOperators(t *testing.T) { 214 selector, err := Parse("notin=in") 215 ItsNil(t, err) 216 217 typed, isTyped := selector.(Equals) 218 ItsEqual(t, true, isTyped) 219 ItsEqual(t, "notin", typed.Key) 220 ItsEqual(t, "in", typed.Value) 221 } 222 223 func Test_Parse_validate(t *testing.T) { 224 _, err := Parse("zoo=bar") 225 ItsNil(t, err) 226 227 _, err = Parse("_zoo=bar") 228 ItsNotNil(t, err) 229 230 _, err = Parse("_zoo=_bar") 231 ItsNotNil(t, err) 232 233 _, err = Parse("zoo=bar,foo=_mar") 234 ItsNotNil(t, err) 235 } 236 237 func Test_Parse_regressionCSVSymbols(t *testing.T) { 238 sel, err := Parse("foo in (bar-bar, baz.baz, buzz_buzz), moo=boo") 239 ItsNil(t, err, "regression is csv values can have '-', '.' and '_' in them") 240 ItsNotEmpty(t, sel.String()) 241 } 242 243 func Test_Parse_regressionCSVEarlyTermination(t *testing.T) { 244 sel, err := Parse("foo in (bar,), moo=boo") 245 ItsNil(t, err) 246 ItsNotEmpty(t, sel.String()) 247 } 248 249 func Test_Parse_regressionCSVMultipleSpaces(t *testing.T) { 250 sel, err := Parse("foo in (bar, foo), moo=boo") 251 ItsNil(t, err) 252 ItsNotEmpty(t, sel.String()) 253 } 254 255 func Test_Parse_regressionIn(t *testing.T) { 256 _, err := Parse("foo in bar, buzz)") 257 ItsNotNil(t, err) 258 } 259 260 func Test_Parse_multiByte(t *testing.T) { 261 selector, err := Parse("함=수,목=록") // number=number, number=rock 262 ItsNil(t, err) 263 ItsNotNil(t, selector) 264 265 typed, isTyped := selector.(And) 266 ItsEqual(t, true, isTyped) 267 ItsEqual(t, 2, Len(typed)) 268 } 269 270 func Test_Parse_options(t *testing.T) { 271 selQuery := "bar=foo@bar" 272 labels := Labels{ 273 "foo": "bar", 274 "bar": "foo@bar", 275 } 276 277 sel, err := Parse(selQuery) 278 ItsNotNil(t, err) 279 ItsNil(t, sel) 280 281 sel, err = Parse(selQuery, SkipValidation) 282 ItsNil(t, err) 283 ItsNotNil(t, sel) 284 285 ItsEqual(t, true, sel.Matches(labels)) 286 } 287 288 func Benchmark_Parse(b *testing.B) { 289 valid := Labels{ 290 "zoo": "mar", 291 "moo": "lar", 292 "thing": "map", 293 } 294 295 for i := 0; i < b.N; i++ { 296 selector, err := Parse("zoo in (mar,lar,dar),moo,!thingy") 297 if err != nil { 298 b.Fail() 299 } 300 if !selector.Matches(valid) { 301 b.Fail() 302 } 303 } 304 } 305 306 func Test_Parse_fuzzRegressions(t *testing.T) { 307 var sel Selector 308 var err error 309 testBadStrings := []string{ 310 "!0!0", 311 "0!=0,!", 312 } 313 for _, str := range testBadStrings { 314 _, err = Parse(str) 315 ItsNotNil(t, err, str, err) 316 } 317 318 testGoodStrings := []string{ 319 "0,!0", 320 "0 in (0), !0", 321 } 322 for _, str := range testGoodStrings { 323 sel, err = Parse(str) 324 ItsNil(t, err) 325 _, err = Parse(sel.String()) 326 ItsNil(t, err) 327 } 328 } 329 330 func TestParse_InRegression(t *testing.T) { 331 good0 := Labels{ 332 "role": "job", 333 "team": "internal-engineering", 334 } 335 good1 := Labels{ 336 "role": "job-worker", 337 "team": "internal-engineering", 338 } 339 bad0 := Labels{ 340 "role": "not-job", 341 } 342 343 sel, err := Parse("role in (job, job-worker),team=internal-engineering") 344 ItsNil(t, err) 345 346 ItsEqual(t, true, sel.Matches(good0)) 347 ItsEqual(t, true, sel.Matches(good1)) 348 ItsEqual(t, false, sel.Matches(bad0)) 349 }