k8s.io/apiserver@v0.31.1/pkg/cel/lazy/lazy_test.go (about) 1 /* 2 Copyright 2023 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 lazy 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/google/cel-go/cel" 24 "github.com/google/cel-go/common/types" 25 "github.com/google/cel-go/common/types/ref" 26 "github.com/google/cel-go/interpreter" 27 28 "k8s.io/apimachinery/pkg/util/version" 29 apiservercel "k8s.io/apiserver/pkg/cel" 30 "k8s.io/apiserver/pkg/cel/environment" 31 ) 32 33 func TestLazyMapType(t *testing.T) { 34 env, variablesType, err := buildTestEnv() 35 if err != nil { 36 t.Fatal(err) 37 } 38 variablesMap := NewMapValue(variablesType) 39 activation := &testActivation{variables: variablesMap} 40 41 // add foo as a string 42 variablesType.Fields["foo"] = apiservercel.NewDeclField("foo", apiservercel.StringType, true, nil, nil) 43 variablesMap.Append("foo", func(_ *MapValue) ref.Val { 44 return types.String("foo-string") 45 }) 46 47 exp := "variables.foo == 'foo-string'" 48 v, err := compileAndRun(env, activation, exp) 49 if err != nil { 50 t.Fatalf("%q: %v", exp, err) 51 } 52 if !v.Value().(bool) { 53 t.Errorf("expected equal but got non-equal") 54 } 55 56 evalCounter := 0 57 // add dict as a map constructed from an expression 58 variablesType.Fields["dict"] = apiservercel.NewDeclField("dict", apiservercel.DynType, true, nil, nil) 59 variablesMap.Append("dict", func(_ *MapValue) ref.Val { 60 evalCounter++ 61 v, err := compileAndRun(env, activation, `{"a": "a"}`) 62 if err != nil { 63 return types.NewErr(err.Error()) 64 } 65 return v 66 }) 67 68 // iterate the map with .all 69 exp = `variables.all(n, n != "")` 70 v, err = compileAndRun(env, activation, exp) 71 if err != nil { 72 t.Fatalf("%q: %v", exp, err) 73 } 74 if v.Value().(bool) != true { 75 t.Errorf("%q: wrong result: %v", exp, v.Value()) 76 } 77 78 // add unused as a string 79 variablesType.Fields["unused"] = apiservercel.NewDeclField("unused", apiservercel.StringType, true, nil, nil) 80 variablesMap.Append("unused", func(_ *MapValue) ref.Val { 81 t.Fatalf("unused variable must not be evaluated") 82 return nil 83 }) 84 85 exp = "variables.dict.a + ' ' + variables.dict.a + ' ' + variables.foo" 86 v, err = compileAndRun(env, activation, exp) 87 if err != nil { 88 t.Fatalf("%q: %v", exp, err) 89 } 90 if v.Value().(string) != "a a foo-string" { 91 t.Errorf("%q: wrong result: %v", exp, v.Value()) 92 } 93 if evalCounter != 1 { 94 t.Errorf("expected eval %d times but got %d", 1, evalCounter) 95 } 96 97 // unused due to boolean short-circuiting 98 // if `variables.unused` is evaluated, the whole test will have a fatal error and exit. 99 exp = "variables.dict.a == 'wrong' && variables.unused == 'unused'" 100 v, err = compileAndRun(env, activation, exp) 101 if err != nil { 102 t.Fatalf("%q: %v", exp, err) 103 } 104 if v.Value().(bool) != false { 105 t.Errorf("%q: wrong result: %v", exp, v.Value()) 106 } 107 } 108 109 type testActivation struct { 110 variables *MapValue 111 } 112 113 func compileAndRun(env *cel.Env, activation *testActivation, exp string) (ref.Val, error) { 114 ast, issues := env.Compile(exp) 115 if issues != nil { 116 return nil, fmt.Errorf("fail to compile: %v", issues) 117 } 118 prog, err := env.Program(ast) 119 if err != nil { 120 return nil, fmt.Errorf("cannot create program: %w", err) 121 } 122 v, _, err := prog.Eval(activation) 123 if err != nil { 124 return nil, fmt.Errorf("cannot eval program: %w", err) 125 } 126 return v, nil 127 } 128 129 func buildTestEnv() (*cel.Env, *apiservercel.DeclType, error) { 130 variablesType := apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, 0) 131 variablesType.Fields = make(map[string]*apiservercel.DeclField) 132 envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true).Extend( 133 environment.VersionedOptions{ 134 IntroducedVersion: version.MajorMinor(1, 28), 135 EnvOptions: []cel.EnvOption{ 136 cel.Variable("variables", variablesType.CelType()), 137 }, 138 DeclTypes: []*apiservercel.DeclType{ 139 variablesType, 140 }, 141 }) 142 if err != nil { 143 return nil, nil, err 144 } 145 // TODO: change to NewExpressions after 1.28 146 env, err := envSet.Env(environment.StoredExpressions) 147 return env, variablesType, err 148 } 149 150 func (a *testActivation) ResolveName(name string) (any, bool) { 151 switch name { 152 case "variables": 153 return a.variables, true 154 default: 155 return nil, false 156 } 157 } 158 159 func (a *testActivation) Parent() interpreter.Activation { 160 return nil 161 }