Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix storing coverage data in cargo projects #3527

Merged
merged 2 commits into from
Sep 20, 2024
Merged
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
4 changes: 2 additions & 2 deletions kani-driver/src/coverage/cov_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl KaniSession {
/// Note: Currently, coverage mappings are not included due to technical
/// limitations. But this is where we should save them.
pub fn save_coverage_metadata(&self, project: &Project, stamp: &String) -> Result<()> {
if self.args.target_dir.is_some() {
if project.input.is_none() {
self.save_coverage_metadata_cargo(project, stamp)
} else {
self.save_coverage_metadata_standalone(project, stamp)
Expand Down Expand Up @@ -97,7 +97,7 @@ impl KaniSession {
results: &Vec<HarnessResult>,
stamp: &String,
) -> Result<()> {
if self.args.target_dir.is_some() {
if project.input.is_none() {
self.save_coverage_results_cargo(results, stamp)
} else {
self.save_coverage_results_standalone(project, results, stamp)
Expand Down
1 change: 1 addition & 0 deletions scripts/kani-regression.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ TESTS=(
"cargo-ui cargo-kani"
"script-based-pre exec"
"coverage coverage-based"
"cargo-coverage cargo-coverage"
"kani-docs cargo-kani"
"kani-fixme kani-fixme"
)
Expand Down
10 changes: 10 additions & 0 deletions tests/cargo-coverage/simple-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright Kani Contributors
# SPDX-License-Identifier: Apache-2.0 OR MIT
[package]
name = "simple-lib"
version = "0.1.0"
edition = "2018"

[dependencies]

[workspace]
25 changes: 25 additions & 0 deletions tests/cargo-coverage/simple-lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
pub mod pair;
pub use pair::Pair;

#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

#[cfg(kani)]
mod kani_tests {
use super::*;

#[kani::proof]
fn test_sum() {
let a: u64 = kani::any();
let b: u64 = kani::any();
let p = Pair::new(a, b);
assert!(p.sum() == a.wrapping_add(b));
}
}
33 changes: 33 additions & 0 deletions tests/cargo-coverage/simple-lib/src/pair.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
pub struct Pair(pub u64, pub u64);

impl Pair {
pub fn new(a: u64, b: u64) -> Self {
Pair(a, b)
}
pub fn sum(&self) -> u64 {
self.0.wrapping_add(self.1)
}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_plus_two() {
let p = Pair::new(1, 2);
assert_eq!(p.sum(), 3);
}
}

#[cfg(kani)]
mod kani_tests {
use super::*;

#[kani::proof]
fn test_one_plus_two() {
let p = Pair::new(1, 2);
assert!(p.sum() == 3);
}
}
10 changes: 10 additions & 0 deletions tests/cargo-coverage/simple-lib/test_one_plus_two.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Source-based code coverage results:

src/pair.rs (pair::Pair::new)\
* 6:5 - 8:6 COVERED

src/pair.rs (pair::Pair::sum)\
* 9:5 - 11:6 COVERED

src/pair.rs (pair::kani_tests::test_one_plus_two)\
* 29:5 - 32:6 COVERED
10 changes: 10 additions & 0 deletions tests/cargo-coverage/simple-lib/test_sum.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Source-based code coverage results:

src/lib.rs (kani_tests::test_sum)\
* 19:5 - 24:6 COVERED

src/pair.rs (pair::Pair::new)\
* 6:5 - 8:6 COVERED

src/pair.rs (pair::Pair::sum)\
* 9:5 - 11:6 COVERED
3 changes: 3 additions & 0 deletions tools/compiletest/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use test::ColorConfig;
pub enum Mode {
Kani,
KaniFixme,
CargoCoverage,
CargoKani,
CargoKaniTest, // `cargo kani --tests`. This is temporary and should be removed when s2n-quic moves --tests to `Cargo.toml`.
CoverageBased,
Expand All @@ -34,6 +35,7 @@ impl FromStr for Mode {
"cargo-kani" => Ok(CargoKani),
"cargo-kani-test" => Ok(CargoKaniTest),
"coverage-based" => Ok(CoverageBased),
"cargo-coverage" => Ok(CargoCoverage),
"exec" => Ok(Exec),
"expected" => Ok(Expected),
"stub-tests" => Ok(Stub),
Expand All @@ -47,6 +49,7 @@ impl fmt::Display for Mode {
let s = match *self {
Kani => "kani",
KaniFixme => "kani-fixme",
CargoCoverage => "cargo-coverage",
CargoKani => "cargo-kani",
CargoKaniTest => "cargo-kani-test",
CoverageBased => "coverage-based",
Expand Down
8 changes: 6 additions & 2 deletions tools/compiletest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ fn collect_tests_from_dir(
tests: &mut Vec<test::TestDescAndFn>,
) -> io::Result<()> {
match config.mode {
Mode::CargoKani | Mode::CargoKaniTest => {
Mode::CargoCoverage | Mode::CargoKani | Mode::CargoKaniTest => {
collect_expected_tests_from_dir(config, dir, relative_dir_path, inputs, tests)
}
Mode::Exec => collect_exec_tests_from_dir(config, dir, relative_dir_path, inputs, tests),
Expand Down Expand Up @@ -378,7 +378,11 @@ fn collect_expected_tests_from_dir(
// output directory corresponding to each to avoid race conditions during
// the testing phase. We immediately return after adding the tests to avoid
// treating `*.rs` files as tests.
assert!(config.mode == Mode::CargoKani || config.mode == Mode::CargoKaniTest);
assert!(
config.mode == Mode::CargoCoverage
|| config.mode == Mode::CargoKani
|| config.mode == Mode::CargoKaniTest
);

let has_cargo_toml = dir.join("Cargo.toml").exists();
for file in fs::read_dir(dir)? {
Expand Down
33 changes: 31 additions & 2 deletions tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use crate::common::KaniFailStep;
use crate::common::{output_base_dir, output_base_name};
use crate::common::{
CargoKani, CargoKaniTest, CoverageBased, Exec, Expected, Kani, KaniFixme, Stub,
CargoCoverage, CargoKani, CargoKaniTest, CoverageBased, Exec, Expected, Kani, KaniFixme, Stub,
};
use crate::common::{Config, TestPaths};
use crate::header::TestProps;
Expand Down Expand Up @@ -74,6 +74,7 @@ impl<'test> TestCx<'test> {
match self.config.mode {
Kani => self.run_kani_test(),
KaniFixme => self.run_kani_test(),
CargoCoverage => self.run_cargo_coverage_test(),
CargoKani => self.run_cargo_kani_test(false),
CargoKaniTest => self.run_cargo_kani_test(true),
CoverageBased => self.run_expected_coverage_test(),
Expand Down Expand Up @@ -313,7 +314,7 @@ impl<'test> TestCx<'test> {
self.compose_and_run(kani)
}

/// Run coverage based output for kani on a single file
/// Run Kani with coverage enabled on a single source file
fn run_kani_with_coverage(&self) -> ProcRes {
let mut kani = Command::new("kani");
if !self.props.compile_flags.is_empty() {
Expand All @@ -329,6 +330,34 @@ impl<'test> TestCx<'test> {
self.compose_and_run(kani)
}

/// Run Kani with coverage enabled on a cargo package
fn run_cargo_coverage_test(&self) {
// We create our own command for the same reasons listed in `run_kani_test` method.
let mut cargo = Command::new("cargo");
// We run `cargo` on the directory where we found the `*.expected` file
let parent_dir = self.testpaths.file.parent().unwrap();
// The name of the function to test is the same as the stem of `*.expected` file
let function_name = self.testpaths.file.file_stem().unwrap().to_str().unwrap();
cargo
.arg("kani")
.arg("--coverage")
.arg("-Zsource-coverage")
.arg("--target-dir")
.arg(self.output_base_dir().join("target"))
.current_dir(parent_dir);

if "expected" != self.testpaths.file.file_name().unwrap() {
cargo.args(["--harness", function_name]);
}
cargo.args(&self.config.extra_args);

let proc_res = self.compose_and_run(cargo);
self.verify_output(&proc_res, &self.testpaths.file);

// TODO: We should probably be checking the exit status somehow
// See https://github.com/model-checking/kani/issues/1895
}

/// Runs an executable file and:
/// * Checks the expected output if an expected file is specified
/// * Checks the exit code (assumed to be 0 by default)
Expand Down
Loading