diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000..d7ed7627e --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +codelab diff --git a/codelab/BUILD.bazel b/codelab/BUILD.bazel new file mode 100644 index 000000000..eea160dc4 --- /dev/null +++ b/codelab/BUILD.bazel @@ -0,0 +1,14 @@ +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//codelab:__subpackages__"], +) + +java_library( + name = "codelab", + exports = ["//codelab/src/main/codelab"], +) + +java_library( + name = "solutions", + exports = ["//codelab/src/main/codelab/solutions"], +) diff --git a/codelab/src/main/codelab/BUILD.bazel b/codelab/src/main/codelab/BUILD.bazel new file mode 100644 index 000000000..2a4be0843 --- /dev/null +++ b/codelab/src/main/codelab/BUILD.bazel @@ -0,0 +1,23 @@ +package( + default_applicable_licenses = [ + "//:license", + ], + default_visibility = ["//codelab:__pkg__"], +) + +java_library( + name = "codelab", + srcs = glob(["*.java"]), + deps = [ + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", # unuseddeps: keep + "@maven//:com_google_guava_guava", # unuseddeps: keep + "//common", # unuseddeps: keep + "//common:compiler_common", # unuseddeps: keep + "//common/types", # unuseddeps: keep + "//common/types:type_providers", # unuseddeps: keep + "//compiler", # unuseddeps: keep + "//compiler:compiler_builder", # unuseddeps: keep + "//runtime", # unuseddeps: keep + # + ], +) diff --git a/codelab/src/main/codelab/Exercise1.java b/codelab/src/main/codelab/Exercise1.java new file mode 100644 index 000000000..dfa80fe51 --- /dev/null +++ b/codelab/src/main/codelab/Exercise1.java @@ -0,0 +1,46 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import dev.cel.common.CelAbstractSyntaxTree; + +/** + * Exercise1 evaluates a simple literal expression: "Hello, World!" + * + *

