...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/celcoststability_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package 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  			// 1st obj and schema args are for "self.val1" field, 2nd for "self.val2" and so on.
    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, // integer with no format is the same as int64
    45  				"type(self.val1) == int":                              4,
    46  				fmt.Sprintf("self.val3 + 1 == %d + 1", math.MaxInt32): 5, // CEL integers are 64 bit
    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, // number with no format is the same as float64
    55  				"type(self.val1) == double": 4,
    56  
    57  				// Use a int64 value with a number openAPI schema type since float representations of whole numbers
    58  				// (e.g. 1.0, 0.0) can convert to int representations (e.g. 1, 0) in yaml to json translation, and
    59  				// then get parsed as int64s.
    60  				"type(self.val7) == double": 4,
    61  				"self.val7 == 1.0":          3,
    62  			},
    63  		},
    64  		{name: "numeric comparisons",
    65  			obj: objs(
    66  				int64(5),      // val1, integer type, integer value
    67  				float64(10.0), // val4, number type, parsed from decimal literal
    68  				float64(10.0), // val5, float type, parsed from decimal literal
    69  				float64(10.0), // val6, double type, parsed from decimal literal
    70  				int64(10),     // val7, number type, parsed from integer literal
    71  				int64(10),     // val8, float type, parsed from integer literal
    72  				int64(10),     // val9, double type, parsed from integer literal
    73  			),
    74  			schema: schemas(integerType, numberType, floatType, doubleType, numberType, floatType, doubleType),
    75  			expectCost: map[string]int64{
    76  				// xref: https://github.com/google/cel-spec/wiki/proposal-210
    77  
    78  				// compare integers with all float types
    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  				// compare literal integers and floats
    87  				"double(5) < 10.0": 1,
    88  				"5 < int(10.0)":    1,
    89  
    90  				// compare integers with literal floats
    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  				// string functions (https://github.com/google/cel-go/blob/v0.9.0/ext/strings.go)
   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  				// strings version 2
   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  				// equal even though order is different
   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,  // equal even though order is different
   283  				"self.objs[0] + self.objs[2] == self.objs[2]": 11, // rhs overwrites lhs values
   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, // equal against a declared list
   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, // concat against a declared list
   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, // equal even though order is different
   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  			// https://github.com/google/cel-spec/blob/master/doc/langdef.md#field-selection
   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, // must check intermediate optional fields (see below no such key error for d2)
   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  				// idiomatic map access
   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  				// field selection also possible if map key is a valid CEL identifier
   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  				// test comprehensions where the field used in predicates is unset on all but one of the elements:
   413  				// - with has checks:
   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  				// undocumented overload of map that takes a filter argument. This is the same as .filter().map()
   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  				// - without has checks:
   423  
   424  				// all() and exists() macros ignore errors from predicates so long as the condition holds for at least one element
   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  		// Kubernetes special types
   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  				// 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are always accessible
   549  				// even if not specified in the schema.
   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  				// in this case 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are specified in the
   592  				// schema, but they would be accessible even if they were not
   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  				// the specified embedded fields are accessible
   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  				// 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are always accessible
   627  				// even if not specified in the schema, regardless of if x-kubernetes-preserve-unknown-fields is set.
   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  				// the object exists
   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  				// typical int-or-string usage would be to check both types
   646  				"type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
   647  				// to require the value be a particular type, guard it with a runtime type check
   648  				"type(self.something) == string && self.something == '25%'": 7,
   649  
   650  				// In Kubernetes 1.24 and later, the CEL type returns false for an int-or-string comparison against the
   651  				// other type, making it safe to write validation rules like:
   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  				// Because the type is dynamic it receives no type checking, and evaluates to false when compared to
   658  				// other types at runtime.
   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  				// typical int-or-string usage would be to check both types
   671  				"type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
   672  				// to require the value be a particular type, guard it with a runtime type check
   673  				"type(self.something) == int && self.something == 1": 7,
   674  
   675  				// In Kubernetes 1.24 and later, the CEL type returns false for an int-or-string comparison against the
   676  				// other type, making it safe to write validation rules like:
   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  				// Because the type is dynamic it receives no type checking, and evaluates to false when compared to
   683  				// other types at runtime.
   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  				// validate that if 'min' is a string that it is a percentage
   711  				`type(self.min) == string && self.min.matches(r'(\d+(\.\d+)?%)')`: 10,
   712  				// validate that 'min' can be either a exact value minimum, or a minimum as a percentage of 'available'
   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  				// fields that are unknown because they were not specified on the object schema are included in equality checks
   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  				// fields specified on the object schema that are unknown because the field's schema is unknown are also included equality checks
   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  				// if the unknown fields are the same, they are equal
   869  				"self.withUnknownList[1] == self.withUnknownList[2]": 7,
   870  
   871  				// if unknown fields are different, they are not equal
   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  				// if all fields are known, equality works as usual
   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  				// we don't run the defaulter in this test suite (depending on it would introduce a cycle)
   889  				// so we fake it :(
   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  				// We treat null fields as absent fields, not as null valued fields.
   911  				// Note that this is different than how we treat nullable list items or map values.
   912  				"type(self.setNullableStr) != null_type": 4,
   913  
   914  				// a field that is set to null is treated the same as an absent field in validation rules
   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, // null check using runtime type checking
   940  				//"self.m['a'] == null",
   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  				// we do not expose a stable type for the self variable, even when it is an object that CEL
   972  				// considers a named type. The only operation developers should be able to perform on the type is
   973  				// equality checking.
   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, // equal even though order is different
  1007  				"self.objs[0][0].k__dot__ == '1'": 6, // '.' is a supported character in identifiers, but it is escaped
  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  				// chained comprehensions
  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  				// nested comprehensions
  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, // or() is short-circuited
  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  			// 1st obj and schema args are for "self.val1" field, 2nd for "self.val2" and so on.
  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, // integer with no format is the same as int64
  1223  				"type(self.val1) == int":                              4,
  1224  				fmt.Sprintf("self.val3 + 1 == %d + 1", math.MaxInt32): 5, // CEL integers are 64 bit
  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, // number with no format is the same as float64
  1232  				"type(self.val1) == double": 4,
  1233  
  1234  				// Use a int64 value with a number openAPI schema type since float representations of whole numbers
  1235  				// (e.g. 1.0, 0.0) can convert to int representations (e.g. 1, 0) in yaml to json translation, and
  1236  				// then get parsed as int64s.
  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  				// xref: https://github.com/google/cel-spec/wiki/proposal-210
  1245  
  1246  				// compare integers with all float types
  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  				// compare literal integers and floats
  1255  				"double(5) < 10.0": 2,
  1256  				"5 < int(10.0)":    2,
  1257  
  1258  				// compare integers with literal floats
  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  				// string functions (https://github.com/google/cel-go/blob/v0.9.0/ext/strings.go)
  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  				// equal even though order is different
  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, // equal even though order is different
  1424  				"self.objs[0] + self.objs[2] == self.objs[2]": 104868, // rhs overwrites lhs values
  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, // equal against a declared list
  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, // concat against a declared list
  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, // equal even though order is different
  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  			// https://github.com/google/cel-spec/blob/master/doc/langdef.md#field-selection
  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, // must check intermediate optional fields (see below no such key error for d2)
  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  				// idiomatic map access
  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  				// field selection also possible if map key is a valid CEL identifier
  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  				// test comprehensions where the field used in predicates is unset on all but one of the elements:
  1541  				// - with has checks:
  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  				// undocumented overload of map that takes a filter argument. This is the same as .filter().map()
  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  				// - without has checks:
  1551  
  1552  				// all() and exists() macros ignore errors from predicates so long as the condition holds for at least one element
  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  		// Kubernetes special types
  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  				// 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are always accessible
  1622  				// even if not specified in the schema.
  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  				// in this case 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are specified in the
  1651  				// schema, but they would be accessible even if they were not
  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  				// the specified embedded fields are accessible
  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  				// 'kind', 'apiVersion', 'metadata.name' and 'metadata.generateName' are always accessible
  1672  				// even if not specified in the schema, regardless of if x-kubernetes-preserve-unknown-fields is set.
  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  				// the object exists
  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  				// typical int-or-string usage would be to check both types
  1688  				"type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
  1689  				// to require the value be a particular type, guard it with a runtime type check
  1690  				"type(self.something) == string && self.something == '25%'": 7,
  1691  
  1692  				// In Kubernetes 1.24 and later, the CEL type returns false for an int-or-string comparison against the
  1693  				// other type, making it safe to write validation rules like:
  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  				// Because the type is dynamic it receives no type checking, and evaluates to false when compared to
  1700  				// other types at runtime.
  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  				// typical int-or-string usage would be to check both types
  1710  				"type(self.something) == int ? self.something == 1 : self.something == '25%'": 7,
  1711  				// to require the value be a particular type, guard it with a runtime type check
  1712  				"type(self.something) == int && self.something == 1": 7,
  1713  
  1714  				// In Kubernetes 1.24 and later, the CEL type returns false for an int-or-string comparison against the
  1715  				// other type, making it safe to write validation rules like:
  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  				// Because the type is dynamic it receives no type checking, and evaluates to false when compared to
  1722  				// other types at runtime.
  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  				// validate that if 'min' is a string that it is a percentage
  1742  				`type(self.min) == string && self.min.matches(r'(\d+(\.\d+)?%)')`: 1258298,
  1743  				// validate that 'min' can be either a exact value minimum, or a minimum as a percentage of 'available'
  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  				// fields that are unknown because they were not specified on the object schema are included in equality checks
  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  				// fields specified on the object schema that are unknown because the field's schema is unknown are also included equality checks
  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  				// if the unknown fields are the same, they are equal
  1834  				"self.withUnknownList[1] == self.withUnknownList[2]": 6,
  1835  
  1836  				// if unknown fields are different, they are not equal
  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  				// if all fields are known, equality works as usual
  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  				// We treat null fields as absent fields, not as null valued fields.
  1866  				// Note that this is different than how we treat nullable list items or map values.
  1867  				"type(self.setNullableStr) != null_type": 4,
  1868  
  1869  				// a field that is set to null is treated the same as an absent field in validation rules
  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, // null check using runtime type checking
  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  				// we do not expose a stable type for the self variable, even when it is an object that CEL
  1896  				// considers a named type. The only operation developers should be able to perform on the type is
  1897  				// equality checking.
  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, // equal even though order is different
  1911  				"self.objs[0][0].k__dot__ == '1'": 6,      // '.' is a supported character in identifiers, but it is escaped
  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  				// chained comprehensions
  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  				// nested comprehensions
  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, // or() is short-circuited
  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