Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/forge/src/cmd/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ impl CoverageArgs {
let filter = self.test.filter(&config)?;
let outcome =
self.test.run_tests(project_root, config, evm_opts, output, &filter, true).await?;
outcome.ensure_ok(false)?;

let known_contracts = outcome.runner.as_ref().unwrap().known_contracts.clone();

Expand Down Expand Up @@ -300,6 +299,10 @@ impl CoverageArgs {
// Output final reports.
self.report(&report)?;

// Check for test failures after generating coverage report.
// This ensures coverage data is written even when tests fail.
outcome.ensure_ok(false)?;

Ok(())
}

Expand Down
63 changes: 63 additions & 0 deletions crates/forge/tests/cli/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2203,3 +2203,66 @@ contract CounterTest is DSTest {
...
"#]]);
});

// Test that coverage files are written even when tests fail.
forgetest!(coverage_with_failing_tests, |prj, cmd| {
prj.insert_ds_test();
prj.add_source(
"Counter.sol",
r#"
contract Counter {
uint256 public number;

function setNumber(uint256 newNumber) public {
number = newNumber;
}

function increment() public {
number++;
}
}
"#,
);

prj.add_source(
"CounterTest.sol",
r#"
import "./test.sol";
import {Counter} from "./Counter.sol";

contract CounterTest is DSTest {
Counter public counter;

function setUp() public {
counter = new Counter();
counter.setNumber(0);
}

function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
}

function test_FailingTest() public {
counter.increment();
// This assertion will fail
assertEq(counter.number(), 999);
}
}
"#,
);

// Run coverage - this should exit with error code 1 due to failing test,
// but the lcov file should still be written.
cmd.arg("coverage").args(["--report=lcov"]).assert_failure();

// Verify that the lcov.info file was created despite test failure
let lcov = prj.root().join("lcov.info");
assert!(lcov.exists(), "lcov.info should be created even when tests fail");

// Verify the coverage data is valid and includes the counter contract
let lcov_content = std::fs::read_to_string(&lcov).unwrap();
assert!(lcov_content.contains("SF:src/Counter.sol"), "Coverage should include Counter.sol");
assert!(lcov_content.contains("FN:"), "Coverage should include function data");
assert!(lcov_content.contains("DA:"), "Coverage should include line hit data");
});
Loading