diff --git a/Cargo.lock b/Cargo.lock index 580cdf70946a..c4709fbe446e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -769,6 +769,20 @@ dependencies = [ "tracing-tree 0.4.0", ] +[[package]] +name = "kani-cov" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "console", + "serde", + "serde_derive", + "serde_json", + "tree-sitter", + "tree-sitter-rust", +] + [[package]] name = "kani-driver" version = "0.56.0" @@ -1811,6 +1825,34 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tree-sitter" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff" +dependencies = [ + "cc", + "regex", + "regex-syntax 0.8.5", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" + +[[package]] +name = "tree-sitter-rust" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "277690f420bf90741dea984f3da038ace46c4fe6047cba57a66822226cde1c93" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "typed-arena" version = "2.0.2" diff --git a/Cargo.toml b/Cargo.toml index 42c6f2c722b6..3c61638025e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "library/std", "tools/compiletest", "tools/build-kani", + "tools/kani-cov", "tools/scanner", "kani-driver", "kani-compiler", diff --git a/scripts/kani-regression.sh b/scripts/kani-regression.sh index be2548235ce6..728129d784b6 100755 --- a/scripts/kani-regression.sh +++ b/scripts/kani-regression.sh @@ -70,6 +70,9 @@ echo "--- Compiletest configuration" cargo run -p compiletest --quiet -- --suite kani --mode cargo-kani --dry-run --verbose echo "-----------------------------" +# Build `kani-cov` +cargo build -p kani-cov + # Extract testing suite information and run compiletest for testp in "${TESTS[@]}"; do testl=($testp) diff --git a/tests/coverage/abort/expected b/tests/coverage/abort/expected index 91142ebf94fc..e9c9727a6f03 100644 --- a/tests/coverage/abort/expected +++ b/tests/coverage/abort/expected @@ -1,13 +1,21 @@ -Source-based code coverage results: - -main.rs (main)\ - * 9:1 - 9:11 COVERED\ - * 10:9 - 10:10 COVERED\ - * 10:14 - 10:18 COVERED\ - * 13:13 - 13:29 COVERED\ - * 14:10 - 15:18 COVERED\ - * 17:13 - 17:29 UNCOVERED\ - * 18:10 - 18:11 COVERED\ - * 20:5 - 20:12 UNCOVERED\ - * 20:20 - 20:41 UNCOVERED\ - * 21:1 - 21:2 UNCOVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Test that the abort() function is respected and nothing beyond it will execute.\ + 5| | \ + 6| | use std::process;\ + 7| | \ + 8| | #[kani::proof]\ + 9| 1| fn main() {\ + 10| 1| for i in 0..4 {\ + 11| | if i == 1 {\ + 12| | // This comes first and it should be reachable.\ + 13| 1| process::abort();\ + 14| 1| }\ + 15| 1| if i == 2 {\ + 16| | // This should never happen.\ + 17| 0| ```process::exit(1)''';\ + 18| 1| } \ + 19| | }\ + 20| 0| ```assert!'''(false, ```"This is unreachable"''');\ + 21| | }\ diff --git a/tests/coverage/assert/expected b/tests/coverage/assert/expected index 46bb664cf6f5..c2f3ffbe733e 100644 --- a/tests/coverage/assert/expected +++ b/tests/coverage/assert/expected @@ -1,9 +1,18 @@ -Source-based code coverage results: - -test.rs (foo) - * 5:1 - 7:13 COVERED\ - * 9:9 - 10:17 COVERED\ - * 10:18 - 13:10 UNCOVERED\ - * 13:10 - 13:11 UNCOVERED\ - * 14:12 - 17:6 COVERED\ - * 18:1 - 18:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | #[kani::proof]\ + 5| 1| fn foo() {\ + 6| 1| let x: i32 = kani::any();\ + 7| 1| if x > 5 {\ + 8| | // fails\ + 9| 1| assert!(x < 4);\ + 10| 1| if x < 3 ```{'''\ + 11| 0| ``` // unreachable'''\ + 12| 0| ``` assert!(x == 2);'''\ + 13| 0| ``` }'''``` '''\ + 14| 1| } else {\ + 15| 1| // passes\ + 16| 1| assert!(x <= 5);\ + 17| 1| }\ + 18| | }\ diff --git a/tests/coverage/assert_eq/expected b/tests/coverage/assert_eq/expected index c2eee7adf803..0cc1e01fbca9 100644 --- a/tests/coverage/assert_eq/expected +++ b/tests/coverage/assert_eq/expected @@ -1,8 +1,11 @@ -Source-based code coverage results: - -test.rs (main)\ - * 5:1 - 6:29 COVERED\ - * 7:25 - 7:27 COVERED\ - * 7:37 - 7:39 COVERED\ - * 8:15 - 10:6 UNCOVERED\ - * 10:6 - 10:7 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | #[kani::proof]\ + 5| 1| fn main() {\ + 6| 1| let x: i32 = kani::any();\ + 7| 1| let y = if x > 10 { 15 } else { 33 };\ + 8| 0| if y > 50 ```{'''\ + 9| 0| ``` assert_eq!(y, 55);'''\ + 10| 1| ``` }'''\ + 11| | }\ diff --git a/tests/coverage/assert_ne/expected b/tests/coverage/assert_ne/expected index c9b727da0f82..73055a14af10 100644 --- a/tests/coverage/assert_ne/expected +++ b/tests/coverage/assert_ne/expected @@ -1,9 +1,14 @@ -Source-based code coverage results: - -test.rs (main)\ - * 5:1 - 7:13 COVERED\ - * 8:13 - 10:18 COVERED\ - * 10:19 - 12:10 UNCOVERED\ - * 12:10 - 12:11 COVERED\ - * 13:6 - 13:7 COVERED\ - * 14:1 - 14:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | #[kani::proof]\ + 5| 1| fn main() {\ + 6| 1| let x: u32 = kani::any();\ + 7| 1| if x > 0 {\ + 8| 1| let y = x / 2;\ + 9| 1| // y is strictly less than x\ + 10| 1| if y == x ```{'''\ + 11| 0| ``` assert_ne!(y, 1);'''\ + 12| 1| ``` }'''\ + 13| 1| }\ + 14| | }\ diff --git a/tests/coverage/break/expected b/tests/coverage/break/expected index 739735cdf1a2..e1570030f6ae 100644 --- a/tests/coverage/break/expected +++ b/tests/coverage/break/expected @@ -1,13 +1,19 @@ -Source-based code coverage results: - -main.rs (find_positive)\ - * 4:1 - 4:47 COVERED\ - * 5:10 - 5:13 COVERED\ - * 5:17 - 5:21 COVERED\ - * 7:20 - 7:29 COVERED\ - * 8:10 - 8:11 COVERED\ - * 11:5 - 11:9 UNCOVERED\ - * 12:1 - 12:2 COVERED - -main.rs (main)\ - * 15:1 - 19:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn find_positive(nums: &[i32]) -> Option {\ + 5| 1| for &num in nums {\ + 6| | if num > 0 {\ + 7| 1| return Some(num);\ + 8| 1| } \ + 9| | }\ + 10| | // `None` is unreachable because there is at least one positive number.\ + 11| 0| ```None'''\ + 12| | }\ + 13| | \ + 14| | #[kani::proof]\ + 15| 1| fn main() {\ + 16| 1| let numbers = [-3, -1, 0, 2, 4];\ + 17| 1| let result = find_positive(&numbers);\ + 18| 1| assert_eq!(result, Some(2));\ + 19| | }\ diff --git a/tests/coverage/compare/expected b/tests/coverage/compare/expected index 153dbfa37d80..3a59376e8ce3 100644 --- a/tests/coverage/compare/expected +++ b/tests/coverage/compare/expected @@ -1,11 +1,16 @@ -Source-based code coverage results: - -main.rs (compare)\ - * 4:1 - 6:14 COVERED\ - * 6:17 - 6:18 COVERED\ - * 6:28 - 6:29 UNCOVERED - -main.rs (main)\ - * 10:1 - 13:14 COVERED\ - * 13:15 - 15:6 COVERED\ - * 15:6 - 15:7 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn compare(x: u16, y: u16) -> u16 {\ + 5| 1| // The case where `x < y` isn't possible so its region is `UNCOVERED`\ + 6| 1| if x >= y { 1 } else { ```0''' }\ + 7| | }\ + 8| | \ + 9| | #[kani::proof]\ + 10| 1| fn main() {\ + 11| 1| let x: u16 = kani::any();\ + 12| 1| let y: u16 = kani::any();\ + 13| 1| if x >= y {\ + 14| 1| compare(x, y);\ + 15| 1| } \ + 16| | }\ diff --git a/tests/coverage/contradiction/expected b/tests/coverage/contradiction/expected index db3676d7da15..7befb09a1f9e 100644 --- a/tests/coverage/contradiction/expected +++ b/tests/coverage/contradiction/expected @@ -1,9 +1,14 @@ -Source-based code coverage results: - -main.rs (contradiction)\ - * 4:1 - 7:13 COVERED\ - * 8:12 - 8:17 COVERED\ - * 8:18 - 10:10 UNCOVERED\ - * 10:10 - 10:11 COVERED\ - * 11:12 - 13:6 COVERED\ - * 14:1 - 14:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | #[kani::proof]\ + 4| 1| fn contradiction() {\ + 5| 1| let x: u8 = kani::any();\ + 6| 1| let mut y: u8 = 0;\ + 7| 1| if x > 5 {\ + 8| 1| if x < 2 ```{'''\ + 9| 0| ``` y = x;'''\ + 10| 1| ``` }'''\ + 11| 1| } else {\ + 12| 1| assert!(x < 10);\ + 13| 1| }\ + 14| | }\ diff --git a/tests/coverage/debug-assert/expected b/tests/coverage/debug-assert/expected index fbe57690d347..82ad7c992ca3 100644 --- a/tests/coverage/debug-assert/expected +++ b/tests/coverage/debug-assert/expected @@ -1,10 +1,15 @@ -Source-based code coverage results: - -main.rs (main)\ - * 10:1 - 10:11 COVERED\ - * 11:9 - 11:10 COVERED\ - * 11:14 - 11:18 COVERED\ - * 12:30 - 12:71 UNCOVERED\ - * 13:9 - 13:23 UNCOVERED\ - * 13:25 - 13:53 UNCOVERED\ - * 15:1 - 15:2 UNCOVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! This test checks that the regions after the `debug_assert` macro are\ + 5| | //! `UNCOVERED`. In fact, for this example, the region associated to `"This\ + 6| | //! should fail and stop the execution"` is also `UNCOVERED` because the macro\ + 7| | //! calls span two regions each.\ + 8| | \ + 9| | #[kani::proof]\ + 10| 1| fn main() {\ + 11| 1| for i in 0..4 {\ + 12| 0| debug_assert!(i > 0, ```"This should fail and stop the execution"''');\ + 13| 0| ```assert!(i == 0''', ```"This should be unreachable"''');\ + 14| | }\ + 15| | }\ diff --git a/tests/coverage/div-zero/expected b/tests/coverage/div-zero/expected index f351005f4f22..503fa9a22a05 100644 --- a/tests/coverage/div-zero/expected +++ b/tests/coverage/div-zero/expected @@ -1,9 +1,11 @@ -Source-based code coverage results: - -test.rs (div)\ - * 4:1 - 5:14 COVERED\ - * 5:17 - 5:22 COVERED\ - * 5:32 - 5:33 UNCOVERED - -test.rs (main)\ - * 9:1 - 11:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn div(x: u16, y: u16) -> u16 {\ + 5| 1| if y != 0 { x / y } else { ```0''' }\ + 6| | }\ + 7| | \ + 8| | #[kani::proof]\ + 9| 1| fn main() {\ + 10| 1| div(11, 3);\ + 11| | }\ diff --git a/tests/coverage/early-return/expected b/tests/coverage/early-return/expected index 53cde3abeaf8..efdd71ee91f0 100644 --- a/tests/coverage/early-return/expected +++ b/tests/coverage/early-return/expected @@ -1,12 +1,19 @@ -Source-based code coverage results: - -main.rs (find_index)\ - * 4:1 - 4:59 COVERED\ - * 5:10 - 5:21 COVERED\ - * 7:20 - 7:31 COVERED\ - * 8:10 - 8:11 COVERED\ - * 10:5 - 10:9 UNCOVERED\ - * 11:1 - 11:2 COVERED - -main.rs (main)\ - * 14:1 - 19:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn find_index(nums: &[i32], target: i32) -> Option {\ + 5| 1| for (index, &num) in nums.iter().enumerate() {\ + 6| | if num == target {\ + 7| 1| return Some(index);\ + 8| 1| } \ + 9| | }\ + 10| 0| ```None'''\ + 11| | }\ + 12| | \ + 13| | #[kani::proof]\ + 14| 1| fn main() {\ + 15| 1| let numbers = [10, 20, 30, 40, 50];\ + 16| 1| let target = 30;\ + 17| 1| let result = find_index(&numbers, target);\ + 18| 1| assert_eq!(result, Some(2));\ + 19| | }\ diff --git a/tests/coverage/if-statement-multi/expected b/tests/coverage/if-statement-multi/expected index 4e8382d10a6f..87b3b80f556d 100644 --- a/tests/coverage/if-statement-multi/expected +++ b/tests/coverage/if-statement-multi/expected @@ -1,11 +1,26 @@ -Source-based code coverage results: - -test.rs (main)\ - * 21:1 - 26:2 COVERED - -test.rs (test_cov)\ - * 16:1 - 17:15 COVERED\ - * 17:19 - 17:28 UNCOVERED\ - * 17:31 - 17:35 COVERED\ - * 17:45 - 17:50 UNCOVERED\ - * 18:1 - 18:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | // kani-flags: --coverage -Zsource-coverage\ + 4| | \ + 5| | //! Checks that we are covering all regions except\ + 6| | //! * the `val == 42` condition\ + 7| | //! * the `false` branch\ + 8| | //!\ + 9| | //! No coverage information is shown for `_other_function` because it's sliced\ + 10| | //! off: \ + 11| | \ + 12| 0| ```fn _other_function() {'''\ + 13| 0| ``` println!("Hello, world!");'''\ + 14| 0| ```}'''\ + 15| | \ + 16| 1| fn test_cov(val: u32) -> bool {\ + 17| 1| if val < 3 || ```val == 42''' { true } else { ```false''' }\ + 18| | }\ + 19| | \ + 20| | #[cfg_attr(kani, kani::proof)]\ + 21| 1| fn main() {\ + 22| 1| let test1 = test_cov(1);\ + 23| 1| let test2 = test_cov(2);\ + 24| 1| assert!(test1);\ + 25| 1| assert!(test2);\ + 26| | }\ diff --git a/tests/coverage/if-statement/expected b/tests/coverage/if-statement/expected index b85b95de9c84..85efc027aa04 100644 --- a/tests/coverage/if-statement/expected +++ b/tests/coverage/if-statement/expected @@ -1,14 +1,20 @@ -Source-based code coverage results: - -main.rs (check_number)\ - * 4:1 - 5:15 COVERED\ - * 7:12 - 7:24 COVERED\ - * 7:27 - 7:46 UNCOVERED\ - * 7:56 - 7:74 COVERED\ - * 8:15 - 8:22 UNCOVERED\ - * 9:9 - 9:19 UNCOVERED\ - * 11:9 - 11:15 UNCOVERED\ - * 13:1 - 13:2 COVERED - -main.rs (main)\ - * 16:1 - 20:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 1| fn check_number(num: i32) -> &'static str {\ + 5| 1| if num > 0 {\ + 6| | // The next line is partially covered\ + 7| 1| if num % 2 == 0 { ```"Positive and Even"''' } else { "Positive and Odd" }\ + 8| 0| } else if ```num < 0''' {\ + 9| 0| ```"Negative"'''\ + 10| | } else {\ + 11| 0| ```"Zero"'''\ + 12| | }\ + 13| | }\ + 14| | \ + 15| | #[kani::proof]\ + 16| 1| fn main() {\ + 17| 1| let number = 7;\ + 18| 1| let result = check_number(number);\ + 19| 1| assert_eq!(result, "Positive and Odd");\ + 20| | }\ diff --git a/tests/coverage/known_issues/assert_uncovered_end/expected b/tests/coverage/known_issues/assert_uncovered_end/expected index ceba065ce424..2f6fca5e4aae 100644 --- a/tests/coverage/known_issues/assert_uncovered_end/expected +++ b/tests/coverage/known_issues/assert_uncovered_end/expected @@ -1,9 +1,14 @@ -Source-based code coverage results: - -test.rs (check_assert)\ - * 9:1 - 10:34 COVERED\ - * 11:14 - 13:6 COVERED\ - * 13:6 - 13:7 UNCOVERED - -test.rs (check_assert::{closure#0})\ - * 10:40 - 10:49 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks that `check_assert` is fully covered. At present, the coverage for\ + 5| | //! this test reports an uncovered single-column region at the end of the `if`\ + 6| | //! statement: \ + 7| | \ + 8| | #[kani::proof]\ + 9| 1| fn check_assert() {\ + 10| 1| let x: u32 = kani::any_where(|val| *val == 5);\ + 11| 1| if x > 3 {\ + 12| 1| assert!(x > 4);\ + 13| 1| }``` '''\ + 14| | }\ diff --git a/tests/coverage/known_issues/assume_assert/expected b/tests/coverage/known_issues/assume_assert/expected index 55f3235d7d24..a65a36099d05 100644 --- a/tests/coverage/known_issues/assume_assert/expected +++ b/tests/coverage/known_issues/assume_assert/expected @@ -1,4 +1,15 @@ -Source-based code coverage results: - -main.rs (check_assume_assert)\ - * 11:1 - 15:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! This test should check that the region after `kani::assume(false)` is\ + 5| | //! `UNCOVERED`. However, due to a technical limitation in `rustc`'s coverage\ + 6| | //! instrumentation, only one `COVERED` region is reported for the whole\ + 7| | //! function. More details in\ + 8| | //! .\ + 9| | \ + 10| | #[kani::proof]\ + 11| 1| fn check_assume_assert() {\ + 12| 1| let a: u8 = kani::any();\ + 13| 1| kani::assume(false);\ + 14| 1| assert!(a < 5);\ + 15| | }\ diff --git a/tests/coverage/known_issues/out-of-bounds/expected b/tests/coverage/known_issues/out-of-bounds/expected index 8ab9e2e15627..3924d21de456 100644 --- a/tests/coverage/known_issues/out-of-bounds/expected +++ b/tests/coverage/known_issues/out-of-bounds/expected @@ -1,7 +1,15 @@ -Source-based code coverage results: - -test.rs (get)\ - * 8:1 - 10:2 COVERED - -test.rs (main)\ - * 13:1 - 15:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! This test should check that the return in `get` is `UNCOVERED`. However, the\ + 5| | //! coverage results currently report that the whole function is `COVERED`,\ + 6| | //! likely due to \ + 7| | \ + 8| 1| fn get(s: &[i16], index: usize) -> i16 {\ + 9| 1| s[index]\ + 10| | }\ + 11| | \ + 12| | #[kani::proof]\ + 13| 1| fn main() {\ + 14| 1| get(&[7, -83, 19], 15);\ + 15| | }\ diff --git a/tests/coverage/known_issues/variant/expected b/tests/coverage/known_issues/variant/expected index 13383ed3bab0..6445c038f060 100644 --- a/tests/coverage/known_issues/variant/expected +++ b/tests/coverage/known_issues/variant/expected @@ -1,14 +1,32 @@ -Source-based code coverage results: - -main.rs (main)\ - * 29:1 - 32:2 COVERED - -main.rs (print_direction)\ - * 16:1 - 16:36 COVERED\ - * 18:11 - 18:14 UNCOVERED\ - * 19:26 - 19:47 UNCOVERED\ - * 20:28 - 20:51 UNCOVERED\ - * 21:28 - 21:51 COVERED\ - * 22:34 - 22:63 UNCOVERED\ - * 24:14 - 24:45 UNCOVERED\ - * 26:1 - 26:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks coverage results in an example with a `match` statement matching on\ + 5| | //! all enum variants. Currently, it does not yield the expected results because\ + 6| | //! it reports the `dir` in the match statement as `UNCOVERED`:\ + 7| | //! \ + 8| | \ + 9| | enum Direction {\ + 10| | Up,\ + 11| | Down,\ + 12| | Left,\ + 13| | Right,\ + 14| | }\ + 15| | \ + 16| 1| fn print_direction(dir: Direction) {\ + 17| | // For some reason, `dir`'s span is reported as `UNCOVERED` too\ + 18| 0| match ```dir''' {\ + 19| 0| Direction::Up => ```println!("Going up!")''',\ + 20| 0| Direction::Down => ```println!("Going down!")''',\ + 21| 1| Direction::Left => println!("Going left!"),\ + 22| 0| Direction::Right if 1 == ```1 => println!("Going right!")''',\ + 23| | // This part is unreachable since we cover all variants in the match.\ + 24| 0| _ => ```println!("Not going anywhere!")''',\ + 25| | }\ + 26| | }\ + 27| | \ + 28| | #[kani::proof]\ + 29| 1| fn main() {\ + 30| 1| let direction = Direction::Left;\ + 31| 1| print_direction(direction);\ + 32| | }\ diff --git a/tests/coverage/multiple-harnesses/expected b/tests/coverage/multiple-harnesses/expected index b5362147fed1..6ae834a3bccb 100644 --- a/tests/coverage/multiple-harnesses/expected +++ b/tests/coverage/multiple-harnesses/expected @@ -1,37 +1,44 @@ -Source-based code coverage results: - -main.rs (estimate_size)\ - * 4:1 - 7:15 COVERED\ - * 8:12 - 8:19 COVERED\ - * 9:20 - 9:21 COVERED\ - * 11:20 - 11:21 COVERED\ - * 13:15 - 13:23 COVERED\ - * 14:12 - 14:20 COVERED\ - * 15:20 - 15:21 COVERED\ - * 17:20 - 17:21 COVERED\ - * 20:12 - 20:20 COVERED\ - * 21:20 - 21:21 COVERED\ - * 23:20 - 23:21 COVERED\ - * 26:1 - 26:2 COVERED - -main.rs (fully_covered)\ - * 39:1 - 44:2 COVERED - -Source-based code coverage results: - -main.rs (estimate_size)\ - * 4:1 - 7:15 COVERED\ - * 8:12 - 8:19 COVERED\ - * 9:20 - 9:21 COVERED\ - * 11:20 - 11:21 COVERED\ - * 13:15 - 13:23 COVERED\ - * 14:12 - 14:20 COVERED\ - * 15:20 - 15:21 COVERED\ - * 17:20 - 17:21 COVERED\ - * 20:12 - 20:20 COVERED\ - * 21:20 - 21:21 COVERED\ - * 23:20 - 23:21 UNCOVERED\ - * 26:1 - 26:2 COVERED - -main.rs (mostly_covered)\ - * 30:1 - 35:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| 2| fn estimate_size(x: u32) -> u32 {\ + 5| 2| assert!(x < 4096);\ + 6| 2| \ + 7| 2| if x < 256 {\ + 8| 2| if x < 128 {\ + 9| 2| return 1;\ + 10| | } else {\ + 11| 2| return 3;\ + 12| | }\ + 13| 2| } else if x < 1024 {\ + 14| 2| if x > 1022 {\ + 15| 2| return 4;\ + 16| | } else {\ + 17| 2| return 5;\ + 18| | }\ + 19| | } else {\ + 20| 2| if x < 2048 {\ + 21| 2| return 7;\ + 22| | } else {\ + 23| 1| return 9;\ + 24| | }\ + 25| | }\ + 26| | }\ + 27| | \ + 28| | #[cfg(kani)]\ + 29| | #[kani::proof]\ + 30| 1| fn mostly_covered() {\ + 31| 1| let x: u32 = kani::any();\ + 32| 1| kani::assume(x < 2048);\ + 33| 1| let y = estimate_size(x);\ + 34| 1| assert!(y < 10);\ + 35| | }\ + 36| | \ + 37| | #[cfg(kani)]\ + 38| | #[kani::proof]\ + 39| 1| fn fully_covered() {\ + 40| 1| let x: u32 = kani::any();\ + 41| 1| kani::assume(x < 4096);\ + 42| 1| let y = estimate_size(x);\ + 43| 1| assert!(y < 10);\ + 44| | }\ diff --git a/tests/coverage/overflow-failure/expected b/tests/coverage/overflow-failure/expected index db4f29d51336..acba327e1d7a 100644 --- a/tests/coverage/overflow-failure/expected +++ b/tests/coverage/overflow-failure/expected @@ -1,9 +1,15 @@ -Source-based code coverage results: - -test.rs (cond_reduce)\ - * 7:1 - 8:18 COVERED\ - * 8:21 - 8:27 COVERED\ - * 8:37 - 8:38 UNCOVERED - -test.rs (main)\ - * 12:1 - 15:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks that Kani reports the correct coverage results in the case of an\ + 5| | //! arithmetic overflow failure (caused by the second call to `cond_reduce`).\ + 6| | \ + 7| 1| fn cond_reduce(thresh: u32, x: u32) -> u32 {\ + 8| 1| if x > thresh { x - 50 } else { ```x''' }\ + 9| | }\ + 10| | \ + 11| | #[kani::proof]\ + 12| 1| fn main() {\ + 13| 1| cond_reduce(60, 70);\ + 14| 1| cond_reduce(40, 42);\ + 15| | }\ diff --git a/tests/coverage/overflow-full-coverage/expected b/tests/coverage/overflow-full-coverage/expected index 4d17761505eb..4daa50db8510 100644 --- a/tests/coverage/overflow-full-coverage/expected +++ b/tests/coverage/overflow-full-coverage/expected @@ -1,9 +1,17 @@ -Source-based code coverage results: - -test.rs (main)\ - * 12:1 - 17:2 COVERED - -test.rs (reduce)\ - * 7:1 - 8:16 COVERED\ - * 8:19 - 8:27 COVERED\ - * 8:37 - 8:38 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks that Kani reports all regions as `COVERED` as expected in this case\ + 5| | //! where arithmetic overflow failures are prevented.\ + 6| | \ + 7| 1| fn reduce(x: u32) -> u32 {\ + 8| 1| if x > 1000 { x - 1000 } else { x }\ + 9| | }\ + 10| | \ + 11| | #[kani::proof]\ + 12| 1| fn main() {\ + 13| 1| reduce(7);\ + 14| 1| reduce(33);\ + 15| 1| reduce(728);\ + 16| 1| reduce(1079);\ + 17| | }\ diff --git a/tests/coverage/while-loop-break/expected b/tests/coverage/while-loop-break/expected index 34afef9ee12c..4e52fc5804e7 100644 --- a/tests/coverage/while-loop-break/expected +++ b/tests/coverage/while-loop-break/expected @@ -1,13 +1,23 @@ -Source-based code coverage results: - -main.rs (find_first_negative)\ - * 7:1 - 8:22 COVERED\ - * 9:11 - 9:29 COVERED\ - * 10:12 - 10:27 COVERED\ - * 11:20 - 11:37 COVERED\ - * 12:10 - 13:19 COVERED\ - * 15:5 - 15:9 UNCOVERED\ - * 16:1 - 16:2 COVERED - -main.rs (main)\ - * 19:1 - 23:2 COVERED + 1| | // Copyright Kani Contributors\ + 2| | // SPDX-License-Identifier: Apache-2.0 OR MIT\ + 3| | \ + 4| | //! Checks coverage results in an example with a `while` loop that returns before\ + 5| | //! running the last iteration.\ + 6| | \ + 7| 1| fn find_first_negative(nums: &[i32]) -> Option {\ + 8| 1| let mut index = 0;\ + 9| 1| while index < nums.len() {\ + 10| 1| if nums[index] < 0 {\ + 11| 1| return Some(nums[index]);\ + 12| 1| }\ + 13| 1| index += 1;\ + 14| | }\ + 15| 0| ```None'''\ + 16| | }\ + 17| | \ + 18| | #[kani::proof]\ + 19| 1| fn main() {\ + 20| 1| let numbers = [1, 2, -3, 4, -5];\ + 21| 1| let result = find_first_negative(&numbers);\ + 22| 1| assert_eq!(result, Some(-3));\ + 23| | }\ diff --git a/tools/compiletest/src/runtest.rs b/tools/compiletest/src/runtest.rs index 6e6425d086c3..1622dc104919 100644 --- a/tools/compiletest/src/runtest.rs +++ b/tools/compiletest/src/runtest.rs @@ -441,8 +441,11 @@ impl<'test> TestCx<'test> { /// Runs Kani in coverage mode on the test file specified by `self.testpaths.file`. fn run_expected_coverage_test(&self) { let proc_res = self.run_kani_with_coverage(); + let cov_results_path = self.extract_cov_results_path(&proc_res); + let (kanimap, kaniraws, kanicov) = self.find_cov_files(&cov_results_path); + let kanicov_proc = self.run_kanicov_report(&kanimap, &kaniraws, &kanicov); let expected_path = self.testpaths.file.parent().unwrap().join("expected"); - self.verify_output(&proc_res, &expected_path); + self.verify_output(&kanicov_proc, &expected_path); } /// Runs Kani with stub implementations of various data structures. @@ -536,11 +539,88 @@ impl<'test> TestCx<'test> { let stamp = crate::stamp(self.config, self.testpaths); fs::write(stamp, "we only support one configuration").unwrap(); } + + /// Run `kani-cov merge` and `kani-cov report` to generate a text-based + /// report and return the `ProcRes` associated to the `kani-cov report` + /// command. + fn run_kanicov_report( + &self, + kanimap: &PathBuf, + kaniraws: &[PathBuf], + kanicov: &PathBuf, + ) -> ProcRes { + let mut kanicov_merge = Command::new("kani-cov"); + kanicov_merge.arg("merge"); + kanicov_merge.args(kaniraws); + kanicov_merge.arg("--output"); + kanicov_merge.arg(kanicov); + let merge_cmd = self.compose_and_run(kanicov_merge); + + if !merge_cmd.status.success() { + self.fatal_proc_rec("test failed: could not run `kani-cov merge` command", &merge_cmd); + } + + let mut kanicov_report = Command::new("kani-cov"); + kanicov_report.arg("report").arg(kanimap).arg("--profile").arg(kanicov); + let report_cmd = self.compose_and_run(kanicov_report); + + if !report_cmd.status.success() { + self.fatal_proc_rec( + "test failed: could not run `kani-cov report` command", + &report_cmd, + ); + } + report_cmd + } + + /// Return the paths to the files to be used for the `kani-cov` commands. + /// Note that `kanimap` and `kaniraws` result from any coverage-enabled Kani + /// run. `kanicov` is the name we will use for the output of the `kani-cov + /// merge` command. + fn find_cov_files(&self, folder_path: &Path) -> (PathBuf, Vec, PathBuf) { + let folder_name = folder_path.file_name().unwrap(); + + let kanimap = folder_path.join(format!("{}_kanimap.json", folder_name.to_string_lossy())); + let kanicov = folder_path.join(format!("{}_kanicov.json", folder_name.to_string_lossy())); + + let kaniraw_glob = format!("{}/*_kaniraw.json", folder_path.display()); + let kaniraws: Vec = glob::glob(&kaniraw_glob) + .expect("Failed to read glob pattern") + .filter_map(|entry| entry.ok()) + .collect(); + + (kanimap, kaniraws, kanicov) + } + + /// Find the path to the folder where the coverage results have been saved. + /// + /// The path is displayed in the output of a coverage-enabled Kani run like + /// this: + /// ```sh + /// Verification Time: XX.XXXXXXXs + /// + /// [info] Coverage results saved to /path/to/cov/results/kanicov__