k8s.io/apiserver@v0.31.1/pkg/cel/library/cost_test.go (about)

     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 library
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/google/cel-go/cel"
    25  	"github.com/google/cel-go/checker"
    26  	"github.com/google/cel-go/common"
    27  	"github.com/google/cel-go/common/ast"
    28  	"github.com/google/cel-go/common/types"
    29  	"github.com/google/cel-go/ext"
    30  	exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
    31  
    32  	"k8s.io/apiserver/pkg/authorization/authorizer"
    33  )
    34  
    35  const (
    36  	intListLiteral       = "[1, 2, 3, 4, 5]"
    37  	uintListLiteral      = "[uint(1), uint(2), uint(3), uint(4), uint(5)]"
    38  	doubleListLiteral    = "[1.0, 2.0, 3.0, 4.0, 5.0]"
    39  	boolListLiteral      = "[false, true, false, true, false]"
    40  	stringListLiteral    = "['012345678901', '012345678901', '012345678901', '012345678901', '012345678901']"
    41  	bytesListLiteral     = "[bytes('012345678901'), bytes('012345678901'), bytes('012345678901'), bytes('012345678901'), bytes('012345678901')]"
    42  	durationListLiteral  = "[duration('1s'), duration('2s'), duration('3s'), duration('4s'), duration('5s')]"
    43  	timestampListLiteral = "[timestamp('2011-01-01T00:00:00.000+01:00'), timestamp('2011-01-02T00:00:00.000+01:00'), " +
    44  		"timestamp('2011-01-03T00:00:00.000+01:00'), timestamp('2011-01-04T00:00:00.000+01:00'), " +
    45  		"timestamp('2011-01-05T00:00:00.000+01:00')]"
    46  	stringLiteral = "'01234567890123456789012345678901234567890123456789'"
    47  )
    48  
    49  type comparableCost struct {
    50  	comparableLiteral     string
    51  	expectedEstimatedCost checker.CostEstimate
    52  	expectedRuntimeCost   uint64
    53  
    54  	param string
    55  }
    56  
    57  func TestListsCost(t *testing.T) {
    58  	cases := []struct {
    59  		opts  []string
    60  		costs []comparableCost
    61  	}{
    62  		{
    63  			opts: []string{".sum()"},
    64  			// 10 cost for the list declaration, the rest is the due to the function call
    65  			costs: []comparableCost{
    66  				{
    67  					comparableLiteral:     intListLiteral,
    68  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
    69  				},
    70  				{
    71  					comparableLiteral:     uintListLiteral,
    72  					expectedEstimatedCost: checker.CostEstimate{Min: 20, Max: 20}, expectedRuntimeCost: 20, // +5 for casts
    73  				},
    74  				{
    75  					comparableLiteral:     doubleListLiteral,
    76  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
    77  				},
    78  				{
    79  					comparableLiteral:     durationListLiteral,
    80  					expectedEstimatedCost: checker.CostEstimate{Min: 20, Max: 20}, expectedRuntimeCost: 20, // +5 for casts
    81  				},
    82  			},
    83  		},
    84  		{
    85  			opts: []string{".isSorted()", ".max()", ".min()"},
    86  			// 10 cost for the list declaration, the rest is the due to the function call
    87  			costs: []comparableCost{
    88  				{
    89  					comparableLiteral:     intListLiteral,
    90  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
    91  				},
    92  				{
    93  					comparableLiteral:     uintListLiteral,
    94  					expectedEstimatedCost: checker.CostEstimate{Min: 20, Max: 20}, expectedRuntimeCost: 20, // +5 for numeric casts
    95  				},
    96  				{
    97  					comparableLiteral:     doubleListLiteral,
    98  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
    99  				},
   100  				{
   101  					comparableLiteral:     boolListLiteral,
   102  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
   103  				},
   104  				{
   105  					comparableLiteral:     stringListLiteral,
   106  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 25}, expectedRuntimeCost: 15, // +5 for string comparisons
   107  				},
   108  				{
   109  					comparableLiteral:     bytesListLiteral,
   110  					expectedEstimatedCost: checker.CostEstimate{Min: 25, Max: 35}, expectedRuntimeCost: 25, // +10 for casts from string to byte, +5 for byte comparisons
   111  				},
   112  				{
   113  					comparableLiteral:     durationListLiteral,
   114  					expectedEstimatedCost: checker.CostEstimate{Min: 20, Max: 20}, expectedRuntimeCost: 20, // +5 for numeric casts
   115  				},
   116  				{
   117  					comparableLiteral:     timestampListLiteral,
   118  					expectedEstimatedCost: checker.CostEstimate{Min: 20, Max: 20}, expectedRuntimeCost: 20, // +5 for casts
   119  				},
   120  			},
   121  		},
   122  	}
   123  	for _, tc := range cases {
   124  		for _, op := range tc.opts {
   125  			for _, typ := range tc.costs {
   126  				t.Run(typ.comparableLiteral+op, func(t *testing.T) {
   127  					e := typ.comparableLiteral + op
   128  					testCost(t, e, typ.expectedEstimatedCost, typ.expectedRuntimeCost)
   129  				})
   130  			}
   131  		}
   132  	}
   133  }
   134  
   135  func TestIndexOfCost(t *testing.T) {
   136  	cases := []struct {
   137  		opts  []string
   138  		costs []comparableCost
   139  	}{
   140  		{
   141  			opts: []string{".indexOf(%s)", ".lastIndexOf(%s)"},
   142  			// 10 cost for the list declaration, the rest is the due to the function call
   143  			costs: []comparableCost{
   144  				{
   145  					comparableLiteral: intListLiteral, param: "3",
   146  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
   147  				},
   148  				{
   149  					comparableLiteral: uintListLiteral, param: "uint(3)",
   150  					expectedEstimatedCost: checker.CostEstimate{Min: 21, Max: 21}, expectedRuntimeCost: 21, // +5 for numeric casts
   151  				},
   152  				{
   153  					comparableLiteral: doubleListLiteral, param: "3.0",
   154  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
   155  				},
   156  				{
   157  					comparableLiteral: boolListLiteral, param: "true",
   158  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 15}, expectedRuntimeCost: 15,
   159  				},
   160  				{
   161  					comparableLiteral: stringListLiteral, param: "'x'",
   162  					expectedEstimatedCost: checker.CostEstimate{Min: 15, Max: 25}, expectedRuntimeCost: 15, // +5 for string comparisons
   163  				},
   164  				{
   165  					comparableLiteral: bytesListLiteral, param: "bytes('x')",
   166  					expectedEstimatedCost: checker.CostEstimate{Min: 26, Max: 36}, expectedRuntimeCost: 26, // +11 for casts from string to byte, +5 for byte comparisons
   167  				},
   168  				{
   169  					comparableLiteral: durationListLiteral, param: "duration('3s')",
   170  					expectedEstimatedCost: checker.CostEstimate{Min: 21, Max: 21}, expectedRuntimeCost: 21, // +6 for casts from duration to byte
   171  				},
   172  				{
   173  					comparableLiteral: timestampListLiteral, param: "timestamp('2011-01-03T00:00:00.000+01:00')",
   174  					expectedEstimatedCost: checker.CostEstimate{Min: 21, Max: 21}, expectedRuntimeCost: 21, // +6 for casts from timestamp to byte
   175  				},
   176  
   177  				// index of operations are also defined for strings
   178  				{
   179  					comparableLiteral: stringLiteral, param: "'123'",
   180  					expectedEstimatedCost: checker.CostEstimate{Min: 5, Max: 5}, expectedRuntimeCost: 5,
   181  				},
   182  			},
   183  		},
   184  	}
   185  	for _, tc := range cases {
   186  		for _, op := range tc.opts {
   187  			for _, typ := range tc.costs {
   188  				opWithParam := fmt.Sprintf(op, typ.param)
   189  				t.Run(typ.comparableLiteral+opWithParam, func(t *testing.T) {
   190  					e := typ.comparableLiteral + opWithParam
   191  					testCost(t, e, typ.expectedEstimatedCost, typ.expectedRuntimeCost)
   192  				})
   193  			}
   194  		}
   195  	}
   196  }
   197  
   198  func TestURLsCost(t *testing.T) {
   199  	cases := []struct {
   200  		ops                []string
   201  		expectEsimatedCost checker.CostEstimate
   202  		expectRuntimeCost  uint64
   203  	}{
   204  		{
   205  			ops:                []string{".getScheme()", ".getHostname()", ".getHost()", ".getPort()", ".getEscapedPath()", ".getQuery()"},
   206  			expectEsimatedCost: checker.CostEstimate{Min: 4, Max: 4},
   207  			expectRuntimeCost:  4,
   208  		},
   209  	}
   210  
   211  	for _, tc := range cases {
   212  		for _, op := range tc.ops {
   213  			t.Run("url."+op, func(t *testing.T) {
   214  				testCost(t, "url('https:://kubernetes.io/')"+op, tc.expectEsimatedCost, tc.expectRuntimeCost)
   215  			})
   216  		}
   217  	}
   218  }
   219  
   220  func TestIPCost(t *testing.T) {
   221  	ipv4 := "ip('192.168.0.1')"
   222  	ipv4BaseEstimatedCost := checker.CostEstimate{Min: 2, Max: 2}
   223  	ipv4BaseRuntimeCost := uint64(2)
   224  
   225  	ipv6 := "ip('2001:db8:3333:4444:5555:6666:7777:8888')"
   226  	ipv6BaseEstimatedCost := checker.CostEstimate{Min: 4, Max: 4}
   227  	ipv6BaseRuntimeCost := uint64(4)
   228  
   229  	testCases := []struct {
   230  		ops                []string
   231  		expectEsimatedCost func(checker.CostEstimate) checker.CostEstimate
   232  		expectRuntimeCost  func(uint64) uint64
   233  	}{
   234  		{
   235  			// For just parsing the IP, the cost is expected to be the base.
   236  			ops:                []string{""},
   237  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate { return c },
   238  			expectRuntimeCost:  func(c uint64) uint64 { return c },
   239  		},
   240  		{
   241  			ops: []string{".family()", ".isUnspecified()", ".isLoopback()", ".isLinkLocalMulticast()", ".isLinkLocalUnicast()", ".isGlobalUnicast()"},
   242  			// For most other operations, the cost is expected to be the base + 1.
   243  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   244  				return checker.CostEstimate{Min: c.Min + 1, Max: c.Max + 1}
   245  			},
   246  			expectRuntimeCost: func(c uint64) uint64 { return c + 1 },
   247  		},
   248  	}
   249  
   250  	for _, tc := range testCases {
   251  		for _, op := range tc.ops {
   252  			t.Run(ipv4+op, func(t *testing.T) {
   253  				testCost(t, ipv4+op, tc.expectEsimatedCost(ipv4BaseEstimatedCost), tc.expectRuntimeCost(ipv4BaseRuntimeCost))
   254  			})
   255  
   256  			t.Run(ipv6+op, func(t *testing.T) {
   257  				testCost(t, ipv6+op, tc.expectEsimatedCost(ipv6BaseEstimatedCost), tc.expectRuntimeCost(ipv6BaseRuntimeCost))
   258  			})
   259  		}
   260  	}
   261  }
   262  
   263  func TestIPIsCanonicalCost(t *testing.T) {
   264  	testCases := []struct {
   265  		op                 string
   266  		expectEsimatedCost checker.CostEstimate
   267  		expectRuntimeCost  uint64
   268  	}{
   269  		{
   270  			op:                 "ip.isCanonical('192.168.0.1')",
   271  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   272  			expectRuntimeCost:  3,
   273  		},
   274  		{
   275  			op:                 "ip.isCanonical('2001:db8:3333:4444:5555:6666:7777:8888')",
   276  			expectEsimatedCost: checker.CostEstimate{Min: 8, Max: 8},
   277  			expectRuntimeCost:  8,
   278  		},
   279  		{
   280  			op:                 "ip.isCanonical('2001:db8::abcd')",
   281  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   282  			expectRuntimeCost:  3,
   283  		},
   284  	}
   285  
   286  	for _, tc := range testCases {
   287  		t.Run(tc.op, func(t *testing.T) {
   288  			testCost(t, tc.op, tc.expectEsimatedCost, tc.expectRuntimeCost)
   289  		})
   290  	}
   291  }
   292  
   293  func TestCIDRCost(t *testing.T) {
   294  	ipv4 := "cidr('192.168.0.0/16')"
   295  	ipv4BaseEstimatedCost := checker.CostEstimate{Min: 2, Max: 2}
   296  	ipv4BaseRuntimeCost := uint64(2)
   297  
   298  	ipv6 := "cidr('2001:db8::/32')"
   299  	ipv6BaseEstimatedCost := checker.CostEstimate{Min: 2, Max: 2}
   300  	ipv6BaseRuntimeCost := uint64(2)
   301  
   302  	type testCase struct {
   303  		ops                []string
   304  		expectEsimatedCost func(checker.CostEstimate) checker.CostEstimate
   305  		expectRuntimeCost  func(uint64) uint64
   306  	}
   307  
   308  	cases := []testCase{
   309  		{
   310  			// For just parsing the IP, the cost is expected to be the base.
   311  			ops:                []string{""},
   312  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate { return c },
   313  			expectRuntimeCost:  func(c uint64) uint64 { return c },
   314  		},
   315  		{
   316  			ops: []string{".ip()", ".prefixLength()", ".masked()"},
   317  			// For most other operations, the cost is expected to be the base + 1.
   318  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   319  				return checker.CostEstimate{Min: c.Min + 1, Max: c.Max + 1}
   320  			},
   321  			expectRuntimeCost: func(c uint64) uint64 { return c + 1 },
   322  		},
   323  	}
   324  
   325  	//nolint:gocritic
   326  	ipv4Cases := append(cases, []testCase{
   327  		{
   328  			ops: []string{".containsCIDR(cidr('192.0.0.0/30'))"},
   329  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   330  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   331  			},
   332  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   333  		},
   334  		{
   335  			ops: []string{".containsCIDR(cidr('192.168.0.0/16'))"},
   336  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   337  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   338  			},
   339  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   340  		},
   341  		{
   342  			ops: []string{".containsCIDR('192.0.0.0/30')"},
   343  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   344  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   345  			},
   346  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   347  		},
   348  		{
   349  			ops: []string{".containsCIDR('192.168.0.0/16')"},
   350  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   351  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   352  			},
   353  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   354  		},
   355  		{
   356  			ops: []string{".containsIP(ip('192.0.0.1'))"},
   357  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   358  				return checker.CostEstimate{Min: c.Min + 2, Max: c.Max + 5}
   359  			},
   360  			expectRuntimeCost: func(c uint64) uint64 { return c + 2 },
   361  		},
   362  		{
   363  			ops: []string{".containsIP(ip('192.169.0.1'))"},
   364  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   365  				return checker.CostEstimate{Min: c.Min + 3, Max: c.Max + 6}
   366  			},
   367  			expectRuntimeCost: func(c uint64) uint64 { return c + 3 },
   368  		},
   369  		{
   370  			ops: []string{".containsIP(ip('192.169.169.250'))"},
   371  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   372  				return checker.CostEstimate{Min: c.Min + 3, Max: c.Max + 6}
   373  			},
   374  			expectRuntimeCost: func(c uint64) uint64 { return c + 3 },
   375  		},
   376  		{
   377  			ops: []string{".containsIP('192.0.0.1')"},
   378  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   379  				return checker.CostEstimate{Min: c.Min + 2, Max: c.Max + 5}
   380  			},
   381  			expectRuntimeCost: func(c uint64) uint64 { return c + 2 },
   382  		},
   383  		{
   384  			ops: []string{".containsIP('192.169.0.1')"},
   385  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   386  				return checker.CostEstimate{Min: c.Min + 3, Max: c.Max + 6}
   387  			},
   388  			expectRuntimeCost: func(c uint64) uint64 { return c + 3 },
   389  		},
   390  	}...)
   391  
   392  	//nolint:gocritic
   393  	ipv6Cases := append(cases, []testCase{
   394  		{
   395  			ops: []string{".containsCIDR(cidr('2001:db8::/126'))"},
   396  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   397  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   398  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   399  			},
   400  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   401  		},
   402  		{
   403  			ops: []string{".containsCIDR(cidr('2001:db8::/32'))"},
   404  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   405  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   406  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   407  			},
   408  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   409  		},
   410  		{
   411  			ops: []string{".containsCIDR('2001:db8::/126')"},
   412  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   413  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   414  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   415  			},
   416  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   417  		},
   418  		{
   419  			ops: []string{".containsCIDR('2001:db8::/32')"},
   420  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   421  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   422  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 9}
   423  			},
   424  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   425  		},
   426  		{
   427  			ops: []string{".containsIP(ip('2001:db8:3333:4444:5555:6666:7777:8888'))"},
   428  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   429  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   430  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 8}
   431  			},
   432  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   433  		},
   434  		{
   435  			ops: []string{".containsIP(ip('2001:db8::1'))"},
   436  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   437  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   438  				return checker.CostEstimate{Min: c.Min + 3, Max: c.Max + 6}
   439  			},
   440  			expectRuntimeCost: func(c uint64) uint64 { return c + 3 },
   441  		},
   442  		{
   443  			ops: []string{".containsIP('2001:db8:3333:4444:5555:6666:7777:8888')"},
   444  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   445  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   446  				return checker.CostEstimate{Min: c.Min + 5, Max: c.Max + 8}
   447  			},
   448  			expectRuntimeCost: func(c uint64) uint64 { return c + 5 },
   449  		},
   450  		{
   451  			ops: []string{".containsIP('2001:db8::1')"},
   452  			// For operations like checking if an IP is in a CIDR, the cost is expected to higher.
   453  			expectEsimatedCost: func(c checker.CostEstimate) checker.CostEstimate {
   454  				return checker.CostEstimate{Min: c.Min + 3, Max: c.Max + 6}
   455  			},
   456  			expectRuntimeCost: func(c uint64) uint64 { return c + 3 },
   457  		},
   458  	}...)
   459  
   460  	for _, tc := range ipv4Cases {
   461  		for _, op := range tc.ops {
   462  			t.Run(ipv4+op, func(t *testing.T) {
   463  				testCost(t, ipv4+op, tc.expectEsimatedCost(ipv4BaseEstimatedCost), tc.expectRuntimeCost(ipv4BaseRuntimeCost))
   464  			})
   465  		}
   466  	}
   467  
   468  	for _, tc := range ipv6Cases {
   469  		for _, op := range tc.ops {
   470  			t.Run(ipv6+op, func(t *testing.T) {
   471  				testCost(t, ipv6+op, tc.expectEsimatedCost(ipv6BaseEstimatedCost), tc.expectRuntimeCost(ipv6BaseRuntimeCost))
   472  			})
   473  		}
   474  	}
   475  }
   476  
   477  func TestStringLibrary(t *testing.T) {
   478  	cases := []struct {
   479  		name               string
   480  		expr               string
   481  		expectEsimatedCost checker.CostEstimate
   482  		expectRuntimeCost  uint64
   483  	}{
   484  		{
   485  			name:               "lowerAscii",
   486  			expr:               "'ABCDEFGHIJ abcdefghij'.lowerAscii()",
   487  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   488  			expectRuntimeCost:  3,
   489  		},
   490  		{
   491  			name:               "lowerAsciiEquals",
   492  			expr:               "'ABCDEFGHIJ abcdefghij'.lowerAscii() == 'abcdefghij ABCDEFGHIJ'.lowerAscii()",
   493  			expectEsimatedCost: checker.CostEstimate{Min: 7, Max: 9},
   494  			expectRuntimeCost:  9,
   495  		},
   496  		{
   497  			name:               "upperAscii",
   498  			expr:               "'ABCDEFGHIJ abcdefghij'.upperAscii()",
   499  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   500  			expectRuntimeCost:  3,
   501  		},
   502  		{
   503  			name:               "upperAsciiEquals",
   504  			expr:               "'ABCDEFGHIJ abcdefghij'.upperAscii() == 'abcdefghij ABCDEFGHIJ'.upperAscii()",
   505  			expectEsimatedCost: checker.CostEstimate{Min: 7, Max: 9},
   506  			expectRuntimeCost:  9,
   507  		},
   508  		{
   509  			name:               "quote",
   510  			expr:               "strings.quote('ABCDEFGHIJ abcdefghij')",
   511  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   512  			expectRuntimeCost:  3,
   513  		},
   514  		{
   515  			name:               "quoteEquals",
   516  			expr:               "strings.quote('ABCDEFGHIJ abcdefghij') == strings.quote('ABCDEFGHIJ abcdefghij')",
   517  			expectEsimatedCost: checker.CostEstimate{Min: 7, Max: 11},
   518  			expectRuntimeCost:  9,
   519  		},
   520  		{
   521  			name:               "replace",
   522  			expr:               "'abc 123 def 123'.replace('123', '456')",
   523  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   524  			expectRuntimeCost:  3,
   525  		},
   526  		{
   527  			name:               "replace between all chars",
   528  			expr:               "'abc 123 def 123'.replace('', 'x')",
   529  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   530  			expectRuntimeCost:  3,
   531  		},
   532  		{
   533  			name:               "replace with empty",
   534  			expr:               "'abc 123 def 123'.replace('123', '')",
   535  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   536  			expectRuntimeCost:  3,
   537  		},
   538  		{
   539  			name:               "replace with limit",
   540  			expr:               "'abc 123 def 123'.replace('123', '456', 1)",
   541  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   542  			expectRuntimeCost:  3,
   543  		},
   544  		{
   545  			name:               "split",
   546  			expr:               "'abc 123 def 123'.split(' ')",
   547  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   548  			expectRuntimeCost:  3,
   549  		},
   550  		{
   551  			name:               "split with limit",
   552  			expr:               "'abc 123 def 123'.split(' ', 1)",
   553  			expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   554  			expectRuntimeCost:  3,
   555  		},
   556  		{
   557  			name:               "substring",
   558  			expr:               "'abc 123 def 123'.substring(5)",
   559  			expectEsimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   560  			expectRuntimeCost:  2,
   561  		},
   562  		{
   563  			name:               "substring with end",
   564  			expr:               "'abc 123 def 123'.substring(5, 8)",
   565  			expectEsimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   566  			expectRuntimeCost:  2,
   567  		},
   568  		{
   569  			name:               "trim",
   570  			expr:               "'  abc 123 def 123  '.trim()",
   571  			expectEsimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   572  			expectRuntimeCost:  2,
   573  		},
   574  		{
   575  			name:               "join with separator",
   576  			expr:               "['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')",
   577  			expectEsimatedCost: checker.CostEstimate{Min: 11, Max: 23},
   578  			expectRuntimeCost:  15,
   579  		},
   580  		{
   581  			name:               "join",
   582  			expr:               "['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join()",
   583  			expectEsimatedCost: checker.CostEstimate{Min: 10, Max: 22},
   584  			expectRuntimeCost:  13,
   585  		},
   586  		{
   587  			name:               "find",
   588  			expr:               "'abc 123 def 123'.find('123')",
   589  			expectEsimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   590  			expectRuntimeCost:  2,
   591  		},
   592  		{
   593  			name:               "findAll",
   594  			expr:               "'abc 123 def 123'.findAll('123')",
   595  			expectEsimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   596  			expectRuntimeCost:  2,
   597  		},
   598  		{
   599  			name:               "findAll with limit",
   600  			expr:               "'abc 123 def 123'.findAll('123', 1)",
   601  			expectEsimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   602  			expectRuntimeCost:  2,
   603  		},
   604  	}
   605  
   606  	for _, tc := range cases {
   607  		t.Run(tc.name, func(t *testing.T) {
   608  			testCost(t, tc.expr, tc.expectEsimatedCost, tc.expectRuntimeCost)
   609  		})
   610  	}
   611  }
   612  
   613  func TestAuthzLibrary(t *testing.T) {
   614  	cases := []struct {
   615  		name                string
   616  		expr                string
   617  		expectEstimatedCost checker.CostEstimate
   618  		expectRuntimeCost   uint64
   619  	}{
   620  		{
   621  			name:                "path",
   622  			expr:                "authorizer.path('/healthz')",
   623  			expectEstimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   624  			expectRuntimeCost:   2,
   625  		},
   626  		{
   627  			name:                "resource",
   628  			expr:                "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend')",
   629  			expectEstimatedCost: checker.CostEstimate{Min: 6, Max: 6},
   630  			expectRuntimeCost:   6,
   631  		},
   632  		{
   633  			name:                "fieldSelector",
   634  			expr:                "authorizer.group('').resource('pods').fieldSelector('spec.nodeName=example-node-name.fully.qualified.domain.name.example.com')",
   635  			expectEstimatedCost: checker.CostEstimate{Min: 1821, Max: 1821},
   636  			expectRuntimeCost:   1821, // authorizer(1) + group(1) + resource(1) + fieldSelector(10 + ceil(71/2)*50=1800 + ceil(71*.1)=8)
   637  		},
   638  		{
   639  			name:                "labelSelector",
   640  			expr:                "authorizer.group('').resource('pods').labelSelector('spec.nodeName=example-node-name.fully.qualified.domain.name.example.com')",
   641  			expectEstimatedCost: checker.CostEstimate{Min: 1821, Max: 1821},
   642  			expectRuntimeCost:   1821, // authorizer(1) + group(1) + resource(1) + fieldSelector(10 + ceil(71/2)*50=1800 + ceil(71*.1)=8)
   643  		},
   644  		{
   645  			name:                "path check allowed",
   646  			expr:                "authorizer.path('/healthz').check('get').allowed()",
   647  			expectEstimatedCost: checker.CostEstimate{Min: 350003, Max: 350003},
   648  			expectRuntimeCost:   350003,
   649  		},
   650  		{
   651  			name:                "resource check allowed",
   652  			expr:                "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()",
   653  			expectEstimatedCost: checker.CostEstimate{Min: 350007, Max: 350007},
   654  			expectRuntimeCost:   350007,
   655  		},
   656  		{
   657  			name:                "resource check reason",
   658  			expr:                "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()",
   659  			expectEstimatedCost: checker.CostEstimate{Min: 350007, Max: 350007},
   660  			expectRuntimeCost:   350007,
   661  		},
   662  		{
   663  			name:                "resource check errored",
   664  			expr:                "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').errored()",
   665  			expectEstimatedCost: checker.CostEstimate{Min: 350007, Max: 350007},
   666  			expectRuntimeCost:   350007,
   667  		},
   668  		{
   669  			name:                "resource check error",
   670  			expr:                "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').error()",
   671  			expectEstimatedCost: checker.CostEstimate{Min: 350007, Max: 350007},
   672  			expectRuntimeCost:   350007,
   673  		},
   674  	}
   675  
   676  	for _, tc := range cases {
   677  		t.Run(tc.name, func(t *testing.T) {
   678  			testCost(t, tc.expr, tc.expectEstimatedCost, tc.expectRuntimeCost)
   679  		})
   680  	}
   681  }
   682  
   683  func TestQuantityCost(t *testing.T) {
   684  	cases := []struct {
   685  		name                string
   686  		expr                string
   687  		expectEstimatedCost checker.CostEstimate
   688  		expectRuntimeCost   uint64
   689  	}{
   690  		{
   691  			name:                "path",
   692  			expr:                `quantity("12Mi")`,
   693  			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
   694  			expectRuntimeCost:   1,
   695  		},
   696  		{
   697  			name:                "isQuantity",
   698  			expr:                `isQuantity("20")`,
   699  			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
   700  			expectRuntimeCost:   1,
   701  		},
   702  		{
   703  			name:                "isQuantity_megabytes",
   704  			expr:                `isQuantity("20M")`,
   705  			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
   706  			expectRuntimeCost:   1,
   707  		},
   708  		{
   709  			name:                "equality_reflexivity",
   710  			expr:                `quantity("200M") == quantity("200M")`,
   711  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 1844674407370955266},
   712  			expectRuntimeCost:   3,
   713  		},
   714  		{
   715  			name:                "equality_symmetry",
   716  			expr:                `quantity("200M") == quantity("0.2G") && quantity("0.2G") == quantity("200M")`,
   717  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3689348814741910532},
   718  			expectRuntimeCost:   6,
   719  		},
   720  		{
   721  			name:                "equality_transitivity",
   722  			expr:                `quantity("2M") == quantity("0.002G") && quantity("2000k") == quantity("2M") && quantity("0.002G") == quantity("2000k")`,
   723  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 5534023222112865798},
   724  			expectRuntimeCost:   9,
   725  		},
   726  		{
   727  			name:                "quantity_less",
   728  			expr:                `quantity("50M").isLessThan(quantity("50Mi"))`,
   729  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   730  			expectRuntimeCost:   3,
   731  		},
   732  		{
   733  			name:                "quantity_greater",
   734  			expr:                `quantity("50Mi").isGreaterThan(quantity("50M"))`,
   735  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   736  			expectRuntimeCost:   3,
   737  		},
   738  		{
   739  			name:                "compare_equal",
   740  			expr:                `quantity("200M").compareTo(quantity("0.2G")) > 0`,
   741  			expectEstimatedCost: checker.CostEstimate{Min: 4, Max: 4},
   742  			expectRuntimeCost:   4,
   743  		},
   744  		{
   745  			name:                "add_quantity",
   746  			expr:                `quantity("50k").add(quantity("20")) == quantity("50.02k")`,
   747  			expectEstimatedCost: checker.CostEstimate{Min: 5, Max: 1844674407370955268},
   748  			expectRuntimeCost:   5,
   749  		},
   750  		{
   751  			name:                "sub_quantity",
   752  			expr:                `quantity("50k").sub(quantity("20")) == quantity("49.98k")`,
   753  			expectEstimatedCost: checker.CostEstimate{Min: 5, Max: 1844674407370955268},
   754  			expectRuntimeCost:   5,
   755  		},
   756  		{
   757  			name:                "sub_int",
   758  			expr:                `quantity("50k").sub(20) == quantity("49980")`,
   759  			expectEstimatedCost: checker.CostEstimate{Min: 4, Max: 1844674407370955267},
   760  			expectRuntimeCost:   4,
   761  		},
   762  		{
   763  			name:                "arith_chain_1",
   764  			expr:                `quantity("50k").add(20).sub(quantity("100k")).asInteger() > 0`,
   765  			expectEstimatedCost: checker.CostEstimate{Min: 6, Max: 6},
   766  			expectRuntimeCost:   6,
   767  		},
   768  		{
   769  			name:                "arith_chain",
   770  			expr:                `quantity("50k").add(20).sub(quantity("100k")).sub(-50000).asInteger() > 0`,
   771  			expectEstimatedCost: checker.CostEstimate{Min: 7, Max: 7},
   772  			expectRuntimeCost:   7,
   773  		},
   774  		{
   775  			name:                "as_integer",
   776  			expr:                `quantity("50k").asInteger() > 0`,
   777  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   778  			expectRuntimeCost:   3,
   779  		},
   780  		{
   781  			name:                "is_integer",
   782  			expr:                `quantity("50").isInteger()`,
   783  			expectEstimatedCost: checker.CostEstimate{Min: 2, Max: 2},
   784  			expectRuntimeCost:   2,
   785  		},
   786  		{
   787  			name:                "as_float",
   788  			expr:                `quantity("50.703k").asApproximateFloat() > 0.0`,
   789  			expectEstimatedCost: checker.CostEstimate{Min: 3, Max: 3},
   790  			expectRuntimeCost:   3,
   791  		},
   792  	}
   793  
   794  	for _, tc := range cases {
   795  		t.Run(tc.name, func(t *testing.T) {
   796  			testCost(t, tc.expr, tc.expectEstimatedCost, tc.expectRuntimeCost)
   797  		})
   798  	}
   799  }
   800  
   801  func TestNameFormatCost(t *testing.T) {
   802  	cases := []struct {
   803  		name                string
   804  		expr                string
   805  		expectEstimatedCost checker.CostEstimate
   806  		expectRuntimeCost   uint64
   807  	}{
   808  		{
   809  			name:                "format.named",
   810  			expr:                `format.named("dns1123subdomain")`,
   811  			expectEstimatedCost: checker.CostEstimate{Min: 1, Max: 1},
   812  			expectRuntimeCost:   1,
   813  		},
   814  		{
   815  			name: "format.dns1123Subdomain.validate",
   816  			expr: `format.named("dns1123Subdomain").value().validate("my-name")`,
   817  			// Estimated cost doesnt know value at runtime so it is
   818  			// using an estimated maximum regex length
   819  			expectEstimatedCost: checker.CostEstimate{Min: 34, Max: 34},
   820  			expectRuntimeCost:   17,
   821  		},
   822  		{
   823  			name:                "format.dns1123label.validate",
   824  			expr:                `format.named("dns1123Label").value().validate("my-name")`,
   825  			expectEstimatedCost: checker.CostEstimate{Min: 34, Max: 34},
   826  			expectRuntimeCost:   10,
   827  		},
   828  	}
   829  
   830  	for _, tc := range cases {
   831  		t.Run(tc.name, func(t *testing.T) {
   832  			testCost(t, tc.expr, tc.expectEstimatedCost, tc.expectRuntimeCost)
   833  		})
   834  	}
   835  }
   836  
   837  func TestSetsCost(t *testing.T) {
   838  	cases := []struct {
   839  		name                string
   840  		expr                string
   841  		expectEstimatedCost checker.CostEstimate
   842  		expectRuntimeCost   uint64
   843  	}{
   844  		{
   845  			name:                "sets",
   846  			expr:                `sets.contains([], [])`,
   847  			expectEstimatedCost: checker.CostEstimate{Min: 21, Max: 21},
   848  			expectRuntimeCost:   21,
   849  		},
   850  		{
   851  			expr:                `sets.contains([1], [])`,
   852  			expectEstimatedCost: checker.CostEstimate{Min: 21, Max: 21},
   853  			expectRuntimeCost:   21,
   854  		},
   855  		{
   856  			expr:                `sets.contains([1], [1])`,
   857  			expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
   858  			expectRuntimeCost:   22,
   859  		},
   860  		{
   861  			expr:                `sets.contains([1], [1, 1])`,
   862  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   863  			expectRuntimeCost:   23,
   864  		},
   865  		{
   866  			expr:                `sets.contains([1, 1], [1])`,
   867  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   868  			expectRuntimeCost:   23,
   869  		},
   870  		{
   871  			expr:                `sets.contains([2, 1], [1])`,
   872  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   873  			expectRuntimeCost:   23,
   874  		},
   875  		{
   876  			expr:                `sets.contains([1, 2, 3, 4], [2, 3])`,
   877  			expectEstimatedCost: checker.CostEstimate{Min: 29, Max: 29},
   878  			expectRuntimeCost:   29,
   879  		},
   880  		{
   881  			expr:                `sets.contains([1], [1.0, 1])`,
   882  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   883  			expectRuntimeCost:   23,
   884  		},
   885  		{
   886  			expr:                `sets.contains([1, 2], [2u, 2.0])`,
   887  			expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
   888  			expectRuntimeCost:   25,
   889  		},
   890  		{
   891  			expr:                `sets.contains([1, 2u], [2, 2.0])`,
   892  			expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
   893  			expectRuntimeCost:   25,
   894  		},
   895  		{
   896  			expr:                `sets.contains([1, 2.0, 3u], [1.0, 2u, 3])`,
   897  			expectEstimatedCost: checker.CostEstimate{Min: 30, Max: 30},
   898  			expectRuntimeCost:   30,
   899  		},
   900  		{
   901  			expr: `sets.contains([[1], [2, 3]], [[2, 3.0]])`,
   902  			// 10 for each list creation, top-level list sizes are 2, 1
   903  			expectEstimatedCost: checker.CostEstimate{Min: 53, Max: 53},
   904  			expectRuntimeCost:   53,
   905  		},
   906  		{
   907  			expr:                `!sets.contains([1], [2])`,
   908  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   909  			expectRuntimeCost:   23,
   910  		},
   911  		{
   912  			expr:                `!sets.contains([1], [1, 2])`,
   913  			expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
   914  			expectRuntimeCost:   24,
   915  		},
   916  		{
   917  			expr:                `!sets.contains([1], ["1", 1])`,
   918  			expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
   919  			expectRuntimeCost:   24,
   920  		},
   921  		{
   922  			expr:                `!sets.contains([1], [1.1, 1u])`,
   923  			expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
   924  			expectRuntimeCost:   24,
   925  		},
   926  
   927  		// set equivalence (note the cost factor is higher as it's basically two contains checks)
   928  		{
   929  			expr:                `sets.equivalent([], [])`,
   930  			expectEstimatedCost: checker.CostEstimate{Min: 21, Max: 21},
   931  			expectRuntimeCost:   21,
   932  		},
   933  		{
   934  			expr:                `sets.equivalent([1], [1])`,
   935  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   936  			expectRuntimeCost:   23,
   937  		},
   938  		{
   939  			expr:                `sets.equivalent([1], [1, 1])`,
   940  			expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
   941  			expectRuntimeCost:   25,
   942  		},
   943  		{
   944  			expr:                `sets.equivalent([1, 1], [1])`,
   945  			expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
   946  			expectRuntimeCost:   25,
   947  		},
   948  		{
   949  			expr:                `sets.equivalent([1], [1u, 1.0])`,
   950  			expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
   951  			expectRuntimeCost:   25,
   952  		},
   953  		{
   954  			expr:                `sets.equivalent([1], [1u, 1.0])`,
   955  			expectEstimatedCost: checker.CostEstimate{Min: 25, Max: 25},
   956  			expectRuntimeCost:   25,
   957  		},
   958  		{
   959  			expr:                `sets.equivalent([1, 2, 3], [3u, 2.0, 1])`,
   960  			expectEstimatedCost: checker.CostEstimate{Min: 39, Max: 39},
   961  			expectRuntimeCost:   39,
   962  		},
   963  		{
   964  			expr:                `sets.equivalent([[1.0], [2, 3]], [[1], [2, 3.0]])`,
   965  			expectEstimatedCost: checker.CostEstimate{Min: 69, Max: 69},
   966  			expectRuntimeCost:   69,
   967  		},
   968  		{
   969  			expr:                `!sets.equivalent([2, 1], [1])`,
   970  			expectEstimatedCost: checker.CostEstimate{Min: 26, Max: 26},
   971  			expectRuntimeCost:   26,
   972  		},
   973  		{
   974  			expr:                `!sets.equivalent([1], [1, 2])`,
   975  			expectEstimatedCost: checker.CostEstimate{Min: 26, Max: 26},
   976  			expectRuntimeCost:   26,
   977  		},
   978  		{
   979  			expr:                `!sets.equivalent([1, 2], [2u, 2, 2.0])`,
   980  			expectEstimatedCost: checker.CostEstimate{Min: 34, Max: 34},
   981  			expectRuntimeCost:   34,
   982  		},
   983  		{
   984  			expr:                `!sets.equivalent([1, 2], [1u, 2, 2.3])`,
   985  			expectEstimatedCost: checker.CostEstimate{Min: 34, Max: 34},
   986  			expectRuntimeCost:   34,
   987  		},
   988  		{
   989  			expr:                `sets.intersects([1], [1])`,
   990  			expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
   991  			expectRuntimeCost:   22,
   992  		},
   993  		{
   994  			expr:                `sets.intersects([1], [1, 1])`,
   995  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
   996  			expectRuntimeCost:   23,
   997  		},
   998  		{
   999  			expr:                `sets.intersects([1, 1], [1])`,
  1000  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
  1001  			expectRuntimeCost:   23,
  1002  		},
  1003  		{
  1004  			expr:                `sets.intersects([2, 1], [1])`,
  1005  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
  1006  			expectRuntimeCost:   23,
  1007  		},
  1008  		{
  1009  			expr:                `sets.intersects([1], [1, 2])`,
  1010  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
  1011  			expectRuntimeCost:   23,
  1012  		},
  1013  		{
  1014  			expr:                `sets.intersects([1], [1.0, 2])`,
  1015  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
  1016  			expectRuntimeCost:   23,
  1017  		},
  1018  		{
  1019  			expr:                `sets.intersects([1, 2], [2u, 2, 2.0])`,
  1020  			expectEstimatedCost: checker.CostEstimate{Min: 27, Max: 27},
  1021  			expectRuntimeCost:   27,
  1022  		},
  1023  		{
  1024  			expr:                `sets.intersects([1, 2], [1u, 2, 2.3])`,
  1025  			expectEstimatedCost: checker.CostEstimate{Min: 27, Max: 27},
  1026  			expectRuntimeCost:   27,
  1027  		},
  1028  		{
  1029  			expr:                `sets.intersects([[1], [2, 3]], [[1, 2], [2, 3.0]])`,
  1030  			expectEstimatedCost: checker.CostEstimate{Min: 65, Max: 65},
  1031  			expectRuntimeCost:   65,
  1032  		},
  1033  		{
  1034  			expr:                `!sets.intersects([], [])`,
  1035  			expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
  1036  			expectRuntimeCost:   22,
  1037  		},
  1038  		{
  1039  			expr:                `!sets.intersects([1], [])`,
  1040  			expectEstimatedCost: checker.CostEstimate{Min: 22, Max: 22},
  1041  			expectRuntimeCost:   22,
  1042  		},
  1043  		{
  1044  			expr:                `!sets.intersects([1], [2])`,
  1045  			expectEstimatedCost: checker.CostEstimate{Min: 23, Max: 23},
  1046  			expectRuntimeCost:   23,
  1047  		},
  1048  		{
  1049  			expr:                `!sets.intersects([1], ["1", 2])`,
  1050  			expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
  1051  			expectRuntimeCost:   24,
  1052  		},
  1053  		{
  1054  			expr:                `!sets.intersects([1], [1.1, 2u])`,
  1055  			expectEstimatedCost: checker.CostEstimate{Min: 24, Max: 24},
  1056  			expectRuntimeCost:   24,
  1057  		},
  1058  	}
  1059  
  1060  	for _, tc := range cases {
  1061  		t.Run(tc.name, func(t *testing.T) {
  1062  			testCost(t, tc.expr, tc.expectEstimatedCost, tc.expectRuntimeCost)
  1063  		})
  1064  	}
  1065  }
  1066  
  1067  func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate, expectRuntimeCost uint64) {
  1068  	originalPanicOnUnknown := panicOnUnknown
  1069  	panicOnUnknown = true
  1070  	t.Cleanup(func() { panicOnUnknown = originalPanicOnUnknown })
  1071  
  1072  	est := &CostEstimator{SizeEstimator: &testCostEstimator{}}
  1073  	env, err := cel.NewEnv(
  1074  		ext.Strings(ext.StringsVersion(2)),
  1075  		URLs(),
  1076  		Regex(),
  1077  		Lists(),
  1078  		Authz(),
  1079  		AuthzSelectors(),
  1080  		Quantity(),
  1081  		ext.Sets(),
  1082  		IP(),
  1083  		CIDR(),
  1084  		Format(),
  1085  		cel.OptionalTypes(),
  1086  		// cel-go v0.17.7 introduced CostEstimatorOptions.
  1087  		// Previous the presence has a cost of 0 but cel fixed it to 1. We still set to 0 here to avoid breaking changes.
  1088  		cel.CostEstimatorOptions(checker.PresenceTestHasCost(false)),
  1089  	)
  1090  	if err != nil {
  1091  		t.Fatalf("%v", err)
  1092  	}
  1093  	env, err = env.Extend(cel.Variable("authorizer", AuthorizerType))
  1094  	if err != nil {
  1095  		t.Fatalf("%v", err)
  1096  	}
  1097  	compiled, issues := env.Compile(expr)
  1098  	if len(issues.Errors()) > 0 {
  1099  		var errList []string
  1100  		for _, issue := range issues.Errors() {
  1101  			errList = append(errList, issue.ToDisplayString(common.NewTextSource(expr)))
  1102  		}
  1103  		t.Fatalf("%v", errList)
  1104  	}
  1105  	estCost, err := env.EstimateCost(compiled, est)
  1106  	if err != nil {
  1107  		t.Fatalf("%v", err)
  1108  	}
  1109  	if estCost.Min != expectEsimatedCost.Min || estCost.Max != expectEsimatedCost.Max {
  1110  		t.Errorf("Expected estimated cost of %d..%d but got %d..%d", expectEsimatedCost.Min, expectEsimatedCost.Max, estCost.Min, estCost.Max)
  1111  	}
  1112  	prog, err := env.Program(compiled, cel.CostTracking(est))
  1113  	if err != nil {
  1114  		t.Fatalf("%v", err)
  1115  	}
  1116  	_, details, err := prog.Eval(map[string]interface{}{"authorizer": NewAuthorizerVal(nil, alwaysAllowAuthorizer{})})
  1117  	if err != nil {
  1118  		t.Fatalf("%v", err)
  1119  	}
  1120  	cost := details.ActualCost()
  1121  	if *cost != expectRuntimeCost {
  1122  		t.Errorf("Expected cost of %d but got %d", expectRuntimeCost, *cost)
  1123  	}
  1124  }
  1125  
  1126  func TestSize(t *testing.T) {
  1127  	exactSize := func(size int) checker.SizeEstimate {
  1128  		return checker.SizeEstimate{Min: uint64(size), Max: uint64(size)}
  1129  	}
  1130  	exactSizes := func(sizes ...int) []checker.SizeEstimate {
  1131  		results := make([]checker.SizeEstimate, len(sizes))
  1132  		for i, size := range sizes {
  1133  			results[i] = exactSize(size)
  1134  		}
  1135  		return results
  1136  	}
  1137  	cases := []struct {
  1138  		name       string
  1139  		function   string
  1140  		overload   string
  1141  		targetSize checker.SizeEstimate
  1142  		argSizes   []checker.SizeEstimate
  1143  		expectSize checker.SizeEstimate
  1144  	}{
  1145  		{
  1146  			name:       "replace empty with char",
  1147  			function:   "replace",
  1148  			targetSize: exactSize(3),     // e.g. abc
  1149  			argSizes:   exactSizes(0, 1), // e.g. replace "" with "_"
  1150  			expectSize: exactSize(7),     // e.g. _a_b_c_
  1151  		},
  1152  		{
  1153  			name:       "maybe replace char with empty",
  1154  			function:   "replace",
  1155  			targetSize: exactSize(3),
  1156  			argSizes:   exactSizes(1, 0),
  1157  			expectSize: checker.SizeEstimate{Min: 0, Max: 3},
  1158  		},
  1159  		{
  1160  			name:       "maybe replace repeated",
  1161  			function:   "replace",
  1162  			targetSize: exactSize(4),
  1163  			argSizes:   exactSizes(2, 4),
  1164  			expectSize: checker.SizeEstimate{Min: 4, Max: 8},
  1165  		},
  1166  		{
  1167  			name:       "maybe replace empty",
  1168  			function:   "replace",
  1169  			targetSize: exactSize(4),
  1170  			argSizes:   []checker.SizeEstimate{{Min: 0, Max: 1}, {Min: 0, Max: 2}},
  1171  			expectSize: checker.SizeEstimate{Min: 0, Max: 14}, // len(__a__a__a__a__) == 14
  1172  		},
  1173  		{
  1174  			name:       "replace non-empty size range, maybe larger",
  1175  			function:   "replace",
  1176  			targetSize: exactSize(4),
  1177  			argSizes:   []checker.SizeEstimate{{Min: 1, Max: 1}, {Min: 1, Max: 2}},
  1178  			expectSize: checker.SizeEstimate{Min: 4, Max: 8},
  1179  		},
  1180  		{
  1181  			name:       "replace non-empty size range, maybe smaller",
  1182  			function:   "replace",
  1183  			targetSize: exactSize(4),
  1184  			argSizes:   []checker.SizeEstimate{{Min: 1, Max: 2}, {Min: 1, Max: 1}},
  1185  			expectSize: checker.SizeEstimate{Min: 2, Max: 4},
  1186  		},
  1187  	}
  1188  
  1189  	originalPanicOnUnknown := panicOnUnknown
  1190  	panicOnUnknown = true
  1191  	t.Cleanup(func() { panicOnUnknown = originalPanicOnUnknown })
  1192  
  1193  	est := &CostEstimator{SizeEstimator: &testCostEstimator{}}
  1194  	for _, tc := range cases {
  1195  		t.Run(tc.name, func(t *testing.T) {
  1196  			var targetNode checker.AstNode = testSizeNode{size: tc.targetSize}
  1197  			argNodes := make([]checker.AstNode, len(tc.argSizes))
  1198  			for i, arg := range tc.argSizes {
  1199  				argNodes[i] = testSizeNode{size: arg}
  1200  			}
  1201  			result := est.EstimateCallCost(tc.function, tc.overload, &targetNode, argNodes)
  1202  			if result.ResultSize == nil {
  1203  				t.Fatalf("Expected ResultSize but got none")
  1204  			}
  1205  			if *result.ResultSize != tc.expectSize {
  1206  				t.Fatalf("Expected %+v but got %+v", tc.expectSize, *result.ResultSize)
  1207  			}
  1208  		})
  1209  	}
  1210  }
  1211  
  1212  type testSizeNode struct {
  1213  	size checker.SizeEstimate
  1214  }
  1215  
  1216  var _ checker.AstNode = (*testSizeNode)(nil)
  1217  
  1218  func (t testSizeNode) Path() []string {
  1219  	return nil // not needed
  1220  }
  1221  
  1222  func (t testSizeNode) Type() *types.Type {
  1223  	return nil // not needed
  1224  }
  1225  
  1226  func (t testSizeNode) Expr() ast.Expr {
  1227  	return nil // not needed
  1228  }
  1229  
  1230  func (t testSizeNode) ComputedSize() *checker.SizeEstimate {
  1231  	return &t.size
  1232  }
  1233  
  1234  type testCostEstimator struct {
  1235  }
  1236  
  1237  func (t *testCostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {
  1238  	expr, err := cel.TypeToExprType(element.Type())
  1239  	if err != nil {
  1240  		return nil
  1241  	}
  1242  	switch expr.GetPrimitive() {
  1243  	case exprpb.Type_STRING:
  1244  		return &checker.SizeEstimate{Min: 0, Max: 12}
  1245  	case exprpb.Type_BYTES:
  1246  		return &checker.SizeEstimate{Min: 0, Max: 12}
  1247  	}
  1248  	return nil
  1249  }
  1250  
  1251  func (t *testCostEstimator) EstimateCallCost(function, overloadId string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {
  1252  	return nil
  1253  }
  1254  
  1255  type alwaysAllowAuthorizer struct{}
  1256  
  1257  func (f alwaysAllowAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
  1258  	return authorizer.DecisionAllow, "", nil
  1259  }