Skip to content

Commit c03e50d

Browse files
nchong-at-awstedinski
authored andcommitted
Rectangle regression test for cargo kani (rust-lang#909)
New regression test that shows unit testing, property-based testing and Kani
1 parent bac92b7 commit c03e50d

File tree

6 files changed

+174
-0
lines changed

6 files changed

+174
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "rectangle-example"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
[dependencies]
7+
8+
[dev-dependencies]
9+
proptest = "1.0.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
Using an implementation of a `Rectangle` we use unit tests, property-based testing and Kani
2+
3+
## Reproducing results locally
4+
5+
### Dependencies
6+
7+
- Rust edition 2018
8+
- [Kani](https://model-checking.github.io/kani/getting-started.html)
9+
10+
If you have problems installing Kani then please file an [issue](https://github.com/model-checking/kani/issues/new/choose).
11+
12+
### Unit testing and proptest
13+
14+
Use `cargo test` to run the unit test and property-based test.
15+
16+
```bash
17+
$ cargo test
18+
# --snip--
19+
running 2 tests
20+
test rectangle::tests::stretched_rectangle_can_hold_original ... ok
21+
test rectangle::proptests::stretched_rectangle_can_hold_original ... ok
22+
```
23+
24+
### Using Kani
25+
26+
Use `cargo kani` to verify the first proof harness `stretched_rectangle_can_hold_original`. As we explain in the post, verification failure is expected.
27+
28+
```bash
29+
$ cargo kani --function stretched_rectangle_can_hold_original --output-format terse
30+
# --snip--
31+
VERIFICATION:- FAILED
32+
```
33+
34+
In order to view a trace (a step-by-step execution of the program) use the `--visualize` flag:
35+
36+
```bash
37+
$ cargo kani --function stretched_rectangle_can_hold_original --output-format terse --visualize
38+
# --snip--
39+
VERIFICATION:- FAILED
40+
# and generates a html report in target/report/html/index.html
41+
```
42+
43+
And open the report in a browser.
44+
45+
After fixing the problem we can re-run Kani (on the proof harness `stretched_rectangle_can_hold_original_fixed`). This time we expect verification success:
46+
47+
```bash
48+
$ cargo kani --function stretched_rectangle_can_hold_original_fixed --output-format terse
49+
# --snip--
50+
VERIFICATION:- SUCCESSFUL
51+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
pub mod rectangle;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// ANCHOR: rectangle_definition
5+
#[derive(Debug, Copy, Clone)]
6+
struct Rectangle {
7+
width: u64,
8+
height: u64,
9+
}
10+
11+
impl Rectangle {
12+
fn can_hold(&self, other: &Rectangle) -> bool {
13+
self.width > other.width && self.height > other.height
14+
}
15+
}
16+
// ANCHOR_END: rectangle_definition
17+
18+
impl Rectangle {
19+
// ANCHOR: stretch_definition
20+
fn stretch(&self, factor: u64) -> Option<Self> {
21+
let w = self.width.checked_mul(factor)?;
22+
let h = self.height.checked_mul(factor)?;
23+
Some(Rectangle { width: w, height: h })
24+
}
25+
// ANCHOR_END: stretch_definition
26+
}
27+
28+
#[cfg(test)]
29+
mod tests {
30+
use super::*;
31+
32+
// ANCHOR: stretch_unit_test
33+
#[test]
34+
fn stretched_rectangle_can_hold_original() {
35+
let original = Rectangle { width: 8, height: 7 };
36+
let factor = 2;
37+
let larger = original.stretch(factor);
38+
assert!(larger.unwrap().can_hold(&original));
39+
}
40+
// ANCHOR_END: stretch_unit_test
41+
}
42+
43+
#[cfg(test)]
44+
mod proptests {
45+
use super::*;
46+
use proptest::num::u64;
47+
use proptest::prelude::*;
48+
49+
proptest! {
50+
// ANCHOR: stretch_proptest
51+
#[test]
52+
fn stretched_rectangle_can_hold_original(width in u64::ANY,
53+
height in u64::ANY,
54+
factor in u64::ANY) {
55+
let original = Rectangle {
56+
width: width,
57+
height: height,
58+
};
59+
if let Some(larger) = original.stretch(factor) {
60+
assert!(larger.can_hold(&original));
61+
}
62+
}
63+
// ANCHOR_END: stretch_proptest
64+
}
65+
}
66+
67+
#[cfg(kani)]
68+
mod verification {
69+
use super::*;
70+
71+
// ANCHOR: stretch_kani
72+
#[kani::proof]
73+
pub fn stretched_rectangle_can_hold_original() {
74+
let original = Rectangle { width: kani::any(), height: kani::any() };
75+
let factor = kani::any();
76+
if let Some(larger) = original.stretch(factor) {
77+
assert!(larger.can_hold(&original));
78+
}
79+
}
80+
// ANCHOR_END: stretch_kani
81+
82+
// ANCHOR: stretch_kani_fixed
83+
#[kani::proof]
84+
pub fn stretched_rectangle_can_hold_original_fixed() {
85+
let original = Rectangle { width: kani::any(), height: kani::any() };
86+
let factor: u8 = kani::any(); //< would like to make u64
87+
kani::assume(0 != original.width);
88+
kani::assume(0 != original.height);
89+
kani::assume(1 < factor);
90+
if let Some(larger) = original.stretch(factor as u64) {
91+
assert!(larger.can_hold(&original));
92+
}
93+
}
94+
// ANCHOR_END: stretch_kani_fixed
95+
96+
// Currently causes Kani timeout
97+
#[kani::proof]
98+
pub fn stretched_rectangle_can_hold_original_fixed_u64() {
99+
let original = Rectangle { width: kani::any(), height: kani::any() };
100+
let factor = kani::any();
101+
kani::assume(0 != original.width);
102+
kani::assume(0 != original.height);
103+
kani::assume(1 < factor);
104+
if let Some(larger) = original.stretch(factor) {
105+
assert!(larger.can_hold(&original));
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VERIFICATION:- FAILED
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VERIFICATION:- SUCCESSFUL

0 commit comments

Comments
 (0)