Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion testing/testrunner/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ cc_library(
hdrs = ["cel_test_context.h"],
deps = [
":cel_expression_source",
":result_matcher",
"//compiler",
"//eval/public:cel_expression",
"//runtime",
Expand All @@ -31,6 +32,7 @@ cc_library(
deps = [
":cel_expression_source",
":cel_test_context",
":result_matcher",
"//checker:validation_result",
"//common:ast",
"//common:ast_proto",
Expand All @@ -51,7 +53,6 @@ cc_library(
"@com_google_absl//absl/strings:string_view",
"@com_google_cel_spec//proto/cel/expr:value_cc_proto",
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
"@com_google_protobuf//:differencer",
"@com_google_protobuf//:protobuf",
],
)
Expand Down Expand Up @@ -80,6 +81,8 @@ cc_test(
deps = [
":cel_expression_source",
":cel_test_context",
":default_result_matcher",
":result_matcher",
":runner_lib",
"//checker:type_checker_builder",
"//checker:validation_result",
Expand Down Expand Up @@ -135,3 +138,34 @@ cc_library(
hdrs = ["cel_expression_source.h"],
deps = ["@com_google_cel_spec//proto/cel/expr:checked_cc_proto"],
)

cc_library(
name = "result_matcher",
hdrs = ["result_matcher.h"],
deps = [
"//common:value",
"@com_google_absl//absl/status",
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
"@com_google_cel_spec//proto/cel/expr:value_cc_proto",
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
"@com_google_protobuf//:protobuf",
],
)

cc_library(
name = "default_result_matcher",
srcs = ["default_result_matcher.cc"],
deps = [
":cel_test_context",
":result_matcher",
"//common:value",
"//common/internal:value_conversion",
"//internal:testing",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings:string_view",
"@com_google_cel_spec//proto/cel/expr:value_cc_proto",
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
"@com_google_protobuf//:differencer",
"@com_google_protobuf//:protobuf",
],
)
35 changes: 33 additions & 2 deletions testing/testrunner/cel_test_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,17 @@
#include "eval/public/cel_expression.h"
#include "runtime/runtime.h"
#include "testing/testrunner/cel_expression_source.h"
#include "testing/testrunner/result_matcher.h"

namespace cel::test {

// Factory function for creating a `ResultMatcher` with default settings.
//
// This is used by `CelTestContext` when a custom result matcher is not
// provided in `CelTestContextOptions`. It ensures that a default matcher is
// always available for performing assertions in tests.
std::unique_ptr<ResultMatcher> CreateDefaultResultMatcher();

// Struct to hold optional parameters for `CelTestContext`.
struct CelTestContextOptions {
// The source for the CEL expression to be evaluated in the test.
Expand All @@ -50,6 +59,10 @@ struct CelTestContextOptions {
// This logic is handled by the test runner when it constructs the final
// activation.
absl::flat_hash_map<std::string, cel::expr::Value> custom_bindings;

// An optional result matcher to be used for assertions. If not provided, a
// default result matcher will be used.
std::unique_ptr<ResultMatcher> result_matcher = nullptr;
};

// The context class for a CEL test, holding configurations needed to evaluate
Expand Down Expand Up @@ -115,6 +128,9 @@ class CelTestContext {
return cel_test_context_options_.custom_bindings;
}

// Returns the result matcher to be used for assertions.
const ResultMatcher& result_matcher() const { return *result_matcher_; }

private:
// Delete copy and move constructors.
CelTestContext(const CelTestContext&) = delete;
Expand All @@ -128,12 +144,24 @@ class CelTestContext {
cel_expression_builder,
CelTestContextOptions options)
: cel_test_context_options_(std::move(options)),
cel_expression_builder_(std::move(cel_expression_builder)) {}
cel_expression_builder_(std::move(cel_expression_builder)) {
if (cel_test_context_options_.result_matcher) {
result_matcher_ = std::move(cel_test_context_options_.result_matcher);
} else {
result_matcher_ = CreateDefaultResultMatcher();
}
}

CelTestContext(std::unique_ptr<const cel::Runtime> runtime,
CelTestContextOptions options)
: cel_test_context_options_(std::move(options)),
runtime_(std::move(runtime)) {}
runtime_(std::move(runtime)) {
if (cel_test_context_options_.result_matcher) {
result_matcher_ = std::move(cel_test_context_options_.result_matcher);
} else {
result_matcher_ = CreateDefaultResultMatcher();
}
}

// Configuration for the expression to be executed.
CelTestContextOptions cel_test_context_options_;
Expand All @@ -148,6 +176,9 @@ class CelTestContext {
// needed to generate Program. Users should either provide a runtime, or the
// CelExpressionBuilder.
std::unique_ptr<const cel::Runtime> runtime_;

// The result matcher to be used for assertions.
std::unique_ptr<const ResultMatcher> result_matcher_;
};

} // namespace cel::test
Expand Down
122 changes: 122 additions & 0 deletions testing/testrunner/default_result_matcher.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 <memory>

#include "cel/expr/eval.pb.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "common/internal/value_conversion.h"
#include "common/value.h"
#include "internal/testing.h"
#include "testing/testrunner/cel_test_context.h"
#include "testing/testrunner/result_matcher.h"
#include "cel/expr/conformance/test/suite.pb.h"
#include "google/protobuf/arena.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/message.h"
#include "google/protobuf/util/field_comparator.h"
#include "google/protobuf/util/message_differencer.h"

namespace cel::test {

namespace {

using ValueProto = ::cel::expr::Value;
using ::cel::expr::conformance::test::TestOutput;

bool IsEqual(const ValueProto& expected, const ValueProto& actual) {
static auto* kFieldComparator = []() {
auto* field_comparator = new google::protobuf::util::DefaultFieldComparator();
field_comparator->set_treat_nan_as_equal(true);
return field_comparator;
}();
static auto* kDifferencer = []() {
auto* differencer = new google::protobuf::util::MessageDifferencer();
differencer->set_message_field_comparison(
google::protobuf::util::MessageDifferencer::EQUIVALENT);
differencer->set_field_comparator(kFieldComparator);
const auto* descriptor = cel::expr::MapValue::descriptor();
const auto* entries_field = descriptor->FindFieldByName("entries");
const auto* key_field =
entries_field->message_type()->FindFieldByName("key");
differencer->TreatAsMap(entries_field, key_field);
return differencer;
}();
return kDifferencer->Compare(expected, actual);
}

MATCHER_P(MatchesValue, expected, "") { return IsEqual(arg, expected); }

class DefaultResultMatcher : public cel::test::ResultMatcher {
public:
void Match(const ResultMatcherParams& params) const override {
const TestOutput& output = params.expected_output;
const auto& computed_output = params.computed_output;
google::protobuf::Arena* arena = params.arena;

if (output.has_result_value()) {
AssertValue(computed_output, output, params.test_context, arena);
} else if (output.has_eval_error()) {
AssertError(computed_output, output);
} else if (output.has_unknown()) {
ADD_FAILURE() << "Unknown assertions not implemented yet.";
} else {
ADD_FAILURE() << "Unexpected output kind.";
}
}

private:
void AssertValue(const cel::Value& computed, const TestOutput& output,
const CelTestContext& test_context,
google::protobuf::Arena* arena) const {
ValueProto expected_value_proto;
const auto* descriptor_pool =
test_context.runtime() != nullptr
? test_context.runtime()->GetDescriptorPool()
: google::protobuf::DescriptorPool::generated_pool();
auto* message_factory = test_context.runtime() != nullptr
? test_context.runtime()->GetMessageFactory()
: google::protobuf::MessageFactory::generated_factory();

ValueProto computed_expr_value;
ASSERT_OK_AND_ASSIGN(
computed_expr_value,
ToExprValue(computed, descriptor_pool, message_factory, arena));
EXPECT_THAT(output.result_value(), MatchesValue(computed_expr_value));
}

void AssertError(const cel::Value& computed, const TestOutput& output) const {
if (!computed.IsError()) {
ADD_FAILURE() << "Expected error but got value: "
<< computed.DebugString();
return;
}
absl::Status computed_status = computed.AsError()->ToStatus();
// We selected the first error in the set for comparison because there is
// only one runtime error that is reported even if there are multiple errors
// in the critical path.
ASSERT_TRUE(output.eval_error().errors_size() == 1)
<< "Expected exactly one error but got: "
<< output.eval_error().errors_size();
ASSERT_EQ(computed_status.message(),
output.eval_error().errors(0).message());
}
};
} // namespace

std::unique_ptr<ResultMatcher> CreateDefaultResultMatcher() {
return std::make_unique<DefaultResultMatcher>();
}
} // namespace cel::test
44 changes: 44 additions & 0 deletions testing/testrunner/result_matcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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.

#ifndef THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_RESULT_MATCHER_H_
#define THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_RESULT_MATCHER_H_

#include "common/value.h"
#include "cel/expr/conformance/test/suite.pb.h"
#include "google/protobuf/arena.h"

namespace cel::test {

// Forward declare CelTestContext to avoid circular includes.
class CelTestContext;

// Parameters passed to the ResultMatcher for performing assertions.
struct ResultMatcherParams {
const cel::expr::conformance::test::TestOutput& expected_output;
const CelTestContext& test_context;
const cel::Value& computed_output;
google::protobuf::Arena* arena;
};

// Interface for a custom result matcher.
class ResultMatcher {
public:
virtual ~ResultMatcher() = default;
virtual void Match(const ResultMatcherParams& params) const = 0;
};

} // namespace cel::test

#endif // THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_RESULT_MATCHER_H_
Loading