///|
fn cobertura_rate(count : CoverageCount) -> String {
(count.percentage() / 100.0).to_string()
}
///|
fn cobertura_method_rate(function : CoverageFunction) -> String {
if function.hits > 0 {
"1"
} else {
"0"
}
}
///|
fn branches_by_line(file : FileCoverage) -> Map[Int, Array[CoverageBranch]] {
let grouped : Map[Int, Array[CoverageBranch]] = Map([])
for branch in file.branches {
grouped.get_or_init(branch.line, () => []).push(branch)
}
grouped
}
///|
fn line_hits_map(file : FileCoverage) -> Map[Int, Int] {
let hits : Map[Int, Int] = Map([])
for line in file.lines {
hits[line.line] = line.hits
}
for branch in file.branches {
if !hits.contains(branch.line) {
hits[branch.line] = 0
}
}
hits
}
///|
fn write_cobertura_methods(output : StringBuilder, file : FileCoverage) -> Unit {
let functions = [
for function in file.functions if function.line is Some(_) => function
]
functions.sort_by(compare_functions)
if functions.is_empty() {
output.write_string(" \n")
return
}
output.write_string(" \n")
for function in functions {
guard function.line is Some(line) else { continue }
output.write_string(
" \n",
)
output.write_string(" \n")
output.write_string(
" \n",
)
output.write_string(" \n")
output.write_string(" \n")
}
output.write_string(" \n")
}
///|
fn write_cobertura_line(
output : StringBuilder,
line : Int,
hits : Int,
branches : Array[CoverageBranch]?,
) -> Unit {
match branches {
None =>
output.write_string(
" \n",
)
Some(items) => {
let covered = items.fold(init=0, (count, branch) => {
if branch.is_covered() {
count + 1
} else {
count
}
})
let total = items.length()
let percent = if total == 0 { 100 } else { covered * 100 / total }
output.write_string(
" \n",
)
output.write_string(" \n")
for branch in items {
let coverage = if branch.is_covered() { 100 } else { 0 }
output.write_string(
" \n",
)
}
output.write_string(" \n")
output.write_string(" \n")
}
}
}
///|
fn write_cobertura_class(output : StringBuilder, file : FileCoverage) -> Unit {
let summary = summarize_canonical_file(file)
let escaped_path = xml_escape_attribute(file.path)
output.write_string(
" \n",
)
write_cobertura_methods(output, file)
output.write_string(" \n")
let hits = line_hits_map(file)
let grouped_branches = branches_by_line(file)
let line_numbers = [ for line, _ in hits => line ]
line_numbers.sort()
for line in line_numbers {
write_cobertura_line(output, line, hits[line], grouped_branches.get(line))
}
output.write_string(" \n")
output.write_string(" \n")
}
///|
/// Encode a report as deterministic Cobertura XML.
///
/// Cobertura carries aggregate branch conditions rather than LCOV branch
/// identities. Mooncov writes each branch as a condition and guarantees that
/// line and branch summary counts survive a mooncov encode/decode round trip.
pub fn to_cobertura(
report : CoverageReport,
source_root? : String = ".",
) -> String raise CoverageError {
let canonical = canonicalize_report(report)
let summary = canonical.files.fold(init=empty_summary(), (acc, file) => {
add_summaries(acc, summarize_canonical_file(file))
})
let output = StringBuilder()
output.write_string("\n")
output.write_string(
"\n",
)
output.write_string(" \n")
output.write_string(
" \{xml_escape_attribute(source_root)}\n",
)
output.write_string(" \n")
output.write_string(" \n")
output.write_string(
" \n",
)
output.write_string(" \n")
for file in canonical.files {
write_cobertura_class(output, file)
}
output.write_string(" \n")
output.write_string(" \n")
output.write_string(" \n")
output.write_string("\n")
output.to_string()
}