Coverage report parsing, merging, filtering, gating, and conversion for MoonBit projects.
Dependencies
| Capability | Status |
|---|---|
| LCOV parse and deterministic write | Supported |
| Coveralls source_files JSON parse and write | Supported |
| Line, branch, and function model | Supported |
| Duplicate-safe multi-report merge | Supported |
| Path normalization and * / ? / ** filters | Supported |
| Overall and per-file summaries | Supported |
| Line, branch, and function threshold gate | Supported |
| Baseline comparison and regression allowances | Supported |
| Unified-diff changed-line coverage | Supported |
| Markdown and full-fidelity JSON output | Supported |
| Cobertura XML parse and write | Supported |
moon add Xpeng/mooncovimport {
"Xpeng/mooncov",
}moon check --target all --deny-warn
moon test --target all --deny-warn///|
test "README: parse and summarize LCOV" {
let input =
#|TN:unit
#|SF:src/lib.mbt
#|FN:1,run
#|FNDA:2,run
#|DA:1,2
#|DA:2,0
#|BRDA:2,0,0,1
#|BRDA:2,0,1,0
#|end_of_record
#|
let report = @mooncov.parse_lcov(input)
let summary = @mooncov.summarize(report)
inspect(summary.lines.covered, content="1")
inspect(summary.lines.total, content="2")
inspect(summary.branches.covered, content="1")
inspect(summary.functions.covered, content="1")
}///|
test "README: merge and gate reports" {
let first = @mooncov.parse_lcov(
"SF:src/lib.mbt\nDA:1,1\nDA:2,0\nend_of_record\n",
)
let second = @mooncov.parse_lcov(
"SF:src/lib.mbt\nDA:1,2\nDA:2,1\nend_of_record\n",
)
let merged = @mooncov.merge_reports([first, second])
inspect(merged.files[0].lines[0].hits, content="3")
inspect(merged.files[0].lines[1].hits, content="1")
let gate = @mooncov.check_thresholds(
merged,
@mooncov.CoverageThresholds::new(lines=100.0),
)
assert_true(gate.passed)
}///|
test "README: baseline regression policy" {
let baseline = @mooncov.parse_lcov(
"SF:src/a.mbt\nDA:1,1\nDA:2,1\nend_of_record\n",
)
let current = @mooncov.parse_lcov(
"SF:src/a.mbt\nDA:1,1\nDA:2,0\nend_of_record\n",
)
let comparison = @mooncov.compare_reports(baseline, current)
inspect(comparison.lines.percentage_delta, content="-50")
let result = @mooncov.check_regressions(
baseline,
current,
@mooncov.RegressionLimits::new(lines=50.0),
)
assert_true(result.passed)
}///|
test "README: normalize and filter paths" {
let report = @mooncov.parse_lcov(
"SF:D:\\repo\\src\\lib.mbt\nDA:1,1\nend_of_record\n" +
"SF:D:\\repo\\generated\\api.mbt\nDA:1,1\nend_of_record\n",
strip_prefix="D:/repo",
)
let selected = @mooncov.select_paths(report, ["src/**/*.mbt"], [
"**/generated/**",
])
inspect(selected.files.length(), content="1")
inspect(selected.files[0].path, content="src/lib.mbt")
}///|
test "README: changed-line gate" {
let report = @mooncov.parse_lcov(
"SF:src/lib.mbt\nDA:10,1\nDA:11,0\nend_of_record\n",
)
let diff =
#|--- a/src/lib.mbt
#|+++ b/src/lib.mbt
#|@@ -9,0 +10,2 @@
#|+covered
#|+missed
#|
let ranges = @mooncov.parse_unified_diff(diff)
let result = @mooncov.check_diff_threshold(report, ranges, 50.0)
assert_true(result.passed)
inspect(result.summary.lines.total, content="2")
}///|
test "README: Coveralls to LCOV" {
let coveralls = "{\"source_files\":[{\"name\":\"src/a.mbt\"," +
"\"coverage\":[1,0,null],\"branches\":[2,0,0,0]}]}"
let report = @mooncov.parse_coveralls(coveralls)
let lcov = @mooncov.to_lcov(report)
assert_true(lcov.contains("DA:1,1"))
assert_true(lcov.contains("DA:2,0"))
assert_true(lcov.contains("BRDA:2,0,0,0"))
}///|
test "README: Cobertura interoperability" {
let xml =
#|<coverage><packages><package><classes>
#| <class filename="src/a.mbt">
#| <lines>
#| <line number="1" hits="2"/>
#| <line number="2" hits="0" branch="true"
#| condition-coverage="50% (1/2)"/>
#| </lines>
#| </class>
#|</classes></package></packages></coverage>
let report = @mooncov.parse_cobertura(xml)
inspect(@mooncov.summarize(report).branches.total, content="2")
let encoded = @mooncov.to_cobertura(report)
assert_true(encoded.contains("branches-valid=\"2\""))
}///|
let lcov : String = @mooncov.to_lcov(report)
///|
let coveralls : String = @mooncov.to_coveralls_json(
report,
service_name="github-actions",
service_job_id="build-42",
)
///|
let cobertura : String = @mooncov.to_cobertura(report)
///|
let markdown : String = @mooncov.to_markdown(report)
///|
let json : String = @mooncov.to_json_report(report)# Built-in demo; no input file required
moon run cmd/main
# Reproduce the repository sample
moon run cmd/main -- summary lcov examples/sample.info
moon run cmd/main -- validate lcov examples/sample.info
moon run cmd/main -- summary cobertura examples/sample.xml
# Convert any accepted format to canonical LCOV or full JSON
moon run cmd/main -- normalize coveralls path/to/coverage.json
moon run cmd/main -- json lcov path/to/lcov.info
# Exit with code 2 if line coverage is below 80%
moon run cmd/main -- gate lcov path/to/lcov.info 80
# Merge two or more inputs of the same format and print LCOV
moon run cmd/main -- merge lcov unit.info integration.info
# Compare two reports or allow at most a 2-point line-coverage drop
moon run cmd/main -- compare lcov baseline.info current.info
moon run cmd/main -- regress lcov baseline.info current.info 2moon fmt --check
moon info --target all
moon check --target all --deny-warn --warn-list +73
moon test --target all --deny-warn
moon build --target all
moon run cmd/main
moon run cmd/main -- summary lcov examples/sample.info
moon run cmd/main -- summary cobertura examples/sample.xml
moon package
git diff --exit-codepub(all) struct CoverageComparison {
baseline : CoverageSummary
current : CoverageSummary
lines : MetricChange
branches : MetricChange
functions : MetricChange
files : Array[FileComparison]
} derive(Debug)pub(all) struct CoverageSummary {
lines : CoverageCount
branches : CoverageCount
functions : CoverageCount
} derive(Eq, Debug)pub(all) struct CoverageThresholds {
lines : Double?
branches : Double?
functions : Double?
} derive(Debug)fn CoverageThresholds::new(lines? : Double, branches? : Double, functions? : Double) -> CoverageThresholdspub(all) struct DiffGateResult {
passed : Bool
summary : DiffSummary
actual : Double
required : Double
} derive(Debug)pub(all) struct DiffSummary {
lines : CoverageCount
changed_ranges : Int
matched_files : Int
unmatched_files : Array[String]
} derive(Eq, Debug)pub(all) struct FileComparison {
path : String
kind : FileChangeKind
before : CoverageSummary
after : CoverageSummary
} derive(Eq, Debug)pub(all) struct FileCoverage {
path : String
test_name : String?
lines : Array[CoverageLine]
branches : Array[CoverageBranch]
functions : Array[CoverageFunction]
} derive(Eq, Debug)fn FileCoverage::add_branch(self : FileCoverage, line : Int, block : String, branch : String, taken : Int?) -> Unit raise CoverageErrorfn FileCoverage::add_function(self : FileCoverage, name : String, hits : Int, line? : Int) -> Unit raise CoverageErrortest {
let file = FileCoverage::new("src/lib.mbt")
inspect(file.path, content="src/lib.mbt")
inspect(file.lines.length(), content="0")
}pub(all) struct GateResult {
passed : Bool
summary : CoverageSummary
violations : Array[ThresholdViolation]
} derive(Debug)pub(all) struct MetricChange {
before : CoverageCount
after : CoverageCount
percentage_delta : Double
} derive(Debug)pub(all) struct RegressionLimits {
lines : Double?
branches : Double?
functions : Double?
} derive(Debug)fn RegressionLimits::new(lines? : Double, branches? : Double, functions? : Double) -> RegressionLimitspub(all) struct RegressionResult {
passed : Bool
comparison : CoverageComparison
violations : Array[RegressionViolation]
} derive(Debug)pub(all) struct RegressionViolation {
metric : CoverageMetric
percentage_delta : Double
allowed_drop : Double
} derive(Debug)pub(all) struct ThresholdViolation {
metric : CoverageMetric
actual : Double
required : Double
} derive(Debug)fn canonicalize_report(report : CoverageReport, strip_prefix? : String) -> CoverageReport raise CoverageErrorfn check_diff_threshold(report : CoverageReport, ranges : ArrayView[ChangedRange], minimum : Double) -> DiffGateResult raise CoverageErrorfn check_regressions(baseline : CoverageReport, current : CoverageReport, limits : RegressionLimits) -> RegressionResult raise CoverageErrorfn check_thresholds(report : CoverageReport, thresholds : CoverageThresholds) -> GateResult raise CoverageErrorfn compare_reports(baseline_report : CoverageReport, current_report : CoverageReport) -> CoverageComparison raise CoverageErrorfn comparison_to_markdown(comparison : CoverageComparison, title? : String, include_files? : Bool) -> Stringfn glob_match(pattern : String, path : String) -> Boolfn merge_reports(reports : ArrayView[CoverageReport], strip_prefix? : String) -> CoverageReport raise CoverageErrorfn normalize_path(path : String, strip_prefix? : String) -> Stringtest {
inspect(
normalize_path(
"D:\\work\\pkg\\.\\src\\..\\src\\lib.mbt",
strip_prefix="D:/work/pkg",
),
content="src/lib.mbt",
)
}test {
let report = parse_lcov(
"TN:unit\nSF:src/lib.mbt\nDA:1,3\nDA:2,0\nend_of_record\n",
)
inspect(report.files.length(), content="1")
inspect(report.files[0].lines[0].hits, content="3")
}fn parse_unified_diff(input : String, strip_prefix? : String) -> Array[ChangedRange] raise CoverageErrorfn select_paths(report : CoverageReport, includes : Array[String], excludes : Array[String]) -> CoverageReportfn summarize_diff(report : CoverageReport, ranges : ArrayView[ChangedRange]) -> DiffSummary raise CoverageErrorfn to_coveralls_json(report : CoverageReport, service_name? : String, service_job_id? : String) -> String raise CoverageErrorfn to_markdown(report : CoverageReport, title? : String, include_files? : Bool) -> String raise CoverageErrorCoverage report parsing, merging, filtering, gating, and conversion for MoonBit projects.
Dependencies