Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f204194

Browse filesBrowse files
CEL Dev Teamcopybara-github
authored andcommitted
Enable coverage collection via the runner library for a single test.
PiperOrigin-RevId: 824348140
1 parent a69f85b commit f204194
Copy full SHA for f204194

File tree

Expand file treeCollapse file tree

7 files changed

+260
-116
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+260
-116
lines changed
Open diff view settings
Collapse file

‎testing/testrunner/BUILD‎

Copy file name to clipboardExpand all lines: testing/testrunner/BUILD
+17-3Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ cc_library(
3636
deps = [
3737
":cel_expression_source",
3838
":cel_test_context",
39+
":coverage_index",
40+
":coverage_reporting",
3941
"//checker:validation_result",
4042
"//common:ast",
4143
"//common:ast_proto",
@@ -49,10 +51,9 @@ cc_library(
4951
"//internal:testing_no_main",
5052
"//runtime",
5153
"//runtime:activation",
52-
"//tools:cel_unparser",
53-
"//tools:navigable_ast",
5454
"@com_google_absl//absl/functional:overload",
5555
"@com_google_absl//absl/status",
56+
"@com_google_absl//absl/status:status_matchers",
5657
"@com_google_absl//absl/status:statusor",
5758
"@com_google_absl//absl/strings",
5859
"@com_google_absl//absl/strings:string_view",
@@ -120,6 +121,19 @@ cc_test(
120121
],
121122
)
122123

124+
cc_library(
125+
name = "coverage_reporting",
126+
srcs = ["coverage_reporting.cc"],
127+
hdrs = ["coverage_reporting.h"],
128+
deps = [
129+
":coverage_index",
130+
"//internal:testing_no_main",
131+
"@com_google_absl//absl/log:absl_log",
132+
"@com_google_absl//absl/strings",
133+
"@com_google_absl//absl/strings:str_format",
134+
],
135+
)
136+
123137
cc_library(
124138
name = "runner",
125139
srcs = ["runner_bin.cc"],
@@ -128,6 +142,7 @@ cc_library(
128142
":cel_test_context",
129143
":cel_test_factories",
130144
":coverage_index",
145+
":coverage_reporting",
131146
":runner_lib",
132147
"//eval/public:cel_expression",
133148
"//internal:status_macros",
@@ -139,7 +154,6 @@ cc_library(
139154
"@com_google_absl//absl/status",
140155
"@com_google_absl//absl/status:statusor",
141156
"@com_google_absl//absl/strings",
142-
"@com_google_absl//absl/strings:str_format",
143157
"@com_google_cel_spec//proto/cel/expr:checked_cc_proto",
144158
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
145159
"@com_google_protobuf//:protobuf",
Collapse file

‎testing/testrunner/cel_test_context.h‎

Copy file name to clipboardExpand all lines: testing/testrunner/cel_test_context.h
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class CelTestContext {
102102
return custom_bindings_;
103103
}
104104

105+
bool enable_coverage() const { return enable_coverage_; }
106+
105107
// Allows the runner to inject the expression source
106108
// parsed from command-line flags.
107109
void SetExpressionSource(CelExpressionSource source) {
@@ -128,6 +130,9 @@ class CelTestContext {
128130
activation_factory_ = std::move(activation_factory);
129131
}
130132

133+
// Allows the runner to enable coverage collection.
134+
void SetEnableCoverage(bool enable) { enable_coverage_ = enable; }
135+
131136
const CelActivationFactoryFn& activation_factory() const {
132137
return activation_factory_;
133138
}
@@ -185,6 +190,9 @@ class CelTestContext {
185190

186191
CelActivationFactoryFn activation_factory_;
187192
AssertFn assert_fn_;
193+
194+
// Whether to enable coverage collection.
195+
bool enable_coverage_ = false;
188196
};
189197

190198
} // namespace cel::test
Collapse file
+124Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 "testing/testrunner/coverage_reporting.h"
16+
17+
#include <cerrno>
18+
#include <cstdlib>
19+
#include <cstring>
20+
#include <fstream>
21+
#include <string>
22+
23+
#include "absl/log/absl_log.h"
24+
#include "absl/strings/str_cat.h"
25+
#include "absl/strings/str_format.h"
26+
#include "absl/strings/str_join.h"
27+
#include "absl/strings/str_replace.h"
28+
#include "absl/strings/string_view.h"
29+
#include "internal/testing.h"
30+
#include "testing/testrunner/coverage_index.h"
31+
32+
namespace cel::test {
33+
void CoverageReportingEnvironment::TearDown() {
34+
CoverageIndex::CoverageReport coverage_report =
35+
coverage_index_.GetCoverageReport();
36+
testing::Test::RecordProperty("CEL Expression",
37+
coverage_report.cel_expression);
38+
std::cout << "CEL Expression: " << coverage_report.cel_expression;
39+
if (coverage_report.nodes == 0) {
40+
testing::Test::RecordProperty("CEL Coverage", "No coverage stats found");
41+
std::cout << "CEL Coverage: " << "No coverage stats found";
42+
return;
43+
}
44+
45+
// Log Node Coverage results
46+
double node_coverage = static_cast<double>(coverage_report.covered_nodes) /
47+
static_cast<double>(coverage_report.nodes) * 100.0;
48+
std::string node_coverage_string =
49+
absl::StrFormat("%.2f%% (%d out of %d nodes covered)", node_coverage,
50+
coverage_report.covered_nodes, coverage_report.nodes);
51+
testing::Test::RecordProperty("AST Node Coverage", node_coverage_string);
52+
std::cout << "AST Node Coverage: " << node_coverage_string;
53+
if (!coverage_report.unencountered_nodes.empty()) {
54+
testing::Test::RecordProperty(
55+
"Interesting Unencountered Nodes",
56+
absl::StrJoin(coverage_report.unencountered_nodes, "\n"));
57+
std::cout << "Interesting Unencountered Nodes: "
58+
<< absl::StrJoin(coverage_report.unencountered_nodes, "\n");
59+
}
60+
61+
// Log Branch Coverage results
62+
double branch_coverage = 0.0;
63+
if (coverage_report.branches > 0) {
64+
branch_coverage =
65+
static_cast<double>(coverage_report.covered_boolean_outcomes) /
66+
static_cast<double>(coverage_report.branches) * 100.0;
67+
}
68+
std::string branch_coverage_string = absl::StrFormat(
69+
"%.2f%% (%d out of %d branch outcomes covered)", branch_coverage,
70+
coverage_report.covered_boolean_outcomes, coverage_report.branches);
71+
testing::Test::RecordProperty("AST Branch Coverage", branch_coverage_string);
72+
std::cout << "AST Branch Coverage: " << branch_coverage_string;
73+
if (!coverage_report.unencountered_branches.empty()) {
74+
testing::Test::RecordProperty(
75+
"Interesting Unencountered Branch Paths",
76+
absl::StrJoin(coverage_report.unencountered_branches, "\n"));
77+
std::cout << "Interesting Unencountered Branch Paths: "
78+
<< absl::StrJoin(coverage_report.unencountered_branches,
79+
"\n");
80+
}
81+
if (!coverage_report.dot_graph.empty()) {
82+
WriteDotGraphToArtifact(coverage_report.dot_graph);
83+
}
84+
}
85+
86+
void CoverageReportingEnvironment::WriteDotGraphToArtifact(
87+
absl::string_view dot_graph) {
88+
// Save DOT graph to file in TEST_UNDECLARED_OUTPUTS_DIR or default dir
89+
const char* outputs_dir_env = std::getenv("TEST_UNDECLARED_OUTPUTS_DIR");
90+
// For non-Bazel/Blaze users, we write to a subdirectory under the current
91+
// working directory.
92+
// NOMUTANTS --cel_artifacts is for non-Bazel/Blaze users only so not
93+
// needed to test in our case.
94+
std::string outputs_dir =
95+
(outputs_dir_env == nullptr) ? "cel_artifacts" : outputs_dir_env;
96+
std::string coverage_dir = absl::StrCat(outputs_dir, "/cel_test_coverage");
97+
// Creates the directory to store CEL test coverage artifacts.
98+
// The second argument, `0755`, sets the directory's permissions in octal
99+
// format, which is a standard for file system operations. It grants:
100+
// - Owner: read, write, and execute permissions (7 = 4+2+1).
101+
// - Group: read and execute permissions (5 = 4+1).
102+
// - Others: read and execute permissions (5 = 4+1).
103+
// This gives the owner full control while allowing other users to access
104+
// the generated artifacts.
105+
int mkdir_result = mkdir(coverage_dir.c_str(), 0755);
106+
// If mkdir fails, it sets the global 'errno' variable to an error code
107+
// indicating the reason. We check this code to specifically ignore the
108+
// EEXIST error, which just means the directory already exists (this is not
109+
// a real failure we need to warn about).
110+
if (mkdir_result == 0 || errno == EEXIST) {
111+
std::string graph_path = absl::StrCat(coverage_dir, "/coverage_graph.txt");
112+
std::ofstream out(graph_path);
113+
if (out.is_open()) {
114+
out << dot_graph;
115+
out.close();
116+
} else {
117+
ABSL_LOG(WARNING) << "Failed to open file for writing: " << graph_path;
118+
}
119+
} else {
120+
ABSL_LOG(WARNING) << "Failed to create directory: " << coverage_dir
121+
<< " (reason: " << strerror(errno) << ")";
122+
}
123+
}
124+
} // namespace cel::test
Collapse file
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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_COVERAGE_REPORTING_H_
16+
#define THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_COVERAGE_REPORTING_H_
17+
18+
#include "absl/strings/string_view.h"
19+
#include "internal/testing.h"
20+
#include "testing/testrunner/coverage_index.h"
21+
22+
namespace cel::test {
23+
// A Google Test Environment that reports CEL coverage results in its TearDown
24+
// phase.
25+
//
26+
// This class encapsulates the logic for calculating coverage statistics and
27+
// logging them as test properties.
28+
class CoverageReportingEnvironment : public testing::Environment {
29+
public:
30+
explicit CoverageReportingEnvironment(CoverageIndex& coverage_index)
31+
: coverage_index_(coverage_index) {};
32+
33+
// Called by the Google Test framework after all tests have run.
34+
void TearDown() override;
35+
36+
private:
37+
// Helper function to write the DOT graph to a test artifact file.
38+
void WriteDotGraphToArtifact(absl::string_view dot_graph);
39+
40+
CoverageIndex& coverage_index_;
41+
};
42+
} // namespace cel::test
43+
#endif // THIRD_PARTY_CEL_CPP_TESTING_TESTRUNNER_COVERAGE_REPORTING_H_

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.