diff --git a/testing/testrunner/BUILD b/testing/testrunner/BUILD index 975b5884d..59b948f8d 100644 --- a/testing/testrunner/BUILD +++ b/testing/testrunner/BUILD @@ -113,9 +113,11 @@ cc_library( name = "runner", srcs = ["runner_bin.cc"], deps = [ + ":cel_expression_source", ":cel_test_context", ":cel_test_factories", ":runner_lib", + "//internal:status_macros", "//internal:testing_no_main", "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/log:absl_check", @@ -123,9 +125,9 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_cel_spec//proto/cel/expr:checked_cc_proto", "@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto", "@com_google_protobuf//:protobuf", - "@com_google_protobuf//src/google/protobuf/io", ], alwayslink = True, ) diff --git a/testing/testrunner/cel_cc_test.bzl b/testing/testrunner/cel_cc_test.bzl index 07581ce3c..26958efc8 100644 --- a/testing/testrunner/cel_cc_test.bzl +++ b/testing/testrunner/cel_cc_test.bzl @@ -14,11 +14,14 @@ """Rules for triggering the cc impl of the CEL test runner.""" +load("@bazel_skylib//lib:paths.bzl", "paths") load("@rules_cc//cc:cc_test.bzl", "cc_test") def cel_cc_test( name, test_suite = "", + cel_expr = "", + is_raw_expr = False, filegroup = "", deps = [], test_data_path = "", @@ -32,6 +35,10 @@ def cel_cc_test( name: str name for the generated artifact test_suite: str label of a file containing a test suite. The file should have a .textproto extension. + cel_expr: The CEL expression source. The meaning of this argument depends on `is_raw_expr`. + is_raw_expr: bool whether the cel_expr is a raw expression string. If False, + cel_expr is treated as a file path. The file type (.cel or .textproto) + is inferred from the extension. filegroup: str label of a filegroup containing the test suite, the config and the checked expression. deps: list of dependencies for the cc_test rule. @@ -39,7 +46,14 @@ def cel_cc_test( test_data_path: absolute path of the directory containing the test files. This is needed only if the test files are not located in the same directory as the BUILD file. """ - data, test_data_path = _update_data_with_test_files(data, filegroup, test_data_path, test_suite) + data, test_data_path = _update_data_with_test_files( + data, + filegroup, + test_data_path, + test_suite, + cel_expr, + is_raw_expr, + ) args = [] test_data_path = test_data_path.lstrip("/") @@ -48,6 +62,24 @@ def cel_cc_test( test_suite = test_data_path + "/" + test_suite args.append("--test_suite_path=" + test_suite) + if cel_expr != "": + expression_kind = "" + expr_source = "" + if is_raw_expr: + expression_kind = "raw" + expr_source = "\"" + cel_expr + "\"" + else: + _, ext = paths.split_extension(cel_expr) + resolved_path = test_data_path + "/" + cel_expr + if ext == ".cel": + expression_kind = "file" + else: + expression_kind = "checked" + expr_source = resolved_path + + args.append("--expression_kind=" + expression_kind) + args.append("--expr_source=" + expr_source) + cc_test( name = name, data = data, @@ -55,7 +87,7 @@ def cel_cc_test( deps = ["//testing/testrunner:runner"] + deps, ) -def _update_data_with_test_files(data, filegroup, test_data_path, test_suite): +def _update_data_with_test_files(data, filegroup, test_data_path, test_suite, cel_expr, is_raw_expr): """Updates the data with the test files.""" if filegroup != "": @@ -63,8 +95,12 @@ def _update_data_with_test_files(data, filegroup, test_data_path, test_suite): elif test_data_path != "" and test_data_path != native.package_name(): if test_suite != "": data = data + [test_data_path + ":" + test_suite] + if cel_expr != "" and not is_raw_expr: + data = data + [test_data_path + ":" + cel_expr] else: test_data_path = native.package_name() if test_suite != "": data = data + [test_suite] + if cel_expr != "" and not is_raw_expr: + data = data + [cel_expr] return data, test_data_path diff --git a/testing/testrunner/cel_test_context.h b/testing/testrunner/cel_test_context.h index 335f25aa4..2db39bdf9 100644 --- a/testing/testrunner/cel_test_context.h +++ b/testing/testrunner/cel_test_context.h @@ -33,9 +33,6 @@ namespace cel::test { // Struct to hold optional parameters for `CelTestContext`. struct CelTestContextOptions { - // The source for the CEL expression to be evaluated in the test. - std::optional expression_source; - // An optional CEL compiler. This is required for test cases where // input or output values are themselves CEL expressions that need to be // resolved at runtime or cel expression source is raw string or cel file. @@ -105,9 +102,8 @@ class CelTestContext { } const CelExpressionSource* absl_nullable expression_source() const { - return cel_test_context_options_.expression_source.has_value() - ? &cel_test_context_options_.expression_source.value() - : nullptr; + return expression_source_.has_value() ? &expression_source_.value() + : nullptr; } const absl::flat_hash_map& @@ -115,6 +111,12 @@ class CelTestContext { return cel_test_context_options_.custom_bindings; } + // Allows the runner to inject the expression source + // parsed from command-line flags. + void SetExpressionSource(CelExpressionSource source) { + expression_source_ = std::move(source); + } + private: // Delete copy and move constructors. CelTestContext(const CelTestContext&) = delete; @@ -135,6 +137,9 @@ class CelTestContext { : cel_test_context_options_(std::move(options)), runtime_(std::move(runtime)) {} + // The source for the CEL expression to be evaluated in the test. + std::optional expression_source_; + // Configuration for the expression to be executed. CelTestContextOptions cel_test_context_options_; diff --git a/testing/testrunner/resources/BUILD b/testing/testrunner/resources/BUILD index 663f81780..a7746a19d 100644 --- a/testing/testrunner/resources/BUILD +++ b/testing/testrunner/resources/BUILD @@ -1,12 +1,17 @@ package(default_visibility = ["//visibility:public"]) exports_files( - ["test.cel"], + [ + "test.cel", + "subtraction_checked_expr.textproto", + "subtraction_checked_expr.binarypb", + ], ) filegroup( name = "resources", srcs = glob([ "*.textproto", + "*.binarypb", ]), ) diff --git a/testing/testrunner/resources/subtraction_checked_expr.binarypb b/testing/testrunner/resources/subtraction_checked_expr.binarypb new file mode 100644 index 000000000..702f8e7a9 --- /dev/null +++ b/testing/testrunner/resources/subtraction_checked_expr.binarypb @@ -0,0 +1,3 @@ +"2_-_" +x" +y \ No newline at end of file diff --git a/testing/testrunner/resources/subtraction_checked_expr.textproto b/testing/testrunner/resources/subtraction_checked_expr.textproto new file mode 100644 index 000000000..660aedf0e --- /dev/null +++ b/testing/testrunner/resources/subtraction_checked_expr.textproto @@ -0,0 +1,42 @@ +# proto-file: google3/google/api/expr/checked.proto +# proto-message: google.api.expr.CheckedExpr + +# CEL expression: x - y (where x is Int64, y is Int64) + +expr { + id: 1 + call_expr { + function: "_-_" + args { + id: 2 + ident_expr { + name: "x" + } + } + args { + id: 3 + ident_expr { + name: "y" + } + } + } +} +# Type information confirming the final result is an Int64 +type_map { + key: 1 + value { + primitive: INT64 + } +} +type_map { + key: 2 + value { + primitive: INT64 + } +} +type_map { + key: 3 + value { + primitive: INT64 + } +} diff --git a/testing/testrunner/resources/test_environment.textproto b/testing/testrunner/resources/test_environment.textproto new file mode 100644 index 000000000..77e3b180f --- /dev/null +++ b/testing/testrunner/resources/test_environment.textproto @@ -0,0 +1,15 @@ +# proto-file: third_party/cel/go/tools/compilecli/compile_input.proto +# proto-message: Environment + +declarations: { + name: "x" + ident: { + type: { primitive: INT64 } + } +} +declarations: { + name: "y" + ident: { + type: { primitive: INT64 } + } +} diff --git a/testing/testrunner/runner_bin.cc b/testing/testrunner/runner_bin.cc index a307d45c0..bd4e0f8c0 100644 --- a/testing/testrunner/runner_bin.cc +++ b/testing/testrunner/runner_bin.cc @@ -18,32 +18,45 @@ #include #include #include +#include +#include #include -#include #include +#include "cel/expr/checked.pb.h" #include "absl/flags/flag.h" #include "absl/log/absl_check.h" #include "absl/log/absl_log.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "internal/status_macros.h" #include "internal/testing.h" +#include "testing/testrunner/cel_expression_source.h" #include "testing/testrunner/cel_test_context.h" #include "testing/testrunner/cel_test_factories.h" #include "testing/testrunner/runner_lib.h" #include "cel/expr/conformance/test/suite.pb.h" -#include "google/protobuf/io/zero_copy_stream_impl.h" #include "google/protobuf/text_format.h" ABSL_FLAG(std::string, test_suite_path, "", "The path to the file containing the test suite to run."); +ABSL_FLAG(std::string, expression_kind, "", + "The kind of expression source: 'raw', 'file', or 'checked'."); +ABSL_FLAG(std::string, expr_source, "", + "The value of the CEL expression source. For 'raw', it's the " + "expression string. For 'file' and 'checked', it's the file path."); namespace { using ::cel::expr::conformance::test::TestCase; using ::cel::expr::conformance::test::TestSuite; +using ::cel::test::CelExpressionSource; +using ::cel::test::CelTestContext; using ::cel::test::TestRunner; +using ::cel::expr::CheckedExpr; class CelTest : public testing::Test { public: @@ -73,21 +86,65 @@ absl::Status RegisterTests(const TestSuite& test_suite, return absl::OkStatus(); } -TestSuite ReadTestSuiteFromPath(std::string_view test_suite_path) { - TestSuite test_suite; - { - std::ifstream in; - in.open(std::string(test_suite_path), - std::ios_base::in | std::ios_base::binary); - if (!in.is_open()) { - ABSL_LOG(FATAL) << "failed to open file: " << test_suite_path; - } - google::protobuf::io::IstreamInputStream stream(&in); - if (!google::protobuf::TextFormat::Parse(&stream, &test_suite)) { - ABSL_LOG(FATAL) << "failed to parse file: " << test_suite_path; - } +absl::StatusOr ReadFileToString(absl::string_view file_path) { + std::ifstream file_stream{std::string(file_path)}; + if (!file_stream.is_open()) { + return absl::NotFoundError( + absl::StrCat("Unable to open file: ", file_path)); + } + std::stringstream buffer; + buffer << file_stream.rdbuf(); + return buffer.str(); +} + +template +absl::StatusOr ReadTextProtoFromFile(absl::string_view file_path) { + CEL_ASSIGN_OR_RETURN(std::string contents, ReadFileToString(file_path)); + T message; + if (!google::protobuf::TextFormat::ParseFromString(contents, &message)) { + return absl::InternalError(absl::StrCat( + "Failed to parse text-format proto from file: ", file_path)); + } + return message; +} + +absl::StatusOr ReadBinaryProtoFromFile( + absl::string_view file_path) { + CheckedExpr message; + std::ifstream file_stream{std::string(file_path), std::ios::binary}; + if (!file_stream.is_open()) { + return absl::NotFoundError( + absl::StrCat("Unable to open file: ", file_path)); + } + if (!message.ParseFromIstream(&file_stream)) { + return absl::InternalError( + absl::StrCat("Failed to parse binary proto from file: ", file_path)); + } + return message; +} + +TestSuite ReadTestSuiteFromPath(absl::string_view test_suite_path) { + absl::StatusOr test_suite_or = + ReadTextProtoFromFile(test_suite_path); + + if (!test_suite_or.ok()) { + ABSL_LOG(FATAL) << "Failed to load test suite from " << test_suite_path + << ": " << test_suite_or.status(); + } + return *std::move(test_suite_or); +} + +absl::StatusOr ReadCheckedExprFromFile( + absl::string_view file_path) { + if (absl::EndsWith(file_path, ".textproto")) { + return ReadTextProtoFromFile(file_path); + } + if (absl::EndsWith(file_path, ".binarypb")) { + return ReadBinaryProtoFromFile(file_path); } - return test_suite; + return absl::InvalidArgumentError(absl::StrCat( + "Unknown file extension for checked expression. ", + "Please use .textproto, .textpb, .pb, or .binarypb: ", file_path)); } TestSuite GetTestSuite() { @@ -108,21 +165,68 @@ TestSuite GetTestSuite() { } return test_suite_factory(); } + +void UpdateWithExpressionFromCommandLineFlags( + CelTestContext& cel_test_context) { + if (absl::GetFlag(FLAGS_expr_source).empty()) { + return; + } + + std::string kind = absl::GetFlag(FLAGS_expression_kind); + std::string value = absl::GetFlag(FLAGS_expr_source); + + std::optional expression_source_from_flags; + if (kind == "raw") { + expression_source_from_flags = + CelExpressionSource::FromRawExpression(value); + } else if (kind == "file") { + expression_source_from_flags = CelExpressionSource::FromCelFile(value); + } else if (kind == "checked") { + absl::StatusOr checked_expr = ReadCheckedExprFromFile(value); + if (!checked_expr.ok()) { + ABSL_LOG(FATAL) << "Failed to read checked expression from file: " + << checked_expr.status(); + } + expression_source_from_flags = + CelExpressionSource::FromCheckedExpr(std::move(*checked_expr)); + } else { + ABSL_LOG(FATAL) << "Unknown expression kind: " << kind; + } + + // Check for conflicting expression sources. + if (cel_test_context.expression_source() != nullptr) { + ABSL_LOG(FATAL) + << "Conflicting expression sources: Test context factory " + "provided an expression source, but one was also provided via " + "command-line flags (--expr_source). Only one can be set."; + } + + if (expression_source_from_flags.has_value()) { + cel_test_context.SetExpressionSource( + std::move(*expression_source_from_flags)); + } +} + } // namespace int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); - // Create a test context using the factory function returned by the global // factory function provider which was initialized by the user. - absl::StatusOr> cel_test_context = - cel::test::internal::GetCelTestContextFactory()(); - if (!cel_test_context.ok()) { - ABSL_LOG(FATAL) << "Failed to create CEL test context: " - << cel_test_context.status(); + absl::StatusOr> + cel_test_context_or = cel::test::internal::GetCelTestContextFactory()(); + if (!cel_test_context_or.ok()) { + ABSL_LOG(FATAL) << "Failed to create CEL test context from factory: " + << cel_test_context_or.status(); } - auto test_runner = - std::make_shared(std::move(cel_test_context.value())); + std::unique_ptr cel_test_context = + std::move(cel_test_context_or.value()); + + // Update the context with an expression from flags, if provided. + // This will FATAL if an expression is set by both the factory and flags. + UpdateWithExpressionFromCommandLineFlags(*cel_test_context); + + auto test_runner = std::make_shared(std::move(cel_test_context)); ABSL_CHECK_OK(RegisterTests(GetTestSuite(), test_runner)); return RUN_ALL_TESTS(); diff --git a/testing/testrunner/runner_lib_test.cc b/testing/testrunner/runner_lib_test.cc index f63952b2c..c51d80176 100644 --- a/testing/testrunner/runner_lib_test.cc +++ b/testing/testrunner/runner_lib_test.cc @@ -160,11 +160,12 @@ TEST_P(TestRunnerParamTest, BasicTestReportsSuccess) { } } )pb"); - ASSERT_OK_AND_ASSIGN( - auto context, CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))})); + ASSERT_OK_AND_ASSIGN(auto context, CreateTestContext( + /*options=*/{})); + + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); + TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); } @@ -186,11 +187,10 @@ TEST_P(TestRunnerParamTest, BasicTestReportsFailure) { } output { result_value { bool_value: false } } )pb"); - ASSERT_OK_AND_ASSIGN( - auto context, CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))})); + ASSERT_OK_AND_ASSIGN(auto context, CreateTestContext( + /*options=*/{})); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); TestRunner test_runner(std::move(context)); EXPECT_NONFATAL_FAILURE(test_runner.RunTest(test_case), "bool_value: true"); // expected true got false @@ -215,12 +215,11 @@ TEST_P(TestRunnerParamTest, DynamicInputAndOutputReportsSuccess) { )pb"); ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, CreateBasicCompiler()); - ASSERT_OK_AND_ASSIGN( - auto context, CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr)), - .compiler = std::move(compiler)})); + ASSERT_OK_AND_ASSIGN(auto context, + CreateTestContext( + /*options=*/{.compiler = std::move(compiler)})); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); } @@ -244,12 +243,11 @@ TEST_P(TestRunnerParamTest, DynamicInputAndOutputReportsFailure) { )pb"); ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, CreateBasicCompiler()); - ASSERT_OK_AND_ASSIGN( - auto context, CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr)), - .compiler = std::move(compiler)})); + ASSERT_OK_AND_ASSIGN(auto context, + CreateTestContext( + /*options=*/{.compiler = std::move(compiler)})); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); TestRunner test_runner(std::move(context)); EXPECT_NONFATAL_FAILURE(test_runner.RunTest(test_case), "int64_value: 5"); // expected 5 got 10 @@ -269,12 +267,10 @@ TEST_P(TestRunnerParamTest, RawExpressionWithCompilerReportsSuccess) { )pb"); ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, CreateBasicCompiler()); - ASSERT_OK_AND_ASSIGN( - auto context, - CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromRawExpression("x - y"), - .compiler = std::move(compiler)})); + ASSERT_OK_AND_ASSIGN(auto context, + CreateTestContext( + /*options=*/{.compiler = std::move(compiler)})); + context->SetExpressionSource(CelExpressionSource::FromRawExpression("x - y")); TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); } @@ -293,12 +289,10 @@ TEST_P(TestRunnerParamTest, RawExpressionWithCompilerReportsFailure) { )pb"); ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, CreateBasicCompiler()); - ASSERT_OK_AND_ASSIGN( - auto context, - CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromRawExpression("x - y"), - .compiler = std::move(compiler)})); + ASSERT_OK_AND_ASSIGN(auto context, + CreateTestContext( + /*options=*/{.compiler = std::move(compiler)})); + context->SetExpressionSource(CelExpressionSource::FromRawExpression("x - y")); TestRunner test_runner(std::move(context)); EXPECT_NONFATAL_FAILURE(test_runner.RunTest(test_case), "int64_value: 7"); // expected 7 got 100 @@ -321,12 +315,10 @@ TEST_P(TestRunnerParamTest, CelFileWithCompilerReportsSuccess) { )pb"); ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, CreateBasicCompiler()); - ASSERT_OK_AND_ASSIGN( - auto context, - CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCelFile(cel_file_path), - .compiler = std::move(compiler)})); + ASSERT_OK_AND_ASSIGN(auto context, + CreateTestContext( + /*options=*/{.compiler = std::move(compiler)})); + context->SetExpressionSource(CelExpressionSource::FromCelFile(cel_file_path)); TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); } @@ -348,12 +340,10 @@ TEST_P(TestRunnerParamTest, CelFileWithCompilerReportsFailure) { )pb"); ASSERT_OK_AND_ASSIGN(std::unique_ptr compiler, CreateBasicCompiler()); - ASSERT_OK_AND_ASSIGN( - auto context, - CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCelFile(cel_file_path), - .compiler = std::move(compiler)})); + ASSERT_OK_AND_ASSIGN(auto context, + CreateTestContext( + /*options=*/{.compiler = std::move(compiler)})); + context->SetExpressionSource(CelExpressionSource::FromCelFile(cel_file_path)); TestRunner test_runner(std::move(context)); EXPECT_NONFATAL_FAILURE(test_runner.RunTest(test_case), "int64_value: 7"); // expected 7 got 123 @@ -379,10 +369,9 @@ TEST_P(TestRunnerParamTest, BasicTestWithCustomBindingsSucceeds) { ASSERT_OK_AND_ASSIGN( auto context, CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr)), - .custom_bindings = std::move(bindings)})); + /*options=*/{.custom_bindings = std::move(bindings)})); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); @@ -408,10 +397,9 @@ TEST_P(TestRunnerParamTest, BasicTestWithCustomBindingsReportsFailure) { ASSERT_OK_AND_ASSIGN( auto context, CreateTestContext( - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr)), - .custom_bindings = std::move(bindings)})); + /*options=*/{.custom_bindings = std::move(bindings)})); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); TestRunner test_runner(std::move(context)); EXPECT_NONFATAL_FAILURE(test_runner.RunTest(test_case), @@ -456,11 +444,12 @@ TEST(TestRunnerStandaloneTest, DynamicInputWithoutCompilerFails) { ASSERT_OK_AND_ASSIGN(auto builder, CreateTestCelExpressionBuilder()); // Create the TestRunner without the compiler. - TestRunner test_runner(CelTestContext::CreateFromCelExpressionBuilder( + auto context = CelTestContext::CreateFromCelExpressionBuilder( /*cel_expression_builder=*/std::move(builder), - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))})); + /*options=*/{}); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); + TestRunner test_runner(std::move(context)); test_runner.RunTest(test_case); }, @@ -513,10 +502,11 @@ TEST(TestRunnerStandaloneTest, output { result_value { bool_value: true } } )pb"); - TestRunner test_runner(CelTestContext::CreateFromRuntime( - std::move(runtime), - /*options=*/{.expression_source = CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))})); + auto context = CelTestContext::CreateFromRuntime(std::move(runtime), + /*options=*/{}); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); + TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); } @@ -573,10 +563,11 @@ TEST(TestRunnerStandaloneTest, BasicTestWithErrorAssertion) { } } )pb"); - TestRunner test_runner(CelTestContext::CreateFromRuntime( - std::move(runtime), - /*options=*/{.expression_source = CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))})); + auto context = CelTestContext::CreateFromRuntime(std::move(runtime), + /*options=*/{}); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); + TestRunner test_runner(std::move(context)); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); } @@ -597,10 +588,11 @@ TEST(TestRunnerStandaloneTest, BasicTestFailsWhenExpectingErrorButGotValue) { } } )pb"); - TestRunner test_runner(CelTestContext::CreateFromRuntime( - std::move(runtime), - /*options=*/{.expression_source = CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))})); + auto context = CelTestContext::CreateFromRuntime(std::move(runtime), + /*options=*/{}); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); + TestRunner test_runner(std::move(context)); EXPECT_NONFATAL_FAILURE(test_runner.RunTest(test_case), "Expected error but got value"); } diff --git a/testing/testrunner/user_tests/BUILD b/testing/testrunner/user_tests/BUILD index 436176f1c..390102edc 100644 --- a/testing/testrunner/user_tests/BUILD +++ b/testing/testrunner/user_tests/BUILD @@ -61,6 +61,56 @@ cc_library( ], ) +cc_library( + name = "raw_expr_and_cel_file_test", + testonly = True, + srcs = ["raw_expr_and_cel_file_test.cc"], + deps = [ + "//checker:type_checker_builder", + "//common:decl", + "//common:type", + "//compiler", + "//compiler:compiler_factory", + "//compiler:standard_library", + "//internal:status_macros", + "//internal:testing_descriptor_pool", + "//runtime", + "//runtime:runtime_builder", + "//runtime:standard_runtime_builder_factory", + "//testing/testrunner:cel_test_context", + "//testing/testrunner:cel_test_factories", + "@com_google_absl//absl/log:absl_check", + "@com_google_absl//absl/log:absl_log", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:string_view", + "@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto", + "@com_google_protobuf//:protobuf", + ], + alwayslink = True, +) + +cc_library( + name = "checked_expr_user_test", + testonly = True, + srcs = ["checked_expr_test.cc"], + deps = [ + "//internal:status_macros", + "//internal:testing_descriptor_pool", + "//runtime", + "//runtime:runtime_builder", + "//runtime:standard_runtime_builder_factory", + "//testing/testrunner:cel_test_context", + "//testing/testrunner:cel_test_factories", + "@com_google_absl//absl/log:absl_check", + "@com_google_absl//absl/log:absl_log", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:string_view", + "@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto", + "@com_google_protobuf//:protobuf", + ], + alwayslink = True, +) + cel_cc_test( name = "simple_test", filegroup = "//testing/testrunner/resources", @@ -86,3 +136,39 @@ cel_cc_test( ":raw_expression_user_test", ], ) + +cel_cc_test( + name = "subtraction_raw_expr_test", + cel_expr = "x - y", + is_raw_expr = True, + deps = [ + ":raw_expr_and_cel_file_test", + ], +) + +cel_cc_test( + name = "subtraction_cel_file_test", + cel_expr = "test.cel", + test_data_path = "//testing/testrunner/resources", + deps = [ + ":raw_expr_and_cel_file_test", + ], +) + +cel_cc_test( + name = "subtraction_checked_expr_test", + cel_expr = "subtraction_checked_expr.textproto", + test_data_path = "//testing/testrunner/resources", + deps = [ + ":checked_expr_user_test", + ], +) + +cel_cc_test( + name = "subtraction_checked_expr_binary_test", + cel_expr = "subtraction_checked_expr.binarypb", + test_data_path = "//testing/testrunner/resources", + deps = [ + ":checked_expr_user_test", + ], +) diff --git a/testing/testrunner/user_tests/checked_expr_test.cc b/testing/testrunner/user_tests/checked_expr_test.cc new file mode 100644 index 000000000..5570cf427 --- /dev/null +++ b/testing/testrunner/user_tests/checked_expr_test.cc @@ -0,0 +1,83 @@ +// Copyright 2025 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. + +#include +#include + +#include "absl/log/absl_check.h" +#include "absl/log/absl_log.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "internal/status_macros.h" +#include "internal/testing_descriptor_pool.h" +#include "runtime/runtime.h" +#include "runtime/runtime_builder.h" +#include "runtime/standard_runtime_builder_factory.h" +#include "testing/testrunner/cel_test_context.h" +#include "testing/testrunner/cel_test_factories.h" +#include "cel/expr/conformance/test/suite.pb.h" +#include "google/protobuf/text_format.h" + +namespace cel::testing { + +using ::cel::test::CelTestContext; + +template +T ParseTextProtoOrDie(absl::string_view text_proto) { + T result; + ABSL_CHECK(google::protobuf::TextFormat::ParseFromString(text_proto, &result)); + return result; +} + +CEL_REGISTER_TEST_SUITE_FACTORY([]() { + return ParseTextProtoOrDie(R"pb( + name: "cli_expression_tests" + description: "Tests designed for expressions passed via CLI flags." + sections: { + name: "subtraction_test" + description: "Tests subtraction of two variables." + tests: { + name: "variable_subtraction" + description: "Test that subtraction of two variables works." + input: { + key: "x" + value { value { int64_value: 10 } } + } + input { + key: "y" + value { value { int64_value: 5 } } + } + output { result_value { int64_value: 5 } } + } + } + )pb"); +}); + +CEL_REGISTER_TEST_CONTEXT_FACTORY( + []() -> absl::StatusOr> { + ABSL_LOG(INFO) << "Creating runtime-only test context for CheckedExpr"; + + // Create a runtime. + CEL_ASSIGN_OR_RETURN(cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + cel::internal::GetTestingDescriptorPool(), {})); + CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + + // Create the context with the runtime, but no compiler. + // The test runner will inject the CheckedExpr source later. + return CelTestContext::CreateFromRuntime(std::move(runtime), + /*options=*/{}); + }); +} // namespace cel::testing diff --git a/testing/testrunner/user_tests/raw_expr_and_cel_file_test.cc b/testing/testrunner/user_tests/raw_expr_and_cel_file_test.cc new file mode 100644 index 000000000..d8d0690ba --- /dev/null +++ b/testing/testrunner/user_tests/raw_expr_and_cel_file_test.cc @@ -0,0 +1,102 @@ +// Copyright 2025 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. + +#include +#include + +#include "absl/log/absl_check.h" +#include "absl/log/absl_log.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "checker/type_checker_builder.h" +#include "common/decl.h" +#include "common/type.h" +#include "compiler/compiler.h" +#include "compiler/compiler_factory.h" +#include "compiler/standard_library.h" +#include "internal/status_macros.h" +#include "internal/testing_descriptor_pool.h" +#include "runtime/runtime.h" +#include "runtime/runtime_builder.h" +#include "runtime/standard_runtime_builder_factory.h" +#include "testing/testrunner/cel_test_context.h" +#include "testing/testrunner/cel_test_factories.h" +#include "cel/expr/conformance/test/suite.pb.h" +#include "google/protobuf/text_format.h" + +namespace cel::testing { + +using ::cel::test::CelTestContext; + +template +T ParseTextProtoOrDie(absl::string_view text_proto) { + T result; + ABSL_CHECK(google::protobuf::TextFormat::ParseFromString(text_proto, &result)); + return result; +} + +CEL_REGISTER_TEST_SUITE_FACTORY([]() { + return ParseTextProtoOrDie(R"pb( + name: "cli_expression_tests" + description: "Tests designed for expressions passed via CLI flags." + sections: { + name: "subtraction_test" + description: "Tests subtraction of two variables." + tests: { + name: "variable_subtraction" + description: "Test that subtraction of two variables works." + input: { + key: "x" + value { value { int64_value: 10 } } + } + input { + key: "y" + value { value { int64_value: 5 } } + } + output { result_value { int64_value: 5 } } + } + } + )pb"); +}); + +CEL_REGISTER_TEST_CONTEXT_FACTORY( + []() -> absl::StatusOr> { + ABSL_LOG(INFO) << "Creating test context for raw_expr and cel_file"; + + // Create a compiler. + CEL_ASSIGN_OR_RETURN( + std::unique_ptr builder, + cel::NewCompilerBuilder(cel::internal::GetTestingDescriptorPool())); + CEL_RETURN_IF_ERROR(builder->AddLibrary(cel::StandardCompilerLibrary())); + cel::TypeCheckerBuilder& checker_builder = builder->GetCheckerBuilder(); + CEL_RETURN_IF_ERROR(checker_builder.AddVariable( + cel::MakeVariableDecl("x", cel::IntType()))); + CEL_RETURN_IF_ERROR(checker_builder.AddVariable( + cel::MakeVariableDecl("y", cel::IntType()))); + + CEL_ASSIGN_OR_RETURN(std::unique_ptr compiler, + std::move(builder)->Build()); + + // Create a runtime. + CEL_ASSIGN_OR_RETURN(cel::RuntimeBuilder runtime_builder, + cel::CreateStandardRuntimeBuilder( + cel::internal::GetTestingDescriptorPool(), {})); + CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, + std::move(runtime_builder).Build()); + + return CelTestContext::CreateFromRuntime( + std::move(runtime), + /*options=*/{.compiler = std::move(compiler)}); + }); +} // namespace cel::testing diff --git a/testing/testrunner/user_tests/raw_expression_test.cc b/testing/testrunner/user_tests/raw_expression_test.cc index 333ba66a1..7409a5338 100644 --- a/testing/testrunner/user_tests/raw_expression_test.cc +++ b/testing/testrunner/user_tests/raw_expression_test.cc @@ -93,11 +93,12 @@ CEL_REGISTER_TEST_CONTEXT_FACTORY( CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, std::move(runtime_builder).Build()); - return CelTestContext::CreateFromRuntime( + auto context = CelTestContext::CreateFromRuntime( std::move(runtime), - /*options=*/{ - .expression_source = - test::CelExpressionSource::FromRawExpression("x + y"), - .compiler = std::move(compiler)}); + /*options=*/{.compiler = std::move(compiler)}); + context->SetExpressionSource( + test::CelExpressionSource::FromRawExpression("x + y")); + + return context; }); } // namespace cel::testing diff --git a/testing/testrunner/user_tests/simple.cc b/testing/testrunner/user_tests/simple.cc index e199f6d17..f52ec7861 100644 --- a/testing/testrunner/user_tests/simple.cc +++ b/testing/testrunner/user_tests/simple.cc @@ -118,10 +118,10 @@ CEL_REGISTER_TEST_CONTEXT_FACTORY( CEL_ASSIGN_OR_RETURN(std::unique_ptr runtime, std::move(runtime_builder).Build()); - return CelTestContext::CreateFromRuntime( - std::move(runtime), - /*options=*/{.expression_source = - test::CelExpressionSource::FromCheckedExpr( - std::move(checked_expr))}); + auto context = CelTestContext::CreateFromRuntime(std::move(runtime), + /*options=*/{}); + context->SetExpressionSource( + test::CelExpressionSource::FromCheckedExpr(std::move(checked_expr))); + return context; }); } // namespace cel::testing