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

Add --exact flag #2527

Merged
merged 17 commits into from
Jun 19, 2023
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
5 changes: 5 additions & 0 deletions kani-driver/src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub struct VerificationArgs {
pub function: Option<String>,
/// If specified, only run harnesses that match this filter. This option can be provided
/// multiple times, which will run all tests matching any of the filters.
/// If used with --exact, the harness filter will only match the exact fully qualified name of a harness.
#[arg(
long = "harness",
conflicts_with = "function",
Expand All @@ -177,6 +178,10 @@ pub struct VerificationArgs {
)]
pub harnesses: Vec<String>,

/// When specified, the harness filter will only match the exact fully qualified name of a harness
#[arg(long, requires("harnesses"))]
pub exact: bool,

/// Link external C files referenced by Rust code.
/// This is an experimental feature and requires `-Z c-ffi` to be used
#[arg(long, hide = true, num_args(1..))]
Expand Down
2 changes: 1 addition & 1 deletion kani-driver/src/harness_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl KaniSession {
}

// We currently omit a summary if there was just 1 harness
if !self.args.common_args.quiet && !self.args.visualize && total != 1 {
if !self.args.common_args.quiet && !self.args.visualize {
if failing > 0 {
println!("Summary:");
}
Expand Down
134 changes: 113 additions & 21 deletions kani-driver/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use anyhow::Result;
use anyhow::{bail, Result};
use std::path::{Path, PathBuf};
use tracing::{debug, trace};

Expand Down Expand Up @@ -121,11 +121,35 @@ impl KaniSession {
BTreeSet::from_iter(self.args.harnesses.iter())
};

let total_harnesses = harnesses.len();
let all_targets = &harnesses;

if harnesses.is_empty() {
Ok(Vec::from(all_harnesses))
} else {
let harnesses = find_proof_harnesses(harnesses, all_harnesses);
Ok(harnesses)
let harnesses_found: Vec<&HarnessMetadata> =
find_proof_harnesses(&harnesses, all_harnesses, self.args.exact);

// If even one harness was not found with --exact, return an error to user
if self.args.exact && harnesses_found.len() < total_harnesses {
let harness_found_names: BTreeSet<&String> =
harnesses_found.iter().map(|&h| &h.pretty_name).collect();

// Check which harnesses are missing from the difference of targets and harnesses_found
let harnesses_missing: Vec<&String> =
all_targets.difference(&harness_found_names).cloned().collect();
let joined_string = harnesses_missing
.iter()
.map(|&s| (*s).clone())
.collect::<Vec<String>>()
.join("`, `");

bail!(
"Failed to match the following harness(es):\n{joined_string}\nPlease specify the fully-qualified name of a harness.",
);
}

Ok(harnesses_found)
}
}
}
Expand Down Expand Up @@ -167,20 +191,31 @@ pub fn mock_proof_harness(
/// At the present time, we use `no_mangle` so collisions shouldn't happen,
/// but this function is written to be robust against that changing in the future.
fn find_proof_harnesses<'a>(
targets: BTreeSet<&String>,
targets: &BTreeSet<&String>,
all_harnesses: &[&'a HarnessMetadata],
exact_filter: bool,
) -> Vec<&'a HarnessMetadata> {
debug!(?targets, "find_proof_harness");
let mut result = vec![];
for md in all_harnesses.iter() {
// Either an exact match, or a substring match. We check the exact first since it's cheaper.
if targets.contains(&md.pretty_name)
|| targets.contains(&md.get_harness_name_unqualified().to_string())
|| targets.iter().any(|target| md.pretty_name.contains(*target))
{
result.push(*md);
if exact_filter {
// Check for exact match only
if targets.contains(&md.pretty_name) {
// if exact match found, stop searching
result.push(*md);
} else {
trace!(skip = md.pretty_name, "find_proof_harnesses");
}
} else {
trace!(skip = md.pretty_name, "find_proof_harnesses");
// Either an exact match, or a substring match. We check the exact first since it's cheaper.
if targets.contains(&md.pretty_name)
|| targets.contains(&md.get_harness_name_unqualified().to_string())
|| targets.iter().any(|target| md.pretty_name.contains(*target))
{
result.push(*md);
} else {
trace!(skip = md.pretty_name, "find_proof_harnesses");
}
}
}
result
Expand All @@ -191,31 +226,88 @@ mod tests {
use super::*;

#[test]
fn check_find_proof_harness() {
fn check_find_proof_harness_without_exact() {
celinval marked this conversation as resolved.
Show resolved Hide resolved
let harnesses = vec![
mock_proof_harness("check_one", None, None, None),
mock_proof_harness("module::check_two", None, None, None),
mock_proof_harness("module::not_check_three", None, None, None),
];
let ref_harnesses = harnesses.iter().collect::<Vec<_>>();

// Check with harness filtering
assert_eq!(
find_proof_harnesses(BTreeSet::from([&"check_three".to_string()]), &ref_harnesses)
.len(),
find_proof_harnesses(
&BTreeSet::from([&"check_three".to_string()]),
&ref_harnesses,
false
)
.len(),
1
);
assert!(
find_proof_harnesses(BTreeSet::from([&"check_two".to_string()]), &ref_harnesses)
.first()
.unwrap()
.mangled_name
find_proof_harnesses(
&BTreeSet::from([&"check_two".to_string()]),
&ref_harnesses,
false
)
.first()
.unwrap()
.mangled_name
== "module::check_two"
);
assert!(
find_proof_harnesses(BTreeSet::from([&"check_one".to_string()]), &ref_harnesses)
find_proof_harnesses(
&BTreeSet::from([&"check_one".to_string()]),
&ref_harnesses,
false
)
.first()
.unwrap()
.mangled_name
== "check_one"
);
}

#[test]
fn check_find_proof_harness_with_exact() {
// Check with exact match
jaisnan marked this conversation as resolved.
Show resolved Hide resolved

let harnesses = vec![
mock_proof_harness("check_one", None, None, None),
mock_proof_harness("module::check_two", None, None, None),
mock_proof_harness("module::not_check_three", None, None, None),
];
let ref_harnesses = harnesses.iter().collect::<Vec<_>>();

assert!(
find_proof_harnesses(
&BTreeSet::from([&"check_three".to_string()]),
&ref_harnesses,
true
)
.is_empty()
);
assert!(
find_proof_harnesses(&BTreeSet::from([&"check_two".to_string()]), &ref_harnesses, true)
.is_empty()
);
assert_eq!(
find_proof_harnesses(&BTreeSet::from([&"check_one".to_string()]), &ref_harnesses, true)
.first()
.unwrap()
.mangled_name
== "check_one"
.mangled_name,
"check_one"
);
assert_eq!(
find_proof_harnesses(
&BTreeSet::from([&"module::not_check_three".to_string()]),
&ref_harnesses,
true
)
.first()
.unwrap()
.mangled_name,
"module::not_check_three"
);
}
}
2 changes: 2 additions & 0 deletions tests/ui/check_summary_for_single_harness/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Checking harness check_foo...
Complete - 1 successfully verified harnesses, 0 failures, 1 total.
9 changes: 9 additions & 0 deletions tests/ui/check_summary_for_single_harness/single_harness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: --harness check_foo --exact
//! Check for the summary line at the end of the verification output

#[kani::proof]
fn check_foo() {
assert!(1 == 1);
}
3 changes: 3 additions & 0 deletions tests/ui/exact-harness/check-qualified-name/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Checking harness first::check_foo...
VERIFICATION:- SUCCESSFUL
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
Complete - 1 successfully verified harnesses, 0 failures, 1 total.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: --harness first::check_foo --exact
//! Ensure that only the specified harness is run

mod first {
#[kani::proof]
fn check_foo() {
assert!(1 == 1);
}

/// A harness that will fail verification if it is run.
#[kani::proof]
fn check_blah() {
assert!(1 == 2);
}

/// A harness that will fail verification if it is run.
#[kani::proof]
fn ignore_third_harness() {
assert!(3 == 2);
}
}
3 changes: 3 additions & 0 deletions tests/ui/exact-harness/check_some/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Checking harness check_second_harness...
Checking harness check_first_harness...
Complete - 2 successfully verified harnesses, 0 failures, 2 total.
19 changes: 19 additions & 0 deletions tests/ui/exact-harness/check_some/select_harnesses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: --harness check_first_harness --harness check_second_harness --exact
//! Ensure that we can select multiple harnesses at a time.
#[kani::proof]
fn check_first_harness() {
assert!(1 == 1);
}

#[kani::proof]
fn check_second_harness() {
assert!(2 == 2);
}

/// A harness that will fail verification if it is run.
#[kani::proof]
fn ignore_third_harness() {
assert!(3 == 2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Checking harness first::harness...
VERIFICATION:- SUCCESSFUL
Complete - 1 successfully verified harnesses, 0 failures, 1 total.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: --harness first::harness --exact
//! Ensure that only the harness specified with --exact is picked up

mod first {
#[kani::proof]
fn harness() {
assert!(1 == 1);
}

/// A harness that will fail verification if it is picked up.
#[kani::proof]
fn harness_1() {
assert!(1 == 2);
}

/// A harness that will fail verification if it is picked up.
#[kani::proof]
fn harness_2() {
assert!(3 == 2);
}
}
3 changes: 3 additions & 0 deletions tests/ui/exact-harness/fail_on_missing/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
error: Failed to match the following harness(es):
check_blah`, `check_foo
Please specify the fully-qualified name of a harness.
39 changes: 39 additions & 0 deletions tests/ui/exact-harness/fail_on_missing/subset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: --harness existing --harness check_blah --harness check_foo --exact
//! Check that we error out with non-matching filters when --exact is used

mod first {
#[kani::proof]
fn check_foo() {
assert!(1 == 2);
}

#[kani::proof]
fn check_blah() {
assert!(2 == 2);
}

/// A harness that will fail verification if it is run.
#[kani::proof]
fn ignore_third_harness() {
assert!(3 == 2);
}
}

// This harness will be picked up
#[kani::proof]
fn existing() {
assert!(1 == 1);
}

#[kani::proof]
fn existing_harness() {
assert!(1 == 2);
}

/// A harness that will fail verification if it is run.
#[kani::proof]
fn ignored_harness() {
assert!(3 == 2);
}
3 changes: 3 additions & 0 deletions tests/ui/exact-harness/incomplete-harness-name/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
error: Failed to match the following harness(es):
ignore_third_harness
Please specify the fully-qualified name of a harness.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: --harness ignore_third_harness --exact
//! Check that we error out with non-matching filters when --exact is used

mod first {
#[kani::proof]
fn check_foo() {
assert!(1 == 1);
}

#[kani::proof]
fn check_blah() {
assert!(2 == 2);
}

/// A harness that will fail verification if it is run.
#[kani::proof]
fn ignore_third_harness() {
assert!(3 == 2);
}
}
3 changes: 3 additions & 0 deletions tests/ui/exact-harness/multiple_matches/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Checking harness second::verify_foo...
Checking harness first::check_blah...
Complete - 2 successfully verified harnesses, 0 failures, 2 total.
Loading