From 984f19ee884da3881a3a54f45c2c34a796cc8135 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Mon, 15 Sep 2025 00:08:34 -0700 Subject: [PATCH] Support checked_expression, raw cel_expressions and cel files via bzl macro PiperOrigin-RevId: 807110591 --- testing/testrunner/BUILD | 4 +- testing/testrunner/cel_cc_test.bzl | 44 ++++- testing/testrunner/cel_test_context.h | 17 +- testing/testrunner/resources/BUILD | 4 +- .../resources/test_environment.textproto | 15 ++ testing/testrunner/runner_bin.cc | 161 +++++++++++++++--- testing/testrunner/runner_lib_test.cc | 146 ++++++++-------- testing/testrunner/user_tests/BUILD | 68 ++++++++ .../user_tests/checked_expr_test.cc | 83 +++++++++ .../user_tests/raw_expr_and_cel_file_test.cc | 102 +++++++++++ .../user_tests/raw_expression_test.cc | 11 +- testing/testrunner/user_tests/simple.cc | 10 +- 12 files changed, 540 insertions(+), 125 deletions(-) create mode 100644 testing/testrunner/resources/test_environment.textproto create mode 100644 testing/testrunner/user_tests/checked_expr_test.cc create mode 100644 testing/testrunner/user_tests/raw_expr_and_cel_file_test.cc diff --git a/testing/testrunner/BUILD b/testing/testrunner/BUILD index 0e8be6ca9..cbd5cd8b3 100644 --- a/testing/testrunner/BUILD +++ b/testing/testrunner/BUILD @@ -116,11 +116,13 @@ cc_library( name = "runner", srcs = ["runner_bin.cc"], deps = [ + ":cel_expression_source", ":cel_test_context", ":cel_test_factories", ":coverage_index", ":runner_lib", "//eval/public:cel_expression", + "//internal:status_macros", "//internal:testing_no_main", "//runtime", "@com_google_absl//absl/flags:flag", @@ -130,9 +132,9 @@ cc_library( "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", + "@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 bbbdd51a1..37a772af8 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 = [], enable_coverage = False, @@ -33,6 +36,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. @@ -41,7 +48,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("/") @@ -52,6 +66,24 @@ def cel_cc_test( args.append("--collect_coverage=" + str(enable_coverage)) + 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) + if ext == ".cel": + expression_kind = "file" + expr_source = test_data_path + "/" + cel_expr + else: + expression_kind = "checked" + expr_source = "$(location " + cel_expr + ")" + + args.append("--expression_kind=" + expression_kind) + args.append("--expr_source=" + expr_source) + cc_test( name = name, data = data, @@ -59,7 +91,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 != "": @@ -67,8 +99,16 @@ 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: + _, ext = paths.split_extension(cel_expr) + if ext == ".cel": + data = data + [test_data_path + ":" + cel_expr] + else: + data = data + [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..bb2f5cb74 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,7 @@ 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_.get(); } const absl::flat_hash_map& @@ -115,6 +110,13 @@ 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::make_unique(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::unique_ptr 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..241746fd5 100644 --- a/testing/testrunner/resources/BUILD +++ b/testing/testrunner/resources/BUILD @@ -1,7 +1,9 @@ package(default_visibility = ["//visibility:public"]) exports_files( - ["test.cel"], + [ + "test.cel", + ], ) filegroup( 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 88a46bba5..51952e7ea 100644 --- a/testing/testrunner/runner_bin.cc +++ b/testing/testrunner/runner_bin.cc @@ -19,41 +19,57 @@ #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/str_format.h" #include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" #include "eval/public/cel_expression.h" +#include "internal/status_macros.h" #include "internal/testing.h" #include "runtime/runtime.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/coverage_index.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."); ABSL_FLAG(bool, collect_coverage, false, "Whether to collect code coverage."); namespace { +constexpr absl::string_view kRawExpressionKind = "raw"; +constexpr absl::string_view kFileExpressionKind = "file"; +constexpr absl::string_view kCheckedExpressionKind = "checked"; + using ::cel::expr::conformance::test::TestCase; using ::cel::expr::conformance::test::TestSuite; +using ::cel::test::CelExpressionSource; using ::cel::test::CelTestContext; using ::cel::test::CoverageIndex; using ::cel::test::TestRunner; +using ::cel::expr::CheckedExpr; using ::google::api::expr::runtime::CelExpressionBuilder; class CoverageReportingEnvironment : public testing::Environment { @@ -144,21 +160,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)); } - return test_suite; + 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 absl::InvalidArgumentError(absl::StrCat( + "Unknown file extension for checked expression. ", + "Please use .textproto, .textpb, .pb, or .binarypb: ", file_path)); } TestSuite GetTestSuite() { @@ -179,36 +239,81 @@ 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 == kRawExpressionKind) { + expression_source_from_flags = + CelExpressionSource::FromRawExpression(value); + } else if (kind == kFileExpressionKind) { + expression_source_from_flags = CelExpressionSource::FromCelFile(value); + } else if (kind == kCheckedExpressionKind) { + 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) + << "Expression source can only be set once and is currently set via " + "the factory."; + } + + 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(); } + std::unique_ptr cel_test_context = + std::move(cel_test_context_or.value()); cel::test::CoverageIndex coverage_index; if (absl::GetFlag(FLAGS_collect_coverage)) { - if (cel_test_context.value()->runtime() != nullptr) { + if (cel_test_context->runtime() != nullptr) { ABSL_CHECK_OK(cel::test::EnableCoverageInRuntime( - const_cast(*cel_test_context.value()->runtime()), + const_cast(*cel_test_context->runtime()), coverage_index)); - } else if (cel_test_context.value()->cel_expression_builder() != nullptr) { + } else if (cel_test_context->cel_expression_builder() != nullptr) { ABSL_CHECK_OK(cel::test::EnableCoverageInCelExpressionBuilder( const_cast( - *cel_test_context.value()->cel_expression_builder()), + *cel_test_context->cel_expression_builder()), coverage_index)); } } - auto test_runner = - std::make_shared(std::move(cel_test_context.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)); // Make sure the checked expression exists during the entire test run since diff --git a/testing/testrunner/runner_lib_test.cc b/testing/testrunner/runner_lib_test.cc index c4b7afefa..1cf88fa64 100644 --- a/testing/testrunner/runner_lib_test.cc +++ b/testing/testrunner/runner_lib_test.cc @@ -161,11 +161,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)); } @@ -187,11 +188,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 @@ -216,12 +216,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)); } @@ -245,12 +244,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 @@ -270,12 +268,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)); } @@ -294,12 +290,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 @@ -322,12 +316,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)); } @@ -349,12 +341,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 @@ -380,10 +370,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)); @@ -409,10 +398,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), @@ -457,11 +445,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); }, @@ -514,10 +503,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)); } @@ -574,10 +564,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)); } @@ -598,10 +589,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"); } @@ -644,10 +636,10 @@ TEST(CoverageTest, RuntimeCoverage) { coverage_index), absl_testing::IsOk()); - auto context = CelTestContext::CreateFromRuntime( - std::move(runtime), - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr(checked_expr)}); + auto context = + CelTestContext::CreateFromRuntime(std::move(runtime), /*options=*/{}); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(checked_expr)); TestRunner test_runner(std::move(context)); coverage_index.Init(checked_expr); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); @@ -706,9 +698,9 @@ TEST(CoverageTest, BuilderCoverage) { absl_testing::IsOk()); auto context = CelTestContext::CreateFromCelExpressionBuilder( - std::move(builder), - /*options=*/{.expression_source = - CelExpressionSource::FromCheckedExpr(checked_expr)}); + std::move(builder), /*options=*/{}); + context->SetExpressionSource( + CelExpressionSource::FromCheckedExpr(checked_expr)); TestRunner test_runner(std::move(context)); coverage_index.Init(checked_expr); EXPECT_NO_FATAL_FAILURE(test_runner.RunTest(test_case)); diff --git a/testing/testrunner/user_tests/BUILD b/testing/testrunner/user_tests/BUILD index f2bf80628..dbd20bde1 100644 --- a/testing/testrunner/user_tests/BUILD +++ b/testing/testrunner/user_tests/BUILD @@ -62,6 +62,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", enable_coverage = True, @@ -89,3 +139,21 @@ 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", + ], +) 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 7e3cafba6..970e717f7 100644 --- a/testing/testrunner/user_tests/simple.cc +++ b/testing/testrunner/user_tests/simple.cc @@ -106,10 +106,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