1 package filtering
2
3 import (
4 "testing"
5
6 syntaxv1 "go.einride.tech/aip/proto/gen/einride/example/syntax/v1"
7 "gotest.tools/v3/assert"
8 )
9
10 func TestChecker(t *testing.T) {
11 t.Parallel()
12 for _, tt := range []struct {
13 filter string
14 declarations []DeclarationOption
15 errorContains string
16 }{
17 {
18 filter: "New York Giants",
19 declarations: []DeclarationOption{
20 DeclareIdent("New", TypeBool),
21 DeclareIdent("York", TypeBool),
22 DeclareIdent("Giants", TypeBool),
23 },
24 errorContains: "undeclared function 'FUZZY'",
25 },
26
27 {
28 filter: "New York Giants OR Yankees",
29 declarations: []DeclarationOption{
30 DeclareStandardFunctions(),
31 DeclareIdent("New", TypeBool),
32 DeclareIdent("York", TypeBool),
33 DeclareIdent("Giants", TypeBool),
34 DeclareIdent("Yankees", TypeBool),
35 },
36 errorContains: "undeclared function 'FUZZY'",
37 },
38
39 {
40 filter: "New York (Giants OR Yankees)",
41 declarations: []DeclarationOption{
42 DeclareStandardFunctions(),
43 DeclareIdent("New", TypeBool),
44 DeclareIdent("York", TypeBool),
45 DeclareIdent("Giants", TypeBool),
46 DeclareIdent("Yankees", TypeBool),
47 },
48 errorContains: "undeclared function 'FUZZY'",
49 },
50
51 {
52 filter: "a b AND c AND d",
53 declarations: []DeclarationOption{
54 DeclareStandardFunctions(),
55 DeclareIdent("a", TypeBool),
56 DeclareIdent("b", TypeBool),
57 DeclareIdent("c", TypeBool),
58 DeclareIdent("d", TypeBool),
59 },
60 errorContains: "undeclared function 'FUZZY'",
61 },
62
63 {
64 filter: "a",
65 declarations: []DeclarationOption{
66 DeclareIdent("a", TypeBool),
67 },
68 },
69
70 {
71 filter: "(a b) AND c AND d",
72 declarations: []DeclarationOption{
73 DeclareStandardFunctions(),
74 DeclareIdent("a", TypeBool),
75 DeclareIdent("b", TypeBool),
76 DeclareIdent("c", TypeBool),
77 DeclareIdent("d", TypeBool),
78 },
79 errorContains: "undeclared function 'FUZZY'",
80 },
81
82 {
83 filter: `author = "Karin Boye" AND NOT read`,
84 declarations: []DeclarationOption{
85 DeclareStandardFunctions(),
86 DeclareIdent("author", TypeString),
87 DeclareIdent("read", TypeBool),
88 },
89 },
90
91 {
92 filter: "a < 10 OR a >= 100",
93 declarations: []DeclarationOption{
94 DeclareStandardFunctions(),
95 DeclareIdent("a", TypeInt),
96 },
97 },
98
99 {
100 filter: "NOT (a OR b)",
101 declarations: []DeclarationOption{
102 DeclareStandardFunctions(),
103 DeclareIdent("a", TypeBool),
104 DeclareIdent("b", TypeBool),
105 },
106 },
107
108 {
109 filter: `-file:".java"`,
110 declarations: []DeclarationOption{
111 DeclareStandardFunctions(),
112 DeclareIdent("file", TypeString),
113 },
114 },
115
116 {
117 filter: "-30",
118 errorContains: "non-bool result type",
119 },
120
121 {
122 filter: "package=com.google",
123 declarations: []DeclarationOption{
124 DeclareStandardFunctions(),
125 DeclareIdent("package", TypeString),
126 DeclareIdent("com", TypeMap(TypeString, TypeString)),
127 },
128 },
129
130 {
131 filter: `msg != 'hello'`,
132 declarations: []DeclarationOption{
133 DeclareStandardFunctions(),
134 DeclareIdent("msg", TypeString),
135 },
136 },
137
138 {
139 filter: `1 > 0`,
140 declarations: []DeclarationOption{
141 DeclareStandardFunctions(),
142 },
143 },
144
145 {
146 filter: `2.5 >= 2.4`,
147 declarations: []DeclarationOption{
148 DeclareStandardFunctions(),
149 },
150 },
151
152 {
153 filter: `foo >= -2.4`,
154 declarations: []DeclarationOption{
155 DeclareStandardFunctions(),
156 DeclareIdent("foo", TypeFloat),
157 },
158 },
159
160 {
161 filter: `foo >= (-2.4)`,
162 declarations: []DeclarationOption{
163 DeclareStandardFunctions(),
164 DeclareIdent("foo", TypeFloat),
165 },
166 },
167
168 {
169 filter: `-2.5 >= -2.4`,
170 declarations: []DeclarationOption{
171 DeclareStandardFunctions(),
172 },
173 },
174
175 {
176 filter: `yesterday < request.time`,
177 declarations: []DeclarationOption{
178 DeclareStandardFunctions(),
179 DeclareIdent("yesterday", TypeTimestamp),
180 },
181
182 errorContains: "undeclared identifier 'request'",
183 },
184
185 {
186 filter: `experiment.rollout <= cohort(request.user)`,
187 declarations: []DeclarationOption{
188 DeclareStandardFunctions(),
189 DeclareFunction("cohort", NewFunctionOverload("cohort_string", TypeFloat, TypeString)),
190 },
191
192 errorContains: "undeclared identifier 'experiment'",
193 },
194
195 {
196 filter: `prod`,
197 declarations: []DeclarationOption{
198 DeclareIdent("prod", TypeBool),
199 },
200 },
201
202 {
203 filter: `expr.type_map.1.type`,
204
205 errorContains: "undeclared identifier 'expr'",
206 },
207
208 {
209 filter: `regex(m.key, '^.*prod.*$')`,
210 declarations: []DeclarationOption{
211 DeclareStandardFunctions(),
212 DeclareIdent("m", TypeMap(TypeString, TypeString)),
213 DeclareFunction("regex", NewFunctionOverload("regex_string", TypeBool, TypeString, TypeString)),
214 },
215 },
216
217 {
218 filter: `math.mem('30mb')`,
219 declarations: []DeclarationOption{
220 DeclareFunction("math.mem", NewFunctionOverload("math.mem_string", TypeBool, TypeString)),
221 },
222 },
223
224 {
225 filter: `(msg.endsWith('world') AND retries < 10)`,
226 declarations: []DeclarationOption{
227 DeclareStandardFunctions(),
228 DeclareIdent("retries", TypeInt),
229 },
230 errorContains: "undeclared function 'msg.endsWith'",
231 },
232
233 {
234 filter: `(endsWith(msg, 'world') AND retries < 10)`,
235 declarations: []DeclarationOption{
236 DeclareStandardFunctions(),
237 DeclareFunction("endsWith", NewFunctionOverload("endsWith_string", TypeBool, TypeString, TypeString)),
238 DeclareIdent("retries", TypeInt),
239 DeclareIdent("msg", TypeString),
240 },
241 },
242
243 {
244 filter: "expire_time > time.now()",
245 declarations: []DeclarationOption{
246 DeclareStandardFunctions(),
247 DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)),
248 DeclareIdent("expire_time", TypeTimestamp),
249 },
250 },
251
252 {
253 filter: `time.now() > timestamp("2012-04-21T11:30:00-04:00")`,
254 declarations: []DeclarationOption{
255 DeclareStandardFunctions(),
256 DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)),
257 },
258 },
259
260 {
261 filter: `time.now() > timestamp("INVALID")`,
262 declarations: []DeclarationOption{
263 DeclareStandardFunctions(),
264 DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)),
265 },
266 errorContains: "invalid timestamp",
267 },
268
269 {
270 filter: `ttl > duration("30s")`,
271 declarations: []DeclarationOption{
272 DeclareStandardFunctions(),
273 DeclareIdent("ttl", TypeDuration),
274 },
275 },
276
277 {
278 filter: `ttl > duration("INVALID")`,
279 declarations: []DeclarationOption{
280 DeclareStandardFunctions(),
281 DeclareIdent("ttl", TypeDuration),
282 },
283 errorContains: "invalid duration",
284 },
285
286 {
287 filter: `ttl > duration(input_field)`,
288 declarations: []DeclarationOption{
289 DeclareStandardFunctions(),
290 DeclareIdent("ttl", TypeDuration),
291 DeclareIdent("input_field", TypeString),
292 },
293 },
294
295 {
296 filter: `create_time > timestamp("2006-01-02T15:04:05+07:00")`,
297 declarations: []DeclarationOption{
298 DeclareStandardFunctions(),
299 DeclareIdent("create_time", TypeTimestamp),
300 },
301 },
302
303 {
304 filter: `create_time > timestamp("2006-01-02T15:04:05+07:00")`,
305 declarations: []DeclarationOption{
306 DeclareStandardFunctions(),
307 DeclareIdent("create_time", TypeTimestamp),
308 },
309 },
310
311 {
312 filter: `
313 start_time > timestamp("2006-01-02T15:04:05+07:00") AND
314 (driver = "driver1" OR start_driver = "driver1" OR end_driver = "driver1")
315 `,
316 declarations: []DeclarationOption{
317 DeclareStandardFunctions(),
318 DeclareIdent("start_time", TypeTimestamp),
319 DeclareIdent("driver", TypeString),
320 DeclareIdent("start_driver", TypeString),
321 DeclareIdent("end_driver", TypeString),
322 },
323 },
324
325 {
326 filter: `annotations:schedule`,
327 declarations: []DeclarationOption{
328 DeclareStandardFunctions(),
329 DeclareIdent("annotations", TypeMap(TypeString, TypeString)),
330 },
331 },
332
333 {
334 filter: `annotations.schedule = "test"`,
335 declarations: []DeclarationOption{
336 DeclareStandardFunctions(),
337 DeclareIdent("annotations", TypeMap(TypeString, TypeString)),
338 },
339 },
340
341 {
342 filter: `enum = ENUM_ONE`,
343 declarations: []DeclarationOption{
344 DeclareStandardFunctions(),
345 DeclareEnumIdent("enum", syntaxv1.Enum(0).Type()),
346 },
347 },
348
349 {
350 filter: `enum = ENUM_ONE AND NOT enum2 = ENUM_TWO`,
351 declarations: []DeclarationOption{
352 DeclareStandardFunctions(),
353 DeclareEnumIdent("enum", syntaxv1.Enum(0).Type()),
354 DeclareEnumIdent("enum2", syntaxv1.Enum(0).Type()),
355 },
356 },
357
358 {
359 filter: `create_time = "2022-08-12 22:22:22"`,
360 declarations: []DeclarationOption{
361 DeclareStandardFunctions(),
362 DeclareIdent("create_time", TypeTimestamp),
363 },
364 errorContains: "invalid timestamp. Should be in RFC3339 format",
365 },
366
367 {
368 filter: `create_time = "2022-08-12T22:22:22+01:00"`,
369 declarations: []DeclarationOption{
370 DeclareStandardFunctions(),
371 DeclareIdent("create_time", TypeTimestamp),
372 },
373 },
374
375 {
376 filter: `create_time != "2022-08-12T22:22:22+01:00"`,
377 declarations: []DeclarationOption{
378 DeclareStandardFunctions(),
379 DeclareIdent("create_time", TypeTimestamp),
380 },
381 },
382
383 {
384 filter: `create_time < "2022-08-12T22:22:22+01:00"`,
385 declarations: []DeclarationOption{
386 DeclareStandardFunctions(),
387 DeclareIdent("create_time", TypeTimestamp),
388 },
389 },
390
391 {
392 filter: `create_time > "2022-08-12T22:22:22+01:00"`,
393 declarations: []DeclarationOption{
394 DeclareStandardFunctions(),
395 DeclareIdent("create_time", TypeTimestamp),
396 },
397 },
398
399 {
400 filter: `create_time <= "2022-08-12T22:22:22+01:00"`,
401 declarations: []DeclarationOption{
402 DeclareStandardFunctions(),
403 DeclareIdent("create_time", TypeTimestamp),
404 },
405 },
406
407 {
408 filter: `create_time >= "2022-08-12T22:22:22+01:00"`,
409 declarations: []DeclarationOption{
410 DeclareStandardFunctions(),
411 DeclareIdent("create_time", TypeTimestamp),
412 },
413 },
414
415 {
416 filter: "<",
417 errorContains: "unexpected token <",
418 },
419
420 {
421 filter: `(-2.5) >= -2.4`,
422 errorContains: "unexpected token >=",
423 },
424
425 {
426 filter: `a = "foo`,
427 errorContains: "unterminated string",
428 },
429
430 {
431 filter: "invalid = foo\xa0\x01bar",
432 errorContains: "invalid UTF-8",
433 },
434 } {
435 tt := tt
436 t.Run(tt.filter, func(t *testing.T) {
437 t.Parallel()
438 var parser Parser
439 parser.Init(tt.filter)
440 parsedExpr, err := parser.Parse()
441 if err != nil && tt.errorContains != "" {
442 assert.ErrorContains(t, err, tt.errorContains)
443 return
444 }
445 assert.NilError(t, err)
446 declarations, err := NewDeclarations(tt.declarations...)
447 if err != nil && tt.errorContains != "" {
448 assert.ErrorContains(t, err, tt.errorContains)
449 return
450 }
451 assert.NilError(t, err)
452 var checker Checker
453 checker.Init(parsedExpr.GetExpr(), parsedExpr.GetSourceInfo(), declarations)
454 checkedExpr, err := checker.Check()
455 if tt.errorContains != "" {
456 assert.ErrorContains(t, err, tt.errorContains)
457 return
458 }
459 assert.NilError(t, err)
460 assert.Assert(t, checkedExpr != nil)
461 })
462 }
463 }
464
View as plain text