///|
/// A covered-item count and its instrumented total.
pub(all) struct CoverageCount {
covered : Int
total : Int
} derive(Debug, Eq)
///|
/// Line, branch, and function totals for a report or source file.
pub(all) struct CoverageSummary {
lines : CoverageCount
branches : CoverageCount
functions : CoverageCount
} derive(Debug, Eq)
///|
/// A summary associated with one source path.
pub(all) struct FileSummary {
path : String
summary : CoverageSummary
} derive(Debug, Eq)
///|
/// Return the percentage in the inclusive range 0..100.
///
/// A metric with no instrumented items is defined as 100%, matching the
/// practical gate semantics that an absent metric cannot fail a build.
pub fn CoverageCount::percentage(self : CoverageCount) -> Double {
if self.total == 0 {
100.0
} else {
self.covered.to_double() * 100.0 / self.total.to_double()
}
}
///|
fn summarize_canonical_file(file : FileCoverage) -> CoverageSummary {
let lines = {
covered: file.lines.fold(init=0, (count, item) => {
if item.is_covered() {
count + 1
} else {
count
}
}),
total: file.lines.length(),
}
let branches = {
covered: file.branches.fold(init=0, (count, item) => {
if item.is_covered() {
count + 1
} else {
count
}
}),
total: file.branches.length(),
}
let functions = {
covered: file.functions.fold(init=0, (count, item) => {
if item.is_covered() {
count + 1
} else {
count
}
}),
total: file.functions.length(),
}
{ lines, branches, functions }
}
///|
fn add_counts(left : CoverageCount, right : CoverageCount) -> CoverageCount {
{ covered: left.covered + right.covered, total: left.total + right.total }
}
///|
fn add_summaries(
left : CoverageSummary,
right : CoverageSummary,
) -> CoverageSummary {
{
lines: add_counts(left.lines, right.lines),
branches: add_counts(left.branches, right.branches),
functions: add_counts(left.functions, right.functions),
}
}
///|
fn empty_summary() -> CoverageSummary {
{
lines: { covered: 0, total: 0 },
branches: { covered: 0, total: 0 },
functions: { covered: 0, total: 0 },
}
}
///|
/// Summarize one source file after coalescing duplicate coverage identities.
pub fn summarize_file(
file : FileCoverage,
) -> CoverageSummary raise CoverageError {
let report : CoverageReport = { files: [file] }
let canonical = canonicalize_report(report)
summarize_canonical_file(canonical.files[0])
}
///|
/// Summarize a whole report after coalescing duplicate paths and entries.
pub fn summarize(
report : CoverageReport,
) -> CoverageSummary raise CoverageError {
let canonical = canonicalize_report(report)
canonical.files.fold(init=empty_summary(), (summary, file) => {
add_summaries(summary, summarize_canonical_file(file))
})
}
///|
/// Produce deterministic per-file summaries sorted by normalized path.
pub fn summarize_files(
report : CoverageReport,
) -> Array[FileSummary] raise CoverageError {
let canonical = canonicalize_report(report)
[
for file in canonical.files => {
{ path: file.path, summary: summarize_canonical_file(file) }
}
]
}