Skip to content

Commit 5fc04d1

Browse files
CEL Dev Teamcopybara-github
authored andcommitted
Support for Dot graph via graphviz
PiperOrigin-RevId: 808883045
1 parent a5487ff commit 5fc04d1

File tree

5 files changed

+279
-12
lines changed

5 files changed

+279
-12
lines changed

testing/testrunner/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ cc_test(
108108
"@com_google_absl//absl/status:statusor",
109109
"@com_google_absl//absl/strings:string_view",
110110
"@com_google_cel_spec//proto/cel/expr/conformance/proto3:test_all_types_cc_proto",
111+
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
111112
"@com_google_protobuf//:protobuf",
112113
],
113114
)
@@ -163,6 +164,7 @@ cc_library(
163164
"@com_google_absl//absl/status",
164165
"@com_google_absl//absl/status:statusor",
165166
"@com_google_absl//absl/strings",
167+
"@com_google_absl//absl/strings:str_format",
166168
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",
167169
],
168170
)

testing/testrunner/coverage_index.cc

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
#include "absl/status/status.h"
2525
#include "absl/status/statusor.h"
2626
#include "absl/strings/str_cat.h"
27+
#include "absl/strings/str_format.h"
28+
#include "absl/strings/str_replace.h"
29+
#include "absl/strings/string_view.h"
2730
#include "common/ast.h"
2831
#include "common/value.h"
2932
#include "eval/compiler/cel_expression_builder_flat_impl.h"
@@ -44,6 +47,32 @@ using ::google::api::expr::runtime::CelExpressionBuilder;
4447
using ::google::api::expr::runtime::Instrumentation;
4548
using ::google::api::expr::runtime::InstrumentationFactory;
4649

50+
std::string EscapeSpecialCharacters(absl::string_view expr_text) {
51+
return absl::StrReplaceAll(expr_text, {{"\\\"", "\""},
52+
{"\"", "\\\""},
53+
{"\n", "\\n"},
54+
{"||", " \\| \\| "},
55+
{"<", "\\<"},
56+
{">", "\\>"},
57+
{"{", "\\{"},
58+
{"}", "\\}"}});
59+
}
60+
61+
std::string KindToString(const NavigableProtoAstNode& node) {
62+
if (node.parent_relation() != ChildKind::kUnspecified &&
63+
node.parent()->expr()->has_comprehension_expr()) {
64+
const cel::expr::Expr::Comprehension& comp =
65+
node.parent()->expr()->comprehension_expr();
66+
if (node.expr()->id() == comp.iter_range().id()) return "IterRange";
67+
if (node.expr()->id() == comp.accu_init().id()) return "AccuInit";
68+
if (node.expr()->id() == comp.loop_condition().id()) return "LoopCondition";
69+
if (node.expr()->id() == comp.loop_step().id()) return "LoopStep";
70+
if (node.expr()->id() == comp.result().id()) return "Result";
71+
}
72+
73+
return absl::StrCat(NodeKindName(node.node_kind()), " Node");
74+
}
75+
4776
const Type* absl_nullable FindCheckerType(const CheckedExpr& expr,
4877
int64_t expr_id) {
4978
if (auto it = expr.type_map().find(expr_id); it != expr.type_map().end()) {
@@ -69,7 +98,7 @@ void TraverseAndCalculateCoverage(
6998
const absl::flat_hash_map<int64_t, CoverageIndex::NodeCoverageStats>&
7099
stats_map,
71100
bool log_unencountered, std::string preceeding_tabs,
72-
CoverageIndex::CoverageReport& report) {
101+
CoverageIndex::CoverageReport& report, std::string& dot_graph) {
73102
int64_t node_id = node.expr()->id();
74103

75104
const CoverageIndex::NodeCoverageStats& stats = stats_map.at(node_id);
@@ -84,6 +113,24 @@ void TraverseAndCalculateCoverage(
84113
(!node.expr()->has_call_expr() ||
85114
node.expr()->call_expr().function() != "cel.@block");
86115

116+
absl::string_view node_coverage_style = kUncoveredNodeStyle;
117+
if (stats.covered) {
118+
if (is_interesting_bool_node) {
119+
if (stats.has_true_branch && stats.has_false_branch) {
120+
node_coverage_style = kCompletelyCoveredNodeStyle;
121+
} else {
122+
node_coverage_style = kPartiallyCoveredNodeStyle;
123+
}
124+
} else {
125+
node_coverage_style = kCompletelyCoveredNodeStyle;
126+
}
127+
}
128+
std::string escaped_expr_text = EscapeSpecialCharacters(expr_text);
129+
dot_graph += absl::StrFormat(
130+
"%d [shape=record, %s, label=\"{<1> exprID: %d | <2> %s} | <3> %s\"];\n",
131+
node_id, node_coverage_style, node_id, KindToString(node),
132+
escaped_expr_text);
133+
87134
bool node_covered = stats.covered;
88135
if (node_covered) {
89136
report.covered_nodes++;
@@ -116,8 +163,10 @@ void TraverseAndCalculateCoverage(
116163
}
117164

118165
for (const auto* child : node.children()) {
166+
dot_graph += absl::StrFormat("%d -> %d;\n", node_id, child->expr()->id());
119167
TraverseAndCalculateCoverage(checked_expr, *child, stats_map,
120-
log_unencountered, preceeding_tabs, report);
168+
log_unencountered, preceeding_tabs, report,
169+
dot_graph);
121170
}
122171
}
123172

@@ -150,8 +199,13 @@ CoverageIndex::CoverageReport CoverageIndex::GetCoverageReport() const {
150199
if (node_coverage_stats_.empty()) {
151200
return report;
152201
}
202+
203+
std::string dot_graph = std::string(kDigraphHeader);
153204
TraverseAndCalculateCoverage(checked_expr_, navigable_ast_.Root(),
154-
node_coverage_stats_, true, "", report);
205+
node_coverage_stats_, true, "", report,
206+
dot_graph);
207+
dot_graph += "}\n";
208+
report.dot_graph = dot_graph;
155209
report.cel_expression =
156210
google::api::expr::Unparse(checked_expr_).value_or("");
157211
return report;

testing/testrunner/coverage_index.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@
2121

2222
#include "absl/container/flat_hash_map.h"
2323
#include "absl/status/status.h"
24+
#include "absl/strings/string_view.h"
2425
#include "common/value.h"
2526
#include "eval/public/cel_expression.h"
2627
#include "runtime/runtime.h"
2728
#include "tools/navigable_ast.h"
2829

2930
namespace cel::test {
31+
inline constexpr absl::string_view kDigraphHeader = "digraph {\n";
32+
inline constexpr absl::string_view kUncoveredNodeStyle =
33+
R"(color="indianred2", style=filled)";
34+
inline constexpr absl::string_view kPartiallyCoveredNodeStyle =
35+
R"(color="lightyellow", style=filled)";
36+
inline constexpr absl::string_view kCompletelyCoveredNodeStyle =
37+
R"(color="lightgreen", style=filled)";
3038

3139
// `CoverageIndex` is a utility for tracking expression coverage based on the
3240
// Abstract Syntax Tree (AST) of a `cel::expr::CheckedExpr`.
@@ -65,6 +73,7 @@ class CoverageIndex {
6573
int64_t covered_boolean_outcomes = 0;
6674
std::vector<std::string> unencountered_nodes;
6775
std::vector<std::string> unencountered_branches;
76+
std::string dot_graph;
6877
};
6978

7079
// Initializes the coverage index with the given checked expression.

0 commit comments

Comments
 (0)