Skip to content

Commit

Permalink
fix/feat(coverage): add --lcov-version (#9462)
Browse files Browse the repository at this point in the history
feat(coverage): add --lcov-version
  • Loading branch information
DaniPopes authored Dec 2, 2024
1 parent ee9d237 commit e5dbb7a
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 14 deletions.
44 changes: 42 additions & 2 deletions crates/forge/bin/cmd/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use foundry_compilers::{
};
use foundry_config::{Config, SolcReq};
use rayon::prelude::*;
use semver::Version;
use semver::{Version, VersionReq};
use std::{
io,
path::{Path, PathBuf},
Expand All @@ -42,6 +42,18 @@ pub struct CoverageArgs {
#[arg(long, value_enum, default_value = "summary")]
report: Vec<CoverageReportKind>,

/// The version of the LCOV "tracefile" format to use.
///
/// Format: `MAJOR[.MINOR]`.
///
/// Main differences:
/// - `1.x`: The original v1 format.
/// - `2.0`: Adds support for "line end" numbers for functions. LCOV 2.1 and onwards may emit
/// an error if this option is not provided.
/// - `2.2`: Changes the format of functions.
#[arg(long, default_value = "1.16", value_parser = parse_lcov_version)]
lcov_version: Version,

/// Enable viaIR with minimum optimization
///
/// This can fix most of the "stack too deep" errors while resulting a
Expand Down Expand Up @@ -295,7 +307,7 @@ impl CoverageArgs {
let path =
root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref()));
let mut file = io::BufWriter::new(fs::create_file(path)?);
LcovReporter::new(&mut file).report(&report)
LcovReporter::new(&mut file, self.lcov_version.clone()).report(&report)
}
CoverageReportKind::Bytecode => {
let destdir = root.join("bytecode-coverage");
Expand Down Expand Up @@ -404,3 +416,31 @@ impl BytecodeData {
)
}
}

fn parse_lcov_version(s: &str) -> Result<Version, String> {
let vr = VersionReq::parse(&format!("={s}")).map_err(|e| e.to_string())?;
let [c] = &vr.comparators[..] else {
return Err("invalid version".to_string());
};
if c.op != semver::Op::Exact {
return Err("invalid version".to_string());
}
if !c.pre.is_empty() {
return Err("pre-releases are not supported".to_string());
}
Ok(Version::new(c.major, c.minor.unwrap_or(0), c.patch.unwrap_or(0)))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn lcov_version() {
assert_eq!(parse_lcov_version("0").unwrap(), Version::new(0, 0, 0));
assert_eq!(parse_lcov_version("1").unwrap(), Version::new(1, 0, 0));
assert_eq!(parse_lcov_version("1.0").unwrap(), Version::new(1, 0, 0));
assert_eq!(parse_lcov_version("1.1").unwrap(), Version::new(1, 1, 0));
assert_eq!(parse_lcov_version("1.11").unwrap(), Version::new(1, 11, 0));
}
}
22 changes: 18 additions & 4 deletions crates/forge/src/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use alloy_primitives::map::HashMap;
use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Row, Table};
use evm_disassembler::disassemble_bytes;
use foundry_common::fs;
use semver::Version;
use std::{
collections::hash_map,
io::Write,
Expand Down Expand Up @@ -83,17 +84,19 @@ fn format_cell(hits: usize, total: usize) -> Cell {
/// [tracefile format]: https://man.archlinux.org/man/geninfo.1.en#TRACEFILE_FORMAT
pub struct LcovReporter<'a> {
out: &'a mut (dyn Write + 'a),
version: Version,
}

impl<'a> LcovReporter<'a> {
/// Create a new LCOV reporter.
pub fn new(out: &'a mut (dyn Write + 'a)) -> Self {
Self { out }
pub fn new(out: &'a mut (dyn Write + 'a), version: Version) -> Self {
Self { out, version }
}
}

