Skip to content
Merged
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
2 changes: 2 additions & 0 deletions testing/testrunner/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ cc_test(
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:string_view",
"@com_google_cel_spec//proto/cel/expr/conformance/proto3:test_all_types_cc_proto",
"@com_google_cel_spec//proto/cel/expr/conformance/test:suite_cc_proto",
"@com_google_protobuf//:protobuf",
],
)
Expand Down Expand Up @@ -163,6 +164,7 @@ cc_library(
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",
],
)
60 changes: 57 additions & 3 deletions testing/testrunner/coverage_index.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "common/ast.h"
#include "common/value.h"
#include "eval/compiler/cel_expression_builder_flat_impl.h"
Expand All @@ -44,6 +47,32 @@ using ::google::api::expr::runtime::CelExpressionBuilder;
using ::google::api::expr::runtime::Instrumentation;
using ::google::api::expr::runtime::InstrumentationFactory;

std::string EscapeSpecialCharacters(absl::string_view expr_text) {
return absl::StrReplaceAll(expr_text, {{"\\\"", "\""},
{"\"", "\\\""},
{"\n", "\\n"},
{"||", " \\| \\| "},
{"<", "\\<"},
{">", "\\>"},
{"{", "\\{"},
{"}", "\\}"}});
}

std::string KindToString(const NavigableProtoAstNode& node) {
if (node.parent_relation() != ChildKind::kUnspecified &&
node.parent()->expr()->has_comprehension_expr()) {
const cel::expr::Expr::Comprehension& comp =
node.parent()->expr()->comprehension_expr();
if (node.expr()->id() == comp.iter_range().id()) return "IterRange";
if (node.expr()->id() == comp.accu_init().id()) return "AccuInit";
if (node.expr()->id() == comp.loop_condition().id()) return "LoopCondition";
if (node.expr()->id() == comp.loop_step().id()) return "LoopStep";
if (node.expr()->id() == comp.result().id()) return "Result";
}

return absl::StrCat(NodeKindName(node.node_kind()), " Node");
}

const Type* absl_nullable FindCheckerType(const CheckedExpr& expr,
int64_t expr_id) {
if (auto it = expr.type_map().find(expr_id); it != expr.type_map().end()) {
Expand All @@ -69,7 +98,7 @@ void TraverseAndCalculateCoverage(
const absl::flat_hash_map<int64_t, CoverageIndex::NodeCoverageStats>&
stats_map,
bool log_unencountered, std::string preceeding_tabs,
CoverageIndex::CoverageReport& report) {
CoverageIndex::CoverageReport& report, std::string& dot_graph) {
int64_t node_id = node.expr()->id();

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

absl::string_view node_coverage_style = kUncoveredNodeStyle;
if (stats.covered) {
if (is_interesting_bool_node) {
if (stats.has_true_branch && stats.has_false_branch) {
node_coverage_style = kCompletelyCoveredNodeStyle;
} else {
node_coverage_style = kPartiallyCoveredNodeStyle;
}
} else {
node_coverage_style = kCompletelyCoveredNodeStyle;
}
}
std::string escaped_expr_text = EscapeSpecialCharacters(expr_text);
dot_graph += absl::StrFormat(
"%d [shape=record, %s, label=\"{<1> exprID: %d | <2> %s} | <3> %s\"];\n",
node_id, node_coverage_style, node_id, KindToString(node),
escaped_expr_text);

bool node_covered = stats.covered;
if (node_covered) {
report.covered_nodes++;
Expand Down Expand Up @@ -116,8 +163,10 @@ void TraverseAndCalculateCoverage(
}

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

Expand Down Expand Up @@ -150,8 +199,13 @@ CoverageIndex::CoverageReport CoverageIndex::GetCoverageReport() const {
if (node_coverage_stats_.empty()) {
return report;
}

std::string dot_graph = std::string(kDigraphHeader);
TraverseAndCalculateCoverage(checked_expr_, navigable_ast_.Root(),
node_coverage_stats_, true, "", report);
node_coverage_stats_, true, "", report,
dot_graph);
dot_graph += "}\n";
report.dot_graph = dot_graph;
report.cel_expression =
google::api::expr::Unparse(checked_expr_).value_or("");
return report;
Expand Down
9 changes: 9 additions & 0 deletions testing/testrunner/coverage_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,20 @@

#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "common/value.h"
#include "eval/public/cel_expression.h"
#include "runtime/runtime.h"
#include "tools/navigable_ast.h"

namespace cel::test {
inline constexpr absl::string_view kDigraphHeader = "digraph {\n";
inline constexpr absl::string_view kUncoveredNodeStyle =
R"(color="indianred2", style=filled)";
inline constexpr absl::string_view kPartiallyCoveredNodeStyle =
R"(color="lightyellow", style=filled)";
inline constexpr absl::string_view kCompletelyCoveredNodeStyle =
R"(color="lightgreen", style=filled)";

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

// Initializes the coverage index with the given checked expression.
Expand Down
Loading