1
16
17 package cel
18
19 import (
20 "context"
21 "fmt"
22 "math"
23 "testing"
24
25 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
26 "k8s.io/apimachinery/pkg/util/validation/field"
27 celconfig "k8s.io/apiserver/pkg/apis/cel"
28 "k8s.io/utils/ptr"
29 )
30
31 func TestCelCostStability(t *testing.T) {
32 cases := []struct {
33 name string
34 schema *schema.Structural
35 obj map[string]interface{}
36 expectCost map[string]int64
37 }{
38 {name: "integers",
39
40 obj: objs(math.MaxInt64, math.MaxInt64, math.MaxInt32, math.MaxInt32, math.MaxInt64, math.MaxInt64),
41 schema: schemas(integerType, integerType, int32Type, int32Type, int64Type, int64Type),
42 expectCost: map[string]int64{
43 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", fmt.Sprintf("%d", math.MaxInt64)): 11,
44 "self.val1 == self.val6": 5,
45 "type(self.val1) == int": 4,
46 fmt.Sprintf("self.val3 + 1 == %d + 1", math.MaxInt32): 5,
47 },
48 },
49 {name: "numbers",
50 obj: objs(math.MaxFloat64, math.MaxFloat64, math.MaxFloat32, math.MaxFloat32, math.MaxFloat64, math.MaxFloat64, int64(1)),
51 schema: schemas(numberType, numberType, floatType, floatType, doubleType, doubleType, doubleType),
52 expectCost: map[string]int64{
53 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", fmt.Sprintf("%f", math.MaxFloat64)): 11,
54 "self.val1 == self.val6": 5,
55 "type(self.val1) == double": 4,
56
57
58
59
60 "type(self.val7) == double": 4,
61 "self.val7 == 1.0": 3,
62 },
63 },
64 {name: "numeric comparisons",
65 obj: objs(
66 int64(5),
67 float64(10.0),
68 float64(10.0),
69 float64(10.0),
70 int64(10),
71 int64(10),
72 int64(10),
73 ),
74 schema: schemas(integerType, numberType, floatType, doubleType, numberType, floatType, doubleType),
75 expectCost: map[string]int64{
76
77
78
79 "double(self.val1) < self.val4": 6,
80 "self.val1 < int(self.val4)": 6,
81 "double(self.val1) < self.val5": 6,
82 "self.val1 < int(self.val5)": 6,
83 "double(self.val1) < self.val6": 6,
84 "self.val1 < int(self.val6)": 6,
85
86
87 "double(5) < 10.0": 1,
88 "5 < int(10.0)": 1,
89
90
91 "double(self.val1) < 10.0": 4,
92 },
93 },
94 {name: "unicode strings",
95 obj: objs("Rook takes π", "Rook takes π"),
96 schema: schemas(stringType, stringType),
97 expectCost: map[string]int64{
98 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "'Rook takes π'"): 14,
99 "self.val1.startsWith('Rook')": 4,
100 "!self.val1.startsWith('knight')": 5,
101 "self.val1.matches('^[^0-9]*$')": 8,
102 "!self.val1.matches('^[0-9]*$')": 7,
103 "type(self.val1) == string": 4,
104 "size(self.val1) == 12": 4,
105
106
107 "self.val1.charAt(3) == 'k'": 4,
108 "self.val1.indexOf('o') == 1": 4,
109 "self.val1.indexOf('o', 2) == 2": 4,
110 "self.val1.replace(' ', 'x') == 'Rookxtakesxπ'": 7,
111 "self.val1.replace(' ', 'x', 1) == 'Rookxtakes π'": 7,
112 "self.val1.split(' ') == ['Rook', 'takes', 'π']": 6,
113 "self.val1.split(' ', 2) == ['Rook', 'takes π']": 6,
114 "self.val1.substring(5) == 'takes π'": 5,
115 "self.val1.substring(0, 4) == 'Rook'": 5,
116 "self.val1.substring(4, 10).trim() == 'takes'": 6,
117 "self.val1.upperAscii() == 'ROOK TAKES π'": 6,
118 "self.val1.lowerAscii() == 'rook takes π'": 6,
119 "self.val1.lowerAscii() == self.val1.lowerAscii()": 10,
120
121 "'%d %s %f %s %s'.format([1, 'abc', 1.0, duration('1m'), timestamp('2000-01-01T00:00:00.000Z')]) == '1 abc 1.000000 60s 2000-01-01T00:00:00Z'": 6,
122 "'%e'.format([3.14]) == '3.140000β―Γβ―10β°β°'": 3,
123 "'%o %o %o'.format([7, 8, 9]) == '7 10 11'": 2,
124 "'%b %b %b'.format([7, 8, 9]) == '111 1000 1001'": 3,
125 },
126 },
127 {name: "escaped strings",
128 obj: objs("l1\nl2", "l1\nl2"),
129 schema: schemas(stringType, stringType),
130 expectCost: map[string]int64{
131 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "'l1\\nl2'"): 11,
132 "self.val1 == '''l1\nl2'''": 3,
133 },
134 },
135 {name: "bytes",
136 obj: objs("QUI=", "QUI="),
137 schema: schemas(byteType, byteType),
138 expectCost: map[string]int64{
139 "self.val1 == self.val2": 5,
140 "self.val1 == b'AB'": 3,
141 "type(self.val1) == bytes": 4,
142 "size(self.val1) == 2": 4,
143 },
144 },
145 {name: "booleans",
146 obj: objs(true, true, false, false),
147 schema: schemas(booleanType, booleanType, booleanType, booleanType),
148 expectCost: map[string]int64{
149 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "true"): 11,
150 "self.val1 != self.val4": 5,
151 "type(self.val1) == bool": 4,
152 },
153 },
154 {name: "duration format",
155 obj: objs("1h2m3s4ms", "1h2m3s4ms"),
156 schema: schemas(durationFormat, durationFormat),
157 expectCost: map[string]int64{
158 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "duration('1h2m3s4ms')"): 11,
159 "self.val1 == duration('1h2m') + duration('3s4ms')": 4,
160 "self.val1.getHours() == 1": 4,
161 "type(self.val1) == google.protobuf.Duration": 4,
162 },
163 },
164 {name: "date format",
165 obj: objs("1997-07-16", "1997-07-16"),
166 schema: schemas(dateFormat, dateFormat),
167 expectCost: map[string]int64{
168 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "timestamp('1997-07-16T00:00:00.000Z')"): 11,
169 "self.val1.getDate() == 16": 4,
170 "type(self.val1) == google.protobuf.Timestamp": 4,
171 },
172 },
173 {name: "date-time format",
174 obj: objs("2011-08-18T19:03:37.010000000+01:00", "2011-08-18T19:03:37.010000000+01:00"),
175 schema: schemas(dateTimeFormat, dateTimeFormat),
176 expectCost: map[string]int64{
177 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "timestamp('2011-08-18T19:03:37.010+01:00')"): 11,
178 "self.val1 == timestamp('2011-08-18T00:00:00.000+01:00') + duration('19h3m37s10ms')": 4,
179 "self.val1.getDate('01:00') == 18": 4,
180 "type(self.val1) == google.protobuf.Timestamp": 4,
181 },
182 },
183 {name: "enums",
184 obj: map[string]interface{}{"enumStr": "Pending"},
185 schema: objectTypePtr(map[string]schema.Structural{"enumStr": {
186 Generic: schema.Generic{
187 Type: "string",
188 },
189 ValueValidation: &schema.ValueValidation{
190 Enum: []schema.JSON{
191 {Object: "Pending"},
192 {Object: "Available"},
193 {Object: "Bound"},
194 {Object: "Released"},
195 {Object: "Failed"},
196 },
197 },
198 }}),
199 expectCost: map[string]int64{
200 "self.enumStr == 'Pending'": 3,
201 "self.enumStr in ['Pending', 'Available']": 2,
202 },
203 },
204 {name: "conversions",
205 obj: objs(int64(10), 10.0, 10.49, 10.5, true, "10", "MTA=", "3723.004s", "1h2m3s4ms", "2011-08-18T19:03:37.01+01:00", "2011-08-18T19:03:37.01+01:00", "2011-08-18T00:00:00Z", "2011-08-18"),
206 schema: schemas(integerType, numberType, numberType, numberType, booleanType, stringType, byteType, stringType, durationFormat, stringType, dateTimeFormat, stringType, dateFormat),
207 expectCost: map[string]int64{
208 "int(self.val2) == self.val1": 6,
209 "double(self.val1) == self.val2": 6,
210 "bytes(self.val6) == self.val7": 6,
211 "string(self.val1) == self.val6": 6,
212 "string(self.val4) == '10.5'": 4,
213 "string(self.val7) == self.val6": 6,
214 "duration(self.val8) == self.val9": 6,
215 "timestamp(self.val10) == self.val11": 6,
216 "string(self.val11) == self.val10": 8,
217 "timestamp(self.val12) == self.val13": 6,
218 "string(self.val13) == self.val12": 7,
219 },
220 },
221 {name: "lists",
222 obj: objs([]interface{}{1, 2, 3}, []interface{}{1, 2, 3}),
223 schema: schemas(listType(&integerType), listType(&integerType)),
224 expectCost: map[string]int64{
225 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "[1, 2, 3]"): 11,
226 "1 in self.val1": 5,
227 "self.val2[0] in self.val1": 8,
228 "!(0 in self.val1)": 6,
229 "self.val1 + self.val2 == [1, 2, 3, 1, 2, 3]": 6,
230 "self.val1 + [4, 5] == [1, 2, 3, 4, 5]": 4,
231 "has(self.val1)": 1,
232 "has(self.val1) && has(self.val2)": 2,
233 },
234 },
235 {name: "listSets",
236 obj: objs([]interface{}{"a", "b", "c"}, []interface{}{"a", "c", "b"}, buildLargeArray(1000)),
237 schema: schemas(listSetType(&stringType), listSetType(&stringType), listSetType(&integerType)),
238 expectCost: map[string]int64{
239
240 "self.val1 == ['c', 'b', 'a']": 3,
241 "self.val1 == self.val2": 5,
242 "'a' in self.val1": 5,
243 "self.val2[0] in self.val1": 8,
244 "!('x' in self.val1)": 6,
245 "self.val1 + self.val2 == ['a', 'b', 'c']": 6,
246 "self.val1 + ['c', 'd'] == ['a', 'b', 'c', 'd']": 4,
247 "sets.contains(self.val1, ['a'])": 6,
248 "sets.equivalent(self.val1, ['a', 'b', 'c'])": 21,
249 "sets.intersects(self.val1, ['a'])": 6,
250 "sets.contains(self.val3, [1])": 1003,
251 "!sets.equivalent(self.val3, [1, 2, 3])": 6004,
252 "sets.intersects(self.val3, [1])": 1003,
253 },
254 },
255 {name: "listMaps",
256 obj: map[string]interface{}{
257 "objs": []interface{}{
258 []interface{}{
259 map[string]interface{}{"k": "a", "v": "1"},
260 map[string]interface{}{"k": "b", "v": "2"},
261 },
262 []interface{}{
263 map[string]interface{}{"k": "b", "v": "2"},
264 map[string]interface{}{"k": "a", "v": "1"},
265 },
266 []interface{}{
267 map[string]interface{}{"k": "b", "v": "3"},
268 map[string]interface{}{"k": "a", "v": "1"},
269 },
270 []interface{}{
271 map[string]interface{}{"k": "c", "v": "4"},
272 },
273 },
274 },
275 schema: objectTypePtr(map[string]schema.Structural{
276 "objs": listType(listMapTypePtr([]string{"k"}, objectTypePtr(map[string]schema.Structural{
277 "k": stringType,
278 "v": stringType,
279 }))),
280 }),
281 expectCost: map[string]int64{
282 "self.objs[0] == self.objs[1]": 7,
283 "self.objs[0] + self.objs[2] == self.objs[2]": 11,
284 "self.objs[2] + self.objs[0] == self.objs[0]": 11,
285
286 "self.objs[0] == [self.objs[0][0], self.objs[0][1]]": 22,
287 "self.objs[0] == [self.objs[0][1], self.objs[0][0]]": 22,
288
289 "self.objs[2] + [self.objs[0][0], self.objs[0][1]] == self.objs[0]": 26,
290 "size(self.objs[0] + [self.objs[3][0]]) == 3": 20,
291 },
292 },
293 {name: "maps",
294 obj: objs(map[string]interface{}{"k1": "a", "k2": "b"}, map[string]interface{}{"k2": "b", "k1": "a"}),
295 schema: schemas(mapType(&stringType), mapType(&stringType)),
296 expectCost: map[string]int64{
297 "self.val1 == self.val2": 5,
298 "'k1' in self.val1": 3,
299 "!('k3' in self.val1)": 4,
300 "self.val1 == {'k1': 'a', 'k2': 'b'}": 3,
301 },
302 },
303 {name: "objects",
304 obj: map[string]interface{}{
305 "objs": []interface{}{
306 map[string]interface{}{"f1": "a", "f2": "b"},
307 map[string]interface{}{"f1": "a", "f2": "b"},
308 },
309 },
310 schema: objectTypePtr(map[string]schema.Structural{
311 "objs": listType(objectTypePtr(map[string]schema.Structural{
312 "f1": stringType,
313 "f2": stringType,
314 })),
315 }),
316 expectCost: map[string]int64{
317 "self.objs[0] == self.objs[1]": 7,
318 },
319 },
320 {name: "object access",
321 obj: map[string]interface{}{
322 "a": map[string]interface{}{
323 "b": 1,
324 "d": nil,
325 },
326 "a1": map[string]interface{}{
327 "b1": map[string]interface{}{
328 "c1": 4,
329 },
330 },
331 "a3": map[string]interface{}{},
332 },
333 schema: objectTypePtr(map[string]schema.Structural{
334 "a": objectType(map[string]schema.Structural{
335 "b": integerType,
336 "c": integerType,
337 "d": withNullable(true, integerType),
338 }),
339 "a1": objectType(map[string]schema.Structural{
340 "b1": objectType(map[string]schema.Structural{
341 "c1": integerType,
342 }),
343 "d2": objectType(map[string]schema.Structural{
344 "e2": integerType,
345 }),
346 }),
347 }),
348
349 expectCost: map[string]int64{
350 "has(self.a.b)": 2,
351 "has(self.a1.b1.c1)": 3,
352 "!(has(self.a1.d2) && has(self.a1.d2.e2))": 3,
353 "!has(self.a1.d2)": 3,
354 },
355 },
356 {name: "map access",
357 obj: map[string]interface{}{
358 "val": map[string]interface{}{
359 "b": 1,
360 "d": 2,
361 },
362 },
363 schema: objectTypePtr(map[string]schema.Structural{
364 "val": mapType(&integerType),
365 }),
366 expectCost: map[string]int64{
367
368 "!('a' in self.val)": 4,
369 "'b' in self.val": 3,
370 "!('c' in self.val)": 4,
371 "'d' in self.val": 3,
372
373 "!has(self.val.a)": 3,
374 "has(self.val.b)": 2,
375 "!has(self.val.c)": 3,
376 "has(self.val.d)": 2,
377 "self.val.all(k, self.val[k] > 0)": 17,
378 "self.val.exists_one(k, self.val[k] == 2)": 14,
379 "!self.val.exists_one(k, self.val[k] > 0)": 17,
380 "size(self.val) == 2": 4,
381 "size(self.val.filter(k, self.val[k] > 1)) == 1": 26,
382 },
383 },
384 {name: "listMap access",
385 obj: map[string]interface{}{
386 "listMap": []interface{}{
387 map[string]interface{}{"k": "a1", "v": "b1"},
388 map[string]interface{}{"k": "a2", "v": "b2"},
389 map[string]interface{}{"k": "a3", "v": "b3", "v2": "z"},
390 },
391 },
392 schema: objectTypePtr(map[string]schema.Structural{
393 "listMap": listMapType([]string{"k"}, objectTypePtr(map[string]schema.Structural{
394 "k": stringType,
395 "v": stringType,
396 "v2": stringType,
397 })),
398 }),
399 expectCost: map[string]int64{
400 "has(self.listMap[0].v)": 3,
401 "self.listMap.all(m, m.k.startsWith('a'))": 21,
402 "self.listMap.all(m, !has(m.v2) || m.v2 == 'z')": 21,
403 "self.listMap.exists(m, m.k.endsWith('1'))": 13,
404 "self.listMap.exists_one(m, m.k == 'a3')": 15,
405 "!self.listMap.all(m, m.k.endsWith('1'))": 18,
406 "!self.listMap.exists(m, m.v == 'x')": 25,
407 "!self.listMap.exists_one(m, m.k.startsWith('a'))": 20,
408 "size(self.listMap.filter(m, m.k == 'a1')) == 1": 27,
409 "self.listMap.exists(m, m.k == 'a1' && m.v == 'b1')": 16,
410 "self.listMap.map(m, m.v).exists(v, v == 'b1')": 55,
411
412
413
414
415 "self.listMap.exists(m, has(m.v2) && m.v2 == 'z')": 21,
416 "!self.listMap.all(m, has(m.v2) && m.v2 != 'z')": 10,
417 "self.listMap.exists_one(m, has(m.v2) && m.v2 == 'z')": 12,
418 "self.listMap.filter(m, has(m.v2) && m.v2 == 'z').size() == 1": 24,
419
420 "self.listMap.map(m, has(m.v2) && m.v2 == 'z', m.v2).size() == 1": 25,
421 "self.listMap.filter(m, has(m.v2) && m.v2 == 'z').map(m, m.v2).size() == 1": 39,
422
423
424
425 "self.listMap.exists(m, m.v2 == 'z')": 24,
426 "!self.listMap.all(m, m.v2 != 'z')": 22,
427 },
428 },
429 {name: "list access",
430 obj: map[string]interface{}{
431 "array": []interface{}{1, 1, 2, 2, 3, 3, 4, 5},
432 },
433 schema: objectTypePtr(map[string]schema.Structural{
434 "array": listType(&integerType),
435 }),
436 expectCost: map[string]int64{
437 "2 in self.array": 10,
438 "self.array.all(e, e > 0)": 43,
439 "self.array.exists(e, e > 2)": 36,
440 "self.array.exists_one(e, e > 4)": 22,
441 "!self.array.all(e, e < 2)": 21,
442 "!self.array.exists(e, e < 0)": 52,
443 "!self.array.exists_one(e, e == 2)": 25,
444 "self.array.all(e, e < 100)": 43,
445 "size(self.array.filter(e, e%2 == 0)) == 3": 68,
446 "self.array.map(e, e * 20).filter(e, e > 50).exists(e, e == 60)": 194,
447 "size(self.array) == 8": 4,
448 },
449 },
450 {name: "listSet access",
451 obj: map[string]interface{}{
452 "set": []interface{}{1, 2, 3, 4, 5},
453 },
454 schema: objectTypePtr(map[string]schema.Structural{
455 "set": listType(&integerType),
456 }),
457 expectCost: map[string]int64{
458 "3 in self.set": 7,
459 "self.set.all(e, e > 0)": 28,
460 "self.set.exists(e, e > 3)": 30,
461 "self.set.exists_one(e, e == 3)": 16,
462 "!self.set.all(e, e < 3)": 21,
463 "!self.set.exists(e, e < 0)": 34,
464 "!self.set.exists_one(e, e > 3)": 19,
465 "self.set.all(e, e < 10)": 28,
466 "size(self.set.filter(e, e%2 == 0)) == 2": 46,
467 "self.set.map(e, e * 20).filter(e, e > 50).exists_one(e, e == 60)": 133,
468 "size(self.set) == 5": 4,
469 },
470 },
471 {name: "typemeta and objectmeta access specified",
472 obj: map[string]interface{}{
473 "apiVersion": "v1",
474 "kind": "Pod",
475 "metadata": map[string]interface{}{
476 "name": "foo",
477 "generateName": "pickItForMe",
478 "namespace": "xyz",
479 },
480 },
481 schema: objectTypePtr(map[string]schema.Structural{
482 "kind": stringType,
483 "apiVersion": stringType,
484 "metadata": objectType(map[string]schema.Structural{
485 "name": stringType,
486 "generateName": stringType,
487 }),
488 }),
489 expectCost: map[string]int64{
490 "self.kind == 'Pod'": 3,
491 "self.apiVersion == 'v1'": 3,
492 "self.metadata.name == 'foo'": 4,
493 "self.metadata.generateName == 'pickItForMe'": 5,
494 },
495 },
496 {name: "typemeta and objectmeta access not specified",
497 obj: map[string]interface{}{
498 "apiVersion": "v1",
499 "kind": "Pod",
500 "metadata": map[string]interface{}{
501 "name": "foo",
502 "generateName": "pickItForMe",
503 "namespace": "xyz",
504 },
505 "spec": map[string]interface{}{
506 "field1": "a",
507 },
508 },
509 schema: objectTypePtr(map[string]schema.Structural{
510 "spec": objectType(map[string]schema.Structural{
511 "field1": stringType,
512 }),
513 }),
514 expectCost: map[string]int64{
515 "self.kind == 'Pod'": 3,
516 "self.apiVersion == 'v1'": 3,
517 "self.metadata.name == 'foo'": 4,
518 "self.metadata.generateName == 'pickItForMe'": 5,
519 "self.spec.field1 == 'a'": 4,
520 },
521 },
522
523
524 {name: "embedded object",
525 obj: map[string]interface{}{
526 "embedded": map[string]interface{}{
527 "apiVersion": "v1",
528 "kind": "Pod",
529 "metadata": map[string]interface{}{
530 "name": "foo",
531 "generateName": "pickItForMe",
532 "namespace": "xyz",
533 },
534 "spec": map[string]interface{}{
535 "field1": "a",
536 },
537 },
538 },
539 schema: objectTypePtr(map[string]schema.Structural{
540 "embedded": {
541 Generic: schema.Generic{Type: "object"},
542 Extensions: schema.Extensions{
543 XEmbeddedResource: true,
544 },
545 },
546 }),
547 expectCost: map[string]int64{
548
549
550 "self.embedded.kind == 'Pod'": 4,
551 "self.embedded.apiVersion == 'v1'": 4,
552 "self.embedded.metadata.name == 'foo'": 5,
553 "self.embedded.metadata.generateName == 'pickItForMe'": 6,
554 },
555 },
556 {name: "embedded object with properties",
557 obj: map[string]interface{}{
558 "embedded": map[string]interface{}{
559 "apiVersion": "v1",
560 "kind": "Pod",
561 "metadata": map[string]interface{}{
562 "name": "foo",
563 "generateName": "pickItForMe",
564 "namespace": "xyz",
565 },
566 "spec": map[string]interface{}{
567 "field1": "a",
568 },
569 },
570 },
571 schema: objectTypePtr(map[string]schema.Structural{
572 "embedded": {
573 Generic: schema.Generic{Type: "object"},
574 Extensions: schema.Extensions{
575 XEmbeddedResource: true,
576 },
577 Properties: map[string]schema.Structural{
578 "kind": stringType,
579 "apiVersion": stringType,
580 "metadata": objectType(map[string]schema.Structural{
581 "name": stringType,
582 "generateName": stringType,
583 }),
584 "spec": objectType(map[string]schema.Structural{
585 "field1": stringType,
586 }),
587 },
588 },
589 }),
590 expectCost: map[string]int64{
591
592
593 "self.embedded.kind == 'Pod'": 4,
594 "self.embedded.apiVersion == 'v1'": 4,
595 "self.embedded.metadata.name == 'foo'": 5,
596 "self.embedded.metadata.generateName == 'pickItForMe'": 6,
597
598 "self.embedded.spec.field1 == 'a'": 5,
599 },
600 },
601 {name: "embedded object with preserve unknown",
602 obj: map[string]interface{}{
603 "embedded": map[string]interface{}{
604 "apiVersion": "v1",
605 "kind": "Pod",
606 "metadata": map[string]interface{}{
607 "name": "foo",
608 "generateName": "pickItForMe",
609 "namespace": "xyz",
610 },
611 "spec": map[string]interface{}{
612 "field1": "a",
613 },
614 },
615 },
616 schema: objectTypePtr(map[string]schema.Structural{
617 "embedded": {
618 Generic: schema.Generic{Type: "object"},
619 Extensions: schema.Extensions{
620 XPreserveUnknownFields: true,
621 XEmbeddedResource: true,
622 },
623 },
624 }),
625 expectCost: map[string]int64{
626
627
628 "self.embedded.kind == 'Pod'": 4,
629 "self.embedded.apiVersion == 'v1'": 4,
630 "self.embedded.metadata.name == 'foo'": 5,
631 "self.embedded.metadata.generateName == 'pickItForMe'": 6,
632
633
634 "has(self.embedded)": 1,
635 },
636 },
637 {name: "string in intOrString",
638 obj: map[string]interface{}{
639 "something": "25%",
640 },
641 schema: objectTypePtr(map[string]schema.Structural{
642 "something": intOrStringType(),
643 }),
644 expectCost: map[string]int64{
645
646 "type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
647
648 "type(self.something) == string && self.something == '25%'": 7,
649
650
651
652 "self.something == '25%'": 3,
653 "self.something != 1": 3,
654 "self.something == 1 || self.something == '25%'": 6,
655 "self.something == '25%' || self.something == 1": 3,
656
657
658
659 "self.something != ['anything']": 3,
660 },
661 },
662 {name: "int in intOrString",
663 obj: map[string]interface{}{
664 "something": int64(1),
665 },
666 schema: objectTypePtr(map[string]schema.Structural{
667 "something": intOrStringType(),
668 }),
669 expectCost: map[string]int64{
670
671 "type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
672
673 "type(self.something) == int && self.something == 1": 7,
674
675
676
677 "self.something == 1": 3,
678 "self.something != 'some string'": 3,
679 "self.something == 1 || self.something == '25%'": 3,
680 "self.something == '25%' || self.something == 1": 6,
681
682
683
684 "self.something != ['anything']": 3,
685 },
686 },
687 {name: "null in intOrString",
688 obj: map[string]interface{}{
689 "something": nil,
690 },
691 schema: objectTypePtr(map[string]schema.Structural{
692 "something": withNullable(true, intOrStringType()),
693 }),
694 expectCost: map[string]int64{
695 "!has(self.something)": 2,
696 },
697 },
698 {name: "percent comparison using intOrString",
699 obj: map[string]interface{}{
700 "min": "50%",
701 "current": 5,
702 "available": 10,
703 },
704 schema: objectTypePtr(map[string]schema.Structural{
705 "min": intOrStringType(),
706 "current": integerType,
707 "available": integerType,
708 }),
709 expectCost: map[string]int64{
710
711 `type(self.min) == string && self.min.matches(r'(\d+(\.\d+)?%)')`: 10,
712
713 "type(self.min) == int ? self.current <= self.min : double(self.current) / double(self.available) >= double(self.min.replace('%', '')) / 100.0": 17,
714 },
715 },
716 {name: "preserve unknown fields",
717 obj: map[string]interface{}{
718 "withUnknown": map[string]interface{}{
719 "field1": "a",
720 "field2": "b",
721 },
722 "withUnknownList": []interface{}{
723 map[string]interface{}{
724 "field1": "a",
725 "field2": "b",
726 },
727 map[string]interface{}{
728 "field1": "x",
729 "field2": "y",
730 },
731 map[string]interface{}{
732 "field1": "x",
733 "field2": "y",
734 },
735 map[string]interface{}{},
736 map[string]interface{}{},
737 },
738 "withUnknownFieldList": []interface{}{
739 map[string]interface{}{
740 "fieldOfUnknownType": "a",
741 },
742 map[string]interface{}{
743 "fieldOfUnknownType": 1,
744 },
745 map[string]interface{}{
746 "fieldOfUnknownType": 1,
747 },
748 },
749 "anyvalList": []interface{}{"a", 2},
750 "anyvalMap": map[string]interface{}{"k": "1"},
751 "anyvalField1": 1,
752 "anyvalField2": "a",
753 },
754 schema: objectTypePtr(map[string]schema.Structural{
755 "withUnknown": {
756 Generic: schema.Generic{Type: "object"},
757 Extensions: schema.Extensions{
758 XPreserveUnknownFields: true,
759 },
760 },
761 "withUnknownList": listType(&schema.Structural{
762 Generic: schema.Generic{Type: "object"},
763 Extensions: schema.Extensions{
764 XPreserveUnknownFields: true,
765 },
766 }),
767 "withUnknownFieldList": listType(&schema.Structural{
768 Generic: schema.Generic{Type: "object"},
769 Properties: map[string]schema.Structural{
770 "fieldOfUnknownType": {
771 Extensions: schema.Extensions{
772 XPreserveUnknownFields: true,
773 },
774 },
775 },
776 }),
777 "anyvalList": listType(&schema.Structural{
778 Extensions: schema.Extensions{
779 XPreserveUnknownFields: true,
780 },
781 }),
782 "anyvalMap": mapType(&schema.Structural{
783 Extensions: schema.Extensions{
784 XPreserveUnknownFields: true,
785 },
786 }),
787 "anyvalField1": {
788 Extensions: schema.Extensions{
789 XPreserveUnknownFields: true,
790 },
791 },
792 "anyvalField2": {
793 Extensions: schema.Extensions{
794 XPreserveUnknownFields: true,
795 },
796 },
797 }),
798 expectCost: map[string]int64{
799 "has(self.withUnknown)": 1,
800 "self.withUnknownList.size() == 5": 4,
801
802 "self.withUnknownList[0] != self.withUnknownList[1]": 7,
803 "self.withUnknownList[1] == self.withUnknownList[2]": 7,
804 "self.withUnknownList[3] == self.withUnknownList[4]": 6,
805
806
807 "self.withUnknownFieldList[0] != self.withUnknownFieldList[1]": 7,
808 "self.withUnknownFieldList[1] == self.withUnknownFieldList[2]": 7,
809 },
810 },
811 {name: "known and unknown fields",
812 obj: map[string]interface{}{
813 "withUnknown": map[string]interface{}{
814 "known": 1,
815 "unknown": "a",
816 },
817 "withUnknownList": []interface{}{
818 map[string]interface{}{
819 "known": 1,
820 "unknown": "a",
821 },
822 map[string]interface{}{
823 "known": 1,
824 "unknown": "b",
825 },
826 map[string]interface{}{
827 "known": 1,
828 "unknown": "b",
829 },
830 map[string]interface{}{
831 "known": 1,
832 },
833 map[string]interface{}{
834 "known": 1,
835 },
836 map[string]interface{}{
837 "known": 2,
838 },
839 },
840 },
841 schema: &schema.Structural{
842 Generic: schema.Generic{
843 Type: "object",
844 },
845 Properties: map[string]schema.Structural{
846 "withUnknown": {
847 Generic: schema.Generic{Type: "object"},
848 Extensions: schema.Extensions{
849 XPreserveUnknownFields: true,
850 },
851 Properties: map[string]schema.Structural{
852 "known": integerType,
853 },
854 },
855 "withUnknownList": listType(&schema.Structural{
856 Generic: schema.Generic{Type: "object"},
857 Extensions: schema.Extensions{
858 XPreserveUnknownFields: true,
859 },
860 Properties: map[string]schema.Structural{
861 "known": integerType,
862 },
863 }),
864 },
865 },
866 expectCost: map[string]int64{
867 "self.withUnknown.known == 1": 4,
868
869 "self.withUnknownList[1] == self.withUnknownList[2]": 7,
870
871
872 "self.withUnknownList[0] != self.withUnknownList[1]": 7,
873 "self.withUnknownList[0] != self.withUnknownList[3]": 7,
874 "self.withUnknownList[0] != self.withUnknownList[5]": 7,
875
876
877 "self.withUnknownList[3] == self.withUnknownList[4]": 7,
878 "self.withUnknownList[4] != self.withUnknownList[5]": 7,
879 },
880 },
881 {name: "field nullability",
882 obj: map[string]interface{}{
883 "setPlainStr": "v1",
884 "setDefaultedStr": "v2",
885 "setNullableStr": "v3",
886 "setToNullNullableStr": nil,
887
888
889
890 "unsetDefaultedStr": "default value",
891 },
892 schema: objectTypePtr(map[string]schema.Structural{
893 "unsetPlainStr": stringType,
894 "unsetDefaultedStr": withDefault("default value", stringType),
895 "unsetNullableStr": withNullable(true, stringType),
896
897 "setPlainStr": stringType,
898 "setDefaultedStr": withDefault("default value", stringType),
899 "setNullableStr": withNullable(true, stringType),
900 "setToNullNullableStr": withNullable(true, stringType),
901 }),
902 expectCost: map[string]int64{
903 "!has(self.unsetPlainStr)": 2,
904 "has(self.unsetDefaultedStr) && self.unsetDefaultedStr == 'default value'": 5,
905 "!has(self.unsetNullableStr)": 2,
906
907 "has(self.setPlainStr) && self.setPlainStr == 'v1'": 4,
908 "has(self.setDefaultedStr) && self.setDefaultedStr == 'v2'": 4,
909 "has(self.setNullableStr) && self.setNullableStr == 'v3'": 4,
910
911
912 "type(self.setNullableStr) != null_type": 4,
913
914
915 "!has(self.setToNullNullableStr)": 2,
916 },
917 },
918 {name: "null values in container types",
919 obj: map[string]interface{}{
920 "m": map[string]interface{}{
921 "a": nil,
922 "b": "not-nil",
923 },
924 "l": []interface{}{
925 nil, "not-nil",
926 },
927 "s": []interface{}{
928 nil, "not-nil",
929 },
930 },
931 schema: objectTypePtr(map[string]schema.Structural{
932 "m": mapType(withNullablePtr(true, stringType)),
933 "l": listType(withNullablePtr(true, stringType)),
934 "s": listSetType(withNullablePtr(true, stringType)),
935 }),
936 expectCost: map[string]int64{
937 "self.m.size() == 2": 4,
938 "'a' in self.m": 3,
939 "type(self.m['a']) == null_type": 5,
940
941 },
942 },
943 {name: "object types are not accessible",
944 obj: map[string]interface{}{
945 "nestedInMap": map[string]interface{}{
946 "k1": map[string]interface{}{
947 "inMapField": 1,
948 },
949 "k2": map[string]interface{}{
950 "inMapField": 2,
951 },
952 },
953 "nestedInList": []interface{}{
954 map[string]interface{}{
955 "inListField": 1,
956 },
957 map[string]interface{}{
958 "inListField": 2,
959 },
960 },
961 },
962 schema: objectTypePtr(map[string]schema.Structural{
963 "nestedInMap": mapType(objectTypePtr(map[string]schema.Structural{
964 "inMapField": integerType,
965 })),
966 "nestedInList": listType(objectTypePtr(map[string]schema.Structural{
967 "inListField": integerType,
968 })),
969 }),
970 expectCost: map[string]int64{
971
972
973
974 "type(self) == type(self)": 5,
975 "type(self.nestedInMap['k1']) == type(self.nestedInMap['k2'])": 9,
976 },
977 },
978 {name: "listMaps with unsupported identity characters in property names",
979 obj: map[string]interface{}{
980 "objs": []interface{}{
981 []interface{}{
982 map[string]interface{}{"k!": "a", "k.": "1"},
983 map[string]interface{}{"k!": "b", "k.": "2"},
984 },
985 []interface{}{
986 map[string]interface{}{"k!": "b", "k.": "2"},
987 map[string]interface{}{"k!": "a", "k.": "1"},
988 },
989 []interface{}{
990 map[string]interface{}{"k!": "b", "k.": "2"},
991 map[string]interface{}{"k!": "c", "k.": "1"},
992 },
993 []interface{}{
994 map[string]interface{}{"k!": "b", "k.": "2"},
995 map[string]interface{}{"k!": "a", "k.": "3"},
996 },
997 },
998 },
999 schema: objectTypePtr(map[string]schema.Structural{
1000 "objs": listType(listMapTypePtr([]string{"k!", "k."}, objectTypePtr(map[string]schema.Structural{
1001 "k!": stringType,
1002 "k.": stringType,
1003 }))),
1004 }),
1005 expectCost: map[string]int64{
1006 "self.objs[0] == self.objs[1]": 7,
1007 "self.objs[0][0].k__dot__ == '1'": 6,
1008 },
1009 },
1010 {name: "container type composition",
1011 obj: map[string]interface{}{
1012 "obj": map[string]interface{}{
1013 "field": "a",
1014 },
1015 "mapOfMap": map[string]interface{}{
1016 "x": map[string]interface{}{
1017 "y": "b",
1018 },
1019 },
1020 "mapOfObj": map[string]interface{}{
1021 "k": map[string]interface{}{
1022 "field2": "c",
1023 },
1024 },
1025 "mapOfListMap": map[string]interface{}{
1026 "o": []interface{}{
1027 map[string]interface{}{
1028 "k": "1",
1029 "v": "d",
1030 },
1031 },
1032 },
1033 "mapOfList": map[string]interface{}{
1034 "l": []interface{}{"e"},
1035 },
1036 "listMapOfObj": []interface{}{
1037 map[string]interface{}{
1038 "k2": "2",
1039 "v2": "f",
1040 },
1041 },
1042 "listOfMap": []interface{}{
1043 map[string]interface{}{
1044 "z": "g",
1045 },
1046 },
1047 "listOfObj": []interface{}{
1048 map[string]interface{}{
1049 "field3": "h",
1050 },
1051 },
1052 "listOfListMap": []interface{}{
1053 []interface{}{
1054 map[string]interface{}{
1055 "k3": "3",
1056 "v3": "i",
1057 },
1058 },
1059 },
1060 },
1061 schema: objectTypePtr(map[string]schema.Structural{
1062 "obj": objectType(map[string]schema.Structural{
1063 "field": stringType,
1064 }),
1065 "mapOfMap": mapType(mapTypePtr(&stringType)),
1066 "mapOfObj": mapType(objectTypePtr(map[string]schema.Structural{
1067 "field2": stringType,
1068 })),
1069 "mapOfListMap": mapType(listMapTypePtr([]string{"k"}, objectTypePtr(map[string]schema.Structural{
1070 "k": stringType,
1071 "v": stringType,
1072 }))),
1073 "mapOfList": mapType(listTypePtr(&stringType)),
1074 "listMapOfObj": listMapType([]string{"k2"}, objectTypePtr(map[string]schema.Structural{
1075 "k2": stringType,
1076 "v2": stringType,
1077 })),
1078 "listOfMap": listType(mapTypePtr(&stringType)),
1079 "listOfObj": listType(objectTypePtr(map[string]schema.Structural{
1080 "field3": stringType,
1081 })),
1082 "listOfListMap": listType(listMapTypePtr([]string{"k3"}, objectTypePtr(map[string]schema.Structural{
1083 "k3": stringType,
1084 "v3": stringType,
1085 }))),
1086 }),
1087 expectCost: map[string]int64{
1088 "self.obj.field == 'a'": 4,
1089 "self.mapOfMap['x']['y'] == 'b'": 5,
1090 "self.mapOfObj['k'].field2 == 'c'": 5,
1091 "self.mapOfListMap['o'].exists(e, e.k == '1' && e.v == 'd')": 14,
1092 "self.mapOfList['l'][0] == 'e'": 5,
1093 "self.listMapOfObj.exists(e, e.k2 == '2' && e.v2 == 'f')": 13,
1094 "self.listOfMap[0]['z'] == 'g'": 5,
1095 "self.listOfObj[0].field3 == 'h'": 5,
1096 "self.listOfListMap[0].exists(e, e.k3 == '3' && e.v3 == 'i')": 14,
1097
1098
1099 "self.mapOfMap.map(k, k).map(k, k).size() == 1": 32,
1100 "self.mapOfListMap.map(k, k).map(k, k).size() == 1": 32,
1101 "self.mapOfList.map(k, k).map(k, k).size() == 1": 32,
1102 "self.listOfMap.map(e, e).map(e, e).size() == 1": 32,
1103 "self.listOfListMap.map(e, e).map(e, e).size() == 1": 32,
1104
1105
1106 "self.mapOfMap.map(k, self.mapOfMap[k].map(m, m)).size() == 1": 34,
1107 "self.mapOfListMap.map(k, self.mapOfListMap[k].map(m, m)).size() == 1": 34,
1108 "self.mapOfList.map(k, self.mapOfList[k].map(l, l)).size() == 1": 34,
1109 "self.listOfMap.map(e, e.map(m, m)).size() == 1": 32,
1110 "self.listOfListMap.map(e, e.map(e, e)).size() == 1": 32,
1111 },
1112 },
1113 {name: "optionals",
1114 obj: map[string]interface{}{
1115 "obj": map[string]interface{}{
1116 "field": "a",
1117 },
1118 "m": map[string]interface{}{
1119 "k": "v",
1120 },
1121 "l": []interface{}{
1122 "a",
1123 },
1124 },
1125 schema: objectTypePtr(map[string]schema.Structural{
1126 "obj": objectType(map[string]schema.Structural{
1127 "field": stringType,
1128 "absentField": stringType,
1129 }),
1130 "m": mapType(&stringType),
1131 "l": listType(&stringType),
1132 }),
1133 expectCost: map[string]int64{
1134 "optional.of('a') != optional.of('b')": 3,
1135 "optional.of('a') != optional.none()": 3,
1136 "optional.of('a').hasValue()": 2,
1137 "optional.of('a').or(optional.of('a')).hasValue()": 2,
1138 "optional.none().or(optional.of('a')).hasValue()": 3,
1139 "optional.of('a').optMap(v, v == 'value').hasValue()": 8,
1140 "self.obj.?field == optional.of('a')": 5,
1141 "self.obj.?absentField == optional.none()": 4,
1142 "self.obj.?field.orValue('v') == 'a'": 4,
1143 "self.m[?'k'] == optional.of('v')": 5,
1144 "self.l[?0] == optional.of('a')": 5,
1145 "optional.ofNonZeroValue(1).hasValue()": 2,
1146 },
1147 },
1148 {name: "quantity",
1149 obj: objs("20", "200M"),
1150 schema: schemas(stringType, stringType),
1151 expectCost: map[string]int64{
1152 `isQuantity(self.val1)`: 3,
1153 `isQuantity(self.val2)`: 3,
1154 `isQuantity("200M")`: 1,
1155 `isQuantity("20Mi")`: 1,
1156 `quantity("200M") == quantity("0.2G") && quantity("0.2G") == quantity("200M")`: 6,
1157 `quantity("2M") == quantity("0.002G") && quantity("2000k") == quantity("2M") && quantity("0.002G") == quantity("2000k")`: 9,
1158 `quantity(self.val1).isLessThan(quantity(self.val2))`: 7,
1159 `quantity("50M").isLessThan(quantity("100M"))`: 3,
1160 `quantity("50Mi").isGreaterThan(quantity("50M"))`: 3,
1161 `quantity("200M").compareTo(quantity("0.2G")) == 0`: 4,
1162 `quantity("50k").add(quantity("20")) == quantity("50.02k")`: 5,
1163 `quantity("50k").sub(20) == quantity("49980")`: 4,
1164 `quantity("50").isInteger()`: 2,
1165 `quantity(self.val1).isInteger()`: 4,
1166 },
1167 },
1168 }
1169
1170 for _, tt := range cases {
1171 tt := tt
1172 t.Run(tt.name, func(t *testing.T) {
1173 t.Parallel()
1174 for validRule, expectedCost := range tt.expectCost {
1175 validRule := validRule
1176 expectedCost := expectedCost
1177 testName := validRule
1178 if len(testName) > 127 {
1179 testName = testName[:127]
1180 }
1181 t.Run(testName, func(t *testing.T) {
1182 t.Parallel()
1183 s := withRule(*tt.schema, validRule)
1184 celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
1185 if celValidator == nil {
1186 t.Fatal("expected non nil validator")
1187 }
1188 ctx := context.TODO()
1189 errs, remainingBudegt := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
1190 for _, err := range errs {
1191 t.Errorf("unexpected error: %v", err)
1192 }
1193 rtCost := celconfig.RuntimeCELCostBudget - remainingBudegt
1194 if rtCost != expectedCost {
1195 t.Fatalf("runtime cost %d does not match expected runtime cost %d", rtCost, expectedCost)
1196 }
1197 })
1198 }
1199 })
1200 }
1201 }
1202
1203 func buildLargeArray(size int) []interface{} {
1204 lArray := make([]interface{}, size)
1205 for i := 0; i < len(lArray); i++ {
1206 lArray[i] = i
1207 }
1208 return lArray
1209 }
1210
1211 func TestCelEstimatedCostStability(t *testing.T) {
1212 cases := []struct {
1213 name string
1214 schema *schema.Structural
1215 expectCost map[string]uint64
1216 }{
1217 {name: "integers",
1218
1219 schema: schemas(integerType, integerType, int32Type, int32Type, int64Type, int64Type),
1220 expectCost: map[string]uint64{
1221 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", fmt.Sprintf("%d", math.MaxInt64)): 8,
1222 "self.val1 == self.val6": 4,
1223 "type(self.val1) == int": 4,
1224 fmt.Sprintf("self.val3 + 1 == %d + 1", math.MaxInt32): 5,
1225 },
1226 },
1227 {name: "numbers",
1228 schema: schemas(numberType, numberType, floatType, floatType, doubleType, doubleType, doubleType),
1229 expectCost: map[string]uint64{
1230 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", fmt.Sprintf("%f", math.MaxFloat64)): 8,
1231 "self.val1 == self.val6": 4,
1232 "type(self.val1) == double": 4,
1233
1234
1235
1236
1237 "type(self.val7) == double": 4,
1238 "self.val7 == 1.0": 2,
1239 },
1240 },
1241 {name: "numeric comparisons",
1242 schema: schemas(integerType, numberType, floatType, doubleType, numberType, floatType, doubleType),
1243 expectCost: map[string]uint64{
1244
1245
1246
1247 "double(self.val1) < self.val4": 6,
1248 "self.val1 < int(self.val4)": 6,
1249 "double(self.val1) < self.val5": 6,
1250 "self.val1 < int(self.val5)": 6,
1251 "double(self.val1) < self.val6": 6,
1252 "self.val1 < int(self.val6)": 6,
1253
1254
1255 "double(5) < 10.0": 2,
1256 "5 < int(10.0)": 2,
1257
1258
1259 "double(self.val1) < 10.0": 4,
1260 },
1261 },
1262 {name: "unicode strings",
1263 schema: schemas(stringType, stringType),
1264 expectCost: map[string]uint64{
1265 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "'Rook takes π'"): 314585,
1266 "self.val1.startsWith('Rook')": 3,
1267 "!self.val1.startsWith('knight')": 4,
1268 "self.val1.matches('^[^0-9]*$')": 943721,
1269 "!self.val1.matches('^[0-9]*$')": 629149,
1270 "type(self.val1) == string": 4,
1271 "size(self.val1) == 12": 4,
1272
1273
1274 "self.val1.charAt(3) == 'k'": 4,
1275 "self.val1.indexOf('o') == 1": 314576,
1276 "self.val1.indexOf('o', 2) == 2": 314576,
1277 "self.val1.replace(' ', 'x') == 'Rookxtakesxπ'": 629150,
1278 "self.val1.replace(' ', 'x', 1) == 'Rookxtakes π'": 629150,
1279 "self.val1.split(' ') == ['Rook', 'takes', 'π']": 629159,
1280 "self.val1.split(' ', 2) == ['Rook', 'takes π']": 629159,
1281 "self.val1.substring(5) == 'takes π'": 314576,
1282 "self.val1.substring(0, 4) == 'Rook'": 314576,
1283 "self.val1.substring(4, 10).trim() == 'takes'": 629149,
1284 "self.val1.upperAscii() == 'ROOK TAKES π'": 314577,
1285 "self.val1.lowerAscii() == 'rook takes π'": 314577,
1286 "self.val1.lowerAscii() == self.val1.lowerAscii()": 943723,
1287 },
1288 },
1289 {name: "escaped strings",
1290 schema: schemas(stringType, stringType),
1291 expectCost: map[string]uint64{
1292 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "'l1\\nl2'"): 314583,
1293 "self.val1 == '''l1\nl2'''": 3,
1294 },
1295 },
1296 {name: "bytes",
1297 schema: schemas(byteType, byteType),
1298 expectCost: map[string]uint64{
1299 "self.val1 == self.val2": 314577,
1300 "self.val1 == b'AB'": 3,
1301 "type(self.val1) == bytes": 4,
1302 "size(self.val1) == 2": 4,
1303 },
1304 },
1305 {name: "booleans",
1306 schema: schemas(booleanType, booleanType, booleanType, booleanType),
1307 expectCost: map[string]uint64{
1308 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "true"): 8,
1309 "self.val1 != self.val4": 4,
1310 "type(self.val1) == bool": 4,
1311 },
1312 },
1313 {name: "duration format",
1314 schema: schemas(durationFormat, durationFormat),
1315 expectCost: map[string]uint64{
1316 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "duration('1h2m3s4ms')"): 16,
1317 "self.val1 == duration('1h2m') + duration('3s4ms')": 6,
1318 "self.val1.getHours() == 1": 4,
1319 "type(self.val1) == google.protobuf.Duration": 4,
1320 },
1321 },
1322 {name: "date format",
1323 schema: schemas(dateFormat, dateFormat),
1324 expectCost: map[string]uint64{
1325 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "timestamp('1997-07-16T00:00:00.000Z')"): 14,
1326 "self.val1.getDate() == 16": 4,
1327 "type(self.val1) == google.protobuf.Timestamp": 4,
1328 },
1329 },
1330 {name: "date-time format",
1331 schema: schemas(dateTimeFormat, dateTimeFormat),
1332 expectCost: map[string]uint64{
1333 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "timestamp('2011-08-18T19:03:37.010+01:00')"): 16,
1334 "self.val1 == timestamp('2011-08-18T00:00:00.000+01:00') + duration('19h3m37s10ms')": 6,
1335 "self.val1.getDate('01:00') == 18": 4,
1336 "type(self.val1) == google.protobuf.Timestamp": 4,
1337 },
1338 },
1339 {name: "enums",
1340 schema: objectTypePtr(map[string]schema.Structural{"enumStr": {
1341 Generic: schema.Generic{
1342 Type: "string",
1343 },
1344 ValueValidation: &schema.ValueValidation{
1345 Enum: []schema.JSON{
1346 {Object: "Pending"},
1347 {Object: "Available"},
1348 {Object: "Bound"},
1349 {Object: "Released"},
1350 {Object: "Failed"},
1351 },
1352 },
1353 }}),
1354 expectCost: map[string]uint64{
1355 "self.enumStr == 'Pending'": 3,
1356 "self.enumStr in ['Pending', 'Available']": 14,
1357 },
1358 },
1359 {name: "conversions",
1360 schema: schemas(integerType, numberType, numberType, numberType, booleanType, stringType, byteType, stringType, durationFormat, stringType, dateTimeFormat, stringType, dateFormat),
1361 expectCost: map[string]uint64{
1362 "int(self.val2) == self.val1": 5,
1363 "double(self.val1) == self.val2": 5,
1364 "bytes(self.val6) == self.val7": 629150,
1365 "string(self.val1) == self.val6": 314578,
1366 "string(self.val4) == '10.5'": 4,
1367 "string(self.val7) == self.val6": 629150,
1368 "duration(self.val8) == self.val9": 6,
1369 "timestamp(self.val10) == self.val11": 6,
1370 "string(self.val11) == self.val10": 314578,
1371 "timestamp(self.val12) == self.val13": 6,
1372 "string(self.val13) == self.val12": 314578,
1373 },
1374 },
1375 {name: "lists",
1376 schema: schemas(listType(&integerType), listType(&integerType)),
1377 expectCost: map[string]uint64{
1378 ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "[1, 2, 3]"): 157317,
1379 "1 in self.val1": 1572865,
1380 "self.val2[0] in self.val1": 1572868,
1381 "!(0 in self.val1)": 1572866,
1382 "self.val1 + self.val2 == [1, 2, 3, 1, 2, 3]": 16,
1383 "self.val1 + [4, 5] == [1, 2, 3, 4, 5]": 24,
1384 "has(self.val1)": 1,
1385 "has(self.val1) && has(self.val2)": 2,
1386 "!has(self.val1)": 2,
1387 "self.val1.all(k, size(self.val1) > 0)": 11010044,
1388 "self.val1.exists_one(k, self.val1 == [2])": 23592949,
1389 "!self.val1.exists_one(k, size(self.val1) > 0)": 9437183,
1390 "size(self.val1) == 2": 4,
1391 "size(self.val1.filter(k, size(self.val1) > 1)) == 1": 26738686,
1392 },
1393 },
1394 {name: "listSets",
1395 schema: schemas(listSetType(&stringType), listSetType(&stringType)),
1396 expectCost: map[string]uint64{
1397
1398 "self.val1 == ['c', 'b', 'a']": 13,
1399 "self.val1 == self.val2": 104862,
1400 "'a' in self.val1": 1048577,
1401 "self.val2[0] in self.val1": 1048580,
1402 "!('x' in self.val1)": 1048578,
1403 "self.val1 + self.val2 == ['a', 'b', 'c']": 16,
1404 "self.val1 + ['c', 'd'] == ['a', 'b', 'c', 'd']": 24,
1405 "has(self.val1)": 1,
1406 "has(self.val1) && has(self.val2)": 2,
1407 "!has(self.val1)": 2,
1408 "self.val1.all(k, size(self.val1) > 0)": 7340028,
1409 "self.val1.exists_one(k, self.val1 == ['a'])": 15728629,
1410 "!self.val1.exists_one(k, size(self.val1) > 0)": 6291455,
1411 "size(self.val1) == 2": 4,
1412 "size(self.val1.filter(k, size(self.val1) > 1)) == 1": 17825790,
1413 },
1414 },
1415 {name: "listMaps",
1416 schema: objectTypePtr(map[string]schema.Structural{
1417 "objs": listType(listMapTypePtr([]string{"k"}, objectTypePtr(map[string]schema.Structural{
1418 "k": stringType,
1419 "v": stringType,
1420 }))),
1421 }),
1422 expectCost: map[string]uint64{
1423 "self.objs[0] == self.objs[1]": 104864,
1424 "self.objs[0] + self.objs[2] == self.objs[2]": 104868,
1425 "self.objs[2] + self.objs[0] == self.objs[0]": 104868,
1426
1427 "self.objs[0] == [self.objs[0][0], self.objs[0][1]]": 22,
1428 "self.objs[0] == [self.objs[0][1], self.objs[0][0]]": 22,
1429
1430 "self.objs[2] + [self.objs[0][0], self.objs[0][1]] == self.objs[0]": 104883,
1431 "size(self.objs[0] + [self.objs[3][0]]) == 3": 20,
1432 "has(self.objs)": 1,
1433 "has(self.objs) && has(self.objs)": 2,
1434 "!has(self.objs)": 2,
1435 "self.objs[0].all(k, size(self.objs[0]) > 0)": 8388604,
1436 "self.objs[0].exists_one(k, size(self.objs[0]) > 0)": 7340030,
1437 "!self.objs[0].exists_one(k, size(self.objs[0]) > 0)": 7340031,
1438 "size(self.objs[0]) == 2": 5,
1439 "size(self.objs[0].filter(k, size(self.objs[0]) > 1)) == 1": 18874366,
1440 },
1441 },
1442 {name: "maps",
1443 schema: schemas(mapType(&stringType), mapType(&stringType)),
1444 expectCost: map[string]uint64{
1445 "self.val1 == self.val2": 39326,
1446 "'k1' in self.val1": 3,
1447 "!('k3' in self.val1)": 4,
1448 "self.val1 == {'k1': 'a', 'k2': 'b'}": 33,
1449 "has(self.val1)": 1,
1450 "has(self.val1) && has(self.val2)": 2,
1451 "!has(self.val1)": 2,
1452 "self.val1.all(k, size(self.val1) > 0)": 2752508,
1453 "self.val1.exists_one(k, size(self.val1) > 0)": 2359294,
1454 "!self.val1.exists_one(k, size(self.val1) > 0)": 2359295,
1455 "size(self.val1) == 2": 4,
1456 "size(self.val1.filter(k, size(self.val1) > 1)) == 1": 6684670,
1457 },
1458 },
1459 {name: "objects",
1460 schema: objectTypePtr(map[string]schema.Structural{
1461 "objs": listType(objectTypePtr(map[string]schema.Structural{
1462 "f1": stringType,
1463 "f2": stringType,
1464 })),
1465 }),
1466 expectCost: map[string]uint64{
1467 "self.objs[0] == self.objs[1]": 6,
1468 },
1469 },
1470 {name: "object access",
1471 schema: objectTypePtr(map[string]schema.Structural{
1472 "a": objectType(map[string]schema.Structural{
1473 "b": integerType,
1474 "c": integerType,
1475 "d": withNullable(true, integerType),
1476 }),
1477 "a1": objectType(map[string]schema.Structural{
1478 "b1": objectType(map[string]schema.Structural{
1479 "c1": integerType,
1480 }),
1481 "d2": objectType(map[string]schema.Structural{
1482 "e2": integerType,
1483 }),
1484 }),
1485 }),
1486
1487 expectCost: map[string]uint64{
1488 "has(self.a.b)": 2,
1489 "has(self.a1.b1.c1)": 3,
1490 "!(has(self.a1.d2) && has(self.a1.d2.e2))": 6,
1491 "!has(self.a1.d2)": 3,
1492 "has(self.a)": 1,
1493 "has(self.a) && has(self.a1)": 2,
1494 "!has(self.a)": 2,
1495 },
1496 },
1497 {name: "map access",
1498 schema: objectTypePtr(map[string]schema.Structural{
1499 "val": mapType(&integerType),
1500 }),
1501 expectCost: map[string]uint64{
1502
1503 "!('a' in self.val)": 4,
1504 "'b' in self.val": 3,
1505 "!('c' in self.val)": 4,
1506 "'d' in self.val": 3,
1507
1508 "!has(self.val.a)": 3,
1509 "has(self.val.b)": 2,
1510 "!has(self.val.c)": 3,
1511 "has(self.val.d)": 2,
1512 "self.val.all(k, self.val[k] > 0)": 3595115,
1513 "self.val.exists_one(k, self.val[k] == 2)": 2696338,
1514 "!self.val.exists_one(k, self.val[k] > 0)": 3145728,
1515 "size(self.val) == 2": 4,
1516 "size(self.val.filter(k, self.val[k] > 1)) == 1": 8089017,
1517 },
1518 },
1519 {name: "listMap access",
1520 schema: objectTypePtr(map[string]schema.Structural{
1521 "listMap": listMapType([]string{"k"}, objectTypePtr(map[string]schema.Structural{
1522 "k": stringType,
1523 "v": stringType,
1524 "v2": stringType,
1525 })),
1526 }),
1527 expectCost: map[string]uint64{
1528 "has(self.listMap[0].v)": 3,
1529 "self.listMap.all(m, m.k.startsWith('a'))": 6291453,
1530 "self.listMap.all(m, !has(m.v2) || m.v2 == 'z')": 8388603,
1531 "self.listMap.exists(m, m.k.endsWith('1'))": 7340028,
1532 "self.listMap.exists_one(m, m.k == 'a3')": 5242879,
1533 "!self.listMap.all(m, m.k.endsWith('1'))": 6291454,
1534 "!self.listMap.exists(m, m.v == 'x')": 7340029,
1535 "!self.listMap.exists_one(m, m.k.startsWith('a'))": 5242880,
1536 "size(self.listMap.filter(m, m.k == 'a1')) == 1": 16777215,
1537 "self.listMap.exists(m, m.k == 'a1' && m.v == 'b1')": 10485753,
1538 "self.listMap.map(m, m.v).exists(v, v == 'b1')": uint64(19922939),
1539
1540
1541
1542
1543 "self.listMap.exists(m, has(m.v2) && m.v2 == 'z')": 8388603,
1544 "!self.listMap.all(m, has(m.v2) && m.v2 != 'z')": 7340029,
1545 "self.listMap.exists_one(m, has(m.v2) && m.v2 == 'z')": 6291454,
1546 "self.listMap.filter(m, has(m.v2) && m.v2 == 'z').size() == 1": 17825790,
1547
1548 "self.listMap.map(m, has(m.v2) && m.v2 == 'z', m.v2).size() == 1": 18874365,
1549 "self.listMap.filter(m, has(m.v2) && m.v2 == 'z').map(m, m.v2).size() == 1": uint64(32505851),
1550
1551
1552
1553 "self.listMap.exists(m, m.v2 == 'z')": 7340028,
1554 "!self.listMap.all(m, m.v2 != 'z')": 6291454,
1555 },
1556 },
1557 {name: "list access",
1558 schema: objectTypePtr(map[string]schema.Structural{
1559 "array": listType(&integerType),
1560 }),
1561 expectCost: map[string]uint64{
1562 "2 in self.array": 1572865,
1563 "self.array.all(e, e > 0)": 7864318,
1564 "self.array.exists(e, e > 2)": 9437181,
1565 "self.array.exists_one(e, e > 4)": 6291456,
1566 "!self.array.all(e, e < 2)": 7864319,
1567 "!self.array.exists(e, e < 0)": 9437182,
1568 "!self.array.exists_one(e, e == 2)": 4718594,
1569 "self.array.all(e, e < 100)": 7864318,
1570 "size(self.array.filter(e, e%2 == 0)) == 3": 25165823,
1571 "self.array.map(e, e * 20).filter(e, e > 50).exists(e, e == 60)": uint64(53477367),
1572 "size(self.array) == 8": 4,
1573 },
1574 },
1575 {name: "listSet access",
1576 schema: objectTypePtr(map[string]schema.Structural{
1577 "set": listType(&integerType),
1578 }),
1579 expectCost: map[string]uint64{
1580 "3 in self.set": 1572865,
1581 "self.set.all(e, e > 0)": 7864318,
1582 "self.set.exists(e, e > 3)": 9437181,
1583 "self.set.exists_one(e, e == 3)": 4718593,
1584 "!self.set.all(e, e < 3)": 7864319,
1585 "!self.set.exists(e, e < 0)": 9437182,
1586 "!self.set.exists_one(e, e > 3)": 6291457,
1587 "self.set.all(e, e < 10)": 7864318,
1588 "size(self.set.filter(e, e%2 == 0)) == 2": 25165823,
1589 "self.set.map(e, e * 20).filter(e, e > 50).exists_one(e, e == 60)": uint64(50331642),
1590 "size(self.set) == 5": 4,
1591 },
1592 },
1593 {name: "typemeta and objectmeta access specified",
1594 schema: objectTypePtr(map[string]schema.Structural{
1595 "kind": stringType,
1596 "apiVersion": stringType,
1597 "metadata": objectType(map[string]schema.Structural{
1598 "name": stringType,
1599 "generateName": stringType,
1600 }),
1601 }),
1602 expectCost: map[string]uint64{
1603 "self.kind == 'Pod'": 3,
1604 "self.apiVersion == 'v1'": 3,
1605 "self.metadata.name == 'foo'": 4,
1606 "self.metadata.generateName == 'pickItForMe'": 5,
1607 },
1608 },
1609
1610
1611 {name: "embedded object",
1612 schema: objectTypePtr(map[string]schema.Structural{
1613 "embedded": {
1614 Generic: schema.Generic{Type: "object"},
1615 Extensions: schema.Extensions{
1616 XEmbeddedResource: true,
1617 },
1618 },
1619 }),
1620 expectCost: map[string]uint64{
1621
1622
1623 "self.embedded.kind == 'Pod'": 4,
1624 "self.embedded.apiVersion == 'v1'": 4,
1625 "self.embedded.metadata.name == 'foo'": 5,
1626 "self.embedded.metadata.generateName == 'pickItForMe'": 6,
1627 },
1628 },
1629 {name: "embedded object with properties",
1630 schema: objectTypePtr(map[string]schema.Structural{
1631 "embedded": {
1632 Generic: schema.Generic{Type: "object"},
1633 Extensions: schema.Extensions{
1634 XEmbeddedResource: true,
1635 },
1636 Properties: map[string]schema.Structural{
1637 "kind": stringType,
1638 "apiVersion": stringType,
1639 "metadata": objectType(map[string]schema.Structural{
1640 "name": stringType,
1641 "generateName": stringType,
1642 }),
1643 "spec": objectType(map[string]schema.Structural{
1644 "field1": stringType,
1645 }),
1646 },
1647 },
1648 }),
1649 expectCost: map[string]uint64{
1650
1651
1652 "self.embedded.kind == 'Pod'": 4,
1653 "self.embedded.apiVersion == 'v1'": 4,
1654 "self.embedded.metadata.name == 'foo'": 5,
1655 "self.embedded.metadata.generateName == 'pickItForMe'": 6,
1656
1657 "self.embedded.spec.field1 == 'a'": 5,
1658 },
1659 },
1660 {name: "embedded object with preserve unknown",
1661 schema: objectTypePtr(map[string]schema.Structural{
1662 "embedded": {
1663 Generic: schema.Generic{Type: "object"},
1664 Extensions: schema.Extensions{
1665 XPreserveUnknownFields: true,
1666 XEmbeddedResource: true,
1667 },
1668 },
1669 }),
1670 expectCost: map[string]uint64{
1671
1672
1673 "self.embedded.kind == 'Pod'": 4,
1674 "self.embedded.apiVersion == 'v1'": 4,
1675 "self.embedded.metadata.name == 'foo'": 5,
1676 "self.embedded.metadata.generateName == 'pickItForMe'": 6,
1677
1678
1679 "has(self.embedded)": 1,
1680 },
1681 },
1682 {name: "string in intOrString",
1683 schema: objectTypePtr(map[string]schema.Structural{
1684 "something": intOrStringType(),
1685 }),
1686 expectCost: map[string]uint64{
1687
1688 "type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
1689
1690 "type(self.something) == string && self.something == '25%'": 7,
1691
1692
1693
1694 "self.something == '25%'": 3,
1695 "self.something != 1": 3,
1696 "self.something == 1 || self.something == '25%'": 6,
1697 "self.something == '25%' || self.something == 1": 6,
1698
1699
1700
1701 "self.something != ['anything']": 13,
1702 },
1703 },
1704 {name: "int in intOrString",
1705 schema: objectTypePtr(map[string]schema.Structural{
1706 "something": intOrStringType(),
1707 }),
1708 expectCost: map[string]uint64{
1709
1710 "type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
1711
1712 "type(self.something) == int && self.something == 1": 7,
1713
1714
1715
1716 "self.something == 1": 3,
1717 "self.something != 'some string'": 4,
1718 "self.something == 1 || self.something == '25%'": 6,
1719 "self.something == '25%' || self.something == 1": 6,
1720
1721
1722
1723 "self.something != ['anything']": 13,
1724 },
1725 },
1726 {name: "null in intOrString",
1727 schema: objectTypePtr(map[string]schema.Structural{
1728 "something": withNullable(true, intOrStringType()),
1729 }),
1730 expectCost: map[string]uint64{
1731 "!has(self.something)": 2,
1732 },
1733 },
1734 {name: "percent comparison using intOrString",
1735 schema: objectTypePtr(map[string]schema.Structural{
1736 "min": intOrStringType(),
1737 "current": integerType,
1738 "available": integerType,
1739 }),
1740 expectCost: map[string]uint64{
1741
1742 `type(self.min) == string && self.min.matches(r'(\d+(\.\d+)?%)')`: 1258298,
1743
1744 "type(self.min) == int ? self.current <= self.min : double(self.current) / double(self.available) >= double(self.min.replace('%', '')) / 100.0": 629162,
1745 },
1746 },
1747 {name: "preserve unknown fields",
1748 schema: objectTypePtr(map[string]schema.Structural{
1749 "withUnknown": {
1750 Generic: schema.Generic{Type: "object"},
1751 Extensions: schema.Extensions{
1752 XPreserveUnknownFields: true,
1753 },
1754 },
1755 "withUnknownList": listType(&schema.Structural{
1756 Generic: schema.Generic{Type: "object"},
1757 Extensions: schema.Extensions{
1758 XPreserveUnknownFields: true,
1759 },
1760 }),
1761 "withUnknownFieldList": listType(&schema.Structural{
1762 Generic: schema.Generic{Type: "object"},
1763 Properties: map[string]schema.Structural{
1764 "fieldOfUnknownType": {
1765 Extensions: schema.Extensions{
1766 XPreserveUnknownFields: true,
1767 },
1768 },
1769 },
1770 }),
1771 "anyvalList": listType(&schema.Structural{
1772 Extensions: schema.Extensions{
1773 XPreserveUnknownFields: true,
1774 },
1775 }),
1776 "anyvalMap": mapType(&schema.Structural{
1777 Extensions: schema.Extensions{
1778 XPreserveUnknownFields: true,
1779 },
1780 }),
1781 "anyvalField1": {
1782 Extensions: schema.Extensions{
1783 XPreserveUnknownFields: true,
1784 },
1785 },
1786 "anyvalField2": {
1787 Extensions: schema.Extensions{
1788 XPreserveUnknownFields: true,
1789 },
1790 },
1791 }),
1792 expectCost: map[string]uint64{
1793 "has(self.withUnknown)": 1,
1794 "self.withUnknownList.size() == 5": 4,
1795
1796 "self.withUnknownList[0] != self.withUnknownList[1]": 6,
1797 "self.withUnknownList[1] == self.withUnknownList[2]": 6,
1798 "self.withUnknownList[3] == self.withUnknownList[4]": 6,
1799
1800
1801 "self.withUnknownFieldList[0] != self.withUnknownFieldList[1]": 6,
1802 "self.withUnknownFieldList[1] == self.withUnknownFieldList[2]": 6,
1803 },
1804 },
1805 {name: "known and unknown fields",
1806 schema: &schema.Structural{
1807 Generic: schema.Generic{
1808 Type: "object",
1809 },
1810 Properties: map[string]schema.Structural{
1811 "withUnknown": {
1812 Generic: schema.Generic{Type: "object"},
1813 Extensions: schema.Extensions{
1814 XPreserveUnknownFields: true,
1815 },
1816 Properties: map[string]schema.Structural{
1817 "known": integerType,
1818 },
1819 },
1820 "withUnknownList": listType(&schema.Structural{
1821 Generic: schema.Generic{Type: "object"},
1822 Extensions: schema.Extensions{
1823 XPreserveUnknownFields: true,
1824 },
1825 Properties: map[string]schema.Structural{
1826 "known": integerType,
1827 },
1828 }),
1829 },
1830 },
1831 expectCost: map[string]uint64{
1832 "self.withUnknown.known == 1": 3,
1833
1834 "self.withUnknownList[1] == self.withUnknownList[2]": 6,
1835
1836
1837 "self.withUnknownList[0] != self.withUnknownList[1]": 6,
1838 "self.withUnknownList[0] != self.withUnknownList[3]": 6,
1839 "self.withUnknownList[0] != self.withUnknownList[5]": 6,
1840
1841
1842 "self.withUnknownList[3] == self.withUnknownList[4]": 6,
1843 "self.withUnknownList[4] != self.withUnknownList[5]": 6,
1844 },
1845 },
1846 {name: "field nullability",
1847 schema: objectTypePtr(map[string]schema.Structural{
1848 "unsetPlainStr": stringType,
1849 "unsetDefaultedStr": withDefault("default value", stringType),
1850 "unsetNullableStr": withNullable(true, stringType),
1851
1852 "setPlainStr": stringType,
1853 "setDefaultedStr": withDefault("default value", stringType),
1854 "setNullableStr": withNullable(true, stringType),
1855 "setToNullNullableStr": withNullable(true, stringType),
1856 }),
1857 expectCost: map[string]uint64{
1858 "!has(self.unsetPlainStr)": 2,
1859 "has(self.unsetDefaultedStr) && self.unsetDefaultedStr == 'default value'": 5,
1860 "!has(self.unsetNullableStr)": 2,
1861
1862 "has(self.setPlainStr) && self.setPlainStr == 'v1'": 4,
1863 "has(self.setDefaultedStr) && self.setDefaultedStr == 'v2'": 4,
1864 "has(self.setNullableStr) && self.setNullableStr == 'v3'": 4,
1865
1866
1867 "type(self.setNullableStr) != null_type": 4,
1868
1869
1870 "!has(self.setToNullNullableStr)": 2,
1871 },
1872 },
1873 {name: "null values in container types",
1874 schema: objectTypePtr(map[string]schema.Structural{
1875 "m": mapType(withNullablePtr(true, stringType)),
1876 "l": listType(withNullablePtr(true, stringType)),
1877 "s": listSetType(withNullablePtr(true, stringType)),
1878 }),
1879 expectCost: map[string]uint64{
1880 "self.m.size() == 2": 4,
1881 "'a' in self.m": 3,
1882 "type(self.m['a']) == null_type": 5,
1883 },
1884 },
1885 {name: "object types are not accessible",
1886 schema: objectTypePtr(map[string]schema.Structural{
1887 "nestedInMap": mapType(objectTypePtr(map[string]schema.Structural{
1888 "inMapField": integerType,
1889 })),
1890 "nestedInList": listType(objectTypePtr(map[string]schema.Structural{
1891 "inListField": integerType,
1892 })),
1893 }),
1894 expectCost: map[string]uint64{
1895
1896
1897
1898 "type(self) == type(self)": uint64(1844674407370955268),
1899 "type(self.nestedInMap['k1']) == type(self.nestedInMap['k2'])": uint64(1844674407370955272),
1900 },
1901 },
1902 {name: "listMaps with unsupported identity characters in property names",
1903 schema: objectTypePtr(map[string]schema.Structural{
1904 "objs": listType(listMapTypePtr([]string{"k!", "k."}, objectTypePtr(map[string]schema.Structural{
1905 "k!": stringType,
1906 "k.": stringType,
1907 }))),
1908 }),
1909 expectCost: map[string]uint64{
1910 "self.objs[0] == self.objs[1]": 104864,
1911 "self.objs[0][0].k__dot__ == '1'": 6,
1912 },
1913 },
1914 {name: "container type composition",
1915 schema: objectTypePtr(map[string]schema.Structural{
1916 "obj": objectType(map[string]schema.Structural{
1917 "field": stringType,
1918 }),
1919 "mapOfMap": withMaxProperties(mapType(ptr.To(
1920 withMaxProperties(mapType(&stringType), ptr.To[int64](10)))), ptr.To[int64](10)),
1921 "mapOfObj": mapType(objectTypePtr(map[string]schema.Structural{
1922 "field2": stringType,
1923 })),
1924 "mapOfListMap": withMaxProperties(mapType(
1925 ptr.To(withMaxItems(listMapType([]string{"k"},
1926 objectTypePtr(map[string]schema.Structural{
1927 "k": stringType,
1928 "v": stringType,
1929 }),
1930 ), ptr.To[int64](10))),
1931 ), ptr.To[int64](10)),
1932 "mapOfList": withMaxProperties(mapType(
1933 ptr.To(withMaxItems(listType(&stringType), ptr.To[int64](10))),
1934 ), ptr.To[int64](10)),
1935 "listMapOfObj": withMaxItems(listMapType([]string{"k2"}, objectTypePtr(map[string]schema.Structural{
1936 "k2": stringType,
1937 "v2": stringType,
1938 })), ptr.To[int64](10)),
1939 "listOfMap": withMaxItems(listType(
1940 ptr.To(withMaxProperties(mapType(&stringType), ptr.To[int64](10))),
1941 ), ptr.To[int64](10)),
1942 "listOfObj": listType(objectTypePtr(map[string]schema.Structural{
1943 "field3": stringType,
1944 })),
1945 "listOfListMap": withMaxItems(listType(
1946 ptr.To(withMaxItems(listMapType([]string{"k"},
1947 objectTypePtr(map[string]schema.Structural{
1948 "k3": stringType,
1949 "v3": stringType,
1950 }),
1951 ), ptr.To[int64](10))),
1952 ), ptr.To[int64](10)),
1953 }),
1954 expectCost: map[string]uint64{
1955 "self.obj.field == 'a'": 4,
1956 "self.mapOfMap['x']['y'] == 'b'": 5,
1957 "self.mapOfObj['k'].field2 == 'c'": 5,
1958 "self.mapOfListMap['o'].exists(e, e.k == '1' && e.v == 'd')": 104,
1959 "self.mapOfList['l'][0] == 'e'": 5,
1960 "self.listMapOfObj.exists(e, e.k2 == '2' && e.v2 == 'f')": 103,
1961 "self.listOfMap[0]['z'] == 'g'": 5,
1962 "self.listOfObj[0].field3 == 'h'": 5,
1963 "self.listOfListMap[0].exists(e, e.k3 == '3' && e.v3 == 'i')": 104,
1964
1965
1966 "self.mapOfMap.map(k, k).map(k, k).size() == 1": 286,
1967 "self.mapOfListMap.map(k, k).map(k, k).size() == 1": 286,
1968 "self.mapOfList.map(k, k).map(k, k).size() == 1": 286,
1969 "self.listOfMap.map(e, e).map(e, e).size() == 1": 286,
1970 "self.listOfListMap.map(e, e).map(e, e).size() == 1": 286,
1971
1972
1973 "self.mapOfMap.map(k, self.mapOfMap[k].map(m, m)).size() == 1": 1585,
1974 "self.mapOfListMap.map(k, self.mapOfListMap[k].map(m, m)).size() == 1": 1585,
1975 "self.mapOfList.map(k, self.mapOfList[k].map(l, l)).size() == 1": 1585,
1976 "self.listOfMap.map(e, e.map(m, m)).size() == 1": 1555,
1977 "self.listOfListMap.map(e, e.map(e, e)).size() == 1": 1555,
1978 },
1979 },
1980 {name: "optionals",
1981 schema: objectTypePtr(map[string]schema.Structural{
1982 "obj": objectType(map[string]schema.Structural{
1983 "field": stringType,
1984 "absentField": stringType,
1985 }),
1986 "m": mapType(&stringType),
1987 "l": listType(&stringType),
1988 }),
1989 expectCost: map[string]uint64{
1990 "optional.of('a') != optional.of('b')": uint64(1844674407370955266),
1991 "optional.of('a') != optional.none()": uint64(1844674407370955266),
1992 "optional.of('a').hasValue()": 2,
1993 "optional.of('a').or(optional.of('a')).hasValue()": 4,
1994 "optional.none().or(optional.of('a')).hasValue()": 4,
1995 "optional.of('a').optMap(v, v == 'value').hasValue()": 17,
1996 "self.obj.?field == optional.of('a')": uint64(1844674407370955268),
1997 "self.obj.?absentField == optional.none()": uint64(1844674407370955268),
1998 "self.obj.?field.orValue('v') == 'a'": 5,
1999 "self.m[?'k'] == optional.of('v')": uint64(1844674407370955268),
2000 "self.l[?0] == optional.of('a')": uint64(1844674407370955268),
2001 "optional.ofNonZeroValue(1).hasValue()": 2,
2002 },
2003 },
2004 {name: "quantity",
2005 schema: schemas(stringType, stringType),
2006 expectCost: map[string]uint64{
2007 `isQuantity(self.val1)`: 314575,
2008 `isQuantity(self.val2)`: 314575,
2009 `isQuantity("200M")`: 1,
2010 `isQuantity("20Mi")`: 1,
2011 `quantity("200M") == quantity("0.2G") && quantity("0.2G") == quantity("200M")`: uint64(3689348814741910532),
2012 `quantity("2M") == quantity("0.002G") && quantity("2000k") == quantity("2M") && quantity("0.002G") == quantity("2000k")`: uint64(5534023222112865798),
2013 `quantity(self.val1).isLessThan(quantity(self.val2))`: 629151,
2014 `quantity("50M").isLessThan(quantity("100M"))`: 3,
2015 `quantity("50Mi").isGreaterThan(quantity("50M"))`: 3,
2016 `quantity("200M").compareTo(quantity("0.2G")) == 0`: 4,
2017 `quantity("50k").add(quantity("20")) == quantity("50.02k")`: uint64(1844674407370955268),
2018 `quantity("50k").sub(20) == quantity("49980")`: uint64(1844674407370955267),
2019 `quantity("50").isInteger()`: 2,
2020 `quantity(self.val1).isInteger()`: 314576,
2021 },
2022 },
2023 }
2024
2025 for _, tt := range cases {
2026 tt := tt
2027 t.Run(tt.name, func(t *testing.T) {
2028 t.Parallel()
2029 for validRule, expectedCost := range tt.expectCost {
2030 validRule := validRule
2031 expectedCost := expectedCost
2032 testName := validRule
2033 if len(testName) > 127 {
2034 testName = testName[:127]
2035 }
2036 t.Run(testName, func(t *testing.T) {
2037 t.Parallel()
2038 s := withRule(*tt.schema, validRule)
2039 t.Run("calc maxLength", schemaChecker(&s, uint64(expectedCost), 0, t))
2040 })
2041 }
2042 })
2043 }
2044 }
2045
View as plain text