Skip to content

Commit 86a96aa

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
support for custom Result matcher
PiperOrigin-RevId: 807110590
1 parent 2abc4a6 commit 86a96aa

File tree

7 files changed

+302
-82
lines changed

7 files changed

+302
-82
lines changed

testing/testrunner/BUILD

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ cc_library(
1313
hdrs = ["cel_test_context.h"],
1414
deps = [
1515
":cel_expression_source",
16+
":result_matcher",
1617
"//compiler",
1718
"//eval/public:cel_expression",
1819
"//runtime",
@@ -31,6 +32,7 @@ cc_library(
3132
deps = [
3233
":cel_expression_source",
3334
":cel_test_context",
35+
":result_matcher",
3436
"//checker:validation_result",
3537
"//common:ast",
3638
"//common:ast_proto",
@@ -51,7 +53,6 @@ cc_library(
5153
"@com_google_absl//absl/strings:string_view",
5254
"@com_google_cel_spec//proto/cel/expr:value_cc_proto",
5355
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
54-
"@com_google_protobuf//:differencer",
5556
"@com_google_protobuf//:protobuf",
5657
],
5758
)
@@ -80,6 +81,8 @@ cc_test(
8081
deps = [
8182
":cel_expression_source",
8283
":cel_test_context",
84+
":default_result_matcher",
85+
":result_matcher",
8386
":runner_lib",
8487
"//checker:type_checker_builder",
8588
"//checker:validation_result",
@@ -135,3 +138,34 @@ cc_library(
135138
hdrs = ["cel_expression_source.h"],
136139
deps = ["@com_google_cel_spec//proto/cel/expr:checked_cc_proto"],
137140
)
141+
142+
cc_library(
143+
name = "result_matcher",
144+
hdrs = ["result_matcher.h"],
145+
deps = [
146+
"//common:value",
147+
"@com_google_absl//absl/status",
148+
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
149+
"@com_google_cel_spec//proto/cel/expr:value_cc_proto",
150+
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
151+
"@com_google_protobuf//:protobuf",
152+
],
153+
)
154+
155+
cc_library(
156+
name = "default_result_matcher",
157+
srcs = ["default_result_matcher.cc"],
158+
deps = [
159+
":cel_test_context",
160+
":result_matcher",
161+
"//common:value",
162+
"//common/internal:value_conversion",
163+
"//internal:testing",
164+
"@com_google_absl//absl/status",
165+
"@com_google_absl//absl/strings:string_view",
166+
"@com_google_cel_spec//proto/cel/expr:value_cc_proto",
167+
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
168+
"@com_google_protobuf//:differencer",
169+
"@com_google_protobuf//:protobuf",
170+
],
171+
)

testing/testrunner/cel_test_context.h

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,17 @@
2929
#include "eval/public/cel_expression.h"
3030
#include "runtime/runtime.h"
3131
#include "testing/testrunner/cel_expression_source.h"
32+
#include "testing/testrunner/result_matcher.h"
33+
3234
namespace cel::test {
3335

36+
// Factory function for creating a `ResultMatcher` with default settings.
37+
//
38+
// This is used by `CelTestContext` when a custom result matcher is not
39+
// provided in `CelTestContextOptions`. It ensures that a default matcher is
40+
// always available for performing assertions in tests.
41+
std::unique_ptr<ResultMatcher> CreateDefaultResultMatcher();
42+
3443
// Struct to hold optional parameters for `CelTestContext`.
3544
struct CelTestContextOptions {
3645
// The source for the CEL expression to be evaluated in the test.
@@ -50,6 +59,10 @@ struct CelTestContextOptions {
5059
// This logic is handled by the test runner when it constructs the final
5160
// activation.
5261
absl::flat_hash_map<std::string, cel::expr::Value> custom_bindings;
62+
63+
// An optional result matcher to be used for assertions. If not provided, a
64+
// default result matcher will be used.
65+
std::unique_ptr<ResultMatcher> result_matcher = nullptr;
5366
};
5467

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

131+
// Returns the result matcher to be used for assertions.
132+
const ResultMatcher& result_matcher() const { return *result_matcher_; }
133+
118134
private:
119135
// Delete copy and move constructors.
120136
CelTestContext(const CelTestContext&) = delete;
@@ -128,12 +144,24 @@ class CelTestContext {
128144
cel_expression_builder,
129145
CelTestContextOptions options)
130146
: cel_test_context_options_(std::move(options)),
131-
cel_expression_builder_(std::move(cel_expression_builder)) {}
147+
cel_expression_builder_(std::move(cel_expression_builder)) {
148+
if (cel_test_context_options_.result_matcher) {
149+
result_matcher_ = std::move(cel_test_context_options_.result_matcher);
150+
} else {
151+
result_matcher_ = CreateDefaultResultMatcher();
152+
}
153+
}
132154

133155
CelTestContext(std::unique_ptr<const cel::Runtime> runtime,
134156
CelTestContextOptions options)
135157
: cel_test_context_options_(std::move(options)),
136-
runtime_(std::move(runtime)) {}
158+
runtime_(std::move(runtime)) {
159+
if (cel_test_context_options_.result_matcher) {
160+
result_matcher_ = std::move(cel_test_context_options_.result_matcher);
161+
} else {
162+
result_matcher_ = CreateDefaultResultMatcher();
163+
}
164+
}
137165

138166
// Configuration for the expression to be executed.
139167
CelTestContextOptions cel_test_context_options_;
@@ -148,6 +176,9 @@ class CelTestContext {
148176
// needed to generate Program. Users should either provide a runtime, or the
149177
// CelExpressionBuilder.
150178
std::unique_ptr<const cel::Runtime> runtime_;
179+
180+
// The result matcher to be used for assertions.
181+
std::unique_ptr<const ResultMatcher> result_matcher_;
151182
};
152183

153184
} // namespace cel::test
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <memory>
16+
17+
#include "cel/expr/eval.pb.h"
18+
#include "absl/status/status.h"
19+
#include "absl/strings/string_view.h"
20+
#include "common/internal/value_conversion.h"
21+
#include "common/value.h"
22+
#include "internal/testing.h"
23+
#include "testing/testrunner/cel_test_context.h"
24+
#include "testing/testrunner/result_matcher.h"
25+
#include "cel/expr/conformance/test/suite.pb.h"
26+
#include "google/protobuf/arena.h"
27+
#include "google/protobuf/descriptor.h"
28+
#include "google/protobuf/message.h"
29+
#include "google/protobuf/util/field_comparator.h"
30+
#include "google/protobuf/util/message_differencer.h"
31+
32+
namespace cel::test {
33+
34+
namespace {
35+
36+
using ValueProto = ::cel::expr::Value;
37+
using ::cel::expr::conformance::test::TestOutput;
38+
39+
bool IsEqual(const ValueProto& expected, const ValueProto& actual) {
40+
static auto* kFieldComparator = []() {
41+
auto* field_comparator = new google::protobuf::util::DefaultFieldComparator();
42+
field_comparator->set_treat_nan_as_equal(true);
43+
return field_comparator;
44+
}();
45+
static auto* kDifferencer = []() {
46+
auto* differencer = new google::protobuf::util::MessageDifferencer();
47+
differencer->set_message_field_comparison(
48+
google::protobuf::util::MessageDifferencer::EQUIVALENT);
49+
differencer->set_field_comparator(kFieldComparator);
50+
const auto* descriptor = cel::expr::MapValue::descriptor();
51+
const auto* entries_field = descriptor->FindFieldByName("entries");
52+
const auto* key_field =
53+
entries_field->message_type()->FindFieldByName("key");
54+
differencer->TreatAsMap(entries_field, key_field);
55+
return differencer;
56+
}();
57+
return kDifferencer->Compare(expected, actual);
58+
}
59+
60+
MATCHER_P(MatchesValue, expected, "") { return IsEqual(arg, expected); }
61+
62+
class DefaultResultMatcher : public cel::test::ResultMatcher {
63+
public:
64+
void Match(const ResultMatcherParams& params) const override {
65+
const TestOutput& output = params.expected_output;
66+
const auto& computed_output = params.computed_output;
67+
google::protobuf::Arena* arena = params.arena;
68+
69+
if (output.has_result_value()) {
70+
AssertValue(computed_output, output, params.test_context, arena);
71+
} else if (output.has_eval_error()) {
72+
AssertError(computed_output, output);
73+
} else if (output.has_unknown()) {
74+
ADD_FAILURE() << "Unknown assertions not implemented yet.";
75+
} else {
76+
ADD_FAILURE() << "Unexpected output kind.";
77+
}
78+
}
79+
80+
private:
81+
void AssertValue(const cel::Value& computed, const TestOutput& output,
82+
const CelTestContext& test_context,
83+
google::protobuf::Arena* arena) const {
84+
ValueProto expected_value_proto;
85+
const auto* descriptor_pool =
86+
test_context.runtime() != nullptr
87+
? test_context.runtime()->GetDescriptorPool()
88+
: google::protobuf::DescriptorPool::generated_pool();
89+
auto* message_factory = test_context.runtime() != nullptr
90+
? test_context.runtime()->GetMessageFactory()
91+
: google::protobuf::MessageFactory::generated_factory();
92+
93+
ValueProto computed_expr_value;
94+
ASSERT_OK_AND_ASSIGN(
95+
computed_expr_value,
96+
ToExprValue(computed, descriptor_pool, message_factory, arena));
97+
EXPECT_THAT(output.result_value(), MatchesValue(computed_expr_value));
98+
}
99+
100+
void AssertError(const cel::Value& computed, const TestOutput& output) const {
101+
if (!computed.IsError()) {
102+
ADD_FAILURE() << "Expected error but got value: "
103+
<< computed.DebugString();
104+
return;
105+
}
106+
absl::Status computed_status = computed.AsError()->ToStatus();
107+
// We selected the first error in the set for comparison because there is
108+
// only one runtime error that is reported even if there are multiple errors
109+
// in the critical path.
110+
ASSERT_TRUE(output.eval_error().errors_size() == 1)
111+
<< "Expected exactly one error but got: "
112+
<< output.eval_error().errors_size();
113+
ASSERT_EQ(computed_status.message(),
114+
output.eval_error().errors(0).message());
115+
}
116+
};
117+
} // namespace
118+
119+
std::unique_ptr<ResultMatcher> CreateDefaultResultMatcher() {
120+
return std::make_unique<DefaultResultMatcher>();
121+
}
122+
} // namespace cel::test
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_RESULT_MATCHER_H_
16+
#define THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_RESULT_MATCHER_H_
17+
18+
#include "common/value.h"
19+
#include "cel/expr/conformance/test/suite.pb.h"
20+
#include "google/protobuf/arena.h"
21+
22+
namespace cel::test {
23+
24+
// Forward declare CelTestContext to avoid circular includes.
25+
class CelTestContext;
26+
27+
// Parameters passed to the ResultMatcher for performing assertions.
28+
struct ResultMatcherParams {
29+
const cel::expr::conformance::test::TestOutput& expected_output;
30+
const CelTestContext& test_context;
31+
const cel::Value& computed_output;
32+
google::protobuf::Arena* arena;
33+
};
34+
35+
// Interface for a custom result matcher.
36+
class ResultMatcher {
37+
public:
38+
virtual ~ResultMatcher() = default;
39+
virtual void Match(const ResultMatcherParams& params) const = 0;
40+
};
41+
42+
} // namespace cel::test
43+
44+
#endif // THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_RESULT_MATCHER_H_

0 commit comments

Comments
 (0)