Compile, eval, profit! + */ +final class Exercise1 { + /** + * Compile the input {@code expression} and produce an AST. This method parses and type-checks the + * given expression to validate the syntax and type-agreement of the expression. + * + * @throws IllegalArgumentException If the expression is malformed due to syntactic or semantic + * errors. + */ + @SuppressWarnings("DoNotCallSuggester") + CelAbstractSyntaxTree compile(String expression) { + throw new UnsupportedOperationException("To be implemented"); + } + + /** + * Evaluates the compiled AST. + * + * @throws IllegalArgumentException If the compiled expression in AST fails to evaluate. + */ + @SuppressWarnings("DoNotCallSuggester") + Object eval(CelAbstractSyntaxTree ast) { + throw new UnsupportedOperationException("To be implemented"); + } +} diff --git a/codelab/src/main/codelab/Exercise2.java b/codelab/src/main/codelab/Exercise2.java new file mode 100644 index 000000000..2720e995e --- /dev/null +++ b/codelab/src/main/codelab/Exercise2.java @@ -0,0 +1,52 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.types.CelType; +import java.util.Map; + +/** + * Exercise2 shows how to declare and use variables in expressions through two examples: + * + *

+ */ +final class Exercise2 { + + /** + * Compiles the input expression with provided variable information. + * + * @throws IllegalArgumentException If the expression is malformed due to syntactic or semantic + * errors. + */ + @SuppressWarnings("DoNotCallSuggester") + CelAbstractSyntaxTree compile(String expression, String variableName, CelType variableType) { + throw new UnsupportedOperationException("To be implemented"); + } + + /** + * Evaluates the compiled AST with the user provided parameter values. + * + * @throws IllegalArgumentException If the compiled expression in AST fails to evaluate. + */ + @SuppressWarnings("DoNotCallSuggester") + Object eval(CelAbstractSyntaxTree ast, Map parameterValues) { + throw new UnsupportedOperationException("To be implemented"); + } +} diff --git a/codelab/src/main/codelab/Exercise3.java b/codelab/src/main/codelab/Exercise3.java new file mode 100644 index 000000000..77b57f339 --- /dev/null +++ b/codelab/src/main/codelab/Exercise3.java @@ -0,0 +1,53 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.SimpleType; +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; + +final class Exercise3 { + + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder().setResultType(SimpleType.BOOL).build(); + private static final CelRuntime CEL_RUNTIME = + CelRuntimeFactory.standardCelRuntimeBuilder().build(); + + /** + * Compiles the given expression and evaluates it. + * + * @throws IllegalArgumentException If the expression fails to compile or evaluate + */ + Object compileAndEvaluate(String expression) { + CelAbstractSyntaxTree ast; + try { + ast = CEL_COMPILER.compile(expression).getAst(); + } catch (CelValidationException e) { + throw new IllegalArgumentException("Failed to compile expression.", e); + } + + try { + CelRuntime.Program program = CEL_RUNTIME.createProgram(ast); + return program.eval(); + } catch (CelEvaluationException e) { + throw new IllegalArgumentException("Evaluation error has occurred.", e); + } + } +} diff --git a/codelab/src/main/codelab/Exercise4.java b/codelab/src/main/codelab/Exercise4.java new file mode 100644 index 000000000..df4d3ab1c --- /dev/null +++ b/codelab/src/main/codelab/Exercise4.java @@ -0,0 +1,77 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import com.google.rpc.context.AttributeContext.Request; +import dev.cel.common.CelAbstractSyntaxTree; +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.runtime.CelEvaluationException; +import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeFactory; +import java.util.Map; + +/** + * Exercise4 demonstrates how to extend CEL with custom functions. + * + *

Declare a "contains" member function on map types that return a boolean indicating whether the + * map contains the key-value pair. + */ +final class Exercise4 { + + /** + * Compiles the input expression. + * + * @throws IllegalArgumentException If the expression is malformed due to syntactic or semantic + * errors. + */ + CelAbstractSyntaxTree compile(String expression) { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("request", StructTypeReference.create(Request.getDescriptor().getFullName())) + .addMessageTypes(Request.getDescriptor()) + .setResultType(SimpleType.BOOL) + // Provide the custom `contains` function declaration here. + .build(); + + try { + return celCompiler.compile(expression).getAst(); + } catch (CelValidationException e) { + throw new IllegalArgumentException("Failed to compile expression.", e); + } + } + + /** + * Evaluates the compiled AST with the user provided parameter values. + * + * @throws IllegalArgumentException If the compiled expression in AST fails to evaluate. + */ + Object eval(CelAbstractSyntaxTree ast, Map parameterValues) { + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + // Provide the custom `contains` function implementation here. + .build(); + + try { + CelRuntime.Program program = celRuntime.createProgram(ast); + return program.eval(parameterValues); + } catch (CelEvaluationException e) { + throw new IllegalArgumentException("Evaluation error has occurred.", e); + } + } +} diff --git a/codelab/src/main/codelab/solutions/BUILD.bazel b/codelab/src/main/codelab/solutions/BUILD.bazel new file mode 100644 index 000000000..9a46187bd --- /dev/null +++ b/codelab/src/main/codelab/solutions/BUILD.bazel @@ -0,0 +1,22 @@ +package( + default_applicable_licenses = [ + "//:license", + ], + default_visibility = ["//codelab:__pkg__"], +) + +java_library( + name = "solutions", + srcs = glob(["*.java"]), + deps = [ + "//common", + "//common:compiler_common", + "//common/types", + "//common/types:type_providers", + "//compiler", + "//compiler:compiler_builder", + "//runtime", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + ], +) diff --git a/codelab/src/main/codelab/solutions/Exercise1.java b/codelab/src/main/codelab/solutions/Exercise1.java new file mode 100644 index 000000000..0807a1931 --- /dev/null +++ b/codelab/src/main/codelab/solutions/Exercise1.java @@ -0,0 +1,90 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.SimpleType; +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; + +/** + * Exercise1 evaluates a simple literal expression: "Hello, World!" + * + *

Compile, eval, profit! + */ +final class Exercise1 { + /** + * Compile the input {@code expression} and produce an AST. This method parses and type-checks the + * given expression to validate the syntax and type-agreement of the expression. + * + * @throws IllegalArgumentException If the expression is malformed due to syntactic or semantic + * errors. + */ + CelAbstractSyntaxTree compile(String expression) { + // Construct a CelCompiler instance. + // "String" is the expected output type for the type-checked expression + // CelCompiler is immutable and when statically configured can be moved to a static final + // member. + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder().setResultType(SimpleType.STRING).build(); + + CelAbstractSyntaxTree ast; + try { + // Parse the expression + ast = celCompiler.parse(expression).getAst(); + } catch (CelValidationException e) { + // Report syntactic errors, if present + throw new IllegalArgumentException( + "Failed to parse expression. Reason: " + e.getMessage(), e); + } + + try { + // Type-check the expression for correctness + ast = celCompiler.check(ast).getAst(); + } catch (CelValidationException e) { + // Report semantic errors, if present. + throw new IllegalArgumentException( + "Failed to type-check expression. Reason: " + e.getMessage(), e); + } + return ast; + } + + /** + * Evaluates the compiled AST. + * + * @throws IllegalArgumentException If the compiled expression in AST fails to evaluate. + */ + Object eval(CelAbstractSyntaxTree ast) { + // Construct a CelRuntime instance + // CelRuntime is immutable just like the compiler and can be moved to a static final member. + CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build(); + + try { + // Plan the program + CelRuntime.Program program = celRuntime.createProgram(ast); + + // Evaluate the program without any additional arguments. + return program.eval(); + } catch (CelEvaluationException e) { + // Report any evaluation errors, if present + throw new IllegalArgumentException( + "Evaluation error has occurred. Reason: " + e.getMessage(), e); + } + } +} diff --git a/codelab/src/main/codelab/solutions/Exercise2.java b/codelab/src/main/codelab/solutions/Exercise2.java new file mode 100644 index 000000000..5a1c1e8cc --- /dev/null +++ b/codelab/src/main/codelab/solutions/Exercise2.java @@ -0,0 +1,82 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import com.google.rpc.context.AttributeContext; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.CelType; +import dev.cel.common.types.SimpleType; +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 java.util.Map; + +/** + * Exercise2 shows how to declare and use variables in expressions through two examples: + * + *

+ */ +final class Exercise2 { + + /** + * Compiles the input expression with provided variable information. + * + * @throws IllegalArgumentException If the expression is malformed due to syntactic or semantic + * errors. + */ + CelAbstractSyntaxTree compile(String expression, String variableName, CelType variableType) { + // Compile (parse + type-check) the expression + // CelCompiler is immutable and when statically configured can be moved to a static final + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar(variableName, variableType) + .addMessageTypes(AttributeContext.Request.getDescriptor()) + .setResultType(SimpleType.BOOL) + .build(); + try { + return celCompiler.compile(expression).getAst(); + } catch (CelValidationException e) { + throw new IllegalArgumentException( + "Failed to compile expression. Reason: " + e.getMessage(), e); + } + } + + /** + * Evaluates the compiled AST with the user provided parameter values. + * + * @throws IllegalArgumentException If the compiled expression in AST fails to evaluate. + */ + Object eval(CelAbstractSyntaxTree ast, Map parameterValues) { + CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build(); + try { + CelRuntime.Program program = celRuntime.createProgram(ast); + + // Evaluate using the provided map as input variables (key: variableName, value: + // variableValue) + return program.eval(parameterValues); + } catch (CelEvaluationException e) { + // Report any evaluation errors, if present + throw new IllegalArgumentException( + "Evaluation error has occurred. Reason: " + e.getMessage(), e); + } + } +} diff --git a/codelab/src/main/codelab/solutions/Exercise3.java b/codelab/src/main/codelab/solutions/Exercise3.java new file mode 100644 index 000000000..590dfd0df --- /dev/null +++ b/codelab/src/main/codelab/solutions/Exercise3.java @@ -0,0 +1,53 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.SimpleType; +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; + +final class Exercise3 { + + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder().setResultType(SimpleType.BOOL).build(); + private static final CelRuntime CEL_RUNTIME = + CelRuntimeFactory.standardCelRuntimeBuilder().build(); + + /** + * Compiles the given expression and evaluates it. + * + * @throws IllegalArgumentException If the expression fails to compile or evaluate + */ + Object compileAndEvaluate(String expression) { + CelAbstractSyntaxTree ast; + try { + ast = CEL_COMPILER.compile(expression).getAst(); + } catch (CelValidationException e) { + throw new IllegalArgumentException("Failed to compile expression.", e); + } + + try { + CelRuntime.Program program = CEL_RUNTIME.createProgram(ast); + return program.eval(); + } catch (CelEvaluationException e) { + throw new IllegalArgumentException("Evaluation error has occurred.", e); + } + } +} diff --git a/codelab/src/main/codelab/solutions/Exercise4.java b/codelab/src/main/codelab/solutions/Exercise4.java new file mode 100644 index 000000000..33366a1e4 --- /dev/null +++ b/codelab/src/main/codelab/solutions/Exercise4.java @@ -0,0 +1,122 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration; +import static dev.cel.common.CelOverloadDecl.newMemberOverload; + +import com.google.common.collect.ImmutableList; +import com.google.rpc.context.AttributeContext.Request; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.MapType; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import dev.cel.common.types.TypeParamType; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntime.CelFunctionBinding; +import dev.cel.runtime.CelRuntimeFactory; +import java.util.Map; + +/** + * Exercise4 demonstrates how to extend CEL with custom functions. + * + *

Declare a "contains" member function on map types that return a boolean indicating whether the + * map contains the key-value pair. + */ +final class Exercise4 { + + /** + * Compiles the input expression. + * + * @throws IllegalArgumentException If the expression is malformed due to syntactic or semantic + * errors. + */ + CelAbstractSyntaxTree compile(String expression) { + // Useful components of the type-signature for 'contains'. + TypeParamType typeParamA = TypeParamType.create("A"); + TypeParamType typeParamB = TypeParamType.create("B"); + MapType mapTypeAB = MapType.create(typeParamA, typeParamB); + + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("request", StructTypeReference.create(Request.getDescriptor().getFullName())) + .addMessageTypes(Request.getDescriptor()) + .setResultType(SimpleType.BOOL) + .addFunctionDeclarations( + newFunctionDeclaration( + "contains", + newMemberOverload( + "map_contains_key_value", + SimpleType.BOOL, + ImmutableList.of(mapTypeAB, typeParamA, typeParamB)))) + .build(); + + try { + return celCompiler.compile(expression).getAst(); + } catch (CelValidationException e) { + throw new IllegalArgumentException("Failed to compile expression.", e); + } + } + + /** + * Evaluates the compiled AST with the user provided parameter values. + * + * @throws IllegalArgumentException If the compiled expression in AST fails to evaluate. + */ + Object eval(CelAbstractSyntaxTree ast, Map parameterValues) { + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addFunctionBindings( + CelFunctionBinding.from( + "map_contains_key_value", + ImmutableList.of(Map.class, String.class, Object.class), + Exercise4::mapContainsKeyValue)) + .build(); + + try { + CelRuntime.Program program = celRuntime.createProgram(ast); + return program.eval(parameterValues); + } catch (CelEvaluationException e) { + throw new IllegalArgumentException("Evaluation error has occurred.", e); + } + } + + /** + * mapContainsKeyValue implements the custom function: map.contains(key, value) -> bool. + * + * @param args, where: + *

    + *
  1. args[0] is the map + *
  2. args[1] is the key + *
  3. args[2] is the value at the key + *
+ * + * @return true If the key was found AND the value at the key equals to the value being checked + */ + @SuppressWarnings("unchecked") // Type-checker guarantees casting safety. + private static boolean mapContainsKeyValue(Object[] args) { + // The declaration of the function ensures that only arguments which match + // the mapContainsKey signature will be provided to the function. + Map map = (Map) args[0]; + String key = (String) args[1]; + Object value = args[2]; + + return map.containsKey(key) && map.containsValue(value); + } +} diff --git a/codelab/src/test/codelab/BUILD.bazel b/codelab/src/test/codelab/BUILD.bazel new file mode 100644 index 000000000..301330ec3 --- /dev/null +++ b/codelab/src/test/codelab/BUILD.bazel @@ -0,0 +1,72 @@ +package(default_applicable_licenses = [ + "//:license", +]) + +java_test( + name = "Exercise1Test", + srcs = ["Exercise1Test.java"], + tags = ["notap"], + test_class = "codelab.Exercise1Test", + deps = [ + "//:java_truth", + "//codelab", + "//common", + "//compiler", + "//compiler:compiler_builder", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Exercise2Test", + srcs = ["Exercise2Test.java"], + tags = ["notap"], + test_class = "codelab.Exercise2Test", + deps = [ + "//:java_truth", + "//codelab", + "//common", + "//common/types", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Exercise3Test", + srcs = ["Exercise3Test.java"], + tags = ["notap"], + test_class = "codelab.Exercise3Test", + deps = [ + "//:java_truth", + "//codelab", + "//runtime", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Exercise4Test", + srcs = ["Exercise4Test.java"], + tags = ["notap"], + test_class = "codelab.Exercise4Test", + deps = [ + "//:java_truth", + "//codelab", + "//common", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +test_suite( + name = "exercise_test_suite", + tags = ["notap"], +) diff --git a/codelab/src/test/codelab/Exercise1Test.java b/codelab/src/test/codelab/Exercise1Test.java new file mode 100644 index 000000000..6d7a16948 --- /dev/null +++ b/codelab/src/test/codelab/Exercise1Test.java @@ -0,0 +1,73 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class Exercise1Test { + private static final String TEST_EXPRESSION = "'Hello, World!'"; + + private final Exercise1 exercise1 = new Exercise1(); + + @Test + public void compile_stringExpression_parseSuccess() { + CelAbstractSyntaxTree ast = exercise1.compile(TEST_EXPRESSION); + assertThat(ast).isNotNull(); + } + + @Test + public void compile_stringExpression_typecheckSuccess() { + CelAbstractSyntaxTree ast = exercise1.compile(TEST_EXPRESSION); + assertThat(ast.isChecked()).isTrue(); + } + + @Test + public void compile_booleanExpression_throwsValidationException() { + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> exercise1.compile("true == true")); + + assertThat(exception).hasMessageThat().contains("Failed to type-check expression."); + assertThat(exception).hasMessageThat().contains("expected type 'string' but found 'bool'"); + } + + @Test + public void evaluate_stringExpression_success() { + CelAbstractSyntaxTree ast = exercise1.compile(TEST_EXPRESSION); + + Object evaluatedResult = exercise1.eval(ast); + + assertThat(evaluatedResult).isEqualTo("Hello, World!"); + } + + @Test + public void evaluate_divideByZeroExpression_throwsEvaluationException() throws Exception { + CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); + CelAbstractSyntaxTree ast = compiler.compile("1/0").getAst(); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> exercise1.eval(ast)); + + assertThat(exception).hasMessageThat().contains("evaluation error: / by zero"); + } +} diff --git a/codelab/src/test/codelab/Exercise2Test.java b/codelab/src/test/codelab/Exercise2Test.java new file mode 100644 index 000000000..24bf28e6b --- /dev/null +++ b/codelab/src/test/codelab/Exercise2Test.java @@ -0,0 +1,67 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.google.rpc.context.AttributeContext; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class Exercise2Test { + private final Exercise2 exercise2 = new Exercise2(); + + @Test + @TestParameters("{value: 5, expectedResult: false}") + @TestParameters("{value: -5, expectedResult: true}") + public void evaluate_negativeExpression(long value, boolean expectedResult) { + CelAbstractSyntaxTree ast = exercise2.compile("value < 0", "value", SimpleType.INT); + + Object evaluatedResult = exercise2.eval(ast, ImmutableMap.of("value", value)); + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{group: 'admin', expectedResult: true}") + @TestParameters("{group: 'users', expectedResult: false}") + public void evaluate_requestAuthorization(String group, boolean expectedResult) { + CelAbstractSyntaxTree ast = + exercise2.compile( + "request.auth.claims.group == 'admin'", + "request", + StructTypeReference.create("google.rpc.context.AttributeContext.Request")); + + AttributeContext.Auth auth = + AttributeContext.Auth.newBuilder() + .setPrincipal("user:me@acme.co") + .setClaims( + Struct.newBuilder() + .putFields("group", Value.newBuilder().setStringValue(group).build())) + .build(); + AttributeContext.Request request = AttributeContext.Request.newBuilder().setAuth(auth).build(); + + Object evaluatedResult = exercise2.eval(ast, ImmutableMap.of("request", request)); + assertThat(evaluatedResult).isEqualTo(expectedResult); + } +} diff --git a/codelab/src/test/codelab/Exercise3Test.java b/codelab/src/test/codelab/Exercise3Test.java new file mode 100644 index 000000000..9c62d9ad5 --- /dev/null +++ b/codelab/src/test/codelab/Exercise3Test.java @@ -0,0 +1,103 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.runtime.CelEvaluationException; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class Exercise3Test { + private final Exercise3 exercise3 = new Exercise3(); + + /** + * Demonstrates CEL's unique feature of commutative logical operators. + * + *

If a logical operation can short-circuit a branch that results in an error, CEL evaluation + * will return the logical result instead of propagating the error. + */ + @Test + @TestParameters("{expression: 'true || true', expectedResult: true}") + @TestParameters("{expression: 'true || false', expectedResult: true}") + @TestParameters("{expression: 'false || true', expectedResult: true}") + @TestParameters("{expression: 'false || false', expectedResult: false}") + @TestParameters("{expression: 'true || (1 / 0 > 2)', expectedResult: true}") + @TestParameters("{expression: '(1 / 0 > 2) || true', expectedResult: true}") + public void evaluate_logicalOrShortCircuits_success(String expression, boolean expectedResult) { + Object evaluatedResult = exercise3.compileAndEvaluate(expression); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + /** Demonstrates a case where an error is surfaced to the user. */ + @Test + @TestParameters("{expression: 'false || (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) || false'}") + public void evaluate_logicalOrFailure_throwsException(String expression) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> exercise3.compileAndEvaluate(expression)); + + assertThat(exception).hasMessageThat().contains("Evaluation error has occurred."); + assertThat(exception).hasCauseThat().isInstanceOf(CelEvaluationException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("evaluation error: / by zero"); + } + + @Test + @TestParameters("{expression: 'todo: Fill me in using logical AND! (&&)', expectedResult: true}") + public void evaluate_logicalAndShortCircuits_success(String expression, boolean expectedResult) { + Object evaluatedResult = exercise3.compileAndEvaluate(expression); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: 'todo: Fill me in using logical AND! (&&)'}") + public void evaluate_logicalAndFailure_throwsException(String expression) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> exercise3.compileAndEvaluate(expression)); + + assertThat(exception).hasMessageThat().contains("Evaluation error has occurred."); + assertThat(exception).hasCauseThat().isInstanceOf(CelEvaluationException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("evaluation error: / by zero"); + } + + @Test + @TestParameters("{expression: 'false ? (1 / 0) > 2 : false', expectedResult: false}") + @TestParameters("{expression: 'todo: Fill me in using ternary! A ? B : C', expectedResult: true}") + public void evaluate_ternaryShortCircuits_success(String expression, boolean expectedResult) { + Object evaluatedResult = exercise3.compileAndEvaluate(expression); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: 'todo: Fill me in using ternary! A ? B : C'}") + public void evaluate_ternaryFailure_throwsException(String expression) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> exercise3.compileAndEvaluate(expression)); + + assertThat(exception).hasMessageThat().contains("Evaluation error has occurred."); + assertThat(exception).hasCauseThat().isInstanceOf(CelEvaluationException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("evaluation error: / by zero"); + } +} diff --git a/codelab/src/test/codelab/Exercise4Test.java b/codelab/src/test/codelab/Exercise4Test.java new file mode 100644 index 000000000..dc6d4524e --- /dev/null +++ b/codelab/src/test/codelab/Exercise4Test.java @@ -0,0 +1,51 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.google.rpc.context.AttributeContext; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.common.CelAbstractSyntaxTree; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class Exercise4Test { + private final Exercise4 exercise4 = new Exercise4(); + + @Test + @TestParameters("{group: 'admin', expectedResult: true}") + @TestParameters("{group: 'users', expectedResult: false}") + public void evaluate_requestContainsGroup_success(String group, boolean expectedResult) { + CelAbstractSyntaxTree ast = exercise4.compile("request.auth.claims.contains('group', 'admin')"); + AttributeContext.Auth auth = + AttributeContext.Auth.newBuilder() + .setPrincipal("user:me@acme.co") + .setClaims( + Struct.newBuilder() + .putFields("group", Value.newBuilder().setStringValue(group).build())) + .build(); + AttributeContext.Request request = AttributeContext.Request.newBuilder().setAuth(auth).build(); + + Object evaluatedResult = exercise4.eval(ast, ImmutableMap.of("request", request)); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } +} diff --git a/codelab/src/test/codelab/solutions/BUILD.bazel b/codelab/src/test/codelab/solutions/BUILD.bazel new file mode 100644 index 000000000..72faee51f --- /dev/null +++ b/codelab/src/test/codelab/solutions/BUILD.bazel @@ -0,0 +1,63 @@ +package(default_applicable_licenses = [ + "//:license", +]) + +java_test( + name = "Exercise1Test", + srcs = ["Exercise1Test.java"], + test_class = "codelab.solutions.Exercise1Test", + deps = [ + "//:java_truth", + "//codelab:solutions", + "//common", + "//compiler", + "//compiler:compiler_builder", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Exercise2Test", + srcs = ["Exercise2Test.java"], + test_class = "codelab.solutions.Exercise2Test", + deps = [ + "//:java_truth", + "//codelab:solutions", + "//common", + "//common/types", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Exercise3Test", + srcs = ["Exercise3Test.java"], + test_class = "codelab.solutions.Exercise3Test", + deps = [ + "//:java_truth", + "//codelab:solutions", + "//runtime", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Exercise4Test", + srcs = ["Exercise4Test.java"], + test_class = "codelab.solutions.Exercise4Test", + deps = [ + "//:java_truth", + "//codelab:solutions", + "//common", + "@com_google_googleapis//google/rpc/context:attribute_context_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) diff --git a/codelab/src/test/codelab/solutions/Exercise1Test.java b/codelab/src/test/codelab/solutions/Exercise1Test.java new file mode 100644 index 000000000..8b01690d9 --- /dev/null +++ b/codelab/src/test/codelab/solutions/Exercise1Test.java @@ -0,0 +1,73 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class Exercise1Test { + private static final String TEST_EXPRESSION = "'Hello, World!'"; + + private final Exercise1 exercise1 = new Exercise1(); + + @Test + public void compile_stringExpression_parseSuccess() { + CelAbstractSyntaxTree ast = exercise1.compile(TEST_EXPRESSION); + assertThat(ast).isNotNull(); + } + + @Test + public void compile_stringExpression_typecheckSuccess() { + CelAbstractSyntaxTree ast = exercise1.compile(TEST_EXPRESSION); + assertThat(ast.isChecked()).isTrue(); + } + + @Test + public void compile_booleanExpression_throwsValidationException() { + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> exercise1.compile("true == true")); + + assertThat(exception).hasMessageThat().contains("Failed to type-check expression."); + assertThat(exception).hasMessageThat().contains("expected type 'string' but found 'bool'"); + } + + @Test + public void evaluate_stringExpression_success() { + CelAbstractSyntaxTree ast = exercise1.compile(TEST_EXPRESSION); + + Object evaluatedResult = exercise1.eval(ast); + + assertThat(evaluatedResult).isEqualTo("Hello, World!"); + } + + @Test + public void evaluate_divideByZeroExpression_throwsEvaluationException() throws Exception { + CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); + CelAbstractSyntaxTree ast = compiler.compile("1/0").getAst(); + + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> exercise1.eval(ast)); + + assertThat(exception).hasMessageThat().contains("evaluation error: / by zero"); + } +} diff --git a/codelab/src/test/codelab/solutions/Exercise2Test.java b/codelab/src/test/codelab/solutions/Exercise2Test.java new file mode 100644 index 000000000..0c0cb4f9f --- /dev/null +++ b/codelab/src/test/codelab/solutions/Exercise2Test.java @@ -0,0 +1,67 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.google.rpc.context.AttributeContext; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class Exercise2Test { + private final Exercise2 exercise2 = new Exercise2(); + + @Test + @TestParameters("{value: 5, expectedResult: false}") + @TestParameters("{value: -5, expectedResult: true}") + public void evaluate_negativeExpression(long value, boolean expectedResult) { + CelAbstractSyntaxTree ast = exercise2.compile("value < 0", "value", SimpleType.INT); + + Object evaluatedResult = exercise2.eval(ast, ImmutableMap.of("value", value)); + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{group: 'admin', expectedResult: true}") + @TestParameters("{group: 'users', expectedResult: false}") + public void evaluate_requestAuthorization(String group, boolean expectedResult) { + CelAbstractSyntaxTree ast = + exercise2.compile( + "request.auth.claims.group == 'admin'", + "request", + StructTypeReference.create("google.rpc.context.AttributeContext.Request")); + + AttributeContext.Auth auth = + AttributeContext.Auth.newBuilder() + .setPrincipal("user:me@acme.co") + .setClaims( + Struct.newBuilder() + .putFields("group", Value.newBuilder().setStringValue(group).build())) + .build(); + AttributeContext.Request request = AttributeContext.Request.newBuilder().setAuth(auth).build(); + + Object evaluatedResult = exercise2.eval(ast, ImmutableMap.of("request", request)); + assertThat(evaluatedResult).isEqualTo(expectedResult); + } +} diff --git a/codelab/src/test/codelab/solutions/Exercise3Test.java b/codelab/src/test/codelab/solutions/Exercise3Test.java new file mode 100644 index 000000000..47d1c3470 --- /dev/null +++ b/codelab/src/test/codelab/solutions/Exercise3Test.java @@ -0,0 +1,113 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.runtime.CelEvaluationException; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class Exercise3Test { + private final Exercise3 exercise3 = new Exercise3(); + + /** + * Demonstrates CEL's unique feature of commutative logical operators. + * + *

If a logical operation can short-circuit a branch that results in an error, CEL evaluation + * will return the logical result instead of propagating the error. + */ + @Test + @TestParameters("{expression: 'true || true', expectedResult: true}") + @TestParameters("{expression: 'true || false', expectedResult: true}") + @TestParameters("{expression: 'false || true', expectedResult: true}") + @TestParameters("{expression: 'false || false', expectedResult: false}") + @TestParameters("{expression: 'true || (1 / 0 > 2)', expectedResult: true}") + @TestParameters("{expression: '(1 / 0 > 2) || true', expectedResult: true}") + public void evaluate_logicalOrShortCircuits_success(String expression, boolean expectedResult) { + Object evaluatedResult = exercise3.compileAndEvaluate(expression); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + /** Demonstrates a case where an error is surfaced to the user. */ + @Test + @TestParameters("{expression: 'false || (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) || false'}") + public void evaluate_logicalOrFailure_throwsException(String expression) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> exercise3.compileAndEvaluate(expression)); + + assertThat(exception).hasMessageThat().contains("Evaluation error has occurred."); + assertThat(exception).hasCauseThat().isInstanceOf(CelEvaluationException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("evaluation error: / by zero"); + } + + @Test + @TestParameters("{expression: 'true && true', expectedResult: true}") + @TestParameters("{expression: 'true && false', expectedResult: false}") + @TestParameters("{expression: 'false && true', expectedResult: false}") + @TestParameters("{expression: 'false && false', expectedResult: false}") + @TestParameters("{expression: 'false && (1 / 0 > 2)', expectedResult: false}") + @TestParameters("{expression: '(1 / 0 > 2) && false', expectedResult: false}") + public void evaluate_logicalAndShortCircuits_success(String expression, boolean expectedResult) { + Object evaluatedResult = exercise3.compileAndEvaluate(expression); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: 'true && (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) && true'}") + public void evaluate_logicalAndFailure_throwsException(String expression) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> exercise3.compileAndEvaluate(expression)); + + assertThat(exception).hasMessageThat().contains("Evaluation error has occurred."); + assertThat(exception).hasCauseThat().isInstanceOf(CelEvaluationException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("evaluation error: / by zero"); + } + + @Test + @TestParameters("{expression: 'false ? (1 / 0) > 2 : false', expectedResult: false}") + @TestParameters("{expression: 'false ? (1 / 0) > 2 : true', expectedResult: true}") + @TestParameters("{expression: 'true ? false : (1 / 0) > 2', expectedResult: false}") + @TestParameters("{expression: 'true ? true : (1 / 0) > 2', expectedResult: true}") + public void evaluate_ternaryShortCircuits_success(String expression, boolean expectedResult) { + Object evaluatedResult = exercise3.compileAndEvaluate(expression); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0) > 2 ? true : true'}") + @TestParameters("{expression: 'true ? (1 / 0) > 2 : true'}") + @TestParameters("{expression: 'false ? true : (1 / 0) > 2'}") + public void evaluate_ternaryFailure_throwsException(String expression) { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> exercise3.compileAndEvaluate(expression)); + + assertThat(exception).hasMessageThat().contains("Evaluation error has occurred."); + assertThat(exception).hasCauseThat().isInstanceOf(CelEvaluationException.class); + assertThat(exception).hasCauseThat().hasMessageThat().contains("evaluation error: / by zero"); + } +} diff --git a/codelab/src/test/codelab/solutions/Exercise4Test.java b/codelab/src/test/codelab/solutions/Exercise4Test.java new file mode 100644 index 000000000..1a734f93a --- /dev/null +++ b/codelab/src/test/codelab/solutions/Exercise4Test.java @@ -0,0 +1,51 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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 codelab.solutions; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.google.rpc.context.AttributeContext; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; +import dev.cel.common.CelAbstractSyntaxTree; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class Exercise4Test { + private final Exercise4 exercise4 = new Exercise4(); + + @Test + @TestParameters("{group: 'admin', expectedResult: true}") + @TestParameters("{group: 'users', expectedResult: false}") + public void evaluate_requestContainsGroup_success(String group, boolean expectedResult) { + CelAbstractSyntaxTree ast = exercise4.compile("request.auth.claims.contains('group', 'admin')"); + AttributeContext.Auth auth = + AttributeContext.Auth.newBuilder() + .setPrincipal("user:me@acme.co") + .setClaims( + Struct.newBuilder() + .putFields("group", Value.newBuilder().setStringValue(group).build())) + .build(); + AttributeContext.Request request = AttributeContext.Request.newBuilder().setAuth(auth).build(); + + Object evaluatedResult = exercise4.eval(ast, ImmutableMap.of("request", request)); + + assertThat(evaluatedResult).isEqualTo(expectedResult); + } +}