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;
4447using ::google::api::expr::runtime::Instrumentation;
4548using ::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+
4776const 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;
0 commit comments