impl CoverageReporter for LcovReporter<'_> {
fn report(self, report: &CoverageReport) -> eyre::Result<()> {
let mut fn_index = 0usize;
for (path, items) in report.items_by_file() {
let summary = CoverageSummary::from_items(items.iter().copied());

Expand All @@ -108,8 +111,19 @@ impl CoverageReporter for LcovReporter<'_> {
match item.kind {
CoverageItemKind::Function { ref name } => {
let name = format!("{}.{name}", item.loc.contract_name);
writeln!(self.out, "FN:{line},{end_line},{name}")?;
writeln!(self.out, "FNDA:{hits},{name}")?;
if self.version >= Version::new(2, 2, 0) {
// v2.2 changed the FN format.
writeln!(self.out, "FNL:{fn_index},{line},{end_line}")?;
writeln!(self.out, "FNA:{fn_index},{hits},{name}")?;
fn_index += 1;
} else if self.version >= Version::new(2, 0, 0) {
// v2.0 added end_line to FN.
writeln!(self.out, "FN:{line},{end_line},{name}")?;
writeln!(self.out, "FNDA:{hits},{name}")?;
} else {
writeln!(self.out, "FN:{line},{name}")?;
writeln!(self.out, "FNDA:{hits},{name}")?;
}
}
CoverageItemKind::Line => {
writeln!(self.out, "DA:{line},{hits}")?;
Expand Down
102 changes: 94 additions & 8 deletions crates/forge/tests/cli/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,52 @@ Wrote LCOV report.

let lcov = prj.root().join("lcov.info");
assert!(lcov.exists(), "lcov.info was not created");
assert_data_eq!(
Data::read_from(&lcov, None),
let default_lcov = str![[r#"
TN:
SF:script/Counter.s.sol
DA:10,0
FN:10,CounterScript.setUp
FNDA:0,CounterScript.setUp
DA:12,0
FN:12,CounterScript.run
FNDA:0,CounterScript.run
DA:13,0
DA:15,0
DA:17,0
FNF:2
FNH:0
LF:5
LH:0
BRF:0
BRH:0
end_of_record
TN:
SF:src/Counter.sol
DA:7,258
FN:7,Counter.setNumber
FNDA:258,Counter.setNumber
DA:8,258
DA:11,1
FN:11,Counter.increment
FNDA:1,Counter.increment
DA:12,1
FNF:2
FNH:2
LF:4
LH:4
BRF:0
BRH:0
end_of_record
"#]];
assert_data_eq!(Data::read_from(&lcov, None), default_lcov.clone());
assert_lcov(
cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=1"]),
default_lcov,
);

assert_lcov(
cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=2"]),
str![[r#"
TN:
SF:script/Counter.s.sol
Expand Down Expand Up @@ -71,7 +115,49 @@ BRF:0
BRH:0
end_of_record
"#]]
"#]],
);

assert_lcov(
cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=2.2"]),
str![[r#"
TN:
SF:script/Counter.s.sol
DA:10,0
FNL:0,10,10
FNA:0,0,CounterScript.setUp
DA:12,0
FNL:1,12,18
FNA:1,0,CounterScript.run
DA:13,0
DA:15,0
DA:17,0
FNF:2
FNH:0
LF:5
LH:0
BRF:0
BRH:0
end_of_record
TN:
SF:src/Counter.sol
DA:7,258
FNL:2,7,9
FNA:2,258,Counter.setNumber
DA:8,258
DA:11,1
FNL:3,11,13
FNA:3,1,Counter.increment
DA:12,1
FNF:2
FNH:2
LF:4
LH:4
BRF:0
BRH:0
end_of_record
"#]],
);
}

Expand Down Expand Up @@ -432,7 +518,7 @@ contract AContractTest is DSTest {
TN:
SF:src/AContract.sol
DA:7,1
FN:7,9,AContract.foo
FN:7,AContract.foo
FNDA:1,AContract.foo
DA:8,1
FNF:1
Expand Down Expand Up @@ -1397,7 +1483,7 @@ contract AContractTest is DSTest {
TN:
SF:src/AContract.sol
DA:9,1
FN:9,9,AContract.increment
FN:9,AContract.increment
FNDA:1,AContract.increment
FNF:1
FNH:1
Expand Down Expand Up @@ -1466,11 +1552,11 @@ contract AContractTest is DSTest {
TN:
SF:src/AContract.sol
DA:7,1
FN:7,9,AContract.constructor
FN:7,AContract.constructor
FNDA:1,AContract.constructor
DA:8,1
DA:11,1
FN:11,13,AContract.receive
FN:11,AContract.receive
FNDA:1,AContract.receive
DA:12,1
FNF:2
Expand Down Expand Up @@ -1530,5 +1616,5 @@ contract AContract {

#[track_caller]
fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) {
cmd.args(["--report=lcov", "--report-file"]).assert_file(data);
cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data());
}

0 comments on commit e5dbb7a

Please sign in to comment.