Skip to content

Commit f6133f9

Browse files
authored
feat(invariant): on failures show original and current sequence len (foundry-rs#9816)
1 parent 6e919af commit f6133f9

File tree

4 files changed

+64
-13
lines changed

4 files changed

+64
-13
lines changed

crates/evm/fuzz/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ pub use inspector::Fuzzer;
3535
pub enum CounterExample {
3636
/// Call used as a counter example for fuzz tests.
3737
Single(BaseCounterExample),
38-
/// Sequence of calls used as a counter example for invariant tests.
39-
Sequence(Vec<BaseCounterExample>),
38+
/// Original sequence size and sequence of calls used as a counter example for invariant tests.
39+
Sequence(usize, Vec<BaseCounterExample>),
4040
}
4141

4242
#[derive(Clone, Debug, Serialize, Deserialize)]

crates/forge/src/result.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,14 @@ impl fmt::Display for TestResult {
446446
CounterExample::Single(ex) => {
447447
write!(s, "; counterexample: {ex}]").unwrap();
448448
}
449-
CounterExample::Sequence(sequence) => {
450-
s.push_str("]\n\t[Sequence]\n");
449+
CounterExample::Sequence(original, sequence) => {
450+
s.push_str(
451+
format!(
452+
"]\n\t[Sequence] (original: {original}, shrunk: {})\n",
453+
sequence.len()
454+
)
455+
.as_str(),
456+
);
451457
for ex in sequence {
452458
writeln!(s, "\t\t{ex}").unwrap();
453459
}
@@ -593,7 +599,7 @@ impl TestResult {
593599
} else {
594600
Some(format!("{invariant_name} persisted failure revert"))
595601
};
596-
self.counterexample = Some(CounterExample::Sequence(call_sequence));
602+
self.counterexample = Some(CounterExample::Sequence(call_sequence.len(), call_sequence));
597603
}
598604

599605
/// Returns the fail result for invariant test setup.

crates/forge/src/runner.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use foundry_evm::{
3131
traces::{load_contracts, TraceKind, TraceMode},
3232
};
3333
use proptest::test_runner::{
34-
FailurePersistence, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner,
34+
FailurePersistence, FileFailurePersistence, RngAlgorithm, TestError, TestRng, TestRunner,
3535
};
3636
use rayon::prelude::*;
3737
use std::{borrow::Cow, cmp::min, collections::BTreeMap, sync::Arc, time::Instant};
@@ -686,7 +686,16 @@ impl<'a> FunctionRunner<'a> {
686686
) {
687687
error!(%err, "Failed to record call sequence");
688688
}
689-
counterexample = Some(CounterExample::Sequence(call_sequence))
689+
690+
let original_seq_len =
691+
if let TestError::Fail(_, calls) = &case_data.test_error {
692+
calls.len()
693+
} else {
694+
call_sequence.len()
695+
};
696+
697+
counterexample =
698+
Some(CounterExample::Sequence(original_seq_len, call_sequence))
690699
}
691700
}
692701
Err(err) => {

crates/forge/tests/it/invariant.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ async fn test_invariant_shrink() {
266266
match get_counterexample!(runner, &filter) {
267267
CounterExample::Single(_) => panic!("CounterExample should be a sequence."),
268268
// `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2.
269-
CounterExample::Sequence(sequence) => {
269+
CounterExample::Sequence(_, sequence) => {
270270
assert!(sequence.len() <= 3);
271271

272272
if sequence.len() == 2 {
@@ -314,7 +314,7 @@ async fn check_shrink_sequence(test_pattern: &str, expected_len: usize) {
314314

315315
match get_counterexample!(runner, &filter) {
316316
CounterExample::Single(_) => panic!("CounterExample should be a sequence."),
317-
CounterExample::Sequence(sequence) => {
317+
CounterExample::Sequence(_, sequence) => {
318318
assert_eq!(sequence.len(), expected_len);
319319
}
320320
};
@@ -346,7 +346,7 @@ async fn test_shrink_big_sequence() {
346346

347347
let initial_sequence = match initial_counterexample {
348348
CounterExample::Single(_) => panic!("CounterExample should be a sequence."),
349-
CounterExample::Sequence(sequence) => sequence,
349+
CounterExample::Sequence(_, sequence) => sequence,
350350
};
351351
// ensure shrinks to same sequence of 77
352352
assert_eq!(initial_sequence.len(), 77);
@@ -379,7 +379,7 @@ async fn test_shrink_big_sequence() {
379379
.unwrap()
380380
{
381381
CounterExample::Single(_) => panic!("CounterExample should be a sequence."),
382-
CounterExample::Sequence(sequence) => sequence,
382+
CounterExample::Sequence(_, sequence) => sequence,
383383
};
384384
// ensure shrinks to same sequence of 77
385385
assert_eq!(new_sequence.len(), 77);
@@ -407,7 +407,7 @@ async fn test_shrink_fail_on_revert() {
407407

408408
match get_counterexample!(runner, &filter) {
409409
CounterExample::Single(_) => panic!("CounterExample should be a sequence."),
410-
CounterExample::Sequence(sequence) => {
410+
CounterExample::Sequence(_, sequence) => {
411411
// ensure shrinks to sequence of 10
412412
assert_eq!(sequence.len(), 10);
413413
}
@@ -696,7 +696,7 @@ async fn test_no_reverts_in_counterexample() {
696696

697697
match get_counterexample!(runner, &filter) {
698698
CounterExample::Single(_) => panic!("CounterExample should be a sequence."),
699-
CounterExample::Sequence(sequence) => {
699+
CounterExample::Sequence(_, sequence) => {
700700
// ensure original counterexample len is 10 (even without shrinking)
701701
assert_eq!(sequence.len(), 10);
702702
}
@@ -1064,3 +1064,39 @@ contract InvariantSelectorsWeightTest is Test {
10641064

10651065
cmd.args(["test", "--fuzz-seed", "119", "--mt", "invariant_selectors_weight"]).assert_success();
10661066
});
1067+
1068+
// Tests original and new counterexample lengths are displayed on failure.
1069+
forgetest_init!(invariant_sequence_len, |prj, cmd| {
1070+
prj.update_config(|config| {
1071+
config.fuzz.seed = Some(U256::from(100u32));
1072+
});
1073+
1074+
prj.add_test(
1075+
"InvariantSequenceLenTest.t.sol",
1076+
r#"
1077+
import {Test} from "forge-std/Test.sol";
1078+
import "src/Counter.sol";
1079+
1080+
contract InvariantSequenceLenTest is Test {
1081+
Counter public counter;
1082+
1083+
function setUp() public {
1084+
counter = new Counter();
1085+
targetContract(address(counter));
1086+
}
1087+
1088+
function invariant_increment() public {
1089+
require(counter.number() / 2 < 100000000000000000000000000000000, "invariant increment failure");
1090+
}
1091+
}
1092+
"#,
1093+
)
1094+
.unwrap();
1095+
1096+
cmd.args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq(str![[r#"
1097+
...
1098+
[FAIL: revert: invariant increment failure]
1099+
[Sequence] (original: 4, shrunk: 1)
1100+
...
1101+
"#]]);
1102+
});

0 commit comments

Comments
 (0)