///|
/// Coverage report for one behavior tree execution trace.
pub(all) struct TraceCoverage {
total_nodes : Int
visited_nodes : Array[String]
missed_nodes : Array[String]
success_nodes : Array[String]
failure_nodes : Array[String]
running_nodes : Array[String]
} derive(Debug, Eq)
///|
pub fn TraceCoverage::visited_count(self : TraceCoverage) -> Int {
self.visited_nodes.length()
}
///|
pub fn TraceCoverage::missed_count(self : TraceCoverage) -> Int {
self.missed_nodes.length()
}
///|
pub fn TraceCoverage::is_full(self : TraceCoverage) -> Bool {
self.missed_nodes.length() == 0
}
///|
pub fn TraceCoverage::percent(self : TraceCoverage) -> Int {
if self.total_nodes == 0 {
100
} else {
self.visited_nodes.length() * 100 / self.total_nodes
}
}
///|
pub fn TraceCoverage::summary(self : TraceCoverage) -> String {
"coverage=" +
self.percent().to_string() +
"%, visited=" +
self.visited_nodes.length().to_string() +
"/" +
self.total_nodes.to_string() +
", missed=" +
self.missed_nodes.length().to_string()
}
///|
pub fn TraceCoverage::lines(self : TraceCoverage) -> Array[String] {
let out = Array::new()
out.push(self.summary())
out.push("visited: " + join_strings(self.visited_nodes, ", "))
if self.missed_nodes.length() > 0 {
out.push("missed: " + join_strings(self.missed_nodes, ", "))
}
if self.success_nodes.length() > 0 {
out.push("success: " + join_strings(self.success_nodes, ", "))
}
if self.failure_nodes.length() > 0 {
out.push("failure: " + join_strings(self.failure_nodes, ", "))
}
if self.running_nodes.length() > 0 {
out.push("running: " + join_strings(self.running_nodes, ", "))
}
out
}
///|
pub fn coverage_from_trace(
tree : BehaviorTree,
events : Array[TickEvent],
) -> TraceCoverage {
let visited = Array::new()
let success = Array::new()
let failure = Array::new()
let running = Array::new()
let mut i = 0
while i < events.length() {
let id = events[i].node_id
if tree.has_node(id) && !string_array_contains(visited, id) {
visited.push(id)
}
if tree.has_node(id) {
match events[i].status {
Success =>
if !string_array_contains(success, id) {
success.push(id)
}
Failure =>
if !string_array_contains(failure, id) {
failure.push(id)
}
Running =>
if !string_array_contains(running, id) {
running.push(id)
}
}
}
i = i + 1
}
let missed = Array::new()
let mut n = 0
while n < tree.nodes.length() {
if !string_array_contains(visited, tree.nodes[n].id) {
missed.push(tree.nodes[n].id)
}
n = n + 1
}
{
total_nodes: tree.node_count(),
visited_nodes: visited,
missed_nodes: missed,
success_nodes: success,
failure_nodes: failure,
running_nodes: running,
}
}
///|
pub fn fixture_coverage(name : String) -> Result[TraceCoverage, BtError] {
match load_fixture(name) {
Ok(doc) => {
let engine = new_engine(doc.tree, blackboard=doc.blackboard)
match engine.run_until_done(max_ticks=32) {
Ok(_) => Ok(coverage_from_trace(doc.tree, engine.trace))
Err(err) => Err(InvalidTree(err.message()))
}
}
Err(err) => Err(err)
}
}
///|
pub(all) struct CatalogCoverage {
fixtures : Int
full_coverage : Int
partial_coverage : Int
lines : Array[String]
} derive(Debug, Eq)
///|
pub fn CatalogCoverage::summary(self : CatalogCoverage) -> String {
"fixtures=" +
self.fixtures.to_string() +
", full=" +
self.full_coverage.to_string() +
", partial=" +
self.partial_coverage.to_string()
}
///|
pub fn fixture_catalog_coverage() -> CatalogCoverage {
let specs = fixture_catalog()
let lines = Array::new()
let mut full = 0
let mut partial = 0
let mut i = 0
while i < specs.length() {
match fixture_coverage(specs[i].name) {
Ok(report) => {
if report.is_full() {
full = full + 1
} else {
partial = partial + 1
}
lines.push(specs[i].name + ": " + report.summary())
}
Err(err) => {
partial = partial + 1
lines.push(specs[i].name + ": error " + err.message())
}
}
i = i + 1
}
{
fixtures: specs.length(),
full_coverage: full,
partial_coverage: partial,
lines,
}
}
///|
pub fn coverage_markdown(name : String, coverage : TraceCoverage) -> String {
let lines = Array::new()
lines.push("## Coverage: " + name)
lines.push("")
let raw = coverage.lines()
let mut i = 0
while i < raw.length() {
lines.push("- " + raw[i])
i = i + 1
}
join_strings(lines, "\n")
}