///|
/// Errors raised while decoding or validating coverage data.
pub(all) suberror CoverageError {
InvalidLineNumber(Int)
InvalidHitCount(Int)
InvalidLcov(Int, String)
InvalidCoveralls(String)
InvalidCobertura(Int, String)
InvalidModel(String)
InvalidThreshold(String, Double)
InvalidDiff(Int, String)
} derive(Debug, Eq)
///|
/// A covered or uncovered source line.
pub(all) struct CoverageLine {
line : Int
hits : Int
} derive(Debug, Eq)
///|
/// A branch arm reported by a coverage producer.
///
/// `taken = None` represents LCOV's `-` value, meaning that the producer
/// instrumented the branch but did not report a numeric execution count.
pub(all) struct CoverageBranch {
line : Int
block : String
branch : String
taken : Int?
} derive(Debug, Eq)
///|
/// A function entry from a coverage report.
///
/// Some formats do not carry a declaration line, so `line` is optional.
pub(all) struct CoverageFunction {
name : String
line : Int?
hits : Int
} derive(Debug, Eq)
///|
/// Coverage data for one normalized source path.
pub(all) struct FileCoverage {
path : String
test_name : String?
lines : Array[CoverageLine]
branches : Array[CoverageBranch]
functions : Array[CoverageFunction]
} derive(Debug, Eq)
///|
/// A format-neutral coverage report.
pub(all) struct CoverageReport {
files : Array[FileCoverage]
} derive(Debug, Eq)
///|
/// Create an empty file report.
///
/// # Example
/// ```mbt check
/// test {
/// let file = FileCoverage::new("src/lib.mbt")
/// inspect(file.path, content="src/lib.mbt")
/// inspect(file.lines.length(), content="0")
/// }
/// ```
pub fn FileCoverage::new(path : String, test_name? : String) -> FileCoverage {
{ path, test_name, lines: [], branches: [], functions: [] }
}
///|
/// Create an empty coverage report.
pub fn CoverageReport::new() -> CoverageReport {
{ files: [] }
}
///|
/// Add line coverage after validating the line number and hit count.
pub fn FileCoverage::add_line(
self : FileCoverage,
line : Int,
hits : Int,
) -> Unit raise CoverageError {
if line <= 0 {
raise InvalidLineNumber(line)
}
if hits < 0 {
raise InvalidHitCount(hits)
}
self.lines.push({ line, hits })
}
///|
/// Add branch coverage after validating its source line and optional count.
pub fn FileCoverage::add_branch(
self : FileCoverage,
line : Int,
block : String,
branch : String,
taken : Int?,
) -> Unit raise CoverageError {
if line <= 0 {
raise InvalidLineNumber(line)
}
if taken is Some(count) && count < 0 {
raise InvalidHitCount(count)
}
self.branches.push({ line, block, branch, taken })
}
///|
/// Add function coverage after validating the optional declaration line and
/// hit count.
pub fn FileCoverage::add_function(
self : FileCoverage,
name : String,
hits : Int,
line? : Int,
) -> Unit raise CoverageError {
if line is Some(value) && value <= 0 {
raise InvalidLineNumber(value)
}
if hits < 0 {
raise InvalidHitCount(hits)
}
self.functions.push({ name, line, hits })
}
///|
/// Add a file to a report.
pub fn CoverageReport::add_file(
self : CoverageReport,
file : FileCoverage,
) -> Unit {
self.files.push(file)
}
///|
/// Return the file with the exact normalized path, if present.
pub fn CoverageReport::find_file(
self : CoverageReport,
path : StringView,
) -> FileCoverage? {
match self.files.search_by(file => path.equal_to_string(file.path)) {
Some(index) => Some(self.files[index])
None => None
}
}
///|
/// Return whether a line or count pair represents executed code.
pub fn CoverageLine::is_covered(self : CoverageLine) -> Bool {
self.hits > 0
}
///|
/// Return whether a branch has a positive execution count.
pub fn CoverageBranch::is_covered(self : CoverageBranch) -> Bool {
self.taken.unwrap_or(0) > 0
}
///|
/// Return whether a function has a positive execution count.
pub fn CoverageFunction::is_covered(self : CoverageFunction) -> Bool {
self.hits > 0
}