| // Copyright 2022 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package ext |
| |
| import ( |
| "fmt" |
| "strings" |
| "testing" |
| |
| "github.com/google/cel-go/cel" |
| "github.com/google/cel-go/common/types" |
| ) |
| |
| func TestMath(t *testing.T) { |
| mathTests := []struct { |
| expr string |
| in any |
| }{ |
| // Tests for math.least |
| {expr: "math.least(-0.5) == -0.5"}, |
| {expr: "math.least(-1) == -1"}, |
| {expr: "math.least(1u) == 1u"}, |
| {expr: "math.least(42.0, -0.5) == -0.5"}, |
| {expr: "math.least(-1, 0) == -1"}, |
| {expr: "math.least(-1, -1) == -1"}, |
| {expr: "math.least(1u, 42u) == 1u"}, |
| {expr: "math.least(42.0, -0.5, -0.25) == -0.5"}, |
| {expr: "math.least(-1, 0, 1) == -1"}, |
| {expr: "math.least(-1, -1, -1) == -1"}, |
| {expr: "math.least(1u, 42u, 0u) == 0u"}, |
| // math.least two arg overloads across type. |
| {expr: "math.least(1, 1.0) == 1"}, |
| {expr: "math.least(1, -2.0) == -2.0"}, |
| {expr: "math.least(2, 1u) == 1u"}, |
| {expr: "math.least(1.5, 2) == 1.5"}, |
| {expr: "math.least(1.5, -2) == -2"}, |
| {expr: "math.least(2.5, 1u) == 1u"}, |
| {expr: "math.least(1u, 2) == 1u"}, |
| {expr: "math.least(1u, -2) == -2"}, |
| {expr: "math.least(2u, 2.5) == 2u"}, |
| // math.least with dynamic values across type. |
| {expr: "math.least(1u, dyn(42)) == 1"}, |
| {expr: "math.least(1u, dyn(42), dyn(0.0)) == 0u"}, |
| // math.least with a list literal. |
| {expr: "math.least([1u, 42u, 0u]) == 0u"}, |
| // math.least with expression arguments. |
| { |
| expr: "math.least(a, b) == a", |
| in: map[string]any{ |
| "a": 1, |
| "b": 2, |
| }, |
| }, |
| { |
| expr: "math.least(numbers) == dyn(a)", |
| in: map[string]any{ |
| "a": -21, |
| "numbers": []float64{-21.0, -10.5, 1.0}, |
| }, |
| }, |
| |
| // Tests for math.greatest |
| {expr: "math.greatest(-0.5) == -0.5"}, |
| {expr: "math.greatest(-1) == -1"}, |
| {expr: "math.greatest(1u) == 1u"}, |
| {expr: "math.greatest(42.0, -0.5) == 42.0"}, |
| {expr: "math.greatest(-1, 0) == 0"}, |
| {expr: "math.greatest(-1, -1) == -1"}, |
| {expr: "math.greatest(1u, 42u) == 42u"}, |
| {expr: "math.greatest(42.0, -0.5, -0.25) == 42.0"}, |
| {expr: "math.greatest(-1, 0, 1) == 1"}, |
| {expr: "math.greatest(-1, -1, -1) == -1"}, |
| {expr: "math.greatest(1u, 42u, 0u) == 42u"}, |
| // math.greatest two arg overloads across type. |
| {expr: "math.greatest(1, 1.0) == 1"}, |
| {expr: "math.greatest(1, -2.0) == 1"}, |
| {expr: "math.greatest(2, 1u) == 2"}, |
| {expr: "math.greatest(1.5, 2) == 2"}, |
| {expr: "math.greatest(1.5, -2) == 1.5"}, |
| {expr: "math.greatest(2.5, 1u) == 2.5"}, |
| {expr: "math.greatest(1u, 2) == 2"}, |
| {expr: "math.greatest(1u, -2) == 1u"}, |
| {expr: "math.greatest(2u, 2.5) == 2.5"}, |
| // math.greatest with dynamic values across type. |
| {expr: "math.greatest(1u, dyn(42)) == 42.0"}, |
| {expr: "math.greatest(1u, dyn(0.0), 0u) == 1"}, |
| // math.greatest with a list literal |
| {expr: "math.greatest([1u, dyn(0.0), 0u]) == 1"}, |
| // math.greatest with expression arguments. |
| { |
| expr: "math.greatest(a, b) == b", |
| in: map[string]any{ |
| "a": 1, |
| "b": 2, |
| }, |
| }, |
| { |
| expr: "math.greatest(numbers) == dyn(a)", |
| in: map[string]any{ |
| "a": 1, |
| "numbers": []float64{-21.0, -10.5, 1.0}, |
| }, |
| }, |
| |
| // Tests for math bitwise operators |
| // Signed bitwise ops |
| {expr: "math.bitAnd(1, 2) == 0"}, |
| {expr: "math.bitAnd(1, -1) == 1"}, |
| {expr: "math.bitAnd(1, 3) == 1"}, |
| {expr: "math.bitOr(1, 2) == 3"}, |
| {expr: "math.bitXor(1, 3) == 2"}, |
| {expr: "math.bitXor(3, 5) == 6"}, |
| {expr: "math.bitNot(1) == -2"}, |
| {expr: "math.bitNot(0) == -1"}, |
| {expr: "math.bitNot(-1) == 0"}, |
| {expr: "math.bitShiftLeft(1, 2) == 4"}, |
| {expr: "math.bitShiftLeft(1, 200) == 0"}, |
| {expr: "math.bitShiftLeft(-1, 200) == 0"}, |
| {expr: "math.bitShiftRight(1024, 2) == 256"}, |
| {expr: "math.bitShiftRight(1024, 64) == 0"}, |
| {expr: "math.bitShiftRight(-1024, 3) == 2305843009213693824"}, |
| {expr: "math.bitShiftRight(-1024, 64) == 0"}, |
| // Unsigned bitwise ops |
| {expr: "math.bitAnd(1u, 2u) == 0u"}, |
| {expr: "math.bitAnd(1u, 3u) == 1u"}, |
| {expr: "math.bitOr(1u, 2u) == 3u"}, |
| {expr: "math.bitXor(1u, 3u) == 2u"}, |
| {expr: "math.bitXor(3u, 5u) == 6u"}, |
| {expr: "math.bitNot(1u) == 18446744073709551614u"}, |
| {expr: "math.bitNot(0u) == 18446744073709551615u"}, |
| {expr: "math.bitShiftLeft(1u, 2) == 4u"}, |
| {expr: "math.bitShiftLeft(1u, 200) == 0u"}, |
| {expr: "math.bitShiftRight(1024u, 2) == 256u"}, |
| {expr: "math.bitShiftRight(1024u, 64) == 0u"}, |
| |
| // Tests for floating point helpers |
| {expr: "math.isNaN(0.0/0.0)"}, |
| {expr: "!math.isNaN(1.0/0.0)"}, |
| {expr: "math.isFinite(1.0/1.5)"}, |
| {expr: "!math.isFinite(1.0/0.0)"}, |
| {expr: "math.isInf(1.0/0.0)"}, |
| |
| // Tests for rounding functions |
| {expr: "math.ceil(1.2) == 2.0"}, |
| {expr: "math.ceil(-1.2) == -1.0"}, |
| {expr: "math.floor(1.2) == 1.0"}, |
| {expr: "math.floor(-1.2) == -2.0"}, |
| {expr: "math.round(1.2) == 1.0"}, |
| {expr: "math.round(1.5) == 2.0"}, |
| {expr: "math.round(-1.5) == -2.0"}, |
| {expr: "math.isNaN(math.round(0.0/0.0))"}, |
| {expr: "math.round(-1.2) == -1.0"}, |
| {expr: "math.trunc(-1.3) == -1.0"}, |
| {expr: "math.trunc(1.3) == 1.0"}, |
| |
| // Tests for signedness related functions |
| {expr: "math.sign(-42) == -1"}, |
| {expr: "math.sign(0) == 0"}, |
| {expr: "math.sign(42) == 1"}, |
| {expr: "math.sign(0u) == 0u"}, |
| {expr: "math.sign(42u) == 1u"}, |
| {expr: "math.sign(-0.3) == -1.0"}, |
| {expr: "math.sign(0.0) == 0.0"}, |
| {expr: "math.isNaN(math.sign(0.0/0.0))"}, |
| {expr: "math.sign(1.0/0.0) == 1.0"}, |
| {expr: "math.sign(-1.0/0.0) == -1.0"}, |
| {expr: "math.sign(0.3) == 1.0"}, |
| {expr: "math.abs(-1) == 1"}, |
| {expr: "math.abs(1) == 1"}, |
| {expr: "math.abs(-234.5) == 234.5"}, |
| {expr: "math.abs(234.5) == 234.5"}, |
| |
| // Tests for Square root function |
| {expr: "math.sqrt(49.0) == 7.0"}, |
| {expr: "math.sqrt(0) == 0.0"}, |
| {expr: "math.sqrt(1) == 1.0"}, |
| {expr: "math.sqrt(25u) == 5.0"}, |
| {expr: "math.sqrt(82) == 9.055385138137417"}, |
| {expr: "math.sqrt(985.25) == 31.388692231439016"}, |
| {expr: "math.isNaN(math.sqrt(-15.34))"}, |
| } |
| |
| env := testMathEnv(t, |
| cel.Variable("a", cel.IntType), |
| cel.Variable("b", cel.IntType), |
| cel.Variable("numbers", cel.ListType(cel.DoubleType)), |
| ) |
| for i, tst := range mathTests { |
| tc := tst |
| t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { |
| var asts []*cel.Ast |
| pAst, iss := env.Parse(tc.expr) |
| if iss.Err() != nil { |
| t.Fatalf("env.Parse(%v) failed: %v", tc.expr, iss.Err()) |
| } |
| asts = append(asts, pAst) |
| cAst, iss := env.Check(pAst) |
| if iss.Err() != nil { |
| t.Fatalf("env.Check(%v) failed: %v", tc.expr, iss.Err()) |
| } |
| asts = append(asts, cAst) |
| |
| for _, ast := range asts { |
| prg, err := env.Program(ast) |
| if err != nil { |
| t.Fatalf("env.Program() failed: %v", err) |
| } |
| in := tc.in |
| if in == nil { |
| in = cel.NoVars() |
| } |
| out, _, err := prg.Eval(in) |
| if err != nil { |
| t.Fatalf("prg.Eval() failed: %v", err) |
| } |
| if out.Value() != true { |
| t.Errorf("prg.Eval() got %v, wanted true for expr: %s", out.Value(), tc.expr) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestMathStaticErrors(t *testing.T) { |
| mathTests := []struct { |
| expr string |
| err string |
| }{ |
| // Tests for math.least |
| { |
| expr: "math.least()", |
| err: "math.least() requires at least one argument", |
| }, |
| { |
| expr: "math.least('hello')", |
| err: "math.least() invalid single argument value", |
| }, |
| { |
| expr: "math.least({})", |
| err: "math.least() invalid single argument value", |
| }, |
| { |
| expr: "math.least(1, true)", |
| err: "math.least() simple literal arguments must be numeric", |
| }, |
| { |
| expr: "math.least(1, 2, true)", |
| err: "math.least() simple literal arguments must be numeric", |
| }, |
| |
| // Tests for math.greatest |
| { |
| expr: "math.greatest()", |
| err: "math.greatest() requires at least one argument", |
| }, |
| { |
| expr: "math.greatest(true)", |
| err: "math.greatest() invalid single argument value", |
| }, |
| { |
| expr: "math.greatest([])", |
| err: "math.greatest() invalid single argument value", |
| }, |
| { |
| expr: "math.greatest([1, true])", |
| err: "math.greatest() invalid single argument value", |
| }, |
| { |
| expr: "math.greatest(1, true)", |
| err: "math.greatest() simple literal arguments must be numeric", |
| }, |
| { |
| expr: "math.greatest(1, 2, true)", |
| err: "math.greatest() simple literal arguments must be numeric", |
| }, |
| } |
| |
| env := testMathEnv(t) |
| for i, tst := range mathTests { |
| tc := tst |
| t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { |
| _, iss := env.Compile(tc.expr) |
| if iss.Err() == nil || !strings.Contains(iss.Err().Error(), tc.err) { |
| t.Errorf("env.Compile(%q) got %v, wanted error %v", tc.expr, iss.Err(), tc.err) |
| } |
| }) |
| } |
| } |
| |
| func TestMathRuntimeErrors(t *testing.T) { |
| mathTests := []struct { |
| expr string |
| err string |
| in any |
| }{ |
| // Tests for math.least |
| { |
| expr: "math.least(a, b)", |
| err: "no such overload: math.@min", |
| in: map[string]any{ |
| "a": []int{}, |
| "b": 1, |
| }, |
| }, |
| { |
| expr: "math.least(b, a)", |
| err: "no such overload: math.@min", |
| in: map[string]any{ |
| "a": []int{}, |
| "b": 1, |
| }, |
| }, |
| { |
| expr: "math.least(a)", |
| err: "math.@min(list) argument must not be empty", |
| in: map[string]any{ |
| "a": []int{}, |
| }, |
| }, |
| { |
| expr: "math.least(a)", |
| err: "no such overload: math.@min", |
| in: map[string]any{ |
| "a": []any{"hello"}, |
| }, |
| }, |
| { |
| expr: "math.least(a)", |
| err: "no such overload: math.@min", |
| in: map[string]any{ |
| "a": []any{[]int{}, []int{}}, |
| }, |
| }, |
| { |
| expr: "math.least(a)", |
| err: "no such overload: math.@min", |
| in: map[string]any{ |
| "a": []any{1, true, 2}, |
| }, |
| }, |
| { |
| expr: "math.least(dyn('string'))", |
| err: "no such overload: math.@min", |
| }, |
| |
| // Tests for math.greatest |
| { |
| expr: "math.greatest(a, b)", |
| err: "no such overload: math.@max", |
| in: map[string]any{ |
| "a": []int{}, |
| "b": 1, |
| }, |
| }, |
| { |
| expr: "math.greatest(b, a)", |
| err: "no such overload: math.@max", |
| in: map[string]any{ |
| "a": []int{}, |
| "b": 1, |
| }, |
| }, |
| { |
| expr: "math.greatest(a)", |
| err: "math.@max(list) argument must not be empty", |
| in: map[string]any{ |
| "a": []int{}, |
| }, |
| }, |
| { |
| expr: "math.greatest(a)", |
| err: "no such overload: math.@max", |
| in: map[string]any{ |
| "a": []any{true}, |
| }, |
| }, |
| { |
| expr: "math.greatest(a)", |
| err: "no such overload: math.@max", |
| in: map[string]any{ |
| "a": []any{1, true, 2}, |
| }, |
| }, |
| { |
| expr: "math.greatest(dyn('string'))", |
| err: "no such overload: math.@max", |
| }, |
| { |
| expr: "math.bitShiftLeft(1, -2) == 4", |
| err: "math.bitShiftLeft() negative offset", |
| }, |
| { |
| expr: "math.bitShiftLeft(1u, -2) == 0u", |
| err: "math.bitShiftLeft() negative offset", |
| }, |
| { |
| expr: "math.bitShiftRight(-1024, -3) == -128", |
| err: "math.bitShiftRight() negative offset", |
| }, |
| { |
| expr: "math.bitShiftRight(1024u, -4) == 1u", |
| err: "math.bitShiftRight() negative offset", |
| }, |
| { |
| expr: "math.abs(-9223372036854775808)", |
| err: "overflow", |
| }, |
| { |
| expr: "math.bitOr(dyn(1.2), 1)", |
| err: "no such overload: math.bitOr(double, int)", |
| }, |
| { |
| expr: "math.bitAnd(2u, dyn(''))", |
| err: "no such overload: math.bitAnd(uint, string)", |
| }, |
| { |
| expr: "math.bitXor(dyn(1), dyn(1u))", |
| err: "no such overload: math.bitXor(int, uint)", |
| }, |
| { |
| expr: "math.bitXor(dyn([]), dyn([1]))", |
| err: "no such overload: math.bitXor(list, list)", |
| }, |
| { |
| expr: "math.bitNot(dyn([1]))", |
| err: "no such overload: math.bitNot(list)", |
| }, |
| { |
| expr: "math.bitShiftLeft(dyn([1]), 1)", |
| err: "no such overload: math.bitShiftLeft(list, int)", |
| }, |
| { |
| expr: "math.bitShiftRight(dyn({}), 1)", |
| err: "no such overload: math.bitShiftRight(map, int)", |
| }, |
| { |
| expr: "math.isInf(dyn(1u))", |
| err: "no such overload: math.isInf(uint)", |
| }, |
| { |
| expr: "math.isFinite(dyn(1u))", |
| err: "no such overload: math.isFinite(uint)", |
| }, |
| { |
| expr: "math.isNaN(dyn(1u))", |
| err: "no such overload: math.isNaN(uint)", |
| }, |
| { |
| expr: "math.sign(dyn(''))", |
| err: "no such overload: math.sign(string)", |
| }, |
| { |
| expr: "math.abs(dyn(''))", |
| err: "no such overload: math.abs(string)", |
| }, |
| { |
| expr: "math.ceil(dyn(''))", |
| err: "no such overload: math.ceil(string)", |
| }, |
| { |
| expr: "math.floor(dyn(''))", |
| err: "no such overload: math.floor(string)", |
| }, |
| { |
| expr: "math.round(dyn(1))", |
| err: "no such overload: math.round(int)", |
| }, |
| { |
| expr: "math.trunc(dyn(1u))", |
| err: "no such overload: math.trunc(uint)", |
| }, |
| { |
| expr: "math.sqrt(dyn(''))", |
| err: "no such overload: math.sqrt(string)", |
| }, |
| } |
| |
| env := testMathEnv(t, |
| cel.Variable("a", cel.DynType), |
| cel.Variable("b", cel.IntType), |
| cel.Variable("numbers", cel.ListType(cel.DoubleType)), |
| ) |
| for i, tst := range mathTests { |
| tc := tst |
| t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { |
| ast, iss := env.Compile(tc.expr) |
| if iss.Err() != nil { |
| t.Fatalf("env.Compile(%q) failed with error %v", tc.expr, iss.Err()) |
| } |
| prg, err := env.Program(ast) |
| if err != nil { |
| t.Fatalf("env.Program(ast) failed: %v", err) |
| } |
| in := tc.in |
| if in == nil { |
| in = cel.NoVars() |
| } |
| _, _, err = prg.Eval(in) |
| if err == nil || !strings.Contains(err.Error(), tc.err) { |
| t.Errorf("prg.Eval() got %v, wanted %v", err, tc.err) |
| } |
| }) |
| } |
| } |
| |
| func TestMathNonMatch(t *testing.T) { |
| var mathTests = []struct { |
| expr string |
| }{ |
| // Even though 'least' is the macro, the call is left unexpanded since the operand is not 'math'. |
| { |
| expr: `100.least(42) == 42`, |
| }, |
| // Even though 'greatest' is the macro, the call is left unexpanded since the operand is not 'math'. |
| { |
| expr: `100.greatest(42) == 100`, |
| }, |
| } |
| env := testMathEnv(t, |
| cel.Function("greatest", |
| cel.MemberOverload("int_greatest_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType, |
| cel.BinaryBinding(maxPair))), |
| cel.Function("least", |
| cel.MemberOverload("int_least_int", []*cel.Type{cel.IntType, cel.IntType}, cel.IntType, |
| cel.BinaryBinding(minPair))), |
| ) |
| for i, tst := range mathTests { |
| tc := tst |
| t.Run(fmt.Sprintf("[%d]", i), func(t *testing.T) { |
| var asts []*cel.Ast |
| pAst, iss := env.Parse(tc.expr) |
| if iss.Err() != nil { |
| t.Fatalf("env.Parse(%v) failed: %v", tc.expr, iss.Err()) |
| } |
| asts = append(asts, pAst) |
| cAst, iss := env.Check(pAst) |
| if iss.Err() != nil { |
| t.Fatalf("env.Check(%v) failed: %v", tc.expr, iss.Err()) |
| } |
| asts = append(asts, cAst) |
| |
| for _, ast := range asts { |
| prg, err := env.Program(ast) |
| if err != nil { |
| t.Fatalf("env.Program() failed: %v", err) |
| } |
| out, _, err := prg.Eval(cel.NoVars()) |
| if err != nil { |
| t.Fatalf("prg.Eval() failed: %v", err) |
| } |
| if out.Value() != true { |
| t.Errorf("prg.Eval() got %v, wanted true for expr: %s", out.Value(), tc.expr) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestMathWithExtension(t *testing.T) { |
| env := testMathEnv(t) |
| _, err := env.Extend(Math()) |
| if err != nil { |
| t.Fatalf("env.Extend(Math()) failed: %v", err) |
| } |
| _, iss := env.Compile("math.least(0, 1, 2) == 0") |
| if iss.Err() != nil { |
| t.Errorf("env.Compile() failed: %v", iss.Err()) |
| } |
| } |
| |
| func TestMathVersions(t *testing.T) { |
| versionCases := []struct { |
| version uint32 |
| supportedFunctions map[string]string |
| }{ |
| { |
| version: 0, |
| supportedFunctions: map[string]string{ |
| "greatest": `math.greatest(1, 2) == 2`, |
| "least": `math.least(2.1, -1.0) == -1.0`, |
| }, |
| }, |
| { |
| version: 1, |
| supportedFunctions: map[string]string{ |
| "ceil": `math.ceil(1.5) == 2.0`, |
| "floor": `math.floor(1.2) == 1.0`, |
| "round": `math.round(1.5) == 2.0`, |
| "trunc": `math.trunc(1.222) == 1.0`, |
| "isInf": `!math.isInf(0.0)`, |
| "isNaN": `math.isNaN(0.0/0.0)`, |
| "isFinite": `math.isFinite(0.0)`, |
| "abs": `math.abs(1.2) == 1.2`, |
| "sign": `math.sign(-1) == -1`, |
| "bitAnd": `math.bitAnd(1, 2) == 0`, |
| "bitOr": `math.bitOr(1, 2) == 3`, |
| "bitXor": `math.bitXor(1, 3) == 2`, |
| "bitNot": `math.bitNot(-1) == 0`, |
| "bitShiftLeft": `math.bitShiftLeft(4, 2) == 16`, |
| "bitShiftRight": `math.bitShiftRight(4, 2) == 1`, |
| }, |
| }, |
| { |
| version: 2, |
| supportedFunctions: map[string]string{ |
| "sqrt": `math.sqrt(25) == 5.0`, |
| }, |
| }, |
| } |
| for _, lib := range versionCases { |
| env, err := cel.NewEnv(Math(MathVersion(lib.version))) |
| if err != nil { |
| t.Fatalf("cel.NewEnv(Math(MathVersion(%d))) failed: %v", lib.version, err) |
| } |
| t.Run(fmt.Sprintf("version=%d", lib.version), func(t *testing.T) { |
| for _, tc := range versionCases { |
| for name, expr := range tc.supportedFunctions { |
| supported := lib.version >= tc.version |
| t.Run(fmt.Sprintf("%s-supported=%t", name, supported), func(t *testing.T) { |
| ast, iss := env.Compile(expr) |
| if supported { |
| if iss.Err() != nil { |
| t.Errorf("unexpected error: %v", iss.Err()) |
| } |
| } else { |
| if iss.Err() == nil || !strings.Contains(iss.Err().Error(), "undeclared reference") { |
| t.Errorf("got error %v, wanted error %s for expr: %s, version: %d", iss.Err(), "undeclared reference", expr, tc.version) |
| } |
| return |
| } |
| prg, err := env.Program(ast) |
| if err != nil { |
| t.Fatalf("env.Program() failed: %v", err) |
| } |
| out, _, err := prg.Eval(cel.NoVars()) |
| if err != nil { |
| t.Fatalf("prg.Eval() failed: %v", err) |
| } |
| if out != types.True { |
| t.Errorf("prg.Eval() got %v, wanted true", out) |
| } |
| }) |
| } |
| } |
| }) |
| } |
| } |
| |
| func testMathEnv(t *testing.T, opts ...cel.EnvOption) *cel.Env { |
| t.Helper() |
| baseOpts := []cel.EnvOption{Math(), cel.EnableMacroCallTracking()} |
| env, err := cel.NewEnv(append(baseOpts, opts...)...) |
| if err != nil { |
| t.Fatalf("cel.NewEnv(Math()) failed: %v", err) |
| } |
| return env |
| } |