mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Merge pull request #121455 from cici37/test_cost
CRD validation rule: Add stability tests for CEL cost estimation
This commit is contained in:
		@@ -116,6 +116,11 @@ func TestCelCostStability(t *testing.T) {
 | 
			
		||||
				"self.val1.upperAscii() == 'ROOK TAKES 👑'":         6,
 | 
			
		||||
				"self.val1.lowerAscii() == 'rook takes 👑'":         6,
 | 
			
		||||
				"self.val1.lowerAscii() == self.val1.lowerAscii()": 10,
 | 
			
		||||
				// strings version 2
 | 
			
		||||
				"'%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,
 | 
			
		||||
				"'%e'.format([3.14]) == '3.140000 × 10⁰⁰'":        3,
 | 
			
		||||
				"'%o %o %o'.format([7, 8, 9]) == '7 10 11'":       2,
 | 
			
		||||
				"'%b %b %b'.format([7, 8, 9]) == '111 1000 1001'": 3,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "escaped strings",
 | 
			
		||||
@@ -1151,3 +1156,792 @@ func TestCelCostStability(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCelEstimatedCostStability(t *testing.T) {
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		schema     *schema.Structural
 | 
			
		||||
		expectCost map[string]uint64
 | 
			
		||||
	}{
 | 
			
		||||
		{name: "integers",
 | 
			
		||||
			// 1st obj and schema args are for "self.val1" field, 2nd for "self.val2" and so on.
 | 
			
		||||
			schema: schemas(integerType, integerType, int32Type, int32Type, int64Type, int64Type),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", fmt.Sprintf("%d", math.MaxInt64)): 8,
 | 
			
		||||
				"self.val1 == self.val6":                              4, // integer with no format is the same as int64
 | 
			
		||||
				"type(self.val1) == int":                              4,
 | 
			
		||||
				fmt.Sprintf("self.val3 + 1 == %d + 1", math.MaxInt32): 5, // CEL integers are 64 bit
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "numbers",
 | 
			
		||||
			schema: schemas(numberType, numberType, floatType, floatType, doubleType, doubleType, doubleType),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", fmt.Sprintf("%f", math.MaxFloat64)): 8,
 | 
			
		||||
				"self.val1 == self.val6":    4, // number with no format is the same as float64
 | 
			
		||||
				"type(self.val1) == double": 4,
 | 
			
		||||
 | 
			
		||||
				// Use a int64 value with a number openAPI schema type since float representations of whole numbers
 | 
			
		||||
				// (e.g. 1.0, 0.0) can convert to int representations (e.g. 1, 0) in yaml to json translation, and
 | 
			
		||||
				// then get parsed as int64s.
 | 
			
		||||
				"type(self.val7) == double": 4,
 | 
			
		||||
				"self.val7 == 1.0":          2,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "numeric comparisons",
 | 
			
		||||
			schema: schemas(integerType, numberType, floatType, doubleType, numberType, floatType, doubleType),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// xref: https://github.com/google/cel-spec/wiki/proposal-210
 | 
			
		||||
 | 
			
		||||
				// compare integers with all float types
 | 
			
		||||
				"double(self.val1) < self.val4": 6,
 | 
			
		||||
				"self.val1 < int(self.val4)":    6,
 | 
			
		||||
				"double(self.val1) < self.val5": 6,
 | 
			
		||||
				"self.val1 < int(self.val5)":    6,
 | 
			
		||||
				"double(self.val1) < self.val6": 6,
 | 
			
		||||
				"self.val1 < int(self.val6)":    6,
 | 
			
		||||
 | 
			
		||||
				// compare literal integers and floats
 | 
			
		||||
				"double(5) < 10.0": 2,
 | 
			
		||||
				"5 < int(10.0)":    2,
 | 
			
		||||
 | 
			
		||||
				// compare integers with literal floats
 | 
			
		||||
				"double(self.val1) < 10.0": 4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "unicode strings",
 | 
			
		||||
			schema: schemas(stringType, stringType),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "'Rook takes 👑'"): 314585,
 | 
			
		||||
				"self.val1.startsWith('Rook')":    3,
 | 
			
		||||
				"!self.val1.startsWith('knight')": 4,
 | 
			
		||||
				"self.val1.matches('^[^0-9]*$')":  943721,
 | 
			
		||||
				"!self.val1.matches('^[0-9]*$')":  629149,
 | 
			
		||||
				"type(self.val1) == string":       4,
 | 
			
		||||
				"size(self.val1) == 12":           4,
 | 
			
		||||
 | 
			
		||||
				// string functions (https://github.com/google/cel-go/blob/v0.9.0/ext/strings.go)
 | 
			
		||||
				"self.val1.charAt(3) == 'k'":                       4,
 | 
			
		||||
				"self.val1.indexOf('o') == 1":                      314576,
 | 
			
		||||
				"self.val1.indexOf('o', 2) == 2":                   314576,
 | 
			
		||||
				"self.val1.replace(' ', 'x') == 'Rookxtakesx👑'":    629150,
 | 
			
		||||
				"self.val1.replace(' ', 'x', 1) == 'Rookxtakes 👑'": 629150,
 | 
			
		||||
				"self.val1.split(' ') == ['Rook', 'takes', '👑']":   629159,
 | 
			
		||||
				"self.val1.split(' ', 2) == ['Rook', 'takes 👑']":   629159,
 | 
			
		||||
				"self.val1.substring(5) == 'takes 👑'":              314576,
 | 
			
		||||
				"self.val1.substring(0, 4) == 'Rook'":              314576,
 | 
			
		||||
				"self.val1.substring(4, 10).trim() == 'takes'":     629149,
 | 
			
		||||
				"self.val1.upperAscii() == 'ROOK TAKES 👑'":         314577,
 | 
			
		||||
				"self.val1.lowerAscii() == 'rook takes 👑'":         314577,
 | 
			
		||||
				"self.val1.lowerAscii() == self.val1.lowerAscii()": 943723,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "escaped strings",
 | 
			
		||||
			schema: schemas(stringType, stringType),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "'l1\\nl2'"): 314583,
 | 
			
		||||
				"self.val1 == '''l1\nl2'''": 3,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "bytes",
 | 
			
		||||
			schema: schemas(byteType, byteType),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.val1 == self.val2":   314577,
 | 
			
		||||
				"self.val1 == b'AB'":       3,
 | 
			
		||||
				"type(self.val1) == bytes": 4,
 | 
			
		||||
				"size(self.val1) == 2":     4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "booleans",
 | 
			
		||||
			schema: schemas(booleanType, booleanType, booleanType, booleanType),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "true"): 8,
 | 
			
		||||
				"self.val1 != self.val4":  4,
 | 
			
		||||
				"type(self.val1) == bool": 4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "duration format",
 | 
			
		||||
			schema: schemas(durationFormat, durationFormat),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "duration('1h2m3s4ms')"): 16,
 | 
			
		||||
				"self.val1 == duration('1h2m') + duration('3s4ms')":                                  6,
 | 
			
		||||
				"self.val1.getHours() == 1":                                                          4,
 | 
			
		||||
				"type(self.val1) == google.protobuf.Duration":                                        4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "date format",
 | 
			
		||||
			schema: schemas(dateFormat, dateFormat),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "timestamp('1997-07-16T00:00:00.000Z')"): 14,
 | 
			
		||||
				"self.val1.getDate() == 16":                    4,
 | 
			
		||||
				"type(self.val1) == google.protobuf.Timestamp": 4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "date-time format",
 | 
			
		||||
			schema: schemas(dateTimeFormat, dateTimeFormat),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "timestamp('2011-08-18T19:03:37.010+01:00')"): 16,
 | 
			
		||||
				"self.val1 == timestamp('2011-08-18T00:00:00.000+01:00') + duration('19h3m37s10ms')":                      6,
 | 
			
		||||
				"self.val1.getDate('01:00') == 18":             4,
 | 
			
		||||
				"type(self.val1) == google.protobuf.Timestamp": 4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "enums",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{"enumStr": {
 | 
			
		||||
				Generic: schema.Generic{
 | 
			
		||||
					Type: "string",
 | 
			
		||||
				},
 | 
			
		||||
				ValueValidation: &schema.ValueValidation{
 | 
			
		||||
					Enum: []schema.JSON{
 | 
			
		||||
						{Object: "Pending"},
 | 
			
		||||
						{Object: "Available"},
 | 
			
		||||
						{Object: "Bound"},
 | 
			
		||||
						{Object: "Released"},
 | 
			
		||||
						{Object: "Failed"},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.enumStr == 'Pending'":                3,
 | 
			
		||||
				"self.enumStr in ['Pending', 'Available']": 14,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "conversions",
 | 
			
		||||
			schema: schemas(integerType, numberType, numberType, numberType, booleanType, stringType, byteType, stringType, durationFormat, stringType, dateTimeFormat, stringType, dateFormat),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"int(self.val2) == self.val1":         5,
 | 
			
		||||
				"double(self.val1) == self.val2":      5,
 | 
			
		||||
				"bytes(self.val6) == self.val7":       629150,
 | 
			
		||||
				"string(self.val1) == self.val6":      314578,
 | 
			
		||||
				"string(self.val4) == '10.5'":         4,
 | 
			
		||||
				"string(self.val7) == self.val6":      629150,
 | 
			
		||||
				"duration(self.val8) == self.val9":    6,
 | 
			
		||||
				"timestamp(self.val10) == self.val11": 6,
 | 
			
		||||
				"string(self.val11) == self.val10":    314578,
 | 
			
		||||
				"timestamp(self.val12) == self.val13": 6,
 | 
			
		||||
				"string(self.val13) == self.val12":    314578,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "lists",
 | 
			
		||||
			schema: schemas(listType(&integerType), listType(&integerType)),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				ValsEqualThemselvesAndDataLiteral("self.val1", "self.val2", "[1, 2, 3]"): 157317,
 | 
			
		||||
				"1 in self.val1":                                      1572865,
 | 
			
		||||
				"self.val2[0] in self.val1":                           1572868,
 | 
			
		||||
				"!(0 in self.val1)":                                   1572866,
 | 
			
		||||
				"self.val1 + self.val2 == [1, 2, 3, 1, 2, 3]":         16,
 | 
			
		||||
				"self.val1 + [4, 5] == [1, 2, 3, 4, 5]":               24,
 | 
			
		||||
				"has(self.val1)":                                      2,
 | 
			
		||||
				"has(self.val1) && has(self.val2)":                    4,
 | 
			
		||||
				"!has(self.val1)":                                     3,
 | 
			
		||||
				"self.val1.all(k, size(self.val1) > 0)":               11010044,
 | 
			
		||||
				"self.val1.exists_one(k, self.val1 == [2])":           23592949,
 | 
			
		||||
				"!self.val1.exists_one(k, size(self.val1) > 0)":       9437183,
 | 
			
		||||
				"size(self.val1) == 2":                                4,
 | 
			
		||||
				"size(self.val1.filter(k, size(self.val1) > 1)) == 1": 26738686,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "listSets",
 | 
			
		||||
			schema: schemas(listSetType(&stringType), listSetType(&stringType)),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// equal even though order is different
 | 
			
		||||
				"self.val1 == ['c', 'b', 'a']":                        13,
 | 
			
		||||
				"self.val1 == self.val2":                              104862,
 | 
			
		||||
				"'a' in self.val1":                                    1048577,
 | 
			
		||||
				"self.val2[0] in self.val1":                           1048580,
 | 
			
		||||
				"!('x' in self.val1)":                                 1048578,
 | 
			
		||||
				"self.val1 + self.val2 == ['a', 'b', 'c']":            16,
 | 
			
		||||
				"self.val1 + ['c', 'd'] == ['a', 'b', 'c', 'd']":      24,
 | 
			
		||||
				"has(self.val1)":                                      2,
 | 
			
		||||
				"has(self.val1) && has(self.val2)":                    4,
 | 
			
		||||
				"!has(self.val1)":                                     3,
 | 
			
		||||
				"self.val1.all(k, size(self.val1) > 0)":               7340028,
 | 
			
		||||
				"self.val1.exists_one(k, self.val1 == ['a'])":         15728629,
 | 
			
		||||
				"!self.val1.exists_one(k, size(self.val1) > 0)":       6291455,
 | 
			
		||||
				"size(self.val1) == 2":                                4,
 | 
			
		||||
				"size(self.val1.filter(k, size(self.val1) > 1)) == 1": 17825790,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "listMaps",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"objs": listType(listMapTypePtr([]string{"k"}, objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"k": stringType,
 | 
			
		||||
					"v": stringType,
 | 
			
		||||
				}))),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.objs[0] == self.objs[1]":                104864, // equal even though order is different
 | 
			
		||||
				"self.objs[0] + self.objs[2] == self.objs[2]": 104868, // rhs overwrites lhs values
 | 
			
		||||
				"self.objs[2] + self.objs[0] == self.objs[0]": 104868,
 | 
			
		||||
 | 
			
		||||
				"self.objs[0] == [self.objs[0][0], self.objs[0][1]]": 22, // equal against a declared list
 | 
			
		||||
				"self.objs[0] == [self.objs[0][1], self.objs[0][0]]": 22,
 | 
			
		||||
 | 
			
		||||
				"self.objs[2] + [self.objs[0][0], self.objs[0][1]] == self.objs[0]": 104883, // concat against a declared list
 | 
			
		||||
				"size(self.objs[0] + [self.objs[3][0]]) == 3":                       20,
 | 
			
		||||
				"has(self.objs)":                                            2,
 | 
			
		||||
				"has(self.objs) && has(self.objs)":                          4,
 | 
			
		||||
				"!has(self.objs)":                                           3,
 | 
			
		||||
				"self.objs[0].all(k, size(self.objs[0]) > 0)":               8388604,
 | 
			
		||||
				"self.objs[0].exists_one(k, size(self.objs[0]) > 0)":        7340030,
 | 
			
		||||
				"!self.objs[0].exists_one(k, size(self.objs[0]) > 0)":       7340031,
 | 
			
		||||
				"size(self.objs[0]) == 2":                                   5,
 | 
			
		||||
				"size(self.objs[0].filter(k, size(self.objs[0]) > 1)) == 1": 18874366,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "maps",
 | 
			
		||||
			schema: schemas(mapType(&stringType), mapType(&stringType)),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.val1 == self.val2":                              39326, // equal even though order is different
 | 
			
		||||
				"'k1' in self.val1":                                   3,
 | 
			
		||||
				"!('k3' in self.val1)":                                4,
 | 
			
		||||
				"self.val1 == {'k1': 'a', 'k2': 'b'}":                 33,
 | 
			
		||||
				"has(self.val1)":                                      2,
 | 
			
		||||
				"has(self.val1) && has(self.val2)":                    4,
 | 
			
		||||
				"!has(self.val1)":                                     3,
 | 
			
		||||
				"self.val1.all(k, size(self.val1) > 0)":               2752508,
 | 
			
		||||
				"self.val1.exists_one(k, size(self.val1) > 0)":        2359294,
 | 
			
		||||
				"!self.val1.exists_one(k, size(self.val1) > 0)":       2359295,
 | 
			
		||||
				"size(self.val1) == 2":                                4,
 | 
			
		||||
				"size(self.val1.filter(k, size(self.val1) > 1)) == 1": 6684670,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "objects",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"objs": listType(objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"f1": stringType,
 | 
			
		||||
					"f2": stringType,
 | 
			
		||||
				})),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.objs[0] == self.objs[1]": 6,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "object access",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"a": objectType(map[string]schema.Structural{
 | 
			
		||||
					"b": integerType,
 | 
			
		||||
					"c": integerType,
 | 
			
		||||
					"d": withNullable(true, integerType),
 | 
			
		||||
				}),
 | 
			
		||||
				"a1": objectType(map[string]schema.Structural{
 | 
			
		||||
					"b1": objectType(map[string]schema.Structural{
 | 
			
		||||
						"c1": integerType,
 | 
			
		||||
					}),
 | 
			
		||||
					"d2": objectType(map[string]schema.Structural{
 | 
			
		||||
						"e2": integerType,
 | 
			
		||||
					}),
 | 
			
		||||
				}),
 | 
			
		||||
			}),
 | 
			
		||||
			// https://github.com/google/cel-spec/blob/master/doc/langdef.md#field-selection
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"has(self.a.b)":                            3,
 | 
			
		||||
				"has(self.a1.b1.c1)":                       4,
 | 
			
		||||
				"!(has(self.a1.d2) && has(self.a1.d2.e2))": 8, // must check intermediate optional fields (see below no such key error for d2)
 | 
			
		||||
				"!has(self.a1.d2)":                         4,
 | 
			
		||||
				"has(self.a)":                              2,
 | 
			
		||||
				"has(self.a) && has(self.a1)":              4,
 | 
			
		||||
				"!has(self.a)":                             3,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "map access",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"val": mapType(&integerType),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// idiomatic map access
 | 
			
		||||
				"!('a' in self.val)": 4,
 | 
			
		||||
				"'b' in self.val":    3,
 | 
			
		||||
				"!('c' in self.val)": 4,
 | 
			
		||||
				"'d' in self.val":    3,
 | 
			
		||||
				// field selection also possible if map key is a valid CEL identifier
 | 
			
		||||
				"!has(self.val.a)":                               4,
 | 
			
		||||
				"has(self.val.b)":                                3,
 | 
			
		||||
				"!has(self.val.c)":                               4,
 | 
			
		||||
				"has(self.val.d)":                                3,
 | 
			
		||||
				"self.val.all(k, self.val[k] > 0)":               3595115,
 | 
			
		||||
				"self.val.exists_one(k, self.val[k] == 2)":       2696338,
 | 
			
		||||
				"!self.val.exists_one(k, self.val[k] > 0)":       3145728,
 | 
			
		||||
				"size(self.val) == 2":                            4,
 | 
			
		||||
				"size(self.val.filter(k, self.val[k] > 1)) == 1": 8089017,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "listMap access",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"listMap": listMapType([]string{"k"}, objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"k":  stringType,
 | 
			
		||||
					"v":  stringType,
 | 
			
		||||
					"v2": stringType,
 | 
			
		||||
				})),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"has(self.listMap[0].v)":                             4,
 | 
			
		||||
				"self.listMap.all(m, m.k.startsWith('a'))":           6291453,
 | 
			
		||||
				"self.listMap.all(m, !has(m.v2) || m.v2 == 'z')":     9437178,
 | 
			
		||||
				"self.listMap.exists(m, m.k.endsWith('1'))":          7340028,
 | 
			
		||||
				"self.listMap.exists_one(m, m.k == 'a3')":            5242879,
 | 
			
		||||
				"!self.listMap.all(m, m.k.endsWith('1'))":            6291454,
 | 
			
		||||
				"!self.listMap.exists(m, m.v == 'x')":                7340029,
 | 
			
		||||
				"!self.listMap.exists_one(m, m.k.startsWith('a'))":   5242880,
 | 
			
		||||
				"size(self.listMap.filter(m, m.k == 'a1')) == 1":     16777215,
 | 
			
		||||
				"self.listMap.exists(m, m.k == 'a1' && m.v == 'b1')": 10485753,
 | 
			
		||||
				"self.listMap.map(m, m.v).exists(v, v == 'b1')":      uint64(18446744073709551615),
 | 
			
		||||
 | 
			
		||||
				// test comprehensions where the field used in predicates is unset on all but one of the elements:
 | 
			
		||||
				// - with has checks:
 | 
			
		||||
 | 
			
		||||
				"self.listMap.exists(m, has(m.v2) && m.v2 == 'z')":             9437178,
 | 
			
		||||
				"!self.listMap.all(m, has(m.v2) && m.v2 != 'z')":               8388604,
 | 
			
		||||
				"self.listMap.exists_one(m, has(m.v2) && m.v2 == 'z')":         7340029,
 | 
			
		||||
				"self.listMap.filter(m, has(m.v2) && m.v2 == 'z').size() == 1": 18874365,
 | 
			
		||||
				// undocumented overload of map that takes a filter argument. This is the same as .filter().map()
 | 
			
		||||
				"self.listMap.map(m, has(m.v2) && m.v2 == 'z', m.v2).size() == 1":           19922940,
 | 
			
		||||
				"self.listMap.filter(m, has(m.v2) && m.v2 == 'z').map(m, m.v2).size() == 1": uint64(18446744073709551615),
 | 
			
		||||
				// - without has checks:
 | 
			
		||||
 | 
			
		||||
				// all() and exists() macros ignore errors from predicates so long as the condition holds for at least one element
 | 
			
		||||
				"self.listMap.exists(m, m.v2 == 'z')": 7340028,
 | 
			
		||||
				"!self.listMap.all(m, m.v2 != 'z')":   6291454,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "list access",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"array": listType(&integerType),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"2 in self.array":                                                1572865,
 | 
			
		||||
				"self.array.all(e, e > 0)":                                       7864318,
 | 
			
		||||
				"self.array.exists(e, e > 2)":                                    9437181,
 | 
			
		||||
				"self.array.exists_one(e, e > 4)":                                6291456,
 | 
			
		||||
				"!self.array.all(e, e < 2)":                                      7864319,
 | 
			
		||||
				"!self.array.exists(e, e < 0)":                                   9437182,
 | 
			
		||||
				"!self.array.exists_one(e, e == 2)":                              4718594,
 | 
			
		||||
				"self.array.all(e, e < 100)":                                     7864318,
 | 
			
		||||
				"size(self.array.filter(e, e%2 == 0)) == 3":                      25165823,
 | 
			
		||||
				"self.array.map(e, e * 20).filter(e, e > 50).exists(e, e == 60)": uint64(18446744073709551615),
 | 
			
		||||
				"size(self.array) == 8":                                          4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "listSet access",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"set": listType(&integerType),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"3 in self.set":                                                    1572865,
 | 
			
		||||
				"self.set.all(e, e > 0)":                                           7864318,
 | 
			
		||||
				"self.set.exists(e, e > 3)":                                        9437181,
 | 
			
		||||
				"self.set.exists_one(e, e == 3)":                                   4718593,
 | 
			
		||||
				"!self.set.all(e, e < 3)":                                          7864319,
 | 
			
		||||
				"!self.set.exists(e, e < 0)":                                       9437182,
 | 
			
		||||
				"!self.set.exists_one(e, e > 3)":                                   6291457,
 | 
			
		||||
				"self.set.all(e, e < 10)":                                          7864318,
 | 
			
		||||
				"size(self.set.filter(e, e%2 == 0)) == 2":                          25165823,
 | 
			
		||||
				"self.set.map(e, e * 20).filter(e, e > 50).exists_one(e, e == 60)": uint64(18446744073709551615),
 | 
			
		||||
				"size(self.set) == 5":                                              4,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "typemeta and objectmeta access specified",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"kind":       stringType,
 | 
			
		||||
				"apiVersion": stringType,
 | 
			
		||||
				"metadata": objectType(map[string]schema.Structural{
 | 
			
		||||
					"name":         stringType,
 | 
			
		||||
					"generateName": stringType,
 | 
			
		||||
				}),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.kind == 'Pod'":                          3,
 | 
			
		||||
				"self.apiVersion == 'v1'":                     3,
 | 
			
		||||
				"self.metadata.name == 'foo'":                 4,
 | 
			
		||||
				"self.metadata.generateName == 'pickItForMe'": 5,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// Kubernetes special types
 | 
			
		||||
		{name: "embedded object",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"embedded": {
 | 
			
		||||
					Generic: schema.Generic{Type: "object"},
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XEmbeddedResource: true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are always accessible
 | 
			
		||||
				// even if not specified in the schema.
 | 
			
		||||
				"self.embedded.kind == 'Pod'":                          4,
 | 
			
		||||
				"self.embedded.apiVersion == 'v1'":                     4,
 | 
			
		||||
				"self.embedded.metadata.name == 'foo'":                 5,
 | 
			
		||||
				"self.embedded.metadata.generateName == 'pickItForMe'": 6,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "embedded object with properties",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"embedded": {
 | 
			
		||||
					Generic: schema.Generic{Type: "object"},
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XEmbeddedResource: true,
 | 
			
		||||
					},
 | 
			
		||||
					Properties: map[string]schema.Structural{
 | 
			
		||||
						"kind":       stringType,
 | 
			
		||||
						"apiVersion": stringType,
 | 
			
		||||
						"metadata": objectType(map[string]schema.Structural{
 | 
			
		||||
							"name":         stringType,
 | 
			
		||||
							"generateName": stringType,
 | 
			
		||||
						}),
 | 
			
		||||
						"spec": objectType(map[string]schema.Structural{
 | 
			
		||||
							"field1": stringType,
 | 
			
		||||
						}),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// in this case 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are specified in the
 | 
			
		||||
				// schema, but they would be accessible even if they were not
 | 
			
		||||
				"self.embedded.kind == 'Pod'":                          4,
 | 
			
		||||
				"self.embedded.apiVersion == 'v1'":                     4,
 | 
			
		||||
				"self.embedded.metadata.name == 'foo'":                 5,
 | 
			
		||||
				"self.embedded.metadata.generateName == 'pickItForMe'": 6,
 | 
			
		||||
				// the specified embedded fields are accessible
 | 
			
		||||
				"self.embedded.spec.field1 == 'a'": 5,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "embedded object with preserve unknown",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"embedded": {
 | 
			
		||||
					Generic: schema.Generic{Type: "object"},
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
						XEmbeddedResource:      true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are always accessible
 | 
			
		||||
				// even if not specified in the schema, regardless of if x-kubernetes-preserve-unknown-fields is set.
 | 
			
		||||
				"self.embedded.kind == 'Pod'":                          4,
 | 
			
		||||
				"self.embedded.apiVersion == 'v1'":                     4,
 | 
			
		||||
				"self.embedded.metadata.name == 'foo'":                 5,
 | 
			
		||||
				"self.embedded.metadata.generateName == 'pickItForMe'": 6,
 | 
			
		||||
 | 
			
		||||
				// the object exists
 | 
			
		||||
				"has(self.embedded)": 2,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "string in intOrString",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"something": intOrStringType(),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// typical int-or-string usage would be to check both types
 | 
			
		||||
				"type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
 | 
			
		||||
				// to require the value be a particular type, guard it with a runtime type check
 | 
			
		||||
				"type(self.something) == string && self.something == '25%'": 7,
 | 
			
		||||
 | 
			
		||||
				// In Kubernetes 1.24 and later, the CEL type returns false for an int-or-string comparison against the
 | 
			
		||||
				// other type, making it safe to write validation rules like:
 | 
			
		||||
				"self.something == '25%'":                        3,
 | 
			
		||||
				"self.something != 1":                            3,
 | 
			
		||||
				"self.something == 1 || self.something == '25%'": 6,
 | 
			
		||||
				"self.something == '25%' || self.something == 1": 6,
 | 
			
		||||
 | 
			
		||||
				// Because the type is dynamic it receives no type checking, and evaluates to false when compared to
 | 
			
		||||
				// other types at runtime.
 | 
			
		||||
				"self.something != ['anything']": 13,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "int in intOrString",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"something": intOrStringType(),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// typical int-or-string usage would be to check both types
 | 
			
		||||
				"type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
 | 
			
		||||
				// to require the value be a particular type, guard it with a runtime type check
 | 
			
		||||
				"type(self.something) == int && self.something == 1": 7,
 | 
			
		||||
 | 
			
		||||
				// In Kubernetes 1.24 and later, the CEL type returns false for an int-or-string comparison against the
 | 
			
		||||
				// other type, making it safe to write validation rules like:
 | 
			
		||||
				"self.something == 1":                            3,
 | 
			
		||||
				"self.something != 'some string'":                4,
 | 
			
		||||
				"self.something == 1 || self.something == '25%'": 6,
 | 
			
		||||
				"self.something == '25%' || self.something == 1": 6,
 | 
			
		||||
 | 
			
		||||
				// Because the type is dynamic it receives no type checking, and evaluates to false when compared to
 | 
			
		||||
				// other types at runtime.
 | 
			
		||||
				"self.something != ['anything']": 13,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "null in intOrString",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"something": withNullable(true, intOrStringType()),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"!has(self.something)": 3,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "percent comparison using intOrString",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"min":       intOrStringType(),
 | 
			
		||||
				"current":   integerType,
 | 
			
		||||
				"available": integerType,
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// validate that if 'min' is a string that it is a percentage
 | 
			
		||||
				`type(self.min) == string && self.min.matches(r'(\d+(\.\d+)?%)')`: 1258298,
 | 
			
		||||
				// validate that 'min' can be either a exact value minimum, or a minimum as a percentage of 'available'
 | 
			
		||||
				"type(self.min) == int ? self.current <= self.min : double(self.current) / double(self.available) >= double(self.min.replace('%', '')) / 100.0": 629162,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "preserve unknown fields",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"withUnknown": {
 | 
			
		||||
					Generic: schema.Generic{Type: "object"},
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"withUnknownList": listType(&schema.Structural{
 | 
			
		||||
					Generic: schema.Generic{Type: "object"},
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
					},
 | 
			
		||||
				}),
 | 
			
		||||
				"withUnknownFieldList": listType(&schema.Structural{
 | 
			
		||||
					Generic: schema.Generic{Type: "object"},
 | 
			
		||||
					Properties: map[string]schema.Structural{
 | 
			
		||||
						"fieldOfUnknownType": {
 | 
			
		||||
							Extensions: schema.Extensions{
 | 
			
		||||
								XPreserveUnknownFields: true,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				}),
 | 
			
		||||
				"anyvalList": listType(&schema.Structural{
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
					},
 | 
			
		||||
				}),
 | 
			
		||||
				"anyvalMap": mapType(&schema.Structural{
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
					},
 | 
			
		||||
				}),
 | 
			
		||||
				"anyvalField1": {
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"anyvalField2": {
 | 
			
		||||
					Extensions: schema.Extensions{
 | 
			
		||||
						XPreserveUnknownFields: true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"has(self.withUnknown)":            2,
 | 
			
		||||
				"self.withUnknownList.size() == 5": 4,
 | 
			
		||||
				// fields that are unknown because they were not specified on the object schema are included in equality checks
 | 
			
		||||
				"self.withUnknownList[0] != self.withUnknownList[1]": 6,
 | 
			
		||||
				"self.withUnknownList[1] == self.withUnknownList[2]": 6,
 | 
			
		||||
				"self.withUnknownList[3] == self.withUnknownList[4]": 6,
 | 
			
		||||
 | 
			
		||||
				// fields specified on the object schema that are unknown because the field's schema is unknown are also included equality checks
 | 
			
		||||
				"self.withUnknownFieldList[0] != self.withUnknownFieldList[1]": 6,
 | 
			
		||||
				"self.withUnknownFieldList[1] == self.withUnknownFieldList[2]": 6,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "known and unknown fields",
 | 
			
		||||
			schema: &schema.Structural{
 | 
			
		||||
				Generic: schema.Generic{
 | 
			
		||||
					Type: "object",
 | 
			
		||||
				},
 | 
			
		||||
				Properties: map[string]schema.Structural{
 | 
			
		||||
					"withUnknown": {
 | 
			
		||||
						Generic: schema.Generic{Type: "object"},
 | 
			
		||||
						Extensions: schema.Extensions{
 | 
			
		||||
							XPreserveUnknownFields: true,
 | 
			
		||||
						},
 | 
			
		||||
						Properties: map[string]schema.Structural{
 | 
			
		||||
							"known": integerType,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"withUnknownList": listType(&schema.Structural{
 | 
			
		||||
						Generic: schema.Generic{Type: "object"},
 | 
			
		||||
						Extensions: schema.Extensions{
 | 
			
		||||
							XPreserveUnknownFields: true,
 | 
			
		||||
						},
 | 
			
		||||
						Properties: map[string]schema.Structural{
 | 
			
		||||
							"known": integerType,
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.withUnknown.known == 1": 3,
 | 
			
		||||
				// if the unknown fields are the same, they are equal
 | 
			
		||||
				"self.withUnknownList[1] == self.withUnknownList[2]": 6,
 | 
			
		||||
 | 
			
		||||
				// if unknown fields are different, they are not equal
 | 
			
		||||
				"self.withUnknownList[0] != self.withUnknownList[1]": 6,
 | 
			
		||||
				"self.withUnknownList[0] != self.withUnknownList[3]": 6,
 | 
			
		||||
				"self.withUnknownList[0] != self.withUnknownList[5]": 6,
 | 
			
		||||
 | 
			
		||||
				// if all fields are known, equality works as usual
 | 
			
		||||
				"self.withUnknownList[3] == self.withUnknownList[4]": 6,
 | 
			
		||||
				"self.withUnknownList[4] != self.withUnknownList[5]": 6,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "field nullability",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"unsetPlainStr":     stringType,
 | 
			
		||||
				"unsetDefaultedStr": withDefault("default value", stringType),
 | 
			
		||||
				"unsetNullableStr":  withNullable(true, stringType),
 | 
			
		||||
 | 
			
		||||
				"setPlainStr":          stringType,
 | 
			
		||||
				"setDefaultedStr":      withDefault("default value", stringType),
 | 
			
		||||
				"setNullableStr":       withNullable(true, stringType),
 | 
			
		||||
				"setToNullNullableStr": withNullable(true, stringType),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"!has(self.unsetPlainStr)": 3,
 | 
			
		||||
				"has(self.unsetDefaultedStr) && self.unsetDefaultedStr == 'default value'": 6,
 | 
			
		||||
				"!has(self.unsetNullableStr)": 3,
 | 
			
		||||
 | 
			
		||||
				"has(self.setPlainStr) && self.setPlainStr == 'v1'":         5,
 | 
			
		||||
				"has(self.setDefaultedStr) && self.setDefaultedStr == 'v2'": 5,
 | 
			
		||||
				"has(self.setNullableStr) && self.setNullableStr == 'v3'":   5,
 | 
			
		||||
				// We treat null fields as absent fields, not as null valued fields.
 | 
			
		||||
				// Note that this is different than how we treat nullable list items or map values.
 | 
			
		||||
				"type(self.setNullableStr) != null_type": 4,
 | 
			
		||||
 | 
			
		||||
				// a field that is set to null is treated the same as an absent field in validation rules
 | 
			
		||||
				"!has(self.setToNullNullableStr)": 3,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "null values in container types",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"m": mapType(withNullablePtr(true, stringType)),
 | 
			
		||||
				"l": listType(withNullablePtr(true, stringType)),
 | 
			
		||||
				"s": listSetType(withNullablePtr(true, stringType)),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.m.size() == 2":             4,
 | 
			
		||||
				"'a' in self.m":                  3,
 | 
			
		||||
				"type(self.m['a']) == null_type": 5, // null check using runtime type checking
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "object types are not accessible",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"nestedInMap": mapType(objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"inMapField": integerType,
 | 
			
		||||
				})),
 | 
			
		||||
				"nestedInList": listType(objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"inListField": integerType,
 | 
			
		||||
				})),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				// we do not expose a stable type for the self variable, even when it is an object that CEL
 | 
			
		||||
				// considers a named type. The only operation developers should be able to perform on the type is
 | 
			
		||||
				// equality checking.
 | 
			
		||||
				"type(self) == type(self)":                                     uint64(1844674407370955268),
 | 
			
		||||
				"type(self.nestedInMap['k1']) == type(self.nestedInMap['k2'])": uint64(1844674407370955272),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "listMaps with unsupported identity characters in property names",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"objs": listType(listMapTypePtr([]string{"k!", "k."}, objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"k!": stringType,
 | 
			
		||||
					"k.": stringType,
 | 
			
		||||
				}))),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.objs[0] == self.objs[1]":    104864, // equal even though order is different
 | 
			
		||||
				"self.objs[0][0].k__dot__ == '1'": 6,      // '.' is a supported character in identifiers, but it is escaped
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "container type composition",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"obj": objectType(map[string]schema.Structural{
 | 
			
		||||
					"field": stringType,
 | 
			
		||||
				}),
 | 
			
		||||
				"mapOfMap": mapType(mapTypePtr(&stringType)),
 | 
			
		||||
				"mapOfObj": mapType(objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"field2": stringType,
 | 
			
		||||
				})),
 | 
			
		||||
				"mapOfListMap": mapType(listMapTypePtr([]string{"k"}, objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"k": stringType,
 | 
			
		||||
					"v": stringType,
 | 
			
		||||
				}))),
 | 
			
		||||
				"mapOfList": mapType(listTypePtr(&stringType)),
 | 
			
		||||
				"listMapOfObj": listMapType([]string{"k2"}, objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"k2": stringType,
 | 
			
		||||
					"v2": stringType,
 | 
			
		||||
				})),
 | 
			
		||||
				"listOfMap": listType(mapTypePtr(&stringType)),
 | 
			
		||||
				"listOfObj": listType(objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"field3": stringType,
 | 
			
		||||
				})),
 | 
			
		||||
				"listOfListMap": listType(listMapTypePtr([]string{"k3"}, objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
					"k3": stringType,
 | 
			
		||||
					"v3": stringType,
 | 
			
		||||
				}))),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"self.obj.field == 'a'":                                       4,
 | 
			
		||||
				"self.mapOfMap['x']['y'] == 'b'":                              5,
 | 
			
		||||
				"self.mapOfObj['k'].field2 == 'c'":                            5,
 | 
			
		||||
				"self.mapOfListMap['o'].exists(e, e.k == '1' && e.v == 'd')":  10485754,
 | 
			
		||||
				"self.mapOfList['l'][0] == 'e'":                               5,
 | 
			
		||||
				"self.listMapOfObj.exists(e, e.k2 == '2' && e.v2 == 'f')":     10485753,
 | 
			
		||||
				"self.listOfMap[0]['z'] == 'g'":                               5,
 | 
			
		||||
				"self.listOfObj[0].field3 == 'h'":                             5,
 | 
			
		||||
				"self.listOfListMap[0].exists(e, e.k3 == '3' && e.v3 == 'i')": 10485754,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{name: "optionals",
 | 
			
		||||
			schema: objectTypePtr(map[string]schema.Structural{
 | 
			
		||||
				"obj": objectType(map[string]schema.Structural{
 | 
			
		||||
					"field":       stringType,
 | 
			
		||||
					"absentField": stringType,
 | 
			
		||||
				}),
 | 
			
		||||
				"m": mapType(&stringType),
 | 
			
		||||
				"l": listType(&stringType),
 | 
			
		||||
			}),
 | 
			
		||||
			expectCost: map[string]uint64{
 | 
			
		||||
				"optional.of('a') != optional.of('b')":                uint64(1844674407370955266),
 | 
			
		||||
				"optional.of('a') != optional.none()":                 uint64(1844674407370955266),
 | 
			
		||||
				"optional.of('a').hasValue()":                         2,
 | 
			
		||||
				"optional.of('a').or(optional.of('a')).hasValue()":    4, // or() is short-circuited
 | 
			
		||||
				"optional.none().or(optional.of('a')).hasValue()":     4,
 | 
			
		||||
				"optional.of('a').optMap(v, v == 'value').hasValue()": 17,
 | 
			
		||||
				"self.obj.?field == optional.of('a')":                 uint64(1844674407370955268),
 | 
			
		||||
				"self.obj.?absentField == optional.none()":            uint64(1844674407370955268),
 | 
			
		||||
				"self.obj.?field.orValue('v') == 'a'":                 5,
 | 
			
		||||
				"self.m[?'k'] == optional.of('v')":                    uint64(1844674407370955268),
 | 
			
		||||
				"self.l[?0] == optional.of('a')":                      uint64(1844674407370955268),
 | 
			
		||||
				"optional.ofNonZeroValue(1).hasValue()":               2,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range cases {
 | 
			
		||||
		tt := tt
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			t.Parallel()
 | 
			
		||||
			for validRule, expectedCost := range tt.expectCost {
 | 
			
		||||
				validRule := validRule
 | 
			
		||||
				expectedCost := expectedCost
 | 
			
		||||
				testName := validRule
 | 
			
		||||
				if len(testName) > 127 {
 | 
			
		||||
					testName = testName[:127]
 | 
			
		||||
				}
 | 
			
		||||
				t.Run(testName, func(t *testing.T) {
 | 
			
		||||
					t.Parallel()
 | 
			
		||||
					s := withRule(*tt.schema, validRule)
 | 
			
		||||
					t.Run("calc maxLength", schemaChecker(&s, uint64(expectedCost), 0, t))
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -409,6 +409,124 @@ func TestAuthzLibrary(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestQuantityCost(t *testing.T) {
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
		name                string
 | 
			
		||||
		expr                string
 | 
			
		||||
		expectEstimatedCost checker.CostEstimate
 | 
			
		||||
		expectRuntimeCost   uint64
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:                "path",
 | 
			
		||||
			expr:                `quantity("12Mi")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
 | 
			
		||||
			expectRuntimeCost:   1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "isQuantity",
 | 
			
		||||
			expr:                `isQuantity("20")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
 | 
			
		||||
			expectRuntimeCost:   1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "isQuantity_megabytes",
 | 
			
		||||
			expr:                `isQuantity("20M")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
 | 
			
		||||
			expectRuntimeCost:   1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "equality_reflexivity",
 | 
			
		||||
			expr:                `quantity("200M") == quantity("200M")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 1844674407370955266},
 | 
			
		||||
			expectRuntimeCost:   3,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "equality_symmetry",
 | 
			
		||||
			expr:                `quantity("200M") == quantity("0.2G") && quantity("0.2G") == quantity("200M")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3689348814741910532},
 | 
			
		||||
			expectRuntimeCost:   6,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "equality_transitivity",
 | 
			
		||||
			expr:                `quantity("2M") == quantity("0.002G") && quantity("2000k") == quantity("2M") && quantity("0.002G") == quantity("2000k")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 5534023222112865798},
 | 
			
		||||
			expectRuntimeCost:   9,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "quantity_less",
 | 
			
		||||
			expr:                `quantity("50M").isLessThan(quantity("50Mi"))`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
 | 
			
		||||
			expectRuntimeCost:   3,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "quantity_greater",
 | 
			
		||||
			expr:                `quantity("50Mi").isGreaterThan(quantity("50M"))`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
 | 
			
		||||
			expectRuntimeCost:   3,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "compare_equal",
 | 
			
		||||
			expr:                `quantity("200M").compareTo(quantity("0.2G")) > 0`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 4, Max: 4},
 | 
			
		||||
			expectRuntimeCost:   4,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "add_quantity",
 | 
			
		||||
			expr:                `quantity("50k").add(quantity("20")) == quantity("50.02k")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 5, Max: 1844674407370955268},
 | 
			
		||||
			expectRuntimeCost:   5,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "sub_quantity",
 | 
			
		||||
			expr:                `quantity("50k").sub(quantity("20")) == quantity("49.98k")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 5, Max: 1844674407370955268},
 | 
			
		||||
			expectRuntimeCost:   5,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "sub_int",
 | 
			
		||||
			expr:                `quantity("50k").sub(20) == quantity("49980")`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 4, Max: 1844674407370955267},
 | 
			
		||||
			expectRuntimeCost:   4,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "arith_chain_1",
 | 
			
		||||
			expr:                `quantity("50k").add(20).sub(quantity("100k")).asInteger() > 0`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 6, Max: 6},
 | 
			
		||||
			expectRuntimeCost:   6,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "arith_chain",
 | 
			
		||||
			expr:                `quantity("50k").add(20).sub(quantity("100k")).sub(-50000).asInteger() > 0`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 7, Max: 7},
 | 
			
		||||
			expectRuntimeCost:   7,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "as_integer",
 | 
			
		||||
			expr:                `quantity("50k").asInteger() > 0`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
 | 
			
		||||
			expectRuntimeCost:   3,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "is_integer",
 | 
			
		||||
			expr:                `quantity("50").isInteger()`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 2, Max: 2},
 | 
			
		||||
			expectRuntimeCost:   2,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                "as_float",
 | 
			
		||||
			expr:                `quantity("50.703k").asApproximateFloat() > 0.0`,
 | 
			
		||||
			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
 | 
			
		||||
			expectRuntimeCost:   3,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range cases {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			testCost(t, tc.expr, tc.expectEstimatedCost, tc.expectRuntimeCost)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate, expectRuntimeCost uint64) {
 | 
			
		||||
	est := &CostEstimator{SizeEstimator: &testCostEstimator{}}
 | 
			
		||||
	env, err := cel.NewEnv(
 | 
			
		||||
@@ -417,6 +535,7 @@ func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate
 | 
			
		||||
		Regex(),
 | 
			
		||||
		Lists(),
 | 
			
		||||
		Authz(),
 | 
			
		||||
		Quantity(),
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("%v", err)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user