diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index 2901e1ff9..265f6d89c 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -17,7 +17,6 @@ java_library( deps = [ "//:java_truth", "//bundle:cel", - "//bundle:cel_experimental_factory", "//bundle:cel_impl", "//bundle:environment", "//bundle:environment_exception", @@ -56,6 +55,7 @@ java_library( "//runtime:evaluation_listener", "//runtime:function_binding", "//runtime:unknown_attributes", + "//testing:cel_runtime_flavor", "//testing/protos:single_file_extension_java_proto", "//testing/protos:single_file_java_proto", "@cel_spec//proto/cel/expr:checked_java_proto", diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 22ef7e2f4..a3ad60d40 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -114,6 +114,7 @@ import dev.cel.runtime.CelUnknownSet; import dev.cel.runtime.CelVariableResolver; import dev.cel.runtime.UnknownContext; +import dev.cel.testing.CelRuntimeFlavor; import dev.cel.testing.testdata.SingleFile; import dev.cel.testing.testdata.SingleFileExtensionsProto; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; @@ -2144,8 +2145,9 @@ public void toBuilder_isImmutable() { } @Test - public void eval_withJsonFieldName(@TestParameter RuntimeEnv runtimeEnv) throws Exception { - Cel cel = runtimeEnv.cel; + public void eval_withJsonFieldName(@TestParameter CelRuntimeFlavor runtimeFlavor) + throws Exception { + Cel cel = setupEnv(runtimeFlavor.builder()); CelAbstractSyntaxTree ast = cel.compile( "file.int32_snake_case_json_name == 1 && " @@ -2176,8 +2178,9 @@ public void eval_withJsonFieldName(@TestParameter RuntimeEnv runtimeEnv) throws } @Test - public void eval_withJsonFieldName_fieldsFallBack(@TestParameter RuntimeEnv runtimeEnv) throws Exception { - Cel cel = runtimeEnv.cel; + public void eval_withJsonFieldName_fieldsFallBack(@TestParameter CelRuntimeFlavor runtimeFlavor) + throws Exception { + Cel cel = setupEnv(runtimeFlavor.builder()); CelAbstractSyntaxTree ast = cel.compile( "dyn(file).int32_snake_case_json_name == 1 && " @@ -2206,8 +2209,9 @@ public void eval_withJsonFieldName_fieldsFallBack(@TestParameter RuntimeEnv runt } @Test - public void eval_withJsonFieldName_extensionFields(@TestParameter RuntimeEnv runtimeEnv) throws Exception { - Cel cel = runtimeEnv.cel; + public void eval_withJsonFieldName_extensionFields(@TestParameter CelRuntimeFlavor runtimeFlavor) + throws Exception { + Cel cel = setupEnv(runtimeFlavor.builder()); CelAbstractSyntaxTree ast = cel.compile( "proto.getExt(file, dev.cel.testing.testdata.int64CamelCaseJsonName) == 5 &&" @@ -2317,33 +2321,21 @@ private static TypeProvider aliasingProvider(ImmutableMap typeAlia }; } - private enum RuntimeEnv { - LEGACY(setupEnv(CelFactory.standardCelBuilder())), - PLANNER(setupEnv(CelExperimentalFactory.plannerCelBuilder())) - ; - - private final Cel cel; - - private static Cel setupEnv(CelBuilder celBuilder) { - ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); - SingleFileExtensionsProto.registerAllExtensions(extensionRegistry); - return celBuilder - .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName())) - .addMessageTypes(SingleFile.getDescriptor()) - .addFileTypes(SingleFileExtensionsProto.getDescriptor()) - .addCompilerLibraries(CelExtensions.protos()) - .setExtensionRegistry(extensionRegistry) - .setOptions( - CelOptions.current() - .enableJsonFieldNames(true) - .enableHeterogeneousNumericComparisons(true) - .enableQuotedIdentifierSyntax(true) - .build()) - .build(); - } - - RuntimeEnv(Cel cel) { - this.cel = cel; - } + private static Cel setupEnv(CelBuilder celBuilder) { + ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); + SingleFileExtensionsProto.registerAllExtensions(extensionRegistry); + return celBuilder + .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName())) + .addMessageTypes(SingleFile.getDescriptor()) + .addFileTypes(SingleFileExtensionsProto.getDescriptor()) + .addCompilerLibraries(CelExtensions.protos()) + .setExtensionRegistry(extensionRegistry) + .setOptions( + CelOptions.current() + .enableJsonFieldNames(true) + .enableHeterogeneousNumericComparisons(true) + .enableQuotedIdentifierSyntax(true) + .build()) + .build(); } } diff --git a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java index 23663f02e..7c298a773 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java @@ -118,29 +118,18 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { @Override public void setRuntimeOptions( CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) { - for (Function function : functions) { - for (CelOverloadDecl overload : function.functionDecl.overloads()) { - switch (overload.overloadId()) { - case MAP_INSERT_OVERLOAD_MAP_MAP: - runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from( - MAP_INSERT_OVERLOAD_MAP_MAP, - Map.class, - Map.class, - (map1, map2) -> mapInsertMap(map1, map2, runtimeEquality))); - break; - case MAP_INSERT_OVERLOAD_KEY_VALUE: - runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from( - MAP_INSERT_OVERLOAD_KEY_VALUE, - ImmutableList.of(Map.class, Object.class, Object.class), - args -> mapInsertKeyValue(args, runtimeEquality))); - break; - default: - // Nothing to add. - } - } - } + runtimeBuilder.addFunctionBindings( + CelFunctionBinding.fromOverloads( + MAP_INSERT_FUNCTION, + CelFunctionBinding.from( + MAP_INSERT_OVERLOAD_MAP_MAP, + Map.class, + Map.class, + (map1, map2) -> mapInsertMap(map1, map2, runtimeEquality)), + CelFunctionBinding.from( + MAP_INSERT_OVERLOAD_KEY_VALUE, + ImmutableList.of(Map.class, Object.class, Object.class), + args -> mapInsertKeyValue(args, runtimeEquality)))); } @Override diff --git a/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java index a98f9db41..498b8555e 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelEncoderExtensions.java @@ -135,9 +135,13 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { functions.forEach( function -> { if (celOptions.evaluateCanonicalTypesToNativeValues()) { - runtimeBuilder.addFunctionBindings(function.nativeBytesFunctionBinding); + runtimeBuilder.addFunctionBindings( + CelFunctionBinding.fromOverloads( + function.getFunction(), function.nativeBytesFunctionBinding)); } else { - runtimeBuilder.addFunctionBindings(function.protoBytesFunctionBinding); + runtimeBuilder.addFunctionBindings( + CelFunctionBinding.fromOverloads( + function.getFunction(), function.protoBytesFunctionBinding)); } }); } diff --git a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java index a91edd822..66621b454 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java @@ -147,7 +147,10 @@ String getFunction() { Function(CelFunctionDecl functionDecl, CelFunctionBinding... functionBindings) { this.functionDecl = functionDecl; - this.functionBindings = ImmutableSet.copyOf(functionBindings); + this.functionBindings = + functionBindings.length == 0 + ? ImmutableSet.of() + : CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindings); } } @@ -246,20 +249,28 @@ public void setRuntimeOptions( switch (overload.overloadId()) { case "list_distinct": runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from( - "list_distinct", Collection.class, (list) -> distinct(list, runtimeEquality))); + CelFunctionBinding.fromOverloads( + "distinct", + CelFunctionBinding.from( + "list_distinct", + Collection.class, + (list) -> distinct(list, runtimeEquality)))); break; case "list_sort": runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from( - "list_sort", Collection.class, (list) -> sort(list, celOptions))); + CelFunctionBinding.fromOverloads( + "sort", + CelFunctionBinding.from( + "list_sort", Collection.class, (list) -> sort(list, celOptions)))); break; case "list_sortByAssociatedKeys": runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from( - "list_sortByAssociatedKeys", - Collection.class, - (list) -> sortByAssociatedKeys(list, celOptions))); + CelFunctionBinding.fromOverloads( + "lists.@sortByAssociatedKeys", + CelFunctionBinding.from( + "list_sortByAssociatedKeys", + Collection.class, + (list) -> sortByAssociatedKeys(list, celOptions)))); break; default: // Nothing to add diff --git a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java index 22336eb22..61f6d8954 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java @@ -663,19 +663,9 @@ String getFunction() { ImmutableSet functionBindingsULongSigned, ImmutableSet functionBindingsULongUnsigned) { this.functionDecl = functionDecl; - this.functionBindings = - functionBindings.isEmpty() - ? ImmutableSet.of() - : CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindings); - this.functionBindingsULongSigned = - functionBindingsULongSigned.isEmpty() - ? ImmutableSet.of() - : CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindingsULongSigned); - this.functionBindingsULongUnsigned = - functionBindingsULongUnsigned.isEmpty() - ? ImmutableSet.of() - : CelFunctionBinding.fromOverloads( - functionDecl.name(), functionBindingsULongUnsigned); + this.functionBindings = functionBindings; + this.functionBindingsULongSigned = functionBindingsULongSigned; + this.functionBindingsULongUnsigned = functionBindingsULongUnsigned; } } @@ -788,11 +778,18 @@ public void setCheckerOptions(CelCheckerBuilder checkerBuilder) { public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { functions.forEach( function -> { - runtimeBuilder.addFunctionBindings(function.functionBindings); - runtimeBuilder.addFunctionBindings( + ImmutableSet.Builder combinedBindings = ImmutableSet.builder(); + combinedBindings.addAll(function.functionBindings); + combinedBindings.addAll( enableUnsignedLongs ? function.functionBindingsULongUnsigned : function.functionBindingsULongSigned); + + ImmutableSet combined = combinedBindings.build(); + if (!combined.isEmpty()) { + runtimeBuilder.addFunctionBindings( + CelFunctionBinding.fromOverloads(function.functionDecl.name(), combined)); + } }); } diff --git a/extensions/src/main/java/dev/cel/extensions/SetsExtensionsRuntimeImpl.java b/extensions/src/main/java/dev/cel/extensions/SetsExtensionsRuntimeImpl.java index a42fba189..a02fdba8a 100644 --- a/extensions/src/main/java/dev/cel/extensions/SetsExtensionsRuntimeImpl.java +++ b/extensions/src/main/java/dev/cel/extensions/SetsExtensionsRuntimeImpl.java @@ -45,28 +45,34 @@ ImmutableSet newFunctionBindings() { for (SetsFunction function : functions) { switch (function) { case CONTAINS: - bindingBuilder.add( - CelFunctionBinding.from( - "list_sets_contains_list", - Collection.class, - Collection.class, - this::containsAll)); + bindingBuilder.addAll( + CelFunctionBinding.fromOverloads( + function.getFunction(), + CelFunctionBinding.from( + "list_sets_contains_list", + Collection.class, + Collection.class, + this::containsAll))); break; case EQUIVALENT: - bindingBuilder.add( - CelFunctionBinding.from( - "list_sets_equivalent_list", - Collection.class, - Collection.class, - (listA, listB) -> containsAll(listA, listB) && containsAll(listB, listA))); + bindingBuilder.addAll( + CelFunctionBinding.fromOverloads( + function.getFunction(), + CelFunctionBinding.from( + "list_sets_equivalent_list", + Collection.class, + Collection.class, + (listA, listB) -> containsAll(listA, listB) && containsAll(listB, listA)))); break; case INTERSECTS: - bindingBuilder.add( - CelFunctionBinding.from( - "list_sets_intersects_list", - Collection.class, - Collection.class, - this::setIntersects)); + bindingBuilder.addAll( + CelFunctionBinding.fromOverloads( + function.getFunction(), + CelFunctionBinding.from( + "list_sets_intersects_list", + Collection.class, + Collection.class, + this::setIntersects))); break; } } diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index a9dbfaca2..eae558946 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -17,6 +17,7 @@ java_library( "//common:options", "//common/exceptions:divide_by_zero", "//common/exceptions:index_out_of_bounds", + "//common/exceptions:invalid_argument", "//common/types", "//common/types:type_providers", "//common/values", @@ -40,6 +41,7 @@ java_library( "//runtime:lite_runtime_factory", "//runtime:partial_vars", "//runtime:unknown_attributes", + "//testing:cel_runtime_flavor", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/test:simple_java_proto", diff --git a/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java index ff9e31432..f09459912 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java @@ -22,40 +22,51 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructTypeReference; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelMacro; import dev.cel.parser.CelStandardMacro; +import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class CelBindingsExtensionsTest { - private static final CelCompiler COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) - .build(); - - private static final CelRuntime RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addLibraries(CelOptionalLibrary.INSTANCE) - .build(); + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + cel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addCompilerLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) + .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .build(); + } @Test public void library() { @@ -93,9 +104,7 @@ private enum BindingTestCase { @Test public void binding_success(@TestParameter BindingTestCase testCase) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(testCase.source).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - boolean evaluatedResult = (boolean) program.eval(); + boolean evaluatedResult = (boolean) eval(testCase.source); assertThat(evaluatedResult).isTrue(); } @@ -103,9 +112,11 @@ public void binding_success(@TestParameter BindingTestCase testCase) throws Exce @Test @TestParameters("{expr: 'false.bind(false, false, false)'}") public void binding_nonCelNamespace_success(String expr) throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.bindings()) + Cel customCel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) + .addCompilerLibraries(CelExtensions.bindings()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "bind", @@ -116,18 +127,16 @@ public void binding_nonCelNamespace_success(String expr) throws Exception { SimpleType.BOOL, SimpleType.BOOL, SimpleType.BOOL))) - .build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "bool_bind_bool_bool_bool", - Arrays.asList(Boolean.class, Boolean.class, Boolean.class, Boolean.class), - (args) -> true)) + CelFunctionBinding.fromOverloads( + "bind", + CelFunctionBinding.from( + "bool_bind_bool_bool_bool", + Arrays.asList(Boolean.class, Boolean.class, Boolean.class, Boolean.class), + (args) -> true))) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile(expr).getAst(); - boolean result = (boolean) celRuntime.createProgram(ast).eval(); + boolean result = (boolean) eval(customCel, expr); assertThat(result).isTrue(); } @@ -135,7 +144,11 @@ public void binding_nonCelNamespace_success(String expr) throws Exception { @TestParameters("{expr: 'cel.bind(bad.name, true, bad.name)'}") public void binding_throwsCompilationException(String expr) throws Exception { CelValidationException e = - assertThrows(CelValidationException.class, () -> COMPILER.compile(expr).getAst()); + assertThrows( + CelValidationException.class, + () -> { + cel.compile(expr).getAst(); + }); assertThat(e).hasMessageThat().contains("cel.bind() variable name must be a simple identifier"); } @@ -143,70 +156,78 @@ public void binding_throwsCompilationException(String expr) throws Exception { @Test @SuppressWarnings("Immutable") // Test only public void lazyBinding_bindingVarNeverReferenced() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() + + AtomicInteger invocation = new AtomicInteger(); + Cel customCel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .setStandardMacros(CelStandardMacro.HAS) .addMessageTypes(TestAllTypes.getDescriptor()) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) - .addLibraries(CelExtensions.bindings()) + .addCompilerLibraries(CelExtensions.bindings()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "get_true", CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) - .build(); - AtomicInteger invocation = new AtomicInteger(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addMessageTypes(TestAllTypes.getDescriptor()) .addFunctionBindings( - CelFunctionBinding.from( - "get_true_overload", - ImmutableList.of(), - arg -> { - invocation.getAndIncrement(); - return true; - })) + CelFunctionBinding.fromOverloads( + "get_true", + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + }))) .build(); - CelAbstractSyntaxTree ast = - celCompiler.compile("cel.bind(t, get_true(), has(msg.single_int64) ? t : false)").getAst(); - boolean result = (boolean) - celRuntime - .createProgram(ast) - .eval(ImmutableMap.of("msg", TestAllTypes.getDefaultInstance())); + eval( + customCel, + "cel.bind(t, get_true(), has(msg.single_int64) ? t : false)", + ImmutableMap.of("msg", TestAllTypes.getDefaultInstance())); assertThat(result).isFalse(); assertThat(invocation.get()).isEqualTo(0); } + @Test + public void lazyBinding_throwsEvaluationException() throws Exception { + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> eval(cel, "cel.bind(t, 1 / 0, t)")); + + assertThat(e).hasMessageThat().contains("/ by zero"); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); + } + @Test @SuppressWarnings("Immutable") // Test only public void lazyBinding_accuInitEvaluatedOnce() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.bindings()) + AtomicInteger invocation = new AtomicInteger(); + Cel customCel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) + .addCompilerLibraries(CelExtensions.bindings()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "get_true", CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) - .build(); - AtomicInteger invocation = new AtomicInteger(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "get_true_overload", - ImmutableList.of(), - arg -> { - invocation.getAndIncrement(); - return true; - })) + CelFunctionBinding.fromOverloads( + "get_true", + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + }))) .build(); - CelAbstractSyntaxTree ast = - celCompiler.compile("cel.bind(t, get_true(), t && t && t && t)").getAst(); - - boolean result = (boolean) celRuntime.createProgram(ast).eval(); + boolean result = (boolean) eval(customCel, "cel.bind(t, get_true(), t && t && t && t)"); assertThat(result).isTrue(); assertThat(invocation.get()).isEqualTo(1); @@ -215,32 +236,32 @@ public void lazyBinding_accuInitEvaluatedOnce() throws Exception { @Test @SuppressWarnings("Immutable") // Test only public void lazyBinding_withNestedBinds() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.bindings()) + AtomicInteger invocation = new AtomicInteger(); + Cel customCel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) + .addCompilerLibraries(CelExtensions.bindings()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "get_true", CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) - .build(); - AtomicInteger invocation = new AtomicInteger(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "get_true_overload", - ImmutableList.of(), - arg -> { - invocation.getAndIncrement(); - return true; - })) + CelFunctionBinding.fromOverloads( + "get_true", + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + }))) .build(); - CelAbstractSyntaxTree ast = - celCompiler - .compile("cel.bind(t1, get_true(), cel.bind(t2, get_true(), t1 && t2 && t1 && t2))") - .getAst(); - - boolean result = (boolean) celRuntime.createProgram(ast).eval(); + boolean result = + (boolean) + eval( + customCel, + "cel.bind(t1, get_true(), cel.bind(t2, get_true(), t1 && t2 && t1 && t2))"); assertThat(result).isTrue(); assertThat(invocation.get()).isEqualTo(2); @@ -249,32 +270,31 @@ public void lazyBinding_withNestedBinds() throws Exception { @Test @SuppressWarnings({"Immutable", "unchecked"}) // Test only public void lazyBinding_boundAttributeInComprehension() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() + AtomicInteger invocation = new AtomicInteger(); + Cel customCel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .setStandardMacros(CelStandardMacro.MAP) - .addLibraries(CelExtensions.bindings()) + .addCompilerLibraries(CelExtensions.bindings()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "get_true", CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) - .build(); - AtomicInteger invocation = new AtomicInteger(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "get_true_overload", - ImmutableList.of(), - arg -> { - invocation.getAndIncrement(); - return true; - })) + CelFunctionBinding.fromOverloads( + "get_true", + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + }))) .build(); - CelAbstractSyntaxTree ast = - celCompiler.compile("cel.bind(x, get_true(), [1,2,3].map(y, y < 0 || x))").getAst(); - - List result = (List) celRuntime.createProgram(ast).eval(); + List result = + (List) eval(customCel, "cel.bind(x, get_true(), [1,2,3].map(y, y < 0 || x))"); assertThat(result).containsExactly(true, true, true); assertThat(invocation.get()).isEqualTo(1); @@ -283,38 +303,55 @@ public void lazyBinding_boundAttributeInComprehension() throws Exception { @Test @SuppressWarnings({"Immutable"}) // Test only public void lazyBinding_boundAttributeInNestedComprehension() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() + AtomicInteger invocation = new AtomicInteger(); + Cel customCel = + runtimeFlavor + .builder() + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) .setStandardMacros(CelStandardMacro.EXISTS) - .addLibraries(CelExtensions.bindings()) + .addCompilerLibraries(CelExtensions.bindings()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "get_true", CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) - .build(); - AtomicInteger invocation = new AtomicInteger(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "get_true_overload", - ImmutableList.of(), - arg -> { - invocation.getAndIncrement(); - return true; - })) + CelFunctionBinding.fromOverloads( + "get_true", + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + }))) .build(); - CelAbstractSyntaxTree ast = - celCompiler - .compile( + boolean result = + (boolean) + eval( + customCel, "cel.bind(x, get_true(), [1,2,3].exists(unused, x && " - + "['a','b','c'].exists(unused_2, x)))") - .getAst(); - - boolean result = (boolean) celRuntime.createProgram(ast).eval(); + + "['a','b','c'].exists(unused_2, x)))"); assertThat(result).isTrue(); assertThat(invocation.get()).isEqualTo(1); } + + private Object eval(Cel cel, String expression) throws Exception { + return eval(cel, expression, ImmutableMap.of()); + } + + private Object eval(Cel cel, String expression, Map variables) throws Exception { + CelAbstractSyntaxTree ast; + if (isParseOnly) { + ast = cel.parse(expression).getAst(); + } else { + ast = cel.compile(expression).getAst(); + } + return cel.createProgram(ast).eval(variables); + } + + private Object eval(String expression) throws Exception { + return eval(this.cel, expression, ImmutableMap.of()); + } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java index 34696b688..9dff95393 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java @@ -15,28 +15,32 @@ package dev.cel.extensions; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import com.google.common.base.Throwables; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.exceptions.CelIndexOutOfBoundsException; +import dev.cel.common.exceptions.CelInvalidArgumentException; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeParamType; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; import dev.cel.parser.CelMacro; import dev.cel.parser.CelStandardMacro; import dev.cel.parser.CelUnparser; import dev.cel.parser.CelUnparserFactory; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,26 +50,35 @@ public class CelComprehensionsExtensionsTest { private static final CelOptions CEL_OPTIONS = CelOptions.current() + .enableHeterogeneousNumericComparisons(true) // Enable macro call population for unparsing .populateMacroCalls(true) .build(); - private static final CelCompiler CEL_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .setOptions(CEL_OPTIONS) - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addLibraries(CelExtensions.comprehensions()) - .addLibraries(CelExtensions.lists()) - .addLibraries(CelExtensions.strings()) - .addLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) - .build(); - private static final CelRuntime CEL_RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addLibraries(CelOptionalLibrary.INSTANCE) - .addLibraries(CelExtensions.lists()) - .addLibraries(CelExtensions.strings()) - .addLibraries(CelExtensions.comprehensions()) - .build(); + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = + runtimeFlavor + .builder() + .setOptions(CEL_OPTIONS) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addCompilerLibraries(CelExtensions.comprehensions()) + .addCompilerLibraries(CelExtensions.lists()) + .addCompilerLibraries(CelExtensions.strings()) + .addCompilerLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) + .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addRuntimeLibraries(CelExtensions.lists()) + .addRuntimeLibraries(CelExtensions.strings()) + .addRuntimeLibraries(CelExtensions.comprehensions()) + .build(); + } private static final CelUnparser UNPARSER = CelUnparserFactory.newUnparser(); @@ -81,6 +94,9 @@ public void library() { "all", "exists", "exists_one", "transformList", "transformMap", "transformMapEntry"); } + + + @Test public void allMacro_twoVarComprehension_success( @TestParameter({ @@ -101,11 +117,7 @@ public void allMacro_twoVarComprehension_success( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(true); + assertEval(expr, true); } @Test @@ -127,11 +139,7 @@ public void existsMacro_twoVarComprehension_success( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(true); + assertEval(expr, true); } @Test @@ -156,11 +164,7 @@ public void exists_oneMacro_twoVarComprehension_success( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(true); + assertEval(expr, true); } @Test @@ -182,11 +186,7 @@ public void transformListMacro_twoVarComprehension_success( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(true); + assertEval(expr, true); } @Test @@ -210,11 +210,7 @@ public void transformMapMacro_twoVarComprehension_success( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(true); + assertEval(expr, true); } @Test @@ -238,24 +234,22 @@ public void transformMapEntryMacro_twoVarComprehension_success( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(true); + assertEval(expr, true); } @Test public void comprehension_onTypeParam_success() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() + Assume.assumeFalse(isParseOnly); + Cel customCel = + runtimeFlavor + .builder() .setOptions(CEL_OPTIONS) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addLibraries(CelExtensions.comprehensions()) + .addCompilerLibraries(CelExtensions.comprehensions()) .addVar("items", TypeParamType.create("T")) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile("items.all(i, v, v > 0)").getAst(); + CelAbstractSyntaxTree ast = customCel.compile("items.all(i, v, v > 0)").getAst(); assertThat(ast.getResultType()).isEqualTo(SimpleType.BOOL); } @@ -275,7 +269,7 @@ public void unparseAST_twoVarComprehension( }) String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); String unparsed = UNPARSER.unparse(ast); assertThat(unparsed).isEqualTo(expr); } @@ -318,8 +312,9 @@ public void unparseAST_twoVarComprehension( "{expr: \"{'hello': 'world', 'greetings': 'tacocat'}.transformMapEntry(k, v, []) == {}\"," + " err: 'no matching overload'}") public void twoVarComprehension_compilerErrors(String expr, String err) throws Exception { + Assume.assumeFalse(isParseOnly); CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains(err); } @@ -339,34 +334,45 @@ public void twoVarComprehension_compilerErrors(String expr, String err) throws E + " '2.0' already exists\"}") public void twoVarComprehension_keyCollision_runtimeError(String expr, String err) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); - - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); - assertThat(e).hasCauseThat().hasMessageThat().contains(err); + if (runtimeFlavor.equals(CelRuntimeFlavor.PLANNER) && expr.contains("2.0")) { + assertEvalException(expr, CelInvalidArgumentException.class, "Unsupported key type: 2.0"); + } else { + assertEvalException(expr, IllegalArgumentException.class, err); + } } @Test public void twoVarComprehension_arithmeticException_runtimeError() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("[0].all(i, k, i/k < k)").getAst(); - - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); - - assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); - assertThat(e).hasCauseThat().hasMessageThat().contains("/ by zero"); + assertEvalException("[0].all(i, k, i/k < k)", CelDivideByZeroException.class, "/ by zero"); } @Test public void twoVarComprehension_outOfBounds_runtimeError() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("[1, 2].exists(i, v, [0][v] > 0)").getAst(); + int expectedIndex = runtimeFlavor.equals(CelRuntimeFlavor.PLANNER) ? 2 : 1; + assertEvalException( + "[1, 2].exists(i, v, [0][v] > 0)", + CelIndexOutOfBoundsException.class, + "Index out of bounds: " + expectedIndex); + } - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); + private void assertEval(String expr, Object expectedResult) throws Exception { + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + Object result = cel.createProgram(ast).eval(); + assertThat(result).isEqualTo(expectedResult); + } - assertThat(e).hasCauseThat().isInstanceOf(CelIndexOutOfBoundsException.class); - assertThat(e).hasCauseThat().hasMessageThat().contains("Index out of bounds: 1"); + private void assertEvalException(String expr, Class causeClass, String messagePart) throws Exception { + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + + Throwable matchedCause = null; + for (Throwable t : Throwables.getCausalChain(e)) { + if (causeClass.isInstance(t) && t.getMessage() != null && t.getMessage().contains(messagePart)) { + matchedCause = t; + break; + } + } + assertWithMessage("Expected cause of type %s with message containing '%s' in cause chain of %s", causeClass.getName(), messagePart, e).that(matchedCause).isNotNull(); } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java index 7eed3dd5a..1f2ce4cb1 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java @@ -19,36 +19,46 @@ import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableMap; +import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; import dev.cel.common.types.SimpleType; import dev.cel.common.values.CelByteString; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public class CelEncoderExtensionsTest { private static final CelOptions CEL_OPTIONS = - CelOptions.current().build(); - - private static final CelCompiler CEL_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .addVar("stringVar", SimpleType.STRING) - .addLibraries(CelExtensions.encoders(CEL_OPTIONS)) - .build(); - private static final CelRuntime CEL_RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .setOptions(CEL_OPTIONS) - .addLibraries(CelExtensions.encoders(CEL_OPTIONS)) - .build(); + CelOptions.current().enableHeterogeneousNumericComparisons(true).build(); + + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = + runtimeFlavor + .builder() + .setOptions(CEL_OPTIONS) + .addCompilerLibraries(CelExtensions.encoders(CEL_OPTIONS)) + .addRuntimeLibraries(CelExtensions.encoders(CEL_OPTIONS)) + .addVar("stringVar", SimpleType.STRING) + .build(); + } + @Test public void library() { @@ -63,22 +73,14 @@ public void library() { @Test public void encode_success() throws Exception { - String encodedBytes = - (String) - CEL_RUNTIME - .createProgram(CEL_COMPILER.compile("base64.encode(b'hello')").getAst()) - .eval(); + String encodedBytes = (String) eval("base64.encode(b'hello')"); assertThat(encodedBytes).isEqualTo("aGVsbG8="); } @Test public void decode_success() throws Exception { - CelByteString decodedBytes = - (CelByteString) - CEL_RUNTIME - .createProgram(CEL_COMPILER.compile("base64.decode('aGVsbG8=')").getAst()) - .eval(); + CelByteString decodedBytes = (CelByteString) eval("base64.decode('aGVsbG8=')"); assertThat(decodedBytes.size()).isEqualTo(5); assertThat(new String(decodedBytes.toByteArray(), ISO_8859_1)).isEqualTo("hello"); @@ -86,12 +88,7 @@ public void decode_success() throws Exception { @Test public void decode_withoutPadding_success() throws Exception { - CelByteString decodedBytes = - (CelByteString) - CEL_RUNTIME - // RFC2045 6.8, padding can be ignored. - .createProgram(CEL_COMPILER.compile("base64.decode('aGVsbG8')").getAst()) - .eval(); + CelByteString decodedBytes = (CelByteString) eval("base64.decode('aGVsbG8')"); assertThat(decodedBytes.size()).isEqualTo(5); assertThat(new String(decodedBytes.toByteArray(), ISO_8859_1)).isEqualTo("hello"); @@ -99,50 +96,53 @@ public void decode_withoutPadding_success() throws Exception { @Test public void roundTrip_success() throws Exception { - String encodedString = - (String) - CEL_RUNTIME - .createProgram(CEL_COMPILER.compile("base64.encode(b'Hello World!')").getAst()) - .eval(); + String encodedString = (String) eval("base64.encode(b'Hello World!')"); CelByteString decodedBytes = - (CelByteString) - CEL_RUNTIME - .createProgram(CEL_COMPILER.compile("base64.decode(stringVar)").getAst()) - .eval(ImmutableMap.of("stringVar", encodedString)); + (CelByteString) eval("base64.decode(stringVar)", ImmutableMap.of("stringVar", encodedString)); assertThat(new String(decodedBytes.toByteArray(), ISO_8859_1)).isEqualTo("Hello World!"); } @Test public void encode_invalidParam_throwsCompilationException() { + Assume.assumeFalse(isParseOnly); CelValidationException e = assertThrows( CelValidationException.class, - () -> CEL_COMPILER.compile("base64.encode('hello')").getAst()); + () -> cel.compile("base64.encode('hello')").getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'base64.encode'"); } @Test public void decode_invalidParam_throwsCompilationException() { + Assume.assumeFalse(isParseOnly); CelValidationException e = assertThrows( CelValidationException.class, - () -> CEL_COMPILER.compile("base64.decode(b'aGVsbG8=')").getAst()); + () -> cel.compile("base64.decode(b'aGVsbG8=')").getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'base64.decode'"); } @Test public void decode_malformedBase64Char_throwsEvaluationException() throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile("base64.decode('z!')").getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval("base64.decode('z!')")); assertThat(e) .hasMessageThat() - .contains("Function 'base64_decode_string' failed with arg(s) 'z!'"); + .contains("failed with arg(s) 'z!'"); assertThat(e).hasCauseThat().hasMessageThat().contains("Illegal base64 character"); } + + private Object eval(String expr) throws Exception { + return eval(expr, ImmutableMap.of()); + } + + private Object eval(String expr, ImmutableMap vars) throws Exception { + CelAbstractSyntaxTree ast = + isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + return cel.createProgram(ast).eval(vars); + } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java index 192630ea3..36f7808ea 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java @@ -17,24 +17,33 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.bundle.Cel; -import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; import dev.cel.extensions.CelStringExtensions.Function; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelRuntime; +import dev.cel.testing.CelRuntimeFlavor; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -@RunWith(JUnit4.class) +@RunWith(TestParameterInjector.class) public class CelExtensionsTest { + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + @Test public void addAllStringExtensions_success() throws Exception { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); Cel cel = - CelFactory.standardCelBuilder() + runtimeFlavor + .builder() .addCompilerLibraries(CelExtensions.strings()) .addRuntimeLibraries(CelExtensions.strings()) .build(); @@ -45,33 +54,45 @@ public void addAllStringExtensions_success() throws Exception { + " hello '.trim() == 'hello' && ['he','llo'].join() == 'hello' && 'hi'.split('') ==" + " ['h','i']"; - boolean evaluatedResult = - (boolean) cel.createProgram(cel.compile(allStringExtExpr).getAst()).eval(); + CelAbstractSyntaxTree ast; + if (isParseOnly) { + ast = cel.parse(allStringExtExpr).getAst(); + } else { + ast = cel.compile(allStringExtExpr).getAst(); + } + boolean evaluatedResult = (boolean) cel.createProgram(ast).eval(); assertThat(evaluatedResult).isTrue(); } @Test public void addSubsetOfStringExtensions_success() throws Exception { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); CelStringExtensions extensions = CelExtensions.strings(Function.SUBSTRING, Function.CHAR_AT); Cel cel = - CelFactory.standardCelBuilder() + runtimeFlavor + .builder() .addCompilerLibraries(extensions) .addRuntimeLibraries(extensions) .build(); - boolean evaluatedResult = - (boolean) - cel.createProgram( - cel.compile("'test'.substring(2) == 'st' && 'hello'.charAt(1) == 'e'").getAst()) - .eval(); + CelAbstractSyntaxTree ast; + String expr = "'test'.substring(2) == 'st' && 'hello'.charAt(1) == 'e'"; + if (isParseOnly) { + ast = cel.parse(expr).getAst(); + } else { + ast = cel.compile(expr).getAst(); + } + boolean evaluatedResult = (boolean) cel.createProgram(ast).eval(); assertThat(evaluatedResult).isTrue(); } @Test public void addStringExtensionsForRuntimeOnly_throwsValidationException() { - Cel cel = CelFactory.standardCelBuilder().addRuntimeLibraries(CelExtensions.strings()).build(); + Assume.assumeFalse(isParseOnly); + Cel cel = runtimeFlavor.builder().addRuntimeLibraries(CelExtensions.strings()).build(); CelValidationException exception = assertThrows( @@ -82,61 +103,101 @@ public void addStringExtensionsForRuntimeOnly_throwsValidationException() { @Test public void addStringExtensionsForCompilerOnly_throwsEvaluationException() throws Exception { - Cel cel = CelFactory.standardCelBuilder().addCompilerLibraries(CelExtensions.strings()).build(); - - CelRuntime.Program program = cel.createProgram(cel.compile("'abcd'.substring(1, 3)").getAst()); - CelEvaluationException exception = assertThrows(CelEvaluationException.class, program::eval); - - assertThat(exception) - .hasMessageThat() - .contains( - "No matching overload for function 'substring'. Overload candidates:" - + " string_substring_int_int"); + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + Cel cel = runtimeFlavor.builder().addCompilerLibraries(CelExtensions.strings()).build(); + + CelAbstractSyntaxTree ast; + String expr = "'abcd'.substring(1, 3)"; + if (isParseOnly) { + ast = cel.parse(expr).getAst(); + } else { + ast = cel.compile(expr).getAst(); + } + if (runtimeFlavor.equals(CelRuntimeFlavor.PLANNER)) { + CelEvaluationException exception = + assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast)); + assertThat(exception) + .hasMessageThat() + .contains("No matching overload for function 'substring'"); + } else { + CelRuntime.Program program = cel.createProgram(ast); + CelEvaluationException exception = assertThrows(CelEvaluationException.class, program::eval); + assertThat(exception) + .hasMessageThat() + .contains("No matching overload for function 'substring'"); + } } @Test public void addAllMathExtensions_success() throws Exception { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); CelOptions celOptions = CelOptions.current().build(); Cel cel = - CelFactory.standardCelBuilder() + runtimeFlavor.builder() .addCompilerLibraries(CelExtensions.math(celOptions)) .addRuntimeLibraries(CelExtensions.math(celOptions)) .build(); String allMathExtExpr = "math.greatest(1, 2.0) == 2.0 && math.least(1, 2.0) == 1"; - boolean evaluatedResult = - (boolean) cel.createProgram(cel.compile(allMathExtExpr).getAst()).eval(); + CelAbstractSyntaxTree ast; + if (isParseOnly) { + ast = cel.parse(allMathExtExpr).getAst(); + } else { + ast = cel.compile(allMathExtExpr).getAst(); + } + boolean evaluatedResult = (boolean) cel.createProgram(ast).eval(); assertThat(evaluatedResult).isTrue(); } @Test public void addSubsetOfMathExtensions_success() throws Exception { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); CelOptions celOptions = CelOptions.current().build(); Cel cel = - CelFactory.standardCelBuilder() + runtimeFlavor.builder() .addCompilerLibraries(CelExtensions.math(celOptions, CelMathExtensions.Function.MAX)) .addRuntimeLibraries(CelExtensions.math(celOptions, CelMathExtensions.Function.MAX)) .build(); - boolean evaluatedResult = - (boolean) cel.createProgram(cel.compile("math.greatest(1, 2.0) == 2.0").getAst()).eval(); + CelAbstractSyntaxTree ast; + String expr = "math.greatest(1, 2.0) == 2.0"; + if (isParseOnly) { + ast = cel.parse(expr).getAst(); + } else { + ast = cel.compile(expr).getAst(); + } + boolean evaluatedResult = (boolean) cel.createProgram(ast).eval(); assertThat(evaluatedResult).isTrue(); - assertThrows(CelValidationException.class, () -> cel.compile("math.least(1,2)").getAst()); + + if (!isParseOnly) { + assertThrows( + CelValidationException.class, () -> cel.compile("math.least(1,2)").getAst()); + } } @Test public void addEncoderExtension_success() throws Exception { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); Cel cel = - CelFactory.standardCelBuilder() + runtimeFlavor.builder() .addCompilerLibraries(CelExtensions.encoders()) .addRuntimeLibraries(CelExtensions.encoders()) .build(); - boolean evaluatedResult = - (boolean) - cel.createProgram(cel.compile("base64.decode('aGVsbG8=') == b'hello'").getAst()).eval(); + CelAbstractSyntaxTree ast; + String expr = "base64.decode('aGVsbG8=') == b'hello'"; + if (isParseOnly) { + ast = cel.parse(expr).getAst(); + } else { + ast = cel.compile(expr).getAst(); + } + boolean evaluatedResult = (boolean) cel.createProgram(ast).eval(); assertThat(evaluatedResult).isTrue(); } diff --git a/extensions/src/test/java/dev/cel/extensions/CelListsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelListsExtensionsTest.java index c4739b18b..441bf41a9 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelListsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelListsExtensionsTest.java @@ -19,10 +19,12 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMultiset; import com.google.common.collect.ImmutableSortedSet; +import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; -import dev.cel.bundle.CelFactory; +import dev.cel.bundle.CelBuilder; +import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; @@ -30,30 +32,30 @@ import dev.cel.expr.conformance.test.SimpleTest; import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; +import dev.cel.testing.CelRuntimeFlavor; +import java.util.Map; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public class CelListsExtensionsTest { - private static final Cel CEL = - CelFactory.standardCelBuilder() - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addCompilerLibraries(CelExtensions.lists()) - .addRuntimeLibraries(CelExtensions.lists()) - .setContainer(CelContainer.ofName("cel.expr.conformance.test")) - .addMessageTypes(SimpleTest.getDescriptor()) - .addVar("non_list", SimpleType.DYN) - .build(); - - private static final Cel CEL_WITH_HETEROGENEOUS_NUMERIC_COMPARISONS = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addCompilerLibraries(CelExtensions.lists()) - .addRuntimeLibraries(CelExtensions.lists()) - .setContainer(CelContainer.ofName("cel.expr.conformance.test")) - .addMessageTypes(SimpleTest.getDescriptor()) - .build(); + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + private Cel celWithHeterogeneousNumericComparisons; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = setupEnv(runtimeFlavor.builder()); + this.celWithHeterogeneousNumericComparisons = + setupEnvWithHeterogeneousNumericComparisons(runtimeFlavor.builder()); + } + @Test public void functionList_byVersion() { @@ -89,8 +91,8 @@ public void macroList_byVersion() { @TestParameters("{expression: 'non_list.slice(1, 3)', expected: '[2, 3]'}") public void slice_success(String expression, String expected) throws Exception { Object result = - CEL.createProgram(CEL.compile(expression).getAst()) - .eval(ImmutableMap.of("non_list", ImmutableSortedSet.of(4L, 1L, 3L, 2L))); + eval( + cel, expression, ImmutableMap.of("non_list", ImmutableSortedSet.of(4L, 1L, 3L, 2L))); assertThat(result).isEqualTo(expectedResult(expected)); } @@ -110,7 +112,7 @@ public void slice_throws(String expression, String expectedError) throws Excepti assertThat( assertThrows( CelEvaluationException.class, - () -> CEL.createProgram(CEL.compile(expression).getAst()).eval())) + () -> eval(cel, expression))) .hasCauseThat() .hasMessageThat() .contains(expectedError); @@ -127,7 +129,7 @@ public void slice_throws(String expression, String expectedError) throws Excepti @TestParameters("{expression: 'dyn([{1: 2}]).flatten() == [{1: 2}]'}") @TestParameters("{expression: 'dyn([1,2,3,4]).flatten() == [1,2,3,4]'}") public void flattenSingleLevel_success(String expression) throws Exception { - boolean result = (boolean) CEL.createProgram(CEL.compile(expression).getAst()).eval(); + boolean result = (boolean) eval(cel, expression); assertThat(result).isTrue(); } @@ -143,7 +145,7 @@ public void flattenSingleLevel_success(String expression) throws Exception { // The overload with the depth accepts and returns a List(dyn), so the following is permitted. @TestParameters("{expression: '[1].flatten(1) == [1]'}") public void flatten_withDepthValue_success(String expression) throws Exception { - boolean result = (boolean) CEL.createProgram(CEL.compile(expression).getAst()).eval(); + boolean result = (boolean) eval(cel, expression); assertThat(result).isTrue(); } @@ -153,11 +155,17 @@ public void flatten_negativeDepth_throws() { CelEvaluationException e = assertThrows( CelEvaluationException.class, - () -> CEL.createProgram(CEL.compile("[1,2,3,4].flatten(-1)").getAst()).eval()); - - assertThat(e) - .hasMessageThat() - .contains("evaluation error at :17: Function 'list_flatten_list_int' failed"); + () -> eval(cel, "[1,2,3,4].flatten(-1)")); + + if (isParseOnly) { + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :17: Function 'flatten' failed"); + } else { + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :17: Function 'list_flatten_list_int' failed"); + } assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("Level must be non-negative"); } @@ -166,9 +174,10 @@ public void flatten_negativeDepth_throws() { @TestParameters("{expression: '[{1: 2}].flatten()'}") @TestParameters("{expression: '[1,2,3,4].flatten()'}") public void flattenSingleLevel_listIsSingleLevel_throws(String expression) { + Assume.assumeFalse(isParseOnly); // Note: Java lacks the capability of conditionally disabling type guards // due to the lack of full-fledged dynamic dispatch. - assertThrows(CelValidationException.class, () -> CEL.compile(expression).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expression).getAst()); } @Test @@ -176,7 +185,7 @@ public void flattenSingleLevel_listIsSingleLevel_throws(String expression) { @TestParameters("{expression: 'lists.range(0) == []'}") @TestParameters("{expression: 'lists.range(-1) == []'}") public void range_success(String expression) throws Exception { - boolean result = (boolean) CEL.createProgram(CEL.compile(expression).getAst()).eval(); + boolean result = (boolean) eval(cel, expression); assertThat(result).isTrue(); } @@ -204,10 +213,11 @@ public void range_success(String expression) throws Exception { @TestParameters("{expression: 'non_list.distinct()', expected: '[1, 2, 3, 4]'}") public void distinct_success(String expression, String expected) throws Exception { Object result = - CEL.createProgram(CEL.compile(expression).getAst()) - .eval( - ImmutableMap.of( - "non_list", ImmutableSortedMultiset.of(1L, 2L, 3L, 4L, 4L, 1L, 3L, 2L))); + eval( + cel, + expression, + ImmutableMap.of( + "non_list", ImmutableSortedMultiset.of(1L, 2L, 3L, 4L, 4L, 1L, 3L, 2L))); assertThat(result).isEqualTo(expectedResult(expected)); } @@ -224,8 +234,8 @@ public void distinct_success(String expression, String expected) throws Exceptio @TestParameters("{expression: 'non_list.reverse()', expected: '[4, 3, 2, 1]'}") public void reverse_success(String expression, String expected) throws Exception { Object result = - CEL.createProgram(CEL.compile(expression).getAst()) - .eval(ImmutableMap.of("non_list", ImmutableSortedSet.of(4L, 1L, 3L, 2L))); + eval( + cel, expression, ImmutableMap.of("non_list", ImmutableSortedSet.of(4L, 1L, 3L, 2L))); assertThat(result).isEqualTo(expectedResult(expected)); } @@ -238,7 +248,7 @@ public void reverse_success(String expression, String expected) throws Exception "{expression: '[\"d\", \"a\", \"b\", \"c\"].sort()', " + "expected: '[\"a\", \"b\", \"c\", \"d\"]'}") public void sort_success(String expression, String expected) throws Exception { - Object result = CEL.createProgram(CEL.compile(expression).getAst()).eval(); + Object result = eval(cel, expression); assertThat(result).isEqualTo(expectedResult(expected)); } @@ -248,10 +258,7 @@ public void sort_success(String expression, String expected) throws Exception { @TestParameters("{expression: '[4, 3, 2, 1].sort()', expected: '[1, 2, 3, 4]'}") public void sort_success_heterogeneousNumbers(String expression, String expected) throws Exception { - Object result = - CEL_WITH_HETEROGENEOUS_NUMERIC_COMPARISONS - .createProgram(CEL_WITH_HETEROGENEOUS_NUMERIC_COMPARISONS.compile(expression).getAst()) - .eval(); + Object result = eval(celWithHeterogeneousNumericComparisons, expression); assertThat(result).isEqualTo(expectedResult(expected)); } @@ -267,10 +274,14 @@ public void sort_success_heterogeneousNumbers(String expression, String expected "{expression: '[SimpleTest{name: \"a\"}, SimpleTest{name: \"b\"}].sort()', " + "expectedError: 'List elements must be comparable'}") public void sort_throws(String expression, String expectedError) throws Exception { + if (expression.equals("[3.0, 2, 1u].sort()")) { + Assume.assumeTrue(!runtimeFlavor.equals(CelRuntimeFlavor.PLANNER)); + } + assertThat( assertThrows( CelEvaluationException.class, - () -> CEL.createProgram(CEL.compile(expression).getAst()).eval())) + () -> eval(cel, expression))) .hasCauseThat() .hasMessageThat() .contains(expectedError); @@ -296,7 +307,7 @@ public void sort_throws(String expression, String expectedError) throws Exceptio + " SimpleTest{name: \"baz\"}," + " SimpleTest{name: \"foo\"}]'}") public void sortBy_success(String expression, String expected) throws Exception { - Object result = CEL.createProgram(CEL.compile(expression).getAst()).eval(); + Object result = eval(cel, expression); assertThat(result).isEqualTo(expectedResult(expected)); } @@ -313,7 +324,7 @@ public void sortBy_throws_validationException(String expression, String expected assertThat( assertThrows( CelValidationException.class, - () -> CEL.createProgram(CEL.compile(expression).getAst()).eval())) + () -> cel.createProgram(cel.compile(expression).getAst()).eval())) .hasMessageThat() .contains(expectedError); } @@ -330,14 +341,46 @@ public void sortBy_throws_evaluationException(String expression, String expected assertThat( assertThrows( CelEvaluationException.class, - () -> CEL.createProgram(CEL.compile(expression).getAst()).eval())) + () -> eval(cel, expression))) .hasCauseThat() .hasMessageThat() .contains(expectedError); } - private static Object expectedResult(String expression) + private static Cel setupEnv(CelBuilder celBuilder) { + return celBuilder + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addCompilerLibraries(CelExtensions.lists()) + .addRuntimeLibraries(CelExtensions.lists()) + .setContainer(CelContainer.ofName("cel.expr.conformance.test")) + .addMessageTypes(SimpleTest.getDescriptor()) + .addVar("non_list", SimpleType.DYN) + .build(); + } + + private static Cel setupEnvWithHeterogeneousNumericComparisons(CelBuilder celBuilder) { + return celBuilder + .setOptions(CelOptions.current().enableHeterogeneousNumericComparisons(true).build()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addCompilerLibraries(CelExtensions.lists()) + .addRuntimeLibraries(CelExtensions.lists()) + .setContainer(CelContainer.ofName("cel.expr.conformance.test")) + .addMessageTypes(SimpleTest.getDescriptor()) + .build(); + } + + private Object eval(Cel cel, String expr) throws Exception { + return eval(cel, expr, ImmutableMap.of()); + } + + private Object eval(Cel cel, String expr, Map vars) throws Exception { + CelAbstractSyntaxTree ast = + isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + return cel.createProgram(ast).eval(vars); + } + + private Object expectedResult(String expression) throws CelEvaluationException, CelValidationException { - return CEL.createProgram(CEL.compile(expression).getAst()).eval(); + return cel.createProgram(cel.compile(expression).getAst()).eval(); } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelMathExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelMathExtensionsTest.java index bcdfb0a21..15ee29dd5 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelMathExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelMathExtensionsTest.java @@ -20,8 +20,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; +import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; @@ -35,34 +37,36 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public class CelMathExtensionsTest { - private static final CelOptions CEL_OPTIONS = - CelOptions.current().enableUnsignedLongs(false).build(); - private static final CelCompiler CEL_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .setOptions(CEL_OPTIONS) - .addLibraries(CelExtensions.math(CEL_OPTIONS)) - .build(); - private static final CelRuntime CEL_RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .setOptions(CEL_OPTIONS) - .addLibraries(CelExtensions.math(CEL_OPTIONS)) - .build(); - private static final CelOptions CEL_UNSIGNED_OPTIONS = CelOptions.current().build(); - private static final CelCompiler CEL_UNSIGNED_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .setOptions(CEL_UNSIGNED_OPTIONS) - .addLibraries(CelExtensions.math(CEL_UNSIGNED_OPTIONS)) - .build(); - private static final CelRuntime CEL_UNSIGNED_RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .setOptions(CEL_UNSIGNED_OPTIONS) - .addLibraries(CelExtensions.math(CEL_UNSIGNED_OPTIONS)) - .build(); + private static final CelOptions CEL_OPTIONS = CelOptions.current().build(); + + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = + runtimeFlavor + .builder() + .setOptions( + CelOptions.current() + .enableHeterogeneousNumericComparisons(runtimeFlavor.equals(CelRuntimeFlavor.PLANNER)) + .build()) + .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS)) + .addRuntimeLibraries(CelExtensions.math(CEL_OPTIONS)) + .build(); + } @Test @TestParameters("{expr: 'math.greatest(-5)', expectedResult: -5}") @@ -97,9 +101,7 @@ public class CelMathExtensionsTest { "{expr: 'math.greatest([dyn(5.4), dyn(10), dyn(3u), dyn(-5.0), dyn(3.5)])', expectedResult:" + " 10}") public void greatest_intResult_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = eval(expr); assertThat(result).isEqualTo(expectedResult); } @@ -136,9 +138,7 @@ public void greatest_intResult_success(String expr, long expectedResult) throws "{expr: 'math.greatest([dyn(5.4), dyn(10.0), dyn(3u), dyn(-5.0), dyn(3.5)])', expectedResult:" + " 10.0}") public void greatest_doubleResult_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = eval(expr); assertThat(result).isEqualTo(expectedResult); } @@ -181,45 +181,9 @@ public void greatest_doubleResult_withUnsignedLongsEnabled_success( assertThat(result).isEqualTo(expectedResult); } - @Test - @TestParameters("{expr: 'math.greatest(5u)', expectedResult: 5}") - @TestParameters("{expr: 'math.greatest(1u, 1.0)', expectedResult: 1}") - @TestParameters("{expr: 'math.greatest(1u, 1)', expectedResult: 1}") - @TestParameters("{expr: 'math.greatest(1u, 1u)', expectedResult: 1}") - @TestParameters("{expr: 'math.greatest(3u, 3.0)', expectedResult: 3}") - @TestParameters("{expr: 'math.greatest(9u, 10u)', expectedResult: 10}") - @TestParameters("{expr: 'math.greatest(15u, 14u)', expectedResult: 15}") - @TestParameters( - "{expr: 'math.greatest(1, 9223372036854775807u)', expectedResult: 9223372036854775807}") - @TestParameters( - "{expr: 'math.greatest(9223372036854775807u, 1)', expectedResult: 9223372036854775807}") - @TestParameters("{expr: 'math.greatest(1u, 1, 1)', expectedResult: 1}") - @TestParameters("{expr: 'math.greatest(3u, 1u, 10u)', expectedResult: 10}") - @TestParameters("{expr: 'math.greatest(1u, 5u, 2u)', expectedResult: 5}") - @TestParameters("{expr: 'math.greatest(-1, 1u, 0u)', expectedResult: 1}") - @TestParameters("{expr: 'math.greatest(dyn(1u), 1, 1.0)', expectedResult: 1}") - @TestParameters("{expr: 'math.greatest(5u, 1.0, 3u)', expectedResult: 5}") - @TestParameters("{expr: 'math.greatest(5.4, 10u, 3u, -5.0, 3.5)', expectedResult: 10}") - @TestParameters( - "{expr: 'math.greatest(5.4, 10, 3u, -5.0, 9223372036854775807)', expectedResult:" - + " 9223372036854775807}") - @TestParameters( - "{expr: 'math.greatest(9223372036854775807, 10, 3u, -5.0, 0)', expectedResult:" - + " 9223372036854775807}") - @TestParameters("{expr: 'math.greatest([5.4, 10, 3u, -5.0, 3.5])', expectedResult: 10}") - @TestParameters( - "{expr: 'math.greatest([dyn(5.4), dyn(10), dyn(3u), dyn(-5.0), dyn(3.5)])', expectedResult:" - + " 10}") - public void greatest_unsignedLongResult_withSignedLongType_success( - String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(expectedResult); - } @Test + @TestParameters("{expr: 'math.greatest(5u)', expectedResult: '5'}") @TestParameters( "{expr: 'math.greatest(18446744073709551615u)', expectedResult: '18446744073709551615'}") @TestParameters("{expr: 'math.greatest(1u, 1.0)', expectedResult: '1'}") @@ -271,9 +235,10 @@ public void greatest_unsignedLongResult_withUnsignedLongType_success( @Test public void greatest_noArgs_throwsCompilationException() { + Assume.assumeFalse(isParseOnly); CelValidationException e = assertThrows( - CelValidationException.class, () -> CEL_COMPILER.compile("math.greatest()").getAst()); + CelValidationException.class, () -> cel.compile("math.greatest()").getAst()); assertThat(e).hasMessageThat().contains("math.greatest() requires at least one argument"); } @@ -283,8 +248,9 @@ public void greatest_noArgs_throwsCompilationException() { @TestParameters("{expr: 'math.greatest({})'}") @TestParameters("{expr: 'math.greatest([])'}") public void greatest_invalidSingleArg_throwsCompilationException(String expr) { + Assume.assumeFalse(isParseOnly); CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("math.greatest() invalid single argument value"); } @@ -297,8 +263,9 @@ public void greatest_invalidSingleArg_throwsCompilationException(String expr) { @TestParameters("{expr: 'math.greatest([1, {}, 2])'}") @TestParameters("{expr: 'math.greatest([1, [], 2])'}") public void greatest_invalidArgs_throwsCompilationException(String expr) { + Assume.assumeFalse(isParseOnly); CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e) .hasMessageThat() @@ -312,12 +279,10 @@ public void greatest_invalidArgs_throwsCompilationException(String expr) { @TestParameters("{expr: 'math.greatest([1, dyn({}), 2])'}") @TestParameters("{expr: 'math.greatest([1, dyn([]), 2])'}") public void greatest_invalidDynArgs_throwsRuntimeException(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); - assertThat(e).hasMessageThat().contains("Function 'math_@max_list_dyn' failed with arg(s)"); + assertThat(e).hasMessageThat().contains("failed with arg(s)"); } @Test @@ -333,11 +298,11 @@ public void greatest_listVariableIsEmpty_throwsRuntimeException() throws Excepti assertThrows( CelEvaluationException.class, () -> - CEL_RUNTIME + cel .createProgram(ast) .eval(ImmutableMap.of("listVar", ImmutableList.of()))); - assertThat(e).hasMessageThat().contains("Function 'math_@max_list_dyn' failed with arg(s)"); + assertThat(e).hasMessageThat().contains("failed with arg(s)"); assertThat(e) .hasCauseThat() .hasMessageThat() @@ -348,24 +313,26 @@ public void greatest_listVariableIsEmpty_throwsRuntimeException() throws Excepti @TestParameters("{expr: '100.greatest(1) == 1'}") @TestParameters("{expr: 'dyn(100).greatest(1) == 1'}") public void greatest_nonProtoNamespace_success(String expr) throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.math(CEL_OPTIONS)) + Cel celInstance = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS)) + .addRuntimeLibraries(CelExtensions.math(CEL_OPTIONS)) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "greatest", CelOverloadDecl.newMemberOverload( "int_greatest_int", SimpleType.INT, SimpleType.INT, SimpleType.INT))) - .build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "int_greatest_int", Long.class, Long.class, (arg1, arg2) -> arg2)) + CelFunctionBinding.fromOverloads( + "greatest", + CelFunctionBinding.from( + "int_greatest_int", Long.class, Long.class, (arg1, arg2) -> arg2))) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile(expr).getAst(); - boolean result = (boolean) celRuntime.createProgram(ast).eval(); + CelAbstractSyntaxTree ast = + isParseOnly ? celInstance.parse(expr).getAst() : celInstance.compile(expr).getAst(); + boolean result = (boolean) celInstance.createProgram(ast).eval(); assertThat(result).isTrue(); } @@ -400,13 +367,14 @@ public void greatest_nonProtoNamespace_success(String expr) throws Exception { "{expr: 'math.least(-9223372036854775808, 10, 3u, -5.0, 0)', expectedResult:" + " -9223372036854775808}") @TestParameters("{expr: 'math.least([5.4, -10, 3u, -5.0, 3.5])', expectedResult: -10}") + @TestParameters("{expr: 'math.least(1, 9223372036854775807u)', expectedResult: 1}") + @TestParameters("{expr: 'math.least(9223372036854775807u, 1)', expectedResult: 1}") + @TestParameters("{expr: 'math.least(9223372036854775807, 10, 3u, 5.0, 0)', expectedResult: 0}") @TestParameters( "{expr: 'math.least([dyn(5.4), dyn(-10), dyn(3u), dyn(-5.0), dyn(3.5)])', expectedResult:" + " -10}") public void least_intResult_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = eval(expr); assertThat(result).isEqualTo(expectedResult); } @@ -443,9 +411,7 @@ public void least_intResult_success(String expr, long expectedResult) throws Exc "{expr: 'math.least([dyn(5.4), dyn(10.0), dyn(3u), dyn(-5.0), dyn(3.5)])', expectedResult:" + " -5.0}") public void least_doubleResult_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = eval(expr); assertThat(result).isEqualTo(expectedResult); } @@ -488,38 +454,16 @@ public void least_doubleResult_withUnsignedLongsEnabled_success( assertThat(result).isEqualTo(expectedResult); } - @Test - @TestParameters("{expr: 'math.least(5u)', expectedResult: 5}") - @TestParameters("{expr: 'math.least(1u, 1.0)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(1u, 1)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(1u, 1u)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(3u, 3.0)', expectedResult: 3}") - @TestParameters("{expr: 'math.least(9u, 10u)', expectedResult: 9}") - @TestParameters("{expr: 'math.least(15u, 14u)', expectedResult: 14}") - @TestParameters("{expr: 'math.least(1, 9223372036854775807u)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(9223372036854775807u, 1)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(1u, 1, 1)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(3u, 1u, 10u)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(1u, 5u, 2u)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(9, 1u, 0u)', expectedResult: 0}") - @TestParameters("{expr: 'math.least(dyn(1u), 1, 1.0)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(5.0, 1u, 3u)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(5.4, 1u, 3u, 9, 3.5)', expectedResult: 1}") - @TestParameters("{expr: 'math.least(5.4, 10, 3u, 5.0, 9223372036854775807)', expectedResult: 3}") - @TestParameters("{expr: 'math.least(9223372036854775807, 10, 3u, 5.0, 0)', expectedResult: 0}") - @TestParameters("{expr: 'math.least([5.4, 10, 3u, 5.0, 3.5])', expectedResult: 3}") - @TestParameters( - "{expr: 'math.least([dyn(5.4), dyn(10), dyn(3u), dyn(5.0), dyn(3.5)])', expectedResult: 3}") - public void least_unsignedLongResult_withSignedLongType_success(String expr, long expectedResult) - throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - - Object result = CEL_RUNTIME.createProgram(ast).eval(); - - assertThat(result).isEqualTo(expectedResult); - } @Test + @TestParameters("{expr: 'math.least(9, 1u, 0u)', expectedResult: '0'}") + @TestParameters("{expr: 'math.least(dyn(1u), 1, 1.0)', expectedResult: '1'}") + @TestParameters("{expr: 'math.least(5.0, 1u, 3u)', expectedResult: '1'}") + @TestParameters("{expr: 'math.least(5.4, 1u, 3u, 9, 3.5)', expectedResult: '1'}") + @TestParameters("{expr: 'math.least(5.4, 10, 3u, 5.0, 9223372036854775807)', expectedResult: '3'}") + @TestParameters("{expr: 'math.least([5.4, 10, 3u, 5.0, 3.5])', expectedResult: '3'}") + @TestParameters( + "{expr: 'math.least([dyn(5.4), dyn(10), dyn(3u), dyn(5.0), dyn(3.5)])', expectedResult: '3'}") @TestParameters( "{expr: 'math.least(18446744073709551615u)', expectedResult: '18446744073709551615'}") @TestParameters("{expr: 'math.least(1u, 1.0)', expectedResult: '1'}") @@ -569,9 +513,10 @@ public void least_unsignedLongResult_withUnsignedLongType_success( @Test public void least_noArgs_throwsCompilationException() { + Assume.assumeFalse(isParseOnly); CelValidationException e = assertThrows( - CelValidationException.class, () -> CEL_COMPILER.compile("math.least()").getAst()); + CelValidationException.class, () -> cel.compile("math.least()").getAst()); assertThat(e).hasMessageThat().contains("math.least() requires at least one argument"); } @@ -581,8 +526,9 @@ public void least_noArgs_throwsCompilationException() { @TestParameters("{expr: 'math.least({})'}") @TestParameters("{expr: 'math.least([])'}") public void least_invalidSingleArg_throwsCompilationException(String expr) { + Assume.assumeFalse(isParseOnly); CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("math.least() invalid single argument value"); } @@ -595,8 +541,9 @@ public void least_invalidSingleArg_throwsCompilationException(String expr) { @TestParameters("{expr: 'math.least([1, {}, 2])'}") @TestParameters("{expr: 'math.least([1, [], 2])'}") public void least_invalidArgs_throwsCompilationException(String expr) { + Assume.assumeFalse(isParseOnly); CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e) .hasMessageThat() @@ -610,12 +557,10 @@ public void least_invalidArgs_throwsCompilationException(String expr) { @TestParameters("{expr: 'math.least([1, dyn({}), 2])'}") @TestParameters("{expr: 'math.least([1, dyn([]), 2])'}") public void least_invalidDynArgs_throwsRuntimeException(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); - assertThat(e).hasMessageThat().contains("Function 'math_@min_list_dyn' failed with arg(s)"); + assertThat(e).hasMessageThat().contains("failed with arg(s)"); } @Test @@ -631,11 +576,11 @@ public void least_listVariableIsEmpty_throwsRuntimeException() throws Exception assertThrows( CelEvaluationException.class, () -> - CEL_RUNTIME + cel .createProgram(ast) .eval(ImmutableMap.of("listVar", ImmutableList.of()))); - assertThat(e).hasMessageThat().contains("Function 'math_@min_list_dyn' failed with arg(s)"); + assertThat(e).hasMessageThat().contains("failed with arg(s)"); assertThat(e) .hasCauseThat() .hasMessageThat() @@ -646,23 +591,26 @@ public void least_listVariableIsEmpty_throwsRuntimeException() throws Exception @TestParameters("{expr: '100.least(1) == 1'}") @TestParameters("{expr: 'dyn(100).least(1) == 1'}") public void least_nonProtoNamespace_success(String expr) throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.math(CEL_OPTIONS)) + Cel celInstance = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.math(CEL_OPTIONS)) + .addRuntimeLibraries(CelExtensions.math(CEL_OPTIONS)) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "least", CelOverloadDecl.newMemberOverload( "int_least", SimpleType.INT, SimpleType.INT, SimpleType.INT))) - .build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from("int_least", Long.class, Long.class, (arg1, arg2) -> arg2)) + CelFunctionBinding.fromOverloads( + "least", + CelFunctionBinding.from( + "int_least", Long.class, Long.class, (arg1, arg2) -> arg2))) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile(expr).getAst(); - boolean result = (boolean) celRuntime.createProgram(ast).eval(); + CelAbstractSyntaxTree ast = + isParseOnly ? celInstance.parse(expr).getAst() : celInstance.compile(expr).getAst(); + boolean result = (boolean) celInstance.createProgram(ast).eval(); assertThat(result).isTrue(); } @@ -676,9 +624,9 @@ public void least_nonProtoNamespace_success(String expr) throws Exception { @TestParameters("{expr: 'math.isNaN(math.sign(0.0/0.0))', expectedResult: true}") @TestParameters("{expr: 'math.isNaN(math.sqrt(-4))', expectedResult: true}") public void isNaN_success(String expr, boolean expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -690,7 +638,7 @@ public void isNaN_success(String expr, boolean expectedResult) throws Exception @TestParameters("{expr: 'math.isNaN(1u)'}") public void isNaN_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.isNaN'"); } @@ -701,9 +649,9 @@ public void isNaN_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.isFinite(1.0/0.0)', expectedResult: false}") @TestParameters("{expr: 'math.isFinite(0.0/0.0)', expectedResult: false}") public void isFinite_success(String expr, boolean expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -715,7 +663,7 @@ public void isFinite_success(String expr, boolean expectedResult) throws Excepti @TestParameters("{expr: 'math.isFinite(1u)'}") public void isFinite_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.isFinite'"); } @@ -726,9 +674,9 @@ public void isFinite_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.isInf(0.0/0.0)', expectedResult: false}") @TestParameters("{expr: 'math.isInf(10.0)', expectedResult: false}") public void isInf_success(String expr, boolean expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -740,7 +688,7 @@ public void isInf_success(String expr, boolean expectedResult) throws Exception @TestParameters("{expr: 'math.isInf(1u)'}") public void isInf_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.isInf'"); } @@ -752,9 +700,9 @@ public void isInf_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.ceil(20.0)' , expectedResult: 20.0}") @TestParameters("{expr: 'math.ceil(0.0/0.0)' , expectedResult: NaN}") public void ceil_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -766,7 +714,7 @@ public void ceil_success(String expr, double expectedResult) throws Exception { @TestParameters("{expr: 'math.ceil(1u)'}") public void ceil_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.ceil'"); } @@ -777,9 +725,9 @@ public void ceil_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.floor(0.0/0.0)' , expectedResult: NaN}") @TestParameters("{expr: 'math.floor(50.0)' , expectedResult: 50.0}") public void floor_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -791,7 +739,7 @@ public void floor_success(String expr, double expectedResult) throws Exception { @TestParameters("{expr: 'math.floor(1u)'}") public void floor_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.floor'"); } @@ -806,9 +754,9 @@ public void floor_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.round(1.0/0.0)' , expectedResult: Infinity}") @TestParameters("{expr: 'math.round(-1.0/0.0)' , expectedResult: -Infinity}") public void round_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -820,7 +768,7 @@ public void round_success(String expr, double expectedResult) throws Exception { @TestParameters("{expr: 'math.round(1u)'}") public void round_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.round'"); } @@ -832,9 +780,9 @@ public void round_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.trunc(1.0/0.0)' , expectedResult: Infinity}") @TestParameters("{expr: 'math.trunc(-1.0/0.0)' , expectedResult: -Infinity}") public void trunc_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -846,7 +794,7 @@ public void trunc_success(String expr, double expectedResult) throws Exception { @TestParameters("{expr: 'math.trunc(1u)'}") public void trunc_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.trunc'"); } @@ -856,9 +804,9 @@ public void trunc_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.abs(-1657643)', expectedResult: 1657643}") @TestParameters("{expr: 'math.abs(-2147483648)', expectedResult: 2147483648}") public void abs_intResult_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -871,9 +819,9 @@ public void abs_intResult_success(String expr, long expectedResult) throws Excep @TestParameters("{expr: 'math.abs(1.0/0.0)' , expectedResult: Infinity}") @TestParameters("{expr: 'math.abs(-1.0/0.0)' , expectedResult: Infinity}") public void abs_doubleResult_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -883,7 +831,7 @@ public void abs_overflow_throwsException() { CelValidationException e = assertThrows( CelValidationException.class, - () -> CEL_COMPILER.compile("math.abs(-9223372036854775809)").getAst()); + () -> cel.compile("math.abs(-9223372036854775809)").getAst()); assertThat(e) .hasMessageThat() @@ -896,9 +844,9 @@ public void abs_overflow_throwsException() { @TestParameters("{expr: 'math.sign(-0)', expectedResult: 0}") @TestParameters("{expr: 'math.sign(11213)', expectedResult: 1}") public void sign_intResult_success(String expr, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -914,9 +862,9 @@ public void sign_intResult_success(String expr, int expectedResult) throws Excep @TestParameters("{expr: 'math.sign(1.0/0.0)' , expectedResult: 1.0}") @TestParameters("{expr: 'math.sign(-1.0/0.0)' , expectedResult: -1.0}") public void sign_doubleResult_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -926,7 +874,7 @@ public void sign_doubleResult_success(String expr, double expectedResult) throws @TestParameters("{expr: 'math.sign(\"\")'}") public void sign_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.sign'"); } @@ -938,9 +886,9 @@ public void sign_invalidArgs_throwsException(String expr) { "{expr: 'math.bitAnd(9223372036854775807,9223372036854775807)' , expectedResult:" + " 9223372036854775807}") public void bitAnd_signedInt_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -950,9 +898,9 @@ public void bitAnd_signedInt_success(String expr, long expectedResult) throws Ex @TestParameters("{expr: 'math.bitAnd(1u,3u)' , expectedResult: 1}") public void bitAnd_unSignedInt_success(String expr, UnsignedLong expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -963,7 +911,7 @@ public void bitAnd_unSignedInt_success(String expr, UnsignedLong expectedResult) @TestParameters("{expr: 'math.bitAnd(1)'}") public void bitAnd_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.bitAnd'"); } @@ -974,7 +922,7 @@ public void bitAnd_maxValArg_throwsException() { assertThrows( CelValidationException.class, () -> - CEL_COMPILER + cel .compile("math.bitAnd(9223372036854775807,9223372036854775809)") .getAst()); @@ -987,9 +935,9 @@ public void bitAnd_maxValArg_throwsException() { @TestParameters("{expr: 'math.bitOr(1,2)' , expectedResult: 3}") @TestParameters("{expr: 'math.bitOr(1,-1)' , expectedResult: -1}") public void bitOr_signedInt_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -998,9 +946,9 @@ public void bitOr_signedInt_success(String expr, long expectedResult) throws Exc @TestParameters("{expr: 'math.bitOr(1u,2u)' , expectedResult: 3}") @TestParameters("{expr: 'math.bitOr(1090u,3u)' , expectedResult: 1091}") public void bitOr_unSignedInt_success(String expr, UnsignedLong expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1011,7 +959,7 @@ public void bitOr_unSignedInt_success(String expr, UnsignedLong expectedResult) @TestParameters("{expr: 'math.bitOr(1)'}") public void bitOr_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.bitOr'"); } @@ -1020,9 +968,9 @@ public void bitOr_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.bitXor(1,2)' , expectedResult: 3}") @TestParameters("{expr: 'math.bitXor(3,5)' , expectedResult: 6}") public void bitXor_signedInt_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1032,9 +980,9 @@ public void bitXor_signedInt_success(String expr, long expectedResult) throws Ex @TestParameters("{expr: 'math.bitXor(3u, 5u)' , expectedResult: 6}") public void bitXor_unSignedInt_success(String expr, UnsignedLong expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1045,7 +993,7 @@ public void bitXor_unSignedInt_success(String expr, UnsignedLong expectedResult) @TestParameters("{expr: 'math.bitXor(1)'}") public void bitXor_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.bitXor'"); } @@ -1055,9 +1003,9 @@ public void bitXor_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.bitNot(0)' , expectedResult: -1}") @TestParameters("{expr: 'math.bitNot(-1)' , expectedResult: 0}") public void bitNot_signedInt_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1067,9 +1015,9 @@ public void bitNot_signedInt_success(String expr, long expectedResult) throws Ex @TestParameters("{expr: 'math.bitNot(12310u)' , expectedResult: 18446744073709539305}") public void bitNot_unSignedInt_success(String expr, UnsignedLong expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1080,7 +1028,7 @@ public void bitNot_unSignedInt_success(String expr, UnsignedLong expectedResult) @TestParameters("{expr: 'math.bitNot(\"\")'}") public void bitNot_invalidArgs_throwsException(String expr) { CelValidationException e = - assertThrows(CelValidationException.class, () -> CEL_COMPILER.compile(expr).getAst()); + assertThrows(CelValidationException.class, () -> cel.compile(expr).getAst()); assertThat(e).hasMessageThat().contains("found no matching overload for 'math.bitNot'"); } @@ -1090,9 +1038,9 @@ public void bitNot_invalidArgs_throwsException(String expr) { @TestParameters("{expr: 'math.bitShiftLeft(12121, 11)' , expectedResult: 24823808}") @TestParameters("{expr: 'math.bitShiftLeft(-1, 64)' , expectedResult: 0}") public void bitShiftLeft_signedInt_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1103,9 +1051,9 @@ public void bitShiftLeft_signedInt_success(String expr, long expectedResult) thr @TestParameters("{expr: 'math.bitShiftLeft(1u, 65)' , expectedResult: 0}") public void bitShiftLeft_unSignedInt_success(String expr, UnsignedLong expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1114,11 +1062,11 @@ public void bitShiftLeft_unSignedInt_success(String expr, UnsignedLong expectedR @TestParameters("{expr: 'math.bitShiftLeft(1, -2)'}") @TestParameters("{expr: 'math.bitShiftLeft(1u, -2)'}") public void bitShiftLeft_invalidArgs_throwsException(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); CelEvaluationException e = assertThrows( - CelEvaluationException.class, () -> CEL_UNSIGNED_RUNTIME.createProgram(ast).eval()); + CelEvaluationException.class, () -> cel.createProgram(ast).eval()); assertThat(e).hasMessageThat().contains("evaluation error"); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); @@ -1131,9 +1079,9 @@ public void bitShiftLeft_invalidArgs_throwsException(String expr) throws Excepti @TestParameters("{expr: 'math.bitShiftRight(12121, 11)' , expectedResult: 5}") @TestParameters("{expr: 'math.bitShiftRight(-1, 64)' , expectedResult: 0}") public void bitShiftRight_signedInt_success(String expr, long expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = cel.compile(expr).getAst(); - Object result = CEL_RUNTIME.createProgram(ast).eval(); + Object result = cel.createProgram(ast).eval(); assertThat(result).isEqualTo(expectedResult); } @@ -1144,9 +1092,7 @@ public void bitShiftRight_signedInt_success(String expr, long expectedResult) th @TestParameters("{expr: 'math.bitShiftRight(1u, 65)' , expectedResult: 0}") public void bitShiftRight_unSignedInt_success(String expr, UnsignedLong expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); - - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = eval(expr); assertThat(result).isEqualTo(expectedResult); } @@ -1155,11 +1101,9 @@ public void bitShiftRight_unSignedInt_success(String expr, UnsignedLong expected @TestParameters("{expr: 'math.bitShiftRight(23111u, -212)'}") @TestParameters("{expr: 'math.bitShiftRight(23, -212)'}") public void bitShiftRight_invalidArgs_throwsException(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); - CelEvaluationException e = assertThrows( - CelEvaluationException.class, () -> CEL_UNSIGNED_RUNTIME.createProgram(ast).eval()); + CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasMessageThat().contains("evaluation error"); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); @@ -1174,10 +1118,14 @@ public void bitShiftRight_invalidArgs_throwsException(String expr) throws Except @TestParameters("{expr: 'math.sqrt(1.0/0.0)', expectedResult: Infinity}") @TestParameters("{expr: 'math.sqrt(-1)', expectedResult: NaN}") public void sqrt_success(String expr, double expectedResult) throws Exception { - CelAbstractSyntaxTree ast = CEL_UNSIGNED_COMPILER.compile(expr).getAst(); - - Object result = CEL_UNSIGNED_RUNTIME.createProgram(ast).eval(); + Object result = eval(expr); assertThat(result).isEqualTo(expectedResult); } + + private Object eval(String expr) throws Exception { + CelAbstractSyntaxTree ast = + isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + return cel.createProgram(ast).eval(); + } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java index 15f6df5be..426dbde85 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java @@ -26,7 +26,6 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; -import dev.cel.bundle.CelFactory; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; @@ -35,8 +34,6 @@ import dev.cel.common.CelValidationException; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructTypeReference; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto2.Proto2ExtensionScopedMessage; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.expr.conformance.proto2.TestAllTypes.NestedEnum; @@ -44,27 +41,35 @@ import dev.cel.parser.CelMacro; import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelFunctionBinding; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) +@SuppressWarnings("SingleTestParameter") public final class CelProtoExtensionsTest { - private static final CelCompiler CEL_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.protos()) - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addFileTypes(TestAllTypesExtensions.getDescriptor()) - .addVar("msg", StructTypeReference.create("cel.expr.conformance.proto2.TestAllTypes")) - .setContainer(CelContainer.ofName("cel.expr.conformance.proto2")) - .build(); + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; - private static final CelRuntime CEL_RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addFileTypes(TestAllTypesExtensions.getDescriptor()) - .build(); + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.protos()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addFileTypes(TestAllTypesExtensions.getDescriptor()) + .addVar("msg", StructTypeReference.create("cel.expr.conformance.proto2.TestAllTypes")) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto2")) + .build(); + } private static final TestAllTypes PACKAGE_SCOPED_EXT_MSG = TestAllTypes.newBuilder() @@ -106,10 +111,10 @@ public void library() { "{expr: 'proto.hasExt(msg, cel.expr.conformance.proto2.repeated_test_all_types)'}") @TestParameters("{expr: '!proto.hasExt(msg, cel.expr.conformance.proto2.test_all_types_ext)'}") public void hasExt_packageScoped_success(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); boolean result = (boolean) - CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); + cel.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); assertThat(result).isTrue(); } @@ -128,10 +133,10 @@ public void hasExt_packageScoped_success(String expr) throws Exception { "{expr: '!proto.hasExt(msg," + " cel.expr.conformance.proto2.Proto2ExtensionScopedMessage.nested_enum_ext)'}") public void hasExt_messageScoped_success(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); boolean result = (boolean) - CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", MESSAGE_SCOPED_EXT_MSG)); + cel.createProgram(ast).eval(ImmutableMap.of("msg", MESSAGE_SCOPED_EXT_MSG)); assertThat(result).isTrue(); } @@ -142,9 +147,10 @@ public void hasExt_messageScoped_success(String expr) throws Exception { public void hasExt_nonProtoNamespace_success(String expr) throws Exception { StructTypeReference proto2MessageTypeReference = StructTypeReference.create("cel.expr.conformance.proto2.TestAllTypes"); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.protos()) + Cel customCel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.protos()) .addVar("msg", proto2MessageTypeReference) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( @@ -154,37 +160,36 @@ public void hasExt_nonProtoNamespace_success(String expr) throws Exception { SimpleType.BOOL, ImmutableList.of( proto2MessageTypeReference, SimpleType.STRING, SimpleType.INT)))) - .build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "msg_hasExt", - ImmutableList.of(TestAllTypes.class, String.class, Long.class), - (arg) -> { - TestAllTypes msg = (TestAllTypes) arg[0]; - String extensionField = (String) arg[1]; - return msg.getAllFields().keySet().stream() - .anyMatch(fd -> fd.getFullName().equals(extensionField)); - })) + CelFunctionBinding.fromOverloads( + "hasExt", + CelFunctionBinding.from( + "msg_hasExt", + ImmutableList.of(TestAllTypes.class, String.class, Long.class), + (arg) -> { + TestAllTypes msg = (TestAllTypes) arg[0]; + String extensionField = (String) arg[1]; + return msg.getAllFields().keySet().stream() + .anyMatch(fd -> fd.getFullName().equals(extensionField)); + }))) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? customCel.parse(expr).getAst() : customCel.compile(expr).getAst(); boolean result = (boolean) - celRuntime.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); + customCel.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); assertThat(result).isTrue(); } @Test public void hasExt_undefinedField_throwsException() { + Assume.assumeFalse(isParseOnly); CelValidationException exception = assertThrows( CelValidationException.class, () -> - CEL_COMPILER - .compile("!proto.hasExt(msg, cel.expr.conformance.proto2.undefined_field)") + cel.compile("!proto.hasExt(msg, cel.expr.conformance.proto2.undefined_field)") .getAst()); assertThat(exception) @@ -204,10 +209,10 @@ public void hasExt_undefinedField_throwsException() { "{expr: 'proto.getExt(msg, cel.expr.conformance.proto2.repeated_test_all_types) ==" + " [TestAllTypes{single_string: ''A''}, TestAllTypes{single_string: ''B''}]'}") public void getExt_packageScoped_success(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); boolean result = (boolean) - CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); + cel.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); assertThat(result).isTrue(); } @@ -221,22 +226,22 @@ public void getExt_packageScoped_success(String expr) throws Exception { "{expr: 'proto.getExt(msg," + " cel.expr.conformance.proto2.Proto2ExtensionScopedMessage.int64_ext) == 1'}") public void getExt_messageScopedSuccess(String expr) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); boolean result = (boolean) - CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", MESSAGE_SCOPED_EXT_MSG)); + cel.createProgram(ast).eval(ImmutableMap.of("msg", MESSAGE_SCOPED_EXT_MSG)); assertThat(result).isTrue(); } @Test public void getExt_undefinedField_throwsException() { + Assume.assumeFalse(isParseOnly); CelValidationException exception = assertThrows( CelValidationException.class, () -> - CEL_COMPILER - .compile("!proto.getExt(msg, cel.expr.conformance.proto2.undefined_field)") + cel.compile("!proto.getExt(msg, cel.expr.conformance.proto2.undefined_field)") .getAst()); assertThat(exception) @@ -250,9 +255,10 @@ public void getExt_undefinedField_throwsException() { public void getExt_nonProtoNamespace_success(String expr) throws Exception { StructTypeReference proto2MessageTypeReference = StructTypeReference.create("cel.expr.conformance.proto2.TestAllTypes"); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.protos()) + Cel customCel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.protos()) .addVar("msg", proto2MessageTypeReference) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( @@ -262,29 +268,28 @@ public void getExt_nonProtoNamespace_success(String expr) throws Exception { SimpleType.DYN, ImmutableList.of( proto2MessageTypeReference, SimpleType.STRING, SimpleType.INT)))) - .build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() .addFunctionBindings( - CelFunctionBinding.from( - "msg_getExt", - ImmutableList.of(TestAllTypes.class, String.class, Long.class), - (arg) -> { - TestAllTypes msg = (TestAllTypes) arg[0]; - String extensionField = (String) arg[1]; - FieldDescriptor extensionDescriptor = - msg.getAllFields().keySet().stream() - .filter(fd -> fd.getFullName().equals(extensionField)) - .findAny() - .get(); - return msg.getField(extensionDescriptor); - })) + CelFunctionBinding.fromOverloads( + "getExt", + CelFunctionBinding.from( + "msg_getExt", + ImmutableList.of(TestAllTypes.class, String.class, Long.class), + (arg) -> { + TestAllTypes msg = (TestAllTypes) arg[0]; + String extensionField = (String) arg[1]; + FieldDescriptor extensionDescriptor = + msg.getAllFields().keySet().stream() + .filter(fd -> fd.getFullName().equals(extensionField)) + .findAny() + .get(); + return msg.getField(extensionDescriptor); + }))) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile(expr).getAst(); + CelAbstractSyntaxTree ast = isParseOnly ? customCel.parse(expr).getAst() : customCel.compile(expr).getAst(); boolean result = (boolean) - celRuntime.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); + customCel.createProgram(ast).eval(ImmutableMap.of("msg", PACKAGE_SCOPED_EXT_MSG)); assertThat(result).isTrue(); } @@ -293,8 +298,9 @@ public void getExt_nonProtoNamespace_success(String expr) throws Exception { public void getExt_onAnyPackedExtensionField_success() throws Exception { ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); TestAllTypesExtensions.registerAllExtensions(extensionRegistry); - Cel cel = - CelFactory.standardCelBuilder() + Cel customCel = + runtimeFlavor + .builder() // CEL-Internal-2 .addCompilerLibraries(CelExtensions.protos()) .addFileTypes(TestAllTypesExtensions.getDescriptor()) @@ -302,12 +308,12 @@ public void getExt_onAnyPackedExtensionField_success() throws Exception { .addVar("msg", StructTypeReference.create("cel.expr.conformance.proto2.TestAllTypes")) .build(); CelAbstractSyntaxTree ast = - cel.compile("proto.getExt(msg, cel.expr.conformance.proto2.int32_ext)").getAst(); + (isParseOnly ? customCel.parse("proto.getExt(msg, cel.expr.conformance.proto2.int32_ext)") : customCel.compile("proto.getExt(msg, cel.expr.conformance.proto2.int32_ext)")).getAst(); Any anyMsg = Any.pack( TestAllTypes.newBuilder().setExtension(TestAllTypesExtensions.int32Ext, 1).build()); - Long result = (Long) cel.createProgram(ast).eval(ImmutableMap.of("msg", anyMsg)); + Long result = (Long) customCel.createProgram(ast).eval(ImmutableMap.of("msg", anyMsg)); assertThat(result).isEqualTo(1); } @@ -344,7 +350,14 @@ private enum ParseErrorTestCase { public void parseErrors(@TestParameter ParseErrorTestCase testcase) { CelValidationException e = assertThrows( - CelValidationException.class, () -> CEL_COMPILER.compile(testcase.expr).getAst()); + CelValidationException.class, + () -> { + if (isParseOnly) { + cel.parse(testcase.expr).getAst(); + } else { + cel.compile(testcase.expr).getAst(); + } + }); assertThat(e).hasMessageThat().isEqualTo(testcase.error); } diff --git a/extensions/src/test/java/dev/cel/extensions/CelRegexExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelRegexExtensionsTest.java index 8a1bef014..924344b25 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelRegexExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelRegexExtensionsTest.java @@ -20,25 +20,38 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; import java.util.Optional; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class CelRegexExtensionsTest { - private static final CelCompiler COMPILER = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(CelExtensions.regex()).build(); - private static final CelRuntime RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder().addLibraries(CelExtensions.regex()).build(); + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.regex()) + .addRuntimeLibraries(CelExtensions.regex()) + .build(); + } + @Test public void library() { @@ -80,11 +93,7 @@ public void library() { public void replaceAll_success(String target, String regex, String replaceStr, String res) throws Exception { String expr = String.format("regex.replace('%s', '%s', '%s')", target, regex, replaceStr); - CelRuntime.Program program = RUNTIME.createProgram(COMPILER.compile(expr).getAst()); - - Object result = program.eval(); - - assertThat(result).isEqualTo(res); + assertThat(eval(expr)).isEqualTo(res); } @Test @@ -93,11 +102,7 @@ public void replace_nested_success() throws Exception { "regex.replace(" + " regex.replace('%(foo) %(bar) %2','%\\\\((\\\\w+)\\\\)','${\\\\1}')," + " '%(\\\\d+)', '$\\\\1')"; - CelRuntime.Program program = RUNTIME.createProgram(COMPILER.compile(expr).getAst()); - - Object result = program.eval(); - - assertThat(result).isEqualTo("${foo} ${bar} $2"); + assertThat(eval(expr)).isEqualTo("${foo} ${bar} $2"); } @Test @@ -118,11 +123,7 @@ public void replace_nested_success() throws Exception { public void replaceCount_success(String t, String re, String rep, long i, String res) throws Exception { String expr = String.format("regex.replace('%s', '%s', '%s', %d)", t, re, rep, i); - CelRuntime.Program program = RUNTIME.createProgram(COMPILER.compile(expr).getAst()); - - Object result = program.eval(); - - assertThat(result).isEqualTo(res); + assertThat(eval(expr)).isEqualTo(res); } @Test @@ -131,10 +132,8 @@ public void replaceCount_success(String t, String re, String rep, long i, String public void replace_invalidRegex_throwsException(String target, String regex, String replaceStr) throws Exception { String expr = String.format("regex.replace('%s', '%s', '%s')", target, regex, replaceStr); - CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e).hasCauseThat().hasMessageThat().contains("Failed to compile regex: "); @@ -143,10 +142,8 @@ public void replace_invalidRegex_throwsException(String target, String regex, St @Test public void replace_invalidCaptureGroupReplaceStr_throwsException() throws Exception { String expr = "regex.replace('test', '(.)', '\\\\2')"; - CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e) @@ -158,10 +155,8 @@ public void replace_invalidCaptureGroupReplaceStr_throwsException() throws Excep @Test public void replace_trailingBackslashReplaceStr_throwsException() throws Exception { String expr = "regex.replace('id=123', 'id=(?P\\\\d+)', '\\\\')"; - CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e) @@ -173,10 +168,8 @@ public void replace_trailingBackslashReplaceStr_throwsException() throws Excepti @Test public void replace_invalidGroupReferenceReplaceStr_throwsException() throws Exception { String expr = "regex.replace('id=123', 'id=(?P\\\\d+)', '\\\\a')"; - CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e) @@ -199,9 +192,7 @@ public void replace_invalidGroupReferenceReplaceStr_throwsException() throws Exc @TestParameters("{target: 'brand', regex: 'brand', expectedResult: 'brand'}") public void extract_success(String target, String regex, String expectedResult) throws Exception { String expr = String.format("regex.extract('%s', '%s')", target, regex); - CelRuntime.Program program = RUNTIME.createProgram(COMPILER.compile(expr).getAst()); - - Object result = program.eval(); + Object result = eval(expr); assertThat(result).isInstanceOf(Optional.class); assertThat((Optional) result).hasValue(expectedResult); @@ -213,9 +204,7 @@ public void extract_success(String target, String regex, String expectedResult) @TestParameters("{target: '', regex: '\\\\w+'}") public void extract_no_match(String target, String regex) throws Exception { String expr = String.format("regex.extract('%s', '%s')", target, regex); - CelRuntime.Program program = RUNTIME.createProgram(COMPILER.compile(expr).getAst()); - - Object result = program.eval(); + Object result = eval(expr); assertThat(result).isInstanceOf(Optional.class); assertThat((Optional) result).isEmpty(); @@ -227,10 +216,8 @@ public void extract_no_match(String target, String regex) throws Exception { public void extract_multipleCaptureGroups_throwsException(String target, String regex) throws Exception { String expr = String.format("regex.extract('%s', '%s')", target, regex); - CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e) @@ -263,9 +250,7 @@ private enum ExtractAllTestCase { @Test public void extractAll_success(@TestParameter ExtractAllTestCase testCase) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(testCase.expr).getAst(); - - Object result = RUNTIME.createProgram(ast).eval(); + Object result = eval(testCase.expr); assertThat(result).isEqualTo(testCase.expectedResult); } @@ -281,10 +266,8 @@ public void extractAll_success(@TestParameter ExtractAllTestCase testCase) throw public void extractAll_multipleCaptureGroups_throwsException(String target, String regex) throws Exception { String expr = String.format("regex.extractAll('%s', '%s')", target, regex); - CelAbstractSyntaxTree ast = COMPILER.compile(expr).getAst(); - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> RUNTIME.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> eval(expr)); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e) @@ -292,4 +275,10 @@ public void extractAll_multipleCaptureGroups_throwsException(String target, Stri .hasMessageThat() .contains("Regular expression has more than one capturing group:"); } + + private Object eval(String expr) throws Exception { + CelAbstractSyntaxTree ast = + isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + return cel.createProgram(ast).eval(); + } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelSetsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelSetsExtensionsTest.java index 1aac5a023..fe5dcb4c7 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelSetsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelSetsExtensionsTest.java @@ -19,8 +19,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; +import dev.cel.bundle.CelBuilder; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; @@ -30,47 +33,34 @@ import dev.cel.common.CelValidationResult; import dev.cel.common.types.ListType; import dev.cel.common.types.SimpleType; -import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; import java.util.List; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class CelSetsExtensionsTest { - private static final CelOptions CEL_OPTIONS = CelOptions.current().build(); - private static final CelCompiler COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .addMessageTypes(TestAllTypes.getDescriptor()) - .setOptions(CEL_OPTIONS) - .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) - .addLibraries(CelExtensions.sets(CEL_OPTIONS)) - .addVar("list", ListType.create(SimpleType.INT)) - .addVar("subList", ListType.create(SimpleType.INT)) - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "new_int", - CelOverloadDecl.newGlobalOverload( - "new_int_int64", SimpleType.INT, SimpleType.INT))) - .build(); - - private static final CelRuntime RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addMessageTypes(TestAllTypes.getDescriptor()) - .addLibraries(CelExtensions.sets(CEL_OPTIONS)) - .setOptions(CEL_OPTIONS) - .addFunctionBindings( - CelFunctionBinding.from( - "new_int_int64", - Long.class, - // Intentionally return java.lang.Integer to test primitive type adaptation - Math::toIntExact)) - .build(); + private static final CelOptions CEL_OPTIONS = + CelOptions.current().enableHeterogeneousNumericComparisons(true).build(); + + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = setupEnv(runtimeFlavor.builder()); + } + @Test public void library() { @@ -87,22 +77,12 @@ public void library() { public void contains_integerListWithSameValue_succeeds() throws Exception { ImmutableList list = ImmutableList.of(1, 2, 3, 4); ImmutableList subList = ImmutableList.of(1, 2, 3, 4); - CelAbstractSyntaxTree ast = COMPILER.compile("sets.contains(list, subList)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(ImmutableMap.of("list", list, "subList", subList)); - - assertThat(result).isEqualTo(true); + assertEval("sets.contains(list, subList)", ImmutableMap.of("list", list, "subList", subList), true); } @Test public void contains_integerListAsExpression_succeeds() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("sets.contains([1, 1], [1])").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(true); + assertEval("sets.contains([1, 1], [1])", true); } @Test @@ -119,12 +99,7 @@ public void contains_integerListAsExpression_succeeds() throws Exception { + " [TestAllTypes{single_int64: 2, single_uint64: 3u}])', expected: false}") public void contains_withProtoMessage_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -133,12 +108,7 @@ public void contains_withProtoMessage_succeeds(String expression, boolean expect @TestParameters("{expression: 'sets.contains([new_int(2)], [1])', expected: false}") public void contains_withFunctionReturningInteger_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -157,12 +127,7 @@ public void contains_withFunctionReturningInteger_succeeds(String expression, bo @TestParameters("{list: [1], subList: [1, 2], expected: false}") public void contains_withIntTypes_succeeds( List list, List subList, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("sets.contains(list, subList)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(ImmutableMap.of("list", list, "subList", subList)); - - assertThat(result).isEqualTo(expected); + assertEval("sets.contains(list, subList)", ImmutableMap.of("list", list, "subList", subList), expected); } @Test @@ -177,12 +142,7 @@ public void contains_withIntTypes_succeeds( @TestParameters("{list: [2, 3.0], subList: [2, 3], expected: true}") public void contains_withDoubleTypes_succeeds( List list, List subList, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("sets.contains(list, subList)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(ImmutableMap.of("list", list, "subList", subList)); - - assertThat(result).isEqualTo(expected); + assertEval("sets.contains(list, subList)", ImmutableMap.of("list", list, "subList", subList), expected); } @Test @@ -193,12 +153,7 @@ public void contains_withDoubleTypes_succeeds( @TestParameters("{expression: 'sets.contains([[1], [2, 3.0]], [[2, 3]])', expected: true}") public void contains_withNestedLists_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -206,19 +161,19 @@ public void contains_withNestedLists_succeeds(String expression, boolean expecte @TestParameters("{expression: 'sets.contains([1], [1, \"1\"])', expected: false}") public void contains_withMixingIntAndString_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test - @TestParameters("{expression: 'sets.contains([1], [\"1\"])'}") - @TestParameters("{expression: 'sets.contains([\"1\"], [1])'}") - public void contains_withMixingIntAndString_throwsException(String expression) throws Exception { - CelValidationResult invalidData = COMPILER.compile(expression); + public void contains_withMixingIntAndString_throwsException( + @TestParameter({ + "sets.contains([1], [\"1\"])", + "sets.contains([\"1\"], [1])" + }) + String expression) + throws Exception { + Assume.assumeFalse(isParseOnly); + CelValidationResult invalidData = cel.compile(expression); assertThat(invalidData.getErrors()).hasSize(1); assertThat(invalidData.getErrors().get(0).getMessage()) @@ -227,12 +182,7 @@ public void contains_withMixingIntAndString_throwsException(String expression) t @Test public void contains_withMixedValues_succeeds() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("sets.contains([1, 2], [2u, 2.0])").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(true); + assertEval("sets.contains([1, 2], [2u, 2.0])", true); } @Test @@ -249,12 +199,7 @@ public void contains_withMixedValues_succeeds() throws Exception { "{expression: 'sets.contains([[[[[[5]]]]]], [[1], [2, 3.0], [[[[[5]]]]]])', expected: false}") public void contains_withMultiLevelNestedList_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -269,12 +214,7 @@ public void contains_withMultiLevelNestedList_succeeds(String expression, boolea + " false}") public void contains_withMapValues_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -289,12 +229,7 @@ public void contains_withMapValues_succeeds(String expression, boolean expected) @TestParameters("{expression: 'sets.equivalent([1, 2], [2, 2, 2])', expected: false}") public void equivalent_withIntTypes_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -308,12 +243,7 @@ public void equivalent_withIntTypes_succeeds(String expression, boolean expected @TestParameters("{expression: 'sets.equivalent([1, 2], [1u, 2, 2.3])', expected: false}") public void equivalent_withMixedTypes_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -338,12 +268,7 @@ public void equivalent_withMixedTypes_succeeds(String expression, boolean expect + " [TestAllTypes{single_int64: 2, single_uint64: 3u}])', expected: false}") public void equivalent_withProtoMessage_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -361,12 +286,7 @@ public void equivalent_withProtoMessage_succeeds(String expression, boolean expe + " expected: false}") public void equivalent_withMapValues_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -391,12 +311,7 @@ public void equivalent_withMapValues_succeeds(String expression, boolean expecte @TestParameters("{expression: 'sets.intersects([1], [1.1, 2u])', expected: false}") public void intersects_withMixedTypes_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object result = program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -414,12 +329,11 @@ public void intersects_withMixedTypes_succeeds(String expression, boolean expect @TestParameters("{expression: 'sets.intersects([{2: 1}], [{1: 1}])', expected: false}") public void intersects_withMapValues_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); + // The LEGACY runtime is not spec compliant, because decimal keys are not allowed for maps. + Assume.assumeFalse( + runtimeFlavor.equals(CelRuntimeFlavor.PLANNER) && expression.contains("1.0:")); - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test @@ -444,25 +358,26 @@ public void intersects_withMapValues_succeeds(String expression, boolean expecte + " [TestAllTypes{single_int64: 2, single_uint64: 3u}])', expected: false}") public void intersects_withProtoMessage_succeeds(String expression, boolean expected) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(expression).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - boolean result = (boolean) program.eval(); - - assertThat(result).isEqualTo(expected); + assertEval(expression, expected); } @Test public void setsExtension_containsFunctionSubset_succeeds() throws Exception { CelSetsExtensions setsExtensions = CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.CONTAINS); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(setsExtensions).build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder().addLibraries(setsExtensions).build(); + Cel cel = + runtimeFlavor + .builder() + .addCompilerLibraries(setsExtensions) + .addRuntimeLibraries(setsExtensions) + .build(); Object evaluatedResult = - celRuntime.createProgram(celCompiler.compile("sets.contains([1, 2], [2])").getAst()).eval(); + cel.createProgram( + isParseOnly + ? cel.parse("sets.contains([1, 2], [2])").getAst() + : cel.compile("sets.contains([1, 2], [2])").getAst()) + .eval(); assertThat(evaluatedResult).isEqualTo(true); } @@ -471,14 +386,18 @@ public void setsExtension_containsFunctionSubset_succeeds() throws Exception { public void setsExtension_equivalentFunctionSubset_succeeds() throws Exception { CelSetsExtensions setsExtensions = CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.EQUIVALENT); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(setsExtensions).build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder().addLibraries(setsExtensions).build(); + Cel cel = + runtimeFlavor + .builder() + .addCompilerLibraries(setsExtensions) + .addRuntimeLibraries(setsExtensions) + .build(); Object evaluatedResult = - celRuntime - .createProgram(celCompiler.compile("sets.equivalent([1, 1], [1])").getAst()) + cel.createProgram( + isParseOnly + ? cel.parse("sets.equivalent([1, 1], [1])").getAst() + : cel.compile("sets.equivalent([1, 1], [1])").getAst()) .eval(); assertThat(evaluatedResult).isEqualTo(true); @@ -488,14 +407,18 @@ public void setsExtension_equivalentFunctionSubset_succeeds() throws Exception { public void setsExtension_intersectsFunctionSubset_succeeds() throws Exception { CelSetsExtensions setsExtensions = CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.INTERSECTS); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(setsExtensions).build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder().addLibraries(setsExtensions).build(); + Cel cel = + runtimeFlavor + .builder() + .addCompilerLibraries(setsExtensions) + .addRuntimeLibraries(setsExtensions) + .build(); Object evaluatedResult = - celRuntime - .createProgram(celCompiler.compile("sets.intersects([1, 1], [1])").getAst()) + cel.createProgram( + isParseOnly + ? cel.parse("sets.intersects([1, 1], [1])").getAst() + : cel.compile("sets.intersects([1, 1], [1])").getAst()) .eval(); assertThat(evaluatedResult).isEqualTo(true); @@ -503,29 +426,74 @@ public void setsExtension_intersectsFunctionSubset_succeeds() throws Exception { @Test public void setsExtension_compileUnallowedFunction_throws() { + Assume.assumeFalse(isParseOnly); CelSetsExtensions setsExtensions = CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.EQUIVALENT); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(setsExtensions).build(); + Cel cel = runtimeFlavor.builder().addCompilerLibraries(setsExtensions).build(); assertThrows( CelValidationException.class, - () -> celCompiler.compile("sets.contains([1, 2], [2])").getAst()); + () -> cel.compile("sets.contains([1, 2], [2])").getAst()); } @Test public void setsExtension_evaluateUnallowedFunction_throws() throws Exception { CelSetsExtensions setsExtensions = CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.CONTAINS, SetsFunction.EQUIVALENT); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(setsExtensions).build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addLibraries(CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.EQUIVALENT)) + CelSetsExtensions runtimeLibrary = + CelExtensions.sets(CelOptions.DEFAULT, SetsFunction.EQUIVALENT); + Cel cel = + runtimeFlavor + .builder() + .addCompilerLibraries(setsExtensions) + .addRuntimeLibraries(runtimeLibrary) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile("sets.contains([1, 2], [2])").getAst(); + CelAbstractSyntaxTree ast = + isParseOnly + ? cel.parse("sets.contains([1, 2], [2])").getAst() + : cel.compile("sets.contains([1, 2], [2])").getAst(); + + if (runtimeFlavor.equals(CelRuntimeFlavor.PLANNER) && !isParseOnly) { + assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast)); + } else { + CelRuntime.Program program = cel.createProgram(ast); + assertThrows(CelEvaluationException.class, () -> program.eval()); + } + } + + private void assertEval(String expr, Object expectedResult) throws Exception { + assertEval(expr, ImmutableMap.of(), expectedResult); + } + + private void assertEval(String expr, ImmutableMap vars, Object expectedResult) throws Exception { + CelAbstractSyntaxTree ast = isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + Object result = cel.createProgram(ast).eval(vars); + assertThat(result).isEqualTo(expectedResult); + } - assertThrows(CelEvaluationException.class, () -> celRuntime.createProgram(ast).eval()); + private static Cel setupEnv(CelBuilder celBuilder) { + return celBuilder + .addMessageTypes(TestAllTypes.getDescriptor()) + .setOptions(CEL_OPTIONS) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .addCompilerLibraries(CelExtensions.sets(CEL_OPTIONS)) + .addRuntimeLibraries(CelExtensions.sets(CEL_OPTIONS)) + .addVar("list", ListType.create(SimpleType.INT)) + .addVar("subList", ListType.create(SimpleType.INT)) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "new_int", + CelOverloadDecl.newGlobalOverload( + "new_int_int64", SimpleType.INT, SimpleType.INT))) + .addFunctionBindings( + CelFunctionBinding.fromOverloads( + "new_int", + CelFunctionBinding.from( + "new_int_int64", + Long.class, + // Intentionally return java.lang.Integer to test primitive type adaptation + Math::toIntExact))) + .build(); } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelStringExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelStringExtensionsTest.java index 3624e0902..a4dc4d6b2 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelStringExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelStringExtensionsTest.java @@ -22,6 +22,7 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; @@ -32,29 +33,40 @@ import dev.cel.extensions.CelStringExtensions.Function; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.testing.CelRuntimeFlavor; import java.util.List; +import org.junit.Assume; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class CelStringExtensionsTest { - private static final CelCompiler COMPILER = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.strings()) - .addVar("s", SimpleType.STRING) - .addVar("separator", SimpleType.STRING) - .addVar("index", SimpleType.INT) - .addVar("offset", SimpleType.INT) - .addVar("indexOfParam", SimpleType.STRING) - .addVar("beginIndex", SimpleType.INT) - .addVar("endIndex", SimpleType.INT) - .addVar("limit", SimpleType.INT) - .build(); - - private static final CelRuntime RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder().addLibraries(CelExtensions.strings()).build(); + @TestParameter public CelRuntimeFlavor runtimeFlavor; + @TestParameter public boolean isParseOnly; + + private Cel cel; + + @Before + public void setUp() { + // Legacy runtime does not support parsed-only evaluation mode. + Assume.assumeFalse(runtimeFlavor.equals(CelRuntimeFlavor.LEGACY) && isParseOnly); + this.cel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.strings()) + .addRuntimeLibraries(CelExtensions.strings()) + .addVar("s", SimpleType.STRING) + .addVar("separator", SimpleType.STRING) + .addVar("index", SimpleType.INT) + .addVar("offset", SimpleType.INT) + .addVar("indexOfParam", SimpleType.STRING) + .addVar("beginIndex", SimpleType.INT) + .addVar("endIndex", SimpleType.INT) + .addVar("limit", SimpleType.INT) + .build(); + } @Test public void library() { @@ -92,10 +104,8 @@ public void library() { @TestParameters("{string: 'πŸ˜πŸ˜‘πŸ˜¦', beginIndex: 3, expectedResult: ''}") public void substring_beginIndex_success(String string, int beginIndex, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.substring(beginIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "beginIndex", beginIndex)); + Object evaluatedResult = + eval("s.substring(beginIndex)", ImmutableMap.of("s", string, "beginIndex", beginIndex)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -108,10 +118,7 @@ public void substring_beginIndex_success(String string, int beginIndex, String e @TestParameters( "{string: 'A!@#$%^&*()-_+=?/<>.,;:''\"\\', expectedResult: 'a!@#$%^&*()-_+=?/<>.,;:''\"\\'}") public void lowerAscii_success(String string, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lowerAscii()").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string)); + Object evaluatedResult = eval("s.lowerAscii()", ImmutableMap.of("s", string)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -127,10 +134,7 @@ public void lowerAscii_success(String string, String expectedResult) throws Exce @TestParameters("{string: 'A😁B πŸ˜‘Cκ°€πŸ˜¦D', expectedResult: 'a😁b πŸ˜‘cκ°€πŸ˜¦d'}") public void lowerAscii_outsideAscii_success(String string, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lowerAscii()").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string)); + Object evaluatedResult = eval("s.lowerAscii()", ImmutableMap.of("s", string)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -161,10 +165,8 @@ public void lowerAscii_outsideAscii_success(String string, String expectedResult + " ['The quick brown ', ' jumps over the lazy dog']}") public void split_ascii_success(String string, String separator, List expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.split(separator)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "separator", separator)); + Object evaluatedResult = + eval("s.split(separator)", ImmutableMap.of("s", string, "separator", separator)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -182,10 +184,8 @@ public void split_ascii_success(String string, String separator, List ex @TestParameters("{string: '😁aπŸ˜¦λ‚˜πŸ˜‘ 😦', separator: '😁aπŸ˜¦λ‚˜πŸ˜‘ 😦', expectedResult: ['','']}") public void split_unicode_success(String string, String separator, List expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.split(separator)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "separator", separator)); + Object evaluatedResult = + eval("s.split(separator)", ImmutableMap.of("s", string, "separator", separator)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -193,8 +193,9 @@ public void split_unicode_success(String string, String separator, List @Test @SuppressWarnings("unchecked") // Test only, need List cast to test mutability public void split_collectionIsMutable() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.split('')").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); + Assume.assumeTrue(!runtimeFlavor.equals(CelRuntimeFlavor.PLANNER)); + CelAbstractSyntaxTree ast = cel.compile("'test'.split('')").getAst(); + CelRuntime.Program program = cel.createProgram(ast); List evaluatedResult = (List) program.eval(); evaluatedResult.add("a"); @@ -207,9 +208,10 @@ public void split_collectionIsMutable() throws Exception { @Test public void split_separatorIsNonString_throwsException() { + Assume.assumeFalse(isParseOnly); CelValidationException exception = assertThrows( - CelValidationException.class, () -> COMPILER.compile("'12'.split(2)").getAst()); + CelValidationException.class, () -> cel.compile("'12'.split(2)").getAst()); assertThat(exception).hasMessageThat().contains("found no matching overload for 'split'"); } @@ -295,11 +297,10 @@ public void split_separatorIsNonString_throwsException() { + " expectedResult: ['The quick brown ', ' jumps over the lazy dog']}") public void split_asciiWithLimit_success( String string, String separator, int limit, List expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.split(separator, limit)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "separator", separator, "limit", limit)); + eval( + "s.split(separator, limit)", + ImmutableMap.of("s", string, "separator", separator, "limit", limit)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -351,11 +352,10 @@ public void split_asciiWithLimit_success( "{string: '😁aπŸ˜¦λ‚˜πŸ˜‘ 😦', separator: '😁aπŸ˜¦λ‚˜πŸ˜‘ 😦', limit: -1, expectedResult: ['','']}") public void split_unicodeWithLimit_success( String string, String separator, int limit, List expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.split(separator, limit)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "separator", separator, "limit", limit)); + eval( + "s.split(separator, limit)", + ImmutableMap.of("s", string, "separator", separator, "limit", limit)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -369,11 +369,12 @@ public void split_unicodeWithLimit_success( @TestParameters("{separator: 'te', limit: 2}") @SuppressWarnings("unchecked") // Test only, need List cast to test mutability public void split_withLimit_collectionIsMutable(String separator, int limit) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.split(separator, limit)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - + Assume.assumeTrue(!runtimeFlavor.equals(CelRuntimeFlavor.PLANNER)); List evaluatedResult = - (List) program.eval(ImmutableMap.of("separator", separator, "limit", limit)); + (List) + eval( + "'test'.split(separator, limit)", + ImmutableMap.of("separator", separator, "limit", limit)); evaluatedResult.add("a"); assertThat(Iterables.getLast(evaluatedResult)).isEqualTo("a"); @@ -381,22 +382,20 @@ public void split_withLimit_collectionIsMutable(String separator, int limit) thr @Test public void split_withLimit_separatorIsNonString_throwsException() { + Assume.assumeFalse(isParseOnly); CelValidationException exception = assertThrows( - CelValidationException.class, () -> COMPILER.compile("'12'.split(2, 3)").getAst()); + CelValidationException.class, () -> cel.compile("'12'.split(2, 3)").getAst()); assertThat(exception).hasMessageThat().contains("found no matching overload for 'split'"); } @Test public void split_withLimitOverflow_throwsException() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.split('', limit)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("limit", 2147483648L))); // INT_MAX + 1 + () -> eval("'test'.split('', limit)", ImmutableMap.of("limit", 2147483648L))); // INT_MAX + 1 assertThat(exception) .hasMessageThat() @@ -416,11 +415,10 @@ public void split_withLimitOverflow_throwsException() throws Exception { @TestParameters("{string: '', beginIndex: 0, endIndex: 0, expectedResult: ''}") public void substring_beginAndEndIndex_ascii_success( String string, int beginIndex, int endIndex, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.substring(beginIndex, endIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "beginIndex", beginIndex, "endIndex", endIndex)); + eval( + "s.substring(beginIndex, endIndex)", + ImmutableMap.of("s", string, "beginIndex", beginIndex, "endIndex", endIndex)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -444,11 +442,10 @@ public void substring_beginAndEndIndex_ascii_success( @TestParameters("{string: 'aπŸ˜λ‚˜', beginIndex: 3, endIndex: 3, expectedResult: ''}") public void substring_beginAndEndIndex_unicode_success( String string, int beginIndex, int endIndex, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.substring(beginIndex, endIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "beginIndex", beginIndex, "endIndex", endIndex)); + eval( + "s.substring(beginIndex, endIndex)", + ImmutableMap.of("s", string, "beginIndex", beginIndex, "endIndex", endIndex)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -458,13 +455,13 @@ public void substring_beginAndEndIndex_unicode_success( @TestParameters("{string: '', beginIndex: 2}") public void substring_beginIndexOutOfRange_ascii_throwsException(String string, int beginIndex) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.substring(beginIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("s", string, "beginIndex", beginIndex))); + () -> + eval( + "s.substring(beginIndex)", + ImmutableMap.of("s", string, "beginIndex", beginIndex))); String exceptionMessage = String.format( @@ -482,13 +479,10 @@ public void substring_beginIndexOutOfRange_ascii_throwsException(String string, @TestParameters("{string: 'πŸ˜κ°€λ‚˜', beginIndex: 4, uniqueCharCount: 3}") public void substring_beginIndexOutOfRange_unicode_throwsException( String string, int beginIndex, int uniqueCharCount) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.substring(beginIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("s", string, "beginIndex", beginIndex))); + () -> eval("s.substring(beginIndex)", ImmutableMap.of("s", string, "beginIndex", beginIndex))); String exceptionMessage = String.format( @@ -505,14 +499,12 @@ public void substring_beginIndexOutOfRange_unicode_throwsException( @TestParameters("{string: 'πŸ˜πŸ˜‘πŸ˜¦', beginIndex: 2, endIndex: 1}") public void substring_beginAndEndIndexOutOfRange_throwsException( String string, int beginIndex, int endIndex) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.substring(beginIndex, endIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, () -> - program.eval( + eval( + "s.substring(beginIndex, endIndex)", ImmutableMap.of("s", string, "beginIndex", beginIndex, "endIndex", endIndex))); String exceptionMessage = @@ -522,13 +514,10 @@ public void substring_beginAndEndIndexOutOfRange_throwsException( @Test public void substring_beginIndexOverflow_throwsException() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'abcd'.substring(beginIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("beginIndex", 2147483648L))); // INT_MAX + 1 + () -> eval("'abcd'.substring(beginIndex)", ImmutableMap.of("beginIndex", 2147483648L))); // INT_MAX + 1 assertThat(exception) .hasMessageThat() @@ -540,13 +529,10 @@ public void substring_beginIndexOverflow_throwsException() throws Exception { @TestParameters("{beginIndex: 2147483648, endIndex: 2147483648}") public void substring_beginOrEndIndexOverflow_throwsException(long beginIndex, long endIndex) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'abcd'.substring(beginIndex, endIndex)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("beginIndex", beginIndex, "endIndex", endIndex))); + () -> eval("'abcd'.substring(beginIndex, endIndex)", ImmutableMap.of("beginIndex", beginIndex, "endIndex", endIndex))); assertThat(exception) .hasMessageThat() @@ -563,10 +549,7 @@ public void substring_beginOrEndIndexOverflow_throwsException(long beginIndex, l @TestParameters("{string: 'world', index: 5, expectedResult: ''}") public void charAt_ascii_success(String string, long index, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.charAt(index)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "index", index)); + Object evaluatedResult = eval("s.charAt(index)", ImmutableMap.of("s", string, "index", index)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -588,10 +571,7 @@ public void charAt_ascii_success(String string, long index, String expectedResul @TestParameters("{string: 'aπŸ˜λ‚˜', index: 3, expectedResult: ''}") public void charAt_unicode_success(String string, long index, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.charAt(index)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "index", index)); + Object evaluatedResult = eval("s.charAt(index)", ImmutableMap.of("s", string, "index", index)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -602,26 +582,20 @@ public void charAt_unicode_success(String string, long index, String expectedRes @TestParameters("{string: 'πŸ˜πŸ˜‘πŸ˜¦', index: -1}") @TestParameters("{string: 'πŸ˜πŸ˜‘πŸ˜¦', index: 4}") public void charAt_outOfBounds_throwsException(String string, long index) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.charAt(index)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("s", string, "index", index))); + () -> eval("s.charAt(index)", ImmutableMap.of("s", string, "index", index))); assertThat(exception).hasMessageThat().contains("charAt failure: Index out of range"); } @Test public void charAt_indexOverflow_throwsException() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.charAt(index)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("index", 2147483648L))); // INT_MAX + 1 + () -> eval("'test'.charAt(index)", ImmutableMap.of("index", 2147483648L))); // INT_MAX + 1 assertThat(exception) .hasMessageThat() @@ -650,10 +624,7 @@ public void charAt_indexOverflow_throwsException() throws Exception { @TestParameters("{string: 'hello mellow', indexOf: ' ', expectedResult: -1}") public void indexOf_ascii_success(String string, String indexOf, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.indexOf(indexOfParam)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "indexOfParam", indexOf)); + Object evaluatedResult = eval("s.indexOf(indexOfParam)", ImmutableMap.of("s", string, "indexOfParam", indexOf)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -682,10 +653,7 @@ public void indexOf_ascii_success(String string, String indexOf, int expectedRes @TestParameters("{string: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€', indexOf: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€πŸ˜', expectedResult: -1}") public void indexOf_unicode_success(String string, String indexOf, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.indexOf(indexOfParam)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string, "indexOfParam", indexOf)); + Object evaluatedResult = eval("s.indexOf(indexOfParam)", ImmutableMap.of("s", string, "indexOfParam", indexOf)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -697,13 +665,10 @@ public void indexOf_unicode_success(String string, String indexOf, int expectedR @TestParameters("{indexOf: 'λ‚˜'}") @TestParameters("{indexOf: '😁'}") public void indexOf_onEmptyString_throwsException(String indexOf) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("''.indexOf(indexOfParam)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("indexOfParam", indexOf))); + () -> eval("''.indexOf(indexOfParam)", ImmutableMap.of("indexOfParam", indexOf))); assertThat(exception).hasMessageThat().contains("indexOf failure: Offset out of range"); } @@ -728,11 +693,10 @@ public void indexOf_onEmptyString_throwsException(String indexOf) throws Excepti @TestParameters("{string: 'hello mellow', indexOf: 'l', offset: 10, expectedResult: -1}") public void indexOf_asciiWithOffset_success( String string, String indexOf, int offset, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.indexOf(indexOfParam, offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "indexOfParam", indexOf, "offset", offset)); + eval( + "s.indexOf(indexOfParam, offset)", + ImmutableMap.of("s", string, "indexOfParam", indexOf, "offset", offset)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -779,11 +743,8 @@ public void indexOf_asciiWithOffset_success( "{string: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€', indexOf: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€πŸ˜', offset: 0, expectedResult: -1}") public void indexOf_unicodeWithOffset_success( String string, String indexOf, int offset, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.indexOf(indexOfParam, offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "indexOfParam", indexOf, "offset", offset)); + eval("s.indexOf(indexOfParam, offset)", ImmutableMap.of("s", string, "indexOfParam", indexOf, "offset", offset)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -797,14 +758,12 @@ public void indexOf_unicodeWithOffset_success( @TestParameters("{string: 'πŸ˜πŸ˜‘ 😦', indexOf: '😦', offset: 4}") public void indexOf_withOffsetOutOfBounds_throwsException( String string, String indexOf, int offset) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.indexOf(indexOfParam, offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, () -> - program.eval( + eval( + "s.indexOf(indexOfParam, offset)", ImmutableMap.of("s", string, "indexOfParam", indexOf, "offset", offset))); assertThat(exception).hasMessageThat().contains("indexOf failure: Offset out of range"); @@ -812,13 +771,10 @@ public void indexOf_withOffsetOutOfBounds_throwsException( @Test public void indexOf_offsetOverflow_throwsException() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.indexOf('t', offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("offset", 2147483648L))); // INT_MAX + 1 + () -> eval("'test'.indexOf('t', offset)", ImmutableMap.of("offset", 2147483648L))); // INT_MAX + 1 assertThat(exception) .hasMessageThat() @@ -835,10 +791,7 @@ public void indexOf_offsetOverflow_throwsException() throws Exception { @TestParameters("{list: '[''x'', '' '', '' y '', ''z '']', expectedResult: 'x y z '}") @TestParameters("{list: '[''hello '', ''world'']', expectedResult: 'hello world'}") public void join_ascii_success(String list, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(String.format("%s.join()", list)).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - String result = (String) program.eval(); + String result = (String) eval(String.format("%s.join()", list)); assertThat(result).isEqualTo(expectedResult); } @@ -847,10 +800,7 @@ public void join_ascii_success(String list, String expectedResult) throws Except @TestParameters("{list: '[''κ°€'', ''😁'']', expectedResult: 'κ°€πŸ˜'}") @TestParameters("{list: '[''πŸ˜πŸ˜¦πŸ˜‘ 😦'', ''λ‚˜'']', expectedResult: 'πŸ˜πŸ˜¦πŸ˜‘ πŸ˜¦λ‚˜'}") public void join_unicode_success(String list, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile(String.format("%s.join()", list)).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - String result = (String) program.eval(); + String result = (String) eval(String.format("%s.join()", list)); assertThat(result).isEqualTo(expectedResult); } @@ -874,11 +824,7 @@ public void join_unicode_success(String list, String expectedResult) throws Exce "{list: '[''hello '', ''world'']', separator: '/', expectedResult: 'hello /world'}") public void join_asciiWithSeparator_success(String list, String separator, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = - COMPILER.compile(String.format("%s.join('%s')", list, separator)).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - String result = (String) program.eval(); + String result = (String) eval(String.format("%s.join('%s')", list, separator)); assertThat(result).isEqualTo(expectedResult); } @@ -893,20 +839,17 @@ public void join_asciiWithSeparator_success(String list, String separator, Strin + " -πŸ˜‘-λ‚˜'}") public void join_unicodeWithSeparator_success( String list, String separator, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = - COMPILER.compile(String.format("%s.join('%s')", list, separator)).getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - String result = (String) program.eval(); + String result = (String) eval(String.format("%s.join('%s')", list, separator)); assertThat(result).isEqualTo(expectedResult); } @Test public void join_separatorIsNonString_throwsException() { + Assume.assumeFalse(isParseOnly); CelValidationException exception = assertThrows( - CelValidationException.class, () -> COMPILER.compile("['x','y'].join(2)").getAst()); + CelValidationException.class, () -> cel.compile("['x','y'].join(2)").getAst()); assertThat(exception).hasMessageThat().contains("found no matching overload for 'join'"); } @@ -935,11 +878,8 @@ public void join_separatorIsNonString_throwsException() { @TestParameters("{string: 'hello mellow', lastIndexOf: ' ', expectedResult: -1}") public void lastIndexOf_ascii_success(String string, String lastIndexOf, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lastIndexOf(indexOfParam)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "indexOfParam", lastIndexOf)); + eval("s.lastIndexOf(indexOfParam)", ImmutableMap.of("s", string, "indexOfParam", lastIndexOf)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -969,11 +909,8 @@ public void lastIndexOf_ascii_success(String string, String lastIndexOf, int exp @TestParameters("{string: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€', lastIndexOf: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€πŸ˜', expectedResult: -1}") public void lastIndexOf_unicode_success(String string, String lastIndexOf, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lastIndexOf(indexOfParam)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "indexOfParam", lastIndexOf)); + eval("s.lastIndexOf(indexOfParam)", ImmutableMap.of("s", string, "indexOfParam", lastIndexOf)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -987,10 +924,7 @@ public void lastIndexOf_unicode_success(String string, String lastIndexOf, int e @TestParameters("{lastIndexOf: '😁'}") public void lastIndexOf_strLengthLessThanSubstrLength_returnsMinusOne(String lastIndexOf) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("''.lastIndexOf(indexOfParam)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", "", "indexOfParam", lastIndexOf)); + Object evaluatedResult = eval("''.lastIndexOf(indexOfParam)", ImmutableMap.of("s", "", "indexOfParam", lastIndexOf)); assertThat(evaluatedResult).isEqualTo(-1); } @@ -1022,11 +956,10 @@ public void lastIndexOf_strLengthLessThanSubstrLength_returnsMinusOne(String las "{string: 'hello mellow', lastIndexOf: 'hello mellowwww ', offset: 11, expectedResult: -1}") public void lastIndexOf_asciiWithOffset_success( String string, String lastIndexOf, int offset, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lastIndexOf(indexOfParam, offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "indexOfParam", lastIndexOf, "offset", offset)); + eval( + "s.lastIndexOf(indexOfParam, offset)", + ImmutableMap.of("s", string, "indexOfParam", lastIndexOf, "offset", offset)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -1097,11 +1030,10 @@ public void lastIndexOf_asciiWithOffset_success( "{string: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€', lastIndexOf: 'aπŸ˜πŸ˜‘ λ‚˜πŸ˜¦πŸ˜πŸ˜‘λ‹€πŸ˜', offset: 8, expectedResult: -1}") public void lastIndexOf_unicodeWithOffset_success( String string, String lastIndexOf, int offset, int expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lastIndexOf(indexOfParam, offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - Object evaluatedResult = - program.eval(ImmutableMap.of("s", string, "indexOfParam", lastIndexOf, "offset", offset)); + eval( + "s.lastIndexOf(indexOfParam, offset)", + ImmutableMap.of("s", string, "indexOfParam", lastIndexOf, "offset", offset)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -1115,14 +1047,12 @@ public void lastIndexOf_unicodeWithOffset_success( @TestParameters("{string: 'πŸ˜πŸ˜‘ 😦', lastIndexOf: '😦', offset: 4}") public void lastIndexOf_withOffsetOutOfBounds_throwsException( String string, String lastIndexOf, int offset) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.lastIndexOf(indexOfParam, offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, () -> - program.eval( + eval( + "s.lastIndexOf(indexOfParam, offset)", ImmutableMap.of("s", string, "indexOfParam", lastIndexOf, "offset", offset))); assertThat(exception).hasMessageThat().contains("lastIndexOf failure: Offset out of range"); @@ -1130,13 +1060,10 @@ public void lastIndexOf_withOffsetOutOfBounds_throwsException( @Test public void lastIndexOf_offsetOverflow_throwsException() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.lastIndexOf('t', offset)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("offset", 2147483648L))); // INT_MAX + 1 + () -> eval("'test'.lastIndexOf('t', offset)", ImmutableMap.of("offset", 2147483648L))); // INT_MAX + 1 assertThat(exception) .hasMessageThat() @@ -1163,13 +1090,8 @@ public void lastIndexOf_offsetOverflow_throwsException() throws Exception { public void replace_ascii_success( String string, String searchString, String replacement, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = - COMPILER - .compile(String.format("'%s'.replace('%s', '%s')", string, searchString, replacement)) - .getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(); + Object evaluatedResult = + eval(String.format("'%s'.replace('%s', '%s')", string, searchString, replacement)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -1188,13 +1110,8 @@ public void replace_ascii_success( public void replace_unicode_success( String string, String searchString, String replacement, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = - COMPILER - .compile(String.format("'%s'.replace('%s', '%s')", string, searchString, replacement)) - .getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(); + Object evaluatedResult = + eval(String.format("'%s'.replace('%s', '%s')", string, searchString, replacement)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -1273,15 +1190,10 @@ public void replace_unicode_success( public void replace_ascii_withLimit_success( String string, String searchString, String replacement, int limit, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = - COMPILER - .compile( - String.format( - "'%s'.replace('%s', '%s', %d)", string, searchString, replacement, limit)) - .getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(); + Object evaluatedResult = + eval( + String.format( + "'%s'.replace('%s', '%s', %d)", string, searchString, replacement, limit)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -1334,28 +1246,20 @@ public void replace_ascii_withLimit_success( public void replace_unicode_withLimit_success( String string, String searchString, String replacement, int limit, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = - COMPILER - .compile( - String.format( - "'%s'.replace('%s', '%s', %d)", string, searchString, replacement, limit)) - .getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(); + Object evaluatedResult = + eval( + String.format( + "'%s'.replace('%s', '%s', %d)", string, searchString, replacement, limit)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @Test public void replace_limitOverflow_throwsException() throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("'test'.replace('','',index)").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - CelEvaluationException exception = assertThrows( CelEvaluationException.class, - () -> program.eval(ImmutableMap.of("index", 2147483648L))); // INT_MAX + 1 + () -> eval("'test'.replace('','',index)", ImmutableMap.of("index", 2147483648L))); // INT_MAX + 1 assertThat(exception) .hasMessageThat() @@ -1406,10 +1310,7 @@ private enum TrimTestCase { @Test public void trim_success(@TestParameter TrimTestCase testCase) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.trim()").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", testCase.text)); + Object evaluatedResult = eval("s.trim()", ImmutableMap.of("s", testCase.text)); assertThat(evaluatedResult).isEqualTo(testCase.expectedResult); } @@ -1422,10 +1323,7 @@ public void trim_success(@TestParameter TrimTestCase testCase) throws Exception @TestParameters( "{string: 'a!@#$%^&*()-_+=?/<>.,;:''\"\\', expectedResult: 'A!@#$%^&*()-_+=?/<>.,;:''\"\\'}") public void upperAscii_success(String string, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.upperAscii()").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string)); + Object evaluatedResult = eval("s.upperAscii()", ImmutableMap.of("s", string)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @@ -1441,30 +1339,26 @@ public void upperAscii_success(String string, String expectedResult) throws Exce @TestParameters("{string: 'a😁b πŸ˜‘cκ°€πŸ˜¦d', expectedResult: 'A😁B πŸ˜‘Cκ°€πŸ˜¦D'}") public void upperAscii_outsideAscii_success(String string, String expectedResult) throws Exception { - CelAbstractSyntaxTree ast = COMPILER.compile("s.upperAscii()").getAst(); - CelRuntime.Program program = RUNTIME.createProgram(ast); - - Object evaluatedResult = program.eval(ImmutableMap.of("s", string)); + Object evaluatedResult = eval("s.upperAscii()", ImmutableMap.of("s", string)); assertThat(evaluatedResult).isEqualTo(expectedResult); } @Test public void stringExtension_functionSubset_success() throws Exception { - CelStringExtensions stringExtensions = - CelExtensions.strings(Function.CHAR_AT, Function.SUBSTRING); - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder().addLibraries(stringExtensions).build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder().addLibraries(stringExtensions).build(); + Assume.assumeFalse(isParseOnly); + Cel customCel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.strings(Function.CHAR_AT, Function.SUBSTRING)) + .addRuntimeLibraries(CelExtensions.strings(Function.CHAR_AT, Function.SUBSTRING)) + .build(); - Object evaluatedResult = - celRuntime - .createProgram( - celCompiler - .compile("'test'.substring(2) == 'st' && 'hello'.charAt(1) == 'e'") - .getAst()) - .eval(); + CelAbstractSyntaxTree ast = + isParseOnly + ? customCel.parse("'test'.substring(2) == 'st' && 'hello'.charAt(1) == 'e'").getAst() + : customCel.compile("'test'.substring(2) == 'st' && 'hello'.charAt(1) == 'e'").getAst(); + Object evaluatedResult = customCel.createProgram(ast).eval(); assertThat(evaluatedResult).isEqualTo(true); } @@ -1570,6 +1464,7 @@ public void stringExtension_compileUnallowedFunction_throws() { .addLibraries(CelExtensions.strings(Function.REPLACE)) .build(); + Assume.assumeFalse(isParseOnly); assertThrows( CelValidationException.class, () -> celCompiler.compile("'test'.substring(2) == 'st'").getAst()); @@ -1577,16 +1472,32 @@ public void stringExtension_compileUnallowedFunction_throws() { @Test public void stringExtension_evaluateUnallowedFunction_throws() throws Exception { - CelCompiler celCompiler = - CelCompilerFactory.standardCelCompilerBuilder() - .addLibraries(CelExtensions.strings(Function.SUBSTRING)) + Assume.assumeFalse(isParseOnly); + Cel customCompilerCel = + runtimeFlavor + .builder() + .addCompilerLibraries(CelExtensions.strings(Function.SUBSTRING)) .build(); - CelRuntime celRuntime = - CelRuntimeFactory.standardCelRuntimeBuilder() - .addLibraries(CelExtensions.strings(Function.REPLACE)) + Cel customRuntimeCel = + runtimeFlavor + .builder() + .addRuntimeLibraries(CelExtensions.strings(Function.REPLACE)) .build(); - CelAbstractSyntaxTree ast = celCompiler.compile("'test'.substring(2) == 'st'").getAst(); + CelAbstractSyntaxTree ast = + isParseOnly + ? customCompilerCel.parse("'test'.substring(2) == 'st'").getAst() + : customCompilerCel.compile("'test'.substring(2) == 'st'").getAst(); + + assertThrows(CelEvaluationException.class, () -> customRuntimeCel.createProgram(ast).eval()); + } + + private Object eval(String expr, ImmutableMap vars) throws Exception { + CelAbstractSyntaxTree ast = + isParseOnly ? cel.parse(expr).getAst() : cel.compile(expr).getAst(); + return cel.createProgram(ast).eval(vars); + } - assertThrows(CelEvaluationException.class, () -> celRuntime.createProgram(ast).eval()); + private Object eval(String expr) throws Exception { + return eval(expr, ImmutableMap.of()); } } diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel index 734aa6879..d1220a41a 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -11,7 +11,6 @@ java_library( deps = [ # "//java/com/google/testing/testsize:annotations", "//bundle:cel", - "//bundle:cel_experimental_factory", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", @@ -37,6 +36,7 @@ java_library( "//runtime:program", "//runtime:unknown_attributes", "//testing:baseline_test_case", + "//testing:cel_runtime_flavor", "@maven//:junit_junit", "@maven//:com_google_testparameterinjector_test_parameter_injector", "//:java_truth", diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index bbb5c6e7e..33dc2d941 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -23,8 +23,6 @@ import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; import dev.cel.bundle.CelBuilder; -import dev.cel.bundle.CelExperimentalFactory; -import dev.cel.bundle.CelFactory; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; @@ -44,6 +42,8 @@ import dev.cel.parser.CelUnparser; import dev.cel.parser.CelUnparserFactory; import dev.cel.runtime.CelFunctionBinding; +import dev.cel.testing.CelRuntimeFlavor; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,72 +57,57 @@ public class ConstantFoldingOptimizerTest { private static final CelUnparser CEL_UNPARSER = CelUnparserFactory.newUnparser(); - @SuppressWarnings("ImmutableEnumChecker") // test only - private enum RuntimeEnv { - LEGACY(setupEnv(CelFactory.standardCelBuilder())), - PLANNER(setupEnv(CelExperimentalFactory.plannerCelBuilder())); - - private final Cel cel; - private final CelOptimizer celOptimizer; - - private static Cel setupEnv(CelBuilder celBuilder) { - return celBuilder - .addVar("x", SimpleType.DYN) - .addVar("y", SimpleType.DYN) - .addVar("list_var", ListType.create(SimpleType.STRING)) - .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.STRING)) - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "get_true", - CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL)), - CelFunctionDecl.newFunctionDeclaration( - "get_list", - CelOverloadDecl.newGlobalOverload( - "get_list_overload", - ListType.create(SimpleType.INT), - ListType.create(SimpleType.INT)))) - .addFunctionBindings( - CelFunctionBinding.from("get_true_overload", ImmutableList.of(), unused -> true)) - .addMessageTypes(TestAllTypes.getDescriptor()) - .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) - .setOptions(CEL_OPTIONS) - .addCompilerLibraries( - CelExtensions.bindings(), - CelOptionalLibrary.INSTANCE, - CelExtensions.math(CEL_OPTIONS), - CelExtensions.strings(), - CelExtensions.sets(CEL_OPTIONS), - CelExtensions.encoders(CEL_OPTIONS)) - .addRuntimeLibraries( - CelOptionalLibrary.INSTANCE, - CelExtensions.math(CEL_OPTIONS), - CelExtensions.strings(), - CelExtensions.sets(CEL_OPTIONS), - CelExtensions.encoders(CEL_OPTIONS)) - .build(); - } - - RuntimeEnv(Cel cel) { - this.cel = cel; - this.celOptimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(cel) - .addAstOptimizers(ConstantFoldingOptimizer.getInstance()) - .build(); - } - - private CelBuilder newCelBuilder() { - switch (this) { - case LEGACY: - return CelFactory.standardCelBuilder(); - case PLANNER: - return CelExperimentalFactory.plannerCelBuilder(); - } - throw new AssertionError("Unknown RuntimeEnv: " + this); - } + @TestParameter CelRuntimeFlavor runtimeFlavor; + + private Cel cel; + private CelOptimizer celOptimizer; + + @Before + public void setUp() { + this.cel = setupEnv(runtimeFlavor.builder()); + this.celOptimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(this.cel) + .addAstOptimizers(ConstantFoldingOptimizer.getInstance()) + .build(); } - @TestParameter RuntimeEnv runtimeEnv; + private static Cel setupEnv(CelBuilder celBuilder) { + return celBuilder + .addVar("x", SimpleType.DYN) + .addVar("y", SimpleType.DYN) + .addVar("list_var", ListType.create(SimpleType.STRING)) + .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.STRING)) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "get_true", + CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL)), + CelFunctionDecl.newFunctionDeclaration( + "get_list", + CelOverloadDecl.newGlobalOverload( + "get_list_overload", + ListType.create(SimpleType.INT), + ListType.create(SimpleType.INT)))) + .addFunctionBindings( + CelFunctionBinding.from("get_true_overload", ImmutableList.of(), unused -> true)) + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .setOptions(CEL_OPTIONS) + .addCompilerLibraries( + CelExtensions.bindings(), + CelOptionalLibrary.INSTANCE, + CelExtensions.math(CEL_OPTIONS), + CelExtensions.strings(), + CelExtensions.sets(CEL_OPTIONS), + CelExtensions.encoders(CEL_OPTIONS)) + .addRuntimeLibraries( + CelOptionalLibrary.INSTANCE, + CelExtensions.math(CEL_OPTIONS), + CelExtensions.strings(), + CelExtensions.sets(CEL_OPTIONS), + CelExtensions.encoders(CEL_OPTIONS)) + .build(); + } @Test @TestParameters("{source: 'null', expected: 'null'}") @@ -270,9 +255,9 @@ private CelBuilder newCelBuilder() { // TODO: Support folding lists with mixed types. This requires mutable lists. // @TestParameters("{source: 'dyn([1]) + [1.0]'}") public void constantFold_success(String source, String expected) throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(source).getAst(); - CelAbstractSyntaxTree optimizedAst = runtimeEnv.celOptimizer.optimize(ast); + CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast); assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo(expected); } @@ -317,8 +302,8 @@ public void constantFold_success(String source, String expected) throws Exceptio public void constantFold_macros_macroCallMetadataPopulated(String source, String expected) throws Exception { Cel cel = - runtimeEnv - .newCelBuilder() + runtimeFlavor + .builder() .addVar("x", SimpleType.DYN) .addVar("y", SimpleType.DYN) .addMessageTypes(TestAllTypes.getDescriptor()) @@ -363,8 +348,8 @@ public void constantFold_macros_macroCallMetadataPopulated(String source, String @TestParameters("{source: 'false ? false : cel.bind(a, true, a)'}") public void constantFold_macros_withoutMacroCallMetadata(String source) throws Exception { Cel cel = - runtimeEnv - .newCelBuilder() + runtimeFlavor + .builder() .addVar("x", SimpleType.DYN) .addVar("y", SimpleType.DYN) .addMessageTypes(TestAllTypes.getDescriptor()) @@ -418,20 +403,20 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E @TestParameters("{source: 'get_list([1, 2]).map(x, x * 2)'}") @TestParameters("{source: '[(x - 1 > 3) ? (x - 1) : 5].exists(x, x - 1 > 3)'}") public void constantFold_noOp(String source) throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(source).getAst(); - CelAbstractSyntaxTree optimizedAst = runtimeEnv.celOptimizer.optimize(ast); + CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast); assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo(source); } @Test public void constantFold_addFoldableFunction_success() throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile("get_true() == get_true()").getAst(); + CelAbstractSyntaxTree ast = cel.compile("get_true() == get_true()").getAst(); ConstantFoldingOptions options = ConstantFoldingOptions.newBuilder().addFoldableFunctions("get_true").build(); CelOptimizer optimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers(ConstantFoldingOptimizer.newInstance(options)) .build(); @@ -442,7 +427,7 @@ public void constantFold_addFoldableFunction_success() throws Exception { @Test public void constantFold_withExpectedResultTypeSet_success() throws Exception { - Cel cel = runtimeEnv.newCelBuilder().setResultType(SimpleType.STRING).build(); + Cel cel = runtimeFlavor.builder().setResultType(SimpleType.STRING).build(); CelOptimizer optimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers(ConstantFoldingOptimizer.getInstance()) @@ -458,8 +443,8 @@ public void constantFold_withExpectedResultTypeSet_success() throws Exception { public void constantFold_withMacroCallPopulated_comprehensionsAreReplacedWithNotSet() throws Exception { Cel cel = - runtimeEnv - .newCelBuilder() + runtimeFlavor + .builder() .addVar("x", SimpleType.DYN) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions(CEL_OPTIONS) @@ -532,9 +517,9 @@ public void constantFold_withMacroCallPopulated_comprehensionsAreReplacedWithNot @Test public void constantFold_astProducesConsistentlyNumberedIds() throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile("[1] + [2] + [3]").getAst(); + CelAbstractSyntaxTree ast = cel.compile("[1] + [2] + [3]").getAst(); - CelAbstractSyntaxTree optimizedAst = runtimeEnv.celOptimizer.optimize(ast); + CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast); assertThat(optimizedAst.getExpr().toString()) .isEqualTo( @@ -555,8 +540,8 @@ public void iterationLimitReached_throws() throws Exception { sb.append(" + ").append(i); } // 0 + 1 + 2 + 3 + ... 200 Cel cel = - runtimeEnv - .newCelBuilder() + runtimeFlavor + .builder() .setOptions( CelOptions.current() .enableHeterogeneousNumericComparisons(true) diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java index 74e3b5b32..04e4e6a1d 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java @@ -24,8 +24,6 @@ // import com.google.testing.testsize.MediumTest; import dev.cel.bundle.Cel; import dev.cel.bundle.CelBuilder; -import dev.cel.bundle.CelExperimentalFactory; -import dev.cel.bundle.CelFactory; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; @@ -44,6 +42,7 @@ import dev.cel.parser.CelUnparserFactory; import dev.cel.runtime.CelFunctionBinding; import dev.cel.testing.BaselineTestCase; +import dev.cel.testing.CelRuntimeFlavor; import java.util.EnumSet; import java.util.Optional; import org.junit.Before; @@ -53,48 +52,41 @@ // @MediumTest @RunWith(TestParameterInjector.class) public class SubexpressionOptimizerBaselineTest extends BaselineTestCase { - private enum RuntimeEnv { - LEGACY(setupCelEnv(CelFactory.standardCelBuilder())), - PLANNER(setupCelEnv(CelExperimentalFactory.plannerCelBuilder())); - - private final Cel cel; - - private static Cel setupCelEnv(CelBuilder celBuilder) { - return celBuilder - .addMessageTypes(TestAllTypes.getDescriptor()) - .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .setOptions( - CelOptions.current() - .populateMacroCalls(true) - .enableHeterogeneousNumericComparisons(true) - .build()) - .addCompilerLibraries( - CelExtensions.optional(), CelExtensions.bindings(), CelExtensions.comprehensions()) - .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "pure_custom_func", - newGlobalOverload("pure_custom_func_overload", SimpleType.INT, SimpleType.INT)), - CelFunctionDecl.newFunctionDeclaration( - "non_pure_custom_func", - newGlobalOverload( - "non_pure_custom_func_overload", SimpleType.INT, SimpleType.INT))) - .addFunctionBindings( - // This is pure, but for the purposes of excluding it as a CSE candidate, pretend that - // it isn't. - CelFunctionBinding.from("non_pure_custom_func_overload", Long.class, val -> val), - CelFunctionBinding.from("pure_custom_func_overload", Long.class, val -> val)) - .addVar("x", SimpleType.DYN) - .addVar("y", SimpleType.DYN) - .addVar("opt_x", OptionalType.create(SimpleType.DYN)) - .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) - .build(); - } - - RuntimeEnv(Cel cel) { - this.cel = cel; - } + private static Cel setupCelEnv(CelBuilder celBuilder) { + return celBuilder + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setOptions( + CelOptions.current() + .populateMacroCalls(true) + .enableHeterogeneousNumericComparisons(true) + .build()) + .addCompilerLibraries( + CelExtensions.optional(), CelExtensions.bindings(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "pure_custom_func", + newGlobalOverload("pure_custom_func_overload", SimpleType.INT, SimpleType.INT)), + CelFunctionDecl.newFunctionDeclaration( + "non_pure_custom_func", + newGlobalOverload("non_pure_custom_func_overload", SimpleType.INT, SimpleType.INT))) + .addFunctionBindings( + // This is pure, but for the purposes of excluding it as a CSE candidate, pretend that + // it isn't. + CelFunctionBinding.fromOverloads( + "non_pure_custom_func", + CelFunctionBinding.from("non_pure_custom_func_overload", Long.class, val -> val))) + .addFunctionBindings( + CelFunctionBinding.fromOverloads( + "pure_custom_func", + CelFunctionBinding.from("pure_custom_func_overload", Long.class, val -> val))) + .addVar("x", SimpleType.DYN) + .addVar("y", SimpleType.DYN) + .addVar("opt_x", OptionalType.create(SimpleType.DYN)) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .build(); } private static final CelUnparser CEL_UNPARSER = CelUnparserFactory.newUnparser(); @@ -124,6 +116,7 @@ private static Cel setupCelEnv(CelBuilder celBuilder) { @Before public void setUp() { + this.cel = setupCelEnv(runtimeFlavor.builder()); overriddenBaseFilePath = ""; } @@ -135,21 +128,44 @@ protected String baselineFileName() { return overriddenBaseFilePath; } - @TestParameter RuntimeEnv runtimeEnv; + @TestParameter CelRuntimeFlavor runtimeFlavor; + + private Cel cel; @Test public void allOptimizers_producesSameEvaluationResult( @TestParameter CseTestOptimizer cseTestOptimizer, @TestParameter CseTestCase cseTestCase) throws Exception { skipBaselineVerification(); - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(cseTestCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(cseTestCase.source).getAst(); + ImmutableMap inputMap = + ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "y", 6L, "opt_x", Optional.of(5L)); + Object expectedEvalResult = cel.createProgram(ast).eval(inputMap); + + CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.newCseOptimizer(cel).optimize(ast); + + Object optimizedEvalResult = cel.createProgram(optimizedAst).eval(inputMap); + assertThat(optimizedEvalResult).isEqualTo(expectedEvalResult); + } + + @Test + public void allOptimizers_producesSameEvaluationResult_parsedOnly( + @TestParameter CseTestCase cseTestCase, @TestParameter CseTestOptimizer cseTestOptimizer) + throws Exception { + skipBaselineVerification(); + if (runtimeFlavor.equals(CelRuntimeFlavor.LEGACY)) { + return; + } + CelAbstractSyntaxTree ast = cel.compile(cseTestCase.source).getAst(); ImmutableMap inputMap = ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "y", 6L, "opt_x", Optional.of(5L)); - Object expectedEvalResult = runtimeEnv.cel.createProgram(ast).eval(inputMap); + Object expectedEvalResult = cel.createProgram(ast).eval(inputMap); - CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.newCseOptimizer(runtimeEnv).optimize(ast); + CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.newCseOptimizer(cel).optimize(ast); + CelAbstractSyntaxTree parsedOnlyOptimizedAst = + CelAbstractSyntaxTree.newParsedAst(optimizedAst.getExpr(), optimizedAst.getSource()); - Object optimizedEvalResult = runtimeEnv.cel.createProgram(optimizedAst).eval(inputMap); + Object optimizedEvalResult = cel.createProgram(parsedOnlyOptimizedAst).eval(inputMap); assertThat(optimizedEvalResult).isEqualTo(expectedEvalResult); } @@ -159,22 +175,20 @@ public void subexpression_unparsed() throws Exception { testOutput().println("Test case: " + cseTestCase.name()); testOutput().println("Source: " + cseTestCase.source); testOutput().println("=====>"); - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(cseTestCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(cseTestCase.source).getAst(); boolean resultPrinted = false; for (CseTestOptimizer cseTestOptimizer : CseTestOptimizer.values()) { String optimizerName = cseTestOptimizer.name(); CelAbstractSyntaxTree optimizedAst; try { - optimizedAst = cseTestOptimizer.newCseOptimizer(runtimeEnv).optimize(ast); + optimizedAst = cseTestOptimizer.newCseOptimizer(cel).optimize(ast); } catch (Exception e) { testOutput().printf("[%s]: Optimization Error: %s", optimizerName, e); continue; } if (!resultPrinted) { Object optimizedEvalResult = - runtimeEnv - .cel - .createProgram(optimizedAst) + cel.createProgram(optimizedAst) .eval( ImmutableMap.of( "msg", TEST_ALL_TYPES_INPUT, "x", 5L, "y", 6L, "opt_x", Optional.of(5L))); @@ -198,17 +212,15 @@ public void constfold_before_subexpression_unparsed() throws Exception { testOutput().println("Test case: " + cseTestCase.name()); testOutput().println("Source: " + cseTestCase.source); testOutput().println("=====>"); - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(cseTestCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(cseTestCase.source).getAst(); boolean resultPrinted = false; for (CseTestOptimizer cseTestOptimizer : EnumSet.allOf(CseTestOptimizer.class)) { String optimizerName = cseTestOptimizer.name(); CelAbstractSyntaxTree optimizedAst = - cseTestOptimizer.newCseWithConstFoldingOptimizer(runtimeEnv).optimize(ast); + cseTestOptimizer.newCseWithConstFoldingOptimizer(cel).optimize(ast); if (!resultPrinted) { Object optimizedEvalResult = - runtimeEnv - .cel - .createProgram(optimizedAst) + cel.createProgram(optimizedAst) .eval( ImmutableMap.of( "msg", TEST_ALL_TYPES_INPUT, "x", 5L, "y", 6L, "opt_x", Optional.of(5L))); @@ -234,9 +246,9 @@ public void subexpression_ast(@TestParameter CseTestOptimizer cseTestOptimizer) testOutput().println("Test case: " + cseTestCase.name()); testOutput().println("Source: " + cseTestCase.source); testOutput().println("=====>"); - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(cseTestCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(cseTestCase.source).getAst(); CelAbstractSyntaxTree optimizedAst = - newCseOptimizer(runtimeEnv.cel, cseTestOptimizer.option).optimize(ast); + newCseOptimizer(cel, cseTestOptimizer.option).optimize(ast); testOutput().println(optimizedAst.getExpr()); } } @@ -245,8 +257,7 @@ public void subexpression_ast(@TestParameter CseTestOptimizer cseTestOptimizer) public void large_expressions_block_common_subexpr() throws Exception { CelOptimizer celOptimizer = newCseOptimizer( - runtimeEnv.cel, - SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()); + cel, SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()); runLargeTestCases(celOptimizer); } @@ -255,7 +266,7 @@ public void large_expressions_block_common_subexpr() throws Exception { public void large_expressions_block_recursion_depth_1() throws Exception { CelOptimizer celOptimizer = newCseOptimizer( - runtimeEnv.cel, + cel, SubexpressionOptimizerOptions.newBuilder() .populateMacroCalls(true) .subexpressionMaxRecursionDepth(1) @@ -268,7 +279,7 @@ public void large_expressions_block_recursion_depth_1() throws Exception { public void large_expressions_block_recursion_depth_2() throws Exception { CelOptimizer celOptimizer = newCseOptimizer( - runtimeEnv.cel, + cel, SubexpressionOptimizerOptions.newBuilder() .populateMacroCalls(true) .subexpressionMaxRecursionDepth(2) @@ -281,7 +292,7 @@ public void large_expressions_block_recursion_depth_2() throws Exception { public void large_expressions_block_recursion_depth_3() throws Exception { CelOptimizer celOptimizer = newCseOptimizer( - runtimeEnv.cel, + cel, SubexpressionOptimizerOptions.newBuilder() .populateMacroCalls(true) .subexpressionMaxRecursionDepth(3) @@ -295,12 +306,10 @@ private void runLargeTestCases(CelOptimizer celOptimizer) throws Exception { testOutput().println("Test case: " + cseTestCase.name()); testOutput().println("Source: " + cseTestCase.source); testOutput().println("=====>"); - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(cseTestCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(cseTestCase.source).getAst(); CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast); Object optimizedEvalResult = - runtimeEnv - .cel - .createProgram(optimizedAst) + cel.createProgram(optimizedAst) .eval( ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "opt_x", Optional.of(5L))); testOutput().println("Result: " + optimizedEvalResult); @@ -349,13 +358,13 @@ private enum CseTestOptimizer { } // Defers building the optimizer until the test runs - private CelOptimizer newCseOptimizer(RuntimeEnv env) { - return SubexpressionOptimizerBaselineTest.newCseOptimizer(env.cel, option); + private CelOptimizer newCseOptimizer(Cel cel) { + return SubexpressionOptimizerBaselineTest.newCseOptimizer(cel, option); } // Defers building the optimizer until the test runs - private CelOptimizer newCseWithConstFoldingOptimizer(RuntimeEnv env) { - return CelOptimizerFactory.standardCelOptimizerBuilder(env.cel) + private CelOptimizer newCseWithConstFoldingOptimizer(Cel cel) { + return CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers( ConstantFoldingOptimizer.getInstance(), SubexpressionOptimizer.newInstance(option)) .build(); diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java index 6e39bab28..e7387d7d8 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java @@ -26,7 +26,6 @@ import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; import dev.cel.bundle.CelBuilder; -import dev.cel.bundle.CelExperimentalFactory; import dev.cel.bundle.CelFactory; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; @@ -61,87 +60,80 @@ import dev.cel.runtime.CelUnknownSet; import dev.cel.runtime.PartialVars; import dev.cel.runtime.Program; +import dev.cel.testing.CelRuntimeFlavor; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public class SubexpressionOptimizerTest { - private enum RuntimeEnv { - LEGACY( - setupCelEnv(CelFactory.standardCelBuilder()), - setupCelForEvaluatingBlock(CelFactory.standardCelBuilder())), - PLANNER( - setupCelEnv(CelExperimentalFactory.plannerCelBuilder()), - setupCelForEvaluatingBlock(CelExperimentalFactory.plannerCelBuilder())); - - private final Cel cel; - private final Cel celForEvaluatingBlock; - - private static Cel setupCelEnv(CelBuilder celBuilder) { - return celBuilder - .addMessageTypes(TestAllTypes.getDescriptor()) - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .setOptions( - CelOptions.current() - .populateMacroCalls(true) - .enableHeterogeneousNumericComparisons(true) - .build()) - .addCompilerLibraries(CelExtensions.bindings(), CelExtensions.strings()) - .addRuntimeLibraries(CelExtensions.strings()) - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "non_pure_custom_func", - newGlobalOverload( - "non_pure_custom_func_overload", SimpleType.INT, SimpleType.INT))) - .addVar("x", SimpleType.DYN) - .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) - .build(); - } - - private static Cel setupCelForEvaluatingBlock(CelBuilder celBuilder) { - return celBuilder - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) - .addFunctionDeclarations( - // These are test only declarations, as the actual function is made internal using @ - // symbol. - // If the main function declaration needs updating, be sure to update the test - // declaration as well. - CelFunctionDecl.newFunctionDeclaration( - "cel.block", - CelOverloadDecl.newGlobalOverload( - "block_test_only_overload", - SimpleType.DYN, - ListType.create(SimpleType.DYN), - SimpleType.DYN)), - SubexpressionOptimizer.newCelBlockFunctionDecl(SimpleType.DYN), - CelFunctionDecl.newFunctionDeclaration( - "get_true", - CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) - // Similarly, this is a test only decl (index0 -> @index0) - .addVarDeclarations( - CelVarDecl.newVarDeclaration("c0", SimpleType.DYN), - CelVarDecl.newVarDeclaration("c1", SimpleType.DYN), - CelVarDecl.newVarDeclaration("index0", SimpleType.DYN), - CelVarDecl.newVarDeclaration("index1", SimpleType.DYN), - CelVarDecl.newVarDeclaration("index2", SimpleType.DYN), - CelVarDecl.newVarDeclaration("@index0", SimpleType.DYN), - CelVarDecl.newVarDeclaration("@index1", SimpleType.DYN), - CelVarDecl.newVarDeclaration("@index2", SimpleType.DYN)) - .addMessageTypes(TestAllTypes.getDescriptor()) - .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) - .build(); - } + private static Cel setupCelEnv(CelBuilder celBuilder) { + return celBuilder + .addMessageTypes(TestAllTypes.getDescriptor()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setOptions( + CelOptions.current() + .populateMacroCalls(true) + .enableHeterogeneousNumericComparisons(true) + .build()) + .addCompilerLibraries(CelExtensions.bindings(), CelExtensions.strings()) + .addRuntimeLibraries(CelExtensions.strings()) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "non_pure_custom_func", + newGlobalOverload("non_pure_custom_func_overload", SimpleType.INT, SimpleType.INT))) + .addVar("x", SimpleType.DYN) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .build(); + } - RuntimeEnv(Cel cel, Cel celForEvaluatingBlock) { - this.cel = cel; - this.celForEvaluatingBlock = celForEvaluatingBlock; - } + private static Cel setupCelForEvaluatingBlock(CelBuilder celBuilder) { + return celBuilder + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addFunctionDeclarations( + // These are test only declarations, as the actual function is made internal using @ + // symbol. + // If the main function declaration needs updating, be sure to update the test + // declaration as well. + CelFunctionDecl.newFunctionDeclaration( + "cel.block", + CelOverloadDecl.newGlobalOverload( + "block_test_only_overload", + SimpleType.DYN, + ListType.create(SimpleType.DYN), + SimpleType.DYN)), + SubexpressionOptimizer.newCelBlockFunctionDecl(SimpleType.DYN), + CelFunctionDecl.newFunctionDeclaration( + "get_true", + CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) + // Similarly, this is a test only decl (index0 -> @index0) + .addVarDeclarations( + CelVarDecl.newVarDeclaration("c0", SimpleType.DYN), + CelVarDecl.newVarDeclaration("c1", SimpleType.DYN), + CelVarDecl.newVarDeclaration("index0", SimpleType.DYN), + CelVarDecl.newVarDeclaration("index1", SimpleType.DYN), + CelVarDecl.newVarDeclaration("index2", SimpleType.DYN), + CelVarDecl.newVarDeclaration("@index0", SimpleType.DYN), + CelVarDecl.newVarDeclaration("@index1", SimpleType.DYN), + CelVarDecl.newVarDeclaration("@index2", SimpleType.DYN)) + .addMessageTypes(TestAllTypes.getDescriptor()) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .build(); } - @TestParameter RuntimeEnv runtimeEnv; + @TestParameter CelRuntimeFlavor runtimeFlavor; + + private Cel cel; + private Cel celForEvaluatingBlock; + + @Before + public void setUp() { + this.cel = setupCelEnv(runtimeFlavor.builder()); + this.celForEvaluatingBlock = setupCelForEvaluatingBlock(runtimeFlavor.builder()); + } private static final CelUnparser CEL_UNPARSER = CelUnparserFactory.newUnparser(); @@ -160,7 +152,7 @@ private static CelBuilder newCelBuilder() { } private CelOptimizer newCseOptimizer(SubexpressionOptimizerOptions options) { - return CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + return CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers(SubexpressionOptimizer.newInstance(options)) .build(); } @@ -174,21 +166,20 @@ public void cse_resultTypeSet_celBlockOptimizationSuccess() throws Exception { SubexpressionOptimizer.newInstance( SubexpressionOptimizerOptions.newBuilder().build())) .build(); - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile("size('a') + size('a') == 2").getAst(); + CelAbstractSyntaxTree ast = cel.compile("size('a') + size('a') == 2").getAst(); CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast); - assertThat(runtimeEnv.cel.createProgram(optimizedAst).eval()).isEqualTo(true); + assertThat(cel.createProgram(optimizedAst).eval()).isEqualTo(true); assertThat(CEL_UNPARSER.unparse(optimizedAst)) .isEqualTo("cel.@block([size(\"a\")], @index0 + @index0 == 2)"); } @Test public void cse_indexEvaluationErrors_throws() throws Exception { - CelAbstractSyntaxTree ast = - runtimeEnv.cel.compile("\"abc\".charAt(10) + \"abc\".charAt(10)").getAst(); + CelAbstractSyntaxTree ast = cel.compile("\"abc\".charAt(10) + \"abc\".charAt(10)").getAst(); CelOptimizer optimizedOptimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers(SubexpressionOptimizer.getInstance()) .build(); @@ -197,7 +188,7 @@ public void cse_indexEvaluationErrors_throws() throws Exception { String unparsed = CEL_UNPARSER.unparse(optimizedAst); assertThat(unparsed).isEqualTo("cel.@block([\"abc\".charAt(10)], @index0 + @index0)"); - Program program = runtimeEnv.cel.createProgram(optimizedAst); + Program program = cel.createProgram(optimizedAst); CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of())); assertThat(e).hasMessageThat().contains("charAt failure: Index out of range: 10"); @@ -205,9 +196,9 @@ public void cse_indexEvaluationErrors_throws() throws Exception { @Test public void cse_withUnknownAttributes() throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile("size(\"a\") == 1 ? x.y : x.y").getAst(); + CelAbstractSyntaxTree ast = cel.compile("size(\"a\") == 1 ? x.y : x.y").getAst(); CelOptimizer optimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers(SubexpressionOptimizer.getInstance()) .build(); @@ -217,9 +208,7 @@ public void cse_withUnknownAttributes() throws Exception { .isEqualTo("cel.@block([x.y], (size(\"a\") == 1) ? @index0 : @index0)"); Object result = - runtimeEnv - .cel - .createProgram(optimizedAst) + cel.createProgram(optimizedAst) .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x"))); assertThat(result).isInstanceOf(CelUnknownSet.class); } @@ -254,7 +243,7 @@ private enum CseNoOpTestCase { @Test public void cse_withCelBind_noop(@TestParameter CseNoOpTestCase testCase) throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(testCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(testCase.source).getAst(); CelAbstractSyntaxTree optimizedAst = newCseOptimizer(SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()) @@ -266,7 +255,7 @@ public void cse_withCelBind_noop(@TestParameter CseNoOpTestCase testCase) throws @Test public void cse_withCelBlock_noop(@TestParameter CseNoOpTestCase testCase) throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(testCase.source).getAst(); + CelAbstractSyntaxTree ast = cel.compile(testCase.source).getAst(); CelAbstractSyntaxTree optimizedAst = newCseOptimizer(SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()) @@ -279,7 +268,7 @@ public void cse_withCelBlock_noop(@TestParameter CseNoOpTestCase testCase) throw @Test public void cse_withComprehensionStructureRetained() throws Exception { CelAbstractSyntaxTree ast = - runtimeEnv.cel.compile("['foo'].map(x, [x+x]) + ['foo'].map(x, [x+x, x+x])").getAst(); + cel.compile("['foo'].map(x, [x+x]) + ['foo'].map(x, [x+x, x+x])").getAst(); CelOptimizer celOptimizer = newCseOptimizer( SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()); @@ -295,12 +284,10 @@ public void cse_withComprehensionStructureRetained() throws Exception { @Test public void cse_applyConstFoldingBefore() throws Exception { CelAbstractSyntaxTree ast = - runtimeEnv - .cel - .compile("size([1+1+1]) + size([1+1+1]) + size([1,1+1+1]) + size([1,1+1+1]) + x") + cel.compile("size([1+1+1]) + size([1+1+1]) + size([1,1+1+1]) + size([1,1+1+1]) + x") .getAst(); CelOptimizer optimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers( ConstantFoldingOptimizer.getInstance(), SubexpressionOptimizer.newInstance( @@ -315,12 +302,10 @@ public void cse_applyConstFoldingBefore() throws Exception { @Test public void cse_applyConstFoldingAfter() throws Exception { CelAbstractSyntaxTree ast = - runtimeEnv - .cel - .compile("size([1+1+1]) + size([1+1+1]) + size([1,1+1+1]) + size([1,1+1+1]) + x") + cel.compile("size([1+1+1]) + size([1+1+1]) + size([1,1+1+1]) + size([1,1+1+1]) + x") .getAst(); CelOptimizer optimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers( SubexpressionOptimizer.newInstance( SubexpressionOptimizerOptions.newBuilder().build()), @@ -335,9 +320,9 @@ public void cse_applyConstFoldingAfter() throws Exception { @Test public void cse_applyConstFoldingAfter_nothingToFold() throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile("size(x) + size(x)").getAst(); + CelAbstractSyntaxTree ast = cel.compile("size(x) + size(x)").getAst(); CelOptimizer optimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers( SubexpressionOptimizer.newInstance( SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()), @@ -360,7 +345,7 @@ public void iterationLimitReached_throws() throws Exception { largeExprBuilder.append("+"); } } - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile(largeExprBuilder.toString()).getAst(); + CelAbstractSyntaxTree ast = cel.compile(largeExprBuilder.toString()).getAst(); CelOptimizationException e = assertThrows( @@ -376,9 +361,9 @@ public void iterationLimitReached_throws() throws Exception { @Test public void celBlock_astExtensionTagged() throws Exception { - CelAbstractSyntaxTree ast = runtimeEnv.cel.compile("size(x) + size(x)").getAst(); + CelAbstractSyntaxTree ast = cel.compile("size(x) + size(x)").getAst(); CelOptimizer optimizer = - CelOptimizerFactory.standardCelOptimizerBuilder(runtimeEnv.cel) + CelOptimizerFactory.standardCelOptimizerBuilder(cel) .addAstOptimizers( SubexpressionOptimizer.newInstance( SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()), @@ -411,7 +396,20 @@ private enum BlockTestCase { public void block_success(@TestParameter BlockTestCase testCase) throws Exception { CelAbstractSyntaxTree ast = compileUsingInternalFunctions(testCase.source); - Object evaluatedResult = runtimeEnv.celForEvaluatingBlock.createProgram(ast).eval(); + Object evaluatedResult = celForEvaluatingBlock.createProgram(ast).eval(); + + assertThat(evaluatedResult).isNotNull(); + } + + @Test + public void block_success_parsedOnly(@TestParameter BlockTestCase testCase) throws Exception { + if (runtimeFlavor.equals(CelRuntimeFlavor.LEGACY)) { + return; + } + CelAbstractSyntaxTree ast = + compileUsingInternalFunctions(testCase.source, /* parsedOnly= */ true); + + Object evaluatedResult = celForEvaluatingBlock.createProgram(ast).eval(); assertThat(evaluatedResult).isNotNull(); } @@ -673,7 +671,7 @@ public void block_containsCycle_throws() throws Exception { CelAbstractSyntaxTree ast = compileUsingInternalFunctions("cel.block([index1,index0],index0)"); CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> runtimeEnv.cel.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); assertThat(e).hasMessageThat().contains("Cycle detected: @index0"); } @@ -684,7 +682,7 @@ public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Except "cel.block([1/0 > 0], (index0 && false) || (index0 && true))"); CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> runtimeEnv.cel.createProgram(ast).eval()); + assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); assertThat(e).hasMessageThat().contains("/ by zero"); assertThat(e).hasMessageThat().doesNotContain("Cycle detected"); @@ -694,10 +692,9 @@ public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Except * Converts AST containing cel.block related test functions to internal functions (e.g: cel.block * -> cel.@block) */ - private CelAbstractSyntaxTree compileUsingInternalFunctions(String expression) + private CelAbstractSyntaxTree compileUsingInternalFunctions(String expression, boolean parsedOnly) throws CelValidationException { - CelAbstractSyntaxTree astToModify = - runtimeEnv.celForEvaluatingBlock.compile(expression).getAst(); + CelAbstractSyntaxTree astToModify = celForEvaluatingBlock.compile(expression).getAst(); CelMutableAst mutableAst = CelMutableAst.fromCelAst(astToModify); CelNavigableMutableAst.fromAst(mutableAst) .getRoot() @@ -719,6 +716,14 @@ private CelAbstractSyntaxTree compileUsingInternalFunctions(String expression) indexExpr.ident().setName(internalIdentName); }); - return runtimeEnv.celForEvaluatingBlock.check(mutableAst.toParsedAst()).getAst(); + if (parsedOnly) { + return mutableAst.toParsedAst(); + } + return celForEvaluatingBlock.check(mutableAst.toParsedAst()).getAst(); + } + + private CelAbstractSyntaxTree compileUsingInternalFunctions(String expression) + throws CelValidationException { + return compileUsingInternalFunctions(expression, false); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index cb2ad5a82..824c918d8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -364,6 +364,7 @@ java_library( deps = [ ":activation_wrapper", ":planned_interpretable", + "//common/exceptions:runtime_exception", "//runtime:accumulated_unknowns", "//runtime:concatenated_list_view", "//runtime:evaluation_exception", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java index 2631bf0b9..2eb30671e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.runtime.AccumulatedUnknowns; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.ConcatenatedListView; @@ -77,8 +78,7 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval if (iterRangeRaw instanceof AccumulatedUnknowns) { return iterRangeRaw; } - Folder folder = new Folder(resolver, accuVar, iterVar, iterVar2); - folder.accuVal = maybeWrapAccumulator(accuInit.eval(folder, frame)); + Folder folder = new Folder(resolver, frame, accuInit, accuVar, iterVar, iterVar2); Object result; if (iterRangeRaw instanceof Map) { @@ -104,11 +104,14 @@ private Object evalMap(Map iterRange, Folder folder, ExecutionFrame frame) boolean cond = (boolean) condition.eval(folder, frame); if (!cond) { + folder.computeResult = true; return result.eval(folder, frame); } folder.accuVal = loopStep.eval(folder, frame); + folder.initialized = true; } + folder.computeResult = true; return result.eval(folder, frame); } @@ -127,12 +130,15 @@ private Object evalList(Collection iterRange, Folder folder, ExecutionFrame f boolean cond = (boolean) condition.eval(folder, frame); if (!cond) { + folder.computeResult = true; return result.eval(folder, frame); } folder.accuVal = loopStep.eval(folder, frame); + folder.initialized = true; index++; } + folder.computeResult = true; return result.eval(folder, frame); } @@ -155,6 +161,8 @@ private static Object maybeUnwrapAccumulator(Object val) { private static class Folder implements ActivationWrapper { private final GlobalResolver resolver; + private final ExecutionFrame frame; + private final PlannedInterpretable accuInit; private final String accuVar; private final String iterVar; private final String iterVar2; @@ -162,9 +170,19 @@ private static class Folder implements ActivationWrapper { private Object iterVarVal; private Object iterVar2Val; private Object accuVal; - - private Folder(GlobalResolver resolver, String accuVar, String iterVar, String iterVar2) { + private boolean initialized = false; + private boolean computeResult = false; + + private Folder( + GlobalResolver resolver, + ExecutionFrame frame, + PlannedInterpretable accuInit, + String accuVar, + String iterVar, + String iterVar2) { this.resolver = resolver; + this.frame = frame; + this.accuInit = accuInit; this.accuVar = accuVar; this.iterVar = iterVar; this.iterVar2 = iterVar2; @@ -183,18 +201,34 @@ public boolean isLocallyBound(String name) { @Override public @Nullable Object resolve(String name) { if (name.equals(accuVar)) { + if (!initialized) { + initialized = true; + try { + accuVal = maybeWrapAccumulator(accuInit.eval(resolver, frame)); + } catch (CelEvaluationException e) { + throw new LazyEvaluationRuntimeException(e); + } + } return accuVal; } - if (name.equals(iterVar)) { - return this.iterVarVal; - } + if (!computeResult) { + if (name.equals(iterVar)) { + return this.iterVarVal; + } - if (name.equals(iterVar2)) { - return this.iterVar2Val; + if (name.equals(iterVar2)) { + return this.iterVar2Val; + } } return resolver.resolve(name); } } + + private static class LazyEvaluationRuntimeException extends CelRuntimeException { + private LazyEvaluationRuntimeException(CelEvaluationException cause) { + super(cause, cause.getErrorCode()); + } + } } diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index 54ce24417..73492d126 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -149,9 +149,10 @@ public void toRuntimeBuilder_propertiesCopied() { assertThat(newRuntimeBuilder.standardFunctionBuilder.build()) .containsExactly(intFunction, equalsOperator) .inOrder(); - assertThat(newRuntimeBuilder.customFunctionBindings).hasSize(2); + assertThat(newRuntimeBuilder.customFunctionBindings).hasSize(3); assertThat(newRuntimeBuilder.customFunctionBindings).containsKey("string_isEmpty"); assertThat(newRuntimeBuilder.customFunctionBindings).containsKey("list_sets_intersects_list"); + assertThat(newRuntimeBuilder.customFunctionBindings).containsKey("sets.intersects"); } @Test diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index c1b2a92b4..b9e68f003 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -11,6 +11,11 @@ java_library( exports = ["//testing/src/main/java/dev/cel/testing:adorner"], ) +java_library( + name = "cel_runtime_flavor", + exports = ["//testing/src/main/java/dev/cel/testing:cel_runtime_flavor"], +) + java_library( name = "line_differ", exports = ["//testing/src/main/java/dev/cel/testing:line_differ"], diff --git a/testing/src/main/java/dev/cel/testing/BUILD.bazel b/testing/src/main/java/dev/cel/testing/BUILD.bazel index 5ee142200..b52026ec4 100644 --- a/testing/src/main/java/dev/cel/testing/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/BUILD.bazel @@ -105,3 +105,14 @@ java_library( "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) + +java_library( + name = "cel_runtime_flavor", + srcs = ["CelRuntimeFlavor.java"], + tags = [ + ], + deps = [ + "//bundle:cel", + "//bundle:cel_experimental_factory", + ], +) diff --git a/testing/src/main/java/dev/cel/testing/CelRuntimeFlavor.java b/testing/src/main/java/dev/cel/testing/CelRuntimeFlavor.java new file mode 100644 index 000000000..576e0c1d3 --- /dev/null +++ b/testing/src/main/java/dev/cel/testing/CelRuntimeFlavor.java @@ -0,0 +1,38 @@ +// Copyright 2026 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 +// +// https://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 dev.cel.testing; + +import dev.cel.bundle.CelBuilder; +import dev.cel.bundle.CelExperimentalFactory; +import dev.cel.bundle.CelFactory; + +/** Enumeration of supported CEL runtime environments for testing. */ +public enum CelRuntimeFlavor { + LEGACY { + @Override + public CelBuilder builder() { + return CelFactory.standardCelBuilder(); + } + }, + PLANNER { + @Override + public CelBuilder builder() { + return CelExperimentalFactory.plannerCelBuilder(); + } + }; + + /** Returns a new {@link CelBuilder} instance for this runtime flavor. */ + public abstract CelBuilder builder(); +}