Skip to content

Commit

Permalink
simplify timeout implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
smartcontracts committed Nov 25, 2024
1 parent 47bf075 commit b2a5f8d
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 37 deletions.
13 changes: 5 additions & 8 deletions crates/config/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ pub struct FuzzConfig {
pub failure_persist_file: Option<String>,
/// show `console.log` in fuzz test, defaults to `false`
pub show_logs: bool,
/// Optional timeout for each property test
pub timeout_secs: Option<u64>,
/// Optionally allow timeouts to not be reported as failures
pub allow_timeouts: bool,
/// Optional timeout (in seconds) for each property test
pub timeout: Option<u64>,
}

impl Default for FuzzConfig {
Expand All @@ -49,8 +47,7 @@ impl Default for FuzzConfig {
failure_persist_dir: None,
failure_persist_file: None,
show_logs: false,
timeout_secs: None,
allow_timeouts: false,
timeout: None,
}
}
}
Expand All @@ -67,8 +64,7 @@ impl FuzzConfig {
failure_persist_dir: Some(cache_dir),
failure_persist_file: Some("failures".to_string()),
show_logs: false,
timeout_secs: None,
allow_timeouts: false,
timeout: None,
}
}
}
Expand Down Expand Up @@ -98,6 +94,7 @@ impl InlineConfigParser for FuzzConfig {
}
"failure-persist-file" => conf_clone.failure_persist_file = Some(value),
"show-logs" => conf_clone.show_logs = parse_config_bool(key, value)?,
"timeout" => conf_clone.timeout = Some(parse_config_u64(key, value)?),
_ => Err(InlineConfigParserError::InvalidConfigProperty(key))?,
}
}
Expand Down
5 changes: 5 additions & 0 deletions crates/config/src/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct InvariantConfig {
pub failure_persist_dir: Option<PathBuf>,
/// Whether to collect and display fuzzed selectors metrics.
pub show_metrics: bool,
/// Optional timeout (in seconds) for each invariant test.
pub timeout: Option<u64>,
}

impl Default for InvariantConfig {
Expand All @@ -51,6 +53,7 @@ impl Default for InvariantConfig {
gas_report_samples: 256,
failure_persist_dir: None,
show_metrics: false,
timeout: None,
}
}
}
Expand All @@ -69,6 +72,7 @@ impl InvariantConfig {
gas_report_samples: 256,
failure_persist_dir: Some(cache_dir),
show_metrics: false,
timeout: None,
}
}

Expand Down Expand Up @@ -108,6 +112,7 @@ impl InlineConfigParser for InvariantConfig {
}
"shrink-run-limit" => conf_clone.shrink_run_limit = parse_config_u32(key, value)?,
"show-metrics" => conf_clone.show_metrics = parse_config_bool(key, value)?,
"timeout" => conf_clone.timeout = Some(parse_config_u64(key, value)?),
_ => Err(InlineConfigParserError::InvalidConfigProperty(key.to_string()))?,
}
}
Expand Down
35 changes: 17 additions & 18 deletions crates/evm/evm/src/executors/fuzz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,21 @@ impl FuzzedExecutor {
let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize;
let show_logs = self.config.show_logs;

// Start a timer if timeout is set
let start_time = self.config.timeout_secs.map(|timeout| {
// Start a timer if timeout is set.
let start_time = self.config.timeout.map(|timeout| {
(std::time::Instant::now(), std::time::Duration::from_secs(timeout))
});

let run_result = self.runner.clone().run(&strategy, |calldata| {
// Check if the timeout has been reached
// Check if the timeout has been reached.
if let Some((start_time, timeout)) = start_time {
if start_time.elapsed() > timeout {
return Err(TestCaseError::fail("Timeout reached"))
// At some point we might want to have a timeout be considered a failure.
// Easiest way to do this is to return an error here if some flag is set.
// Will correctly NOT increment the number of runs that is presented to the
// user because that number is calculated as the length of gas_by_case which
// doesn't get pushed to if we hit this branch.
return Ok(());
}
}

Expand Down Expand Up @@ -205,23 +210,17 @@ impl FuzzedExecutor {
}
Err(TestError::Fail(reason, _)) => {
let reason = reason.to_string();
result.reason = (!reason.is_empty()).then_some(reason.clone());
result.reason = (!reason.is_empty()).then_some(reason);

if reason == "Timeout reached" {
if self.config.allow_timeouts {
result.success = true;
}
let args = if let Some(data) = calldata.get(4..) {
func.abi_decode_input(data, false).unwrap_or_default()
} else {
let args = if let Some(data) = calldata.get(4..) {
func.abi_decode_input(data, false).unwrap_or_default()
} else {
vec![]
};
vec![]
};

result.counterexample = Some(CounterExample::Single(
BaseCounterExample::from_fuzz_call(calldata, args, call.traces),
));
}
result.counterexample = Some(CounterExample::Single(
BaseCounterExample::from_fuzz_call(calldata, args, call.traces),
));
}
}

Expand Down
17 changes: 17 additions & 0 deletions crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,24 @@ impl<'a> InvariantExecutor<'a> {
let (invariant_test, invariant_strategy) =
self.prepare_test(&invariant_contract, fuzz_fixtures)?;

// Start a timer if timeout is set.
let start_time = self.config.timeout.map(|timeout| {
(std::time::Instant::now(), std::time::Duration::from_secs(timeout))
});

let _ = self.runner.run(&invariant_strategy, |first_input| {
// Check if the timeout has been reached.
if let Some((start_time, timeout)) = start_time {
if start_time.elapsed() > timeout {
// At some point we might want to have a timeout be considered a failure.
// Easiest way to do this is to return an error here if some flag is set.
// Will correctly NOT increment the number of runs that is presented to the
// user because that number is calculated as the length of fuzz_cases which
// doesn't get push to if we hit this branch.
return Ok(());
}
}

// Create current invariant run data.
let mut current_run = InvariantTestRun::new(
first_input,
Expand Down
15 changes: 4 additions & 11 deletions crates/forge/bin/cmd/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,8 @@ pub struct TestArgs {
pub fuzz_runs: Option<u64>,

/// Timeout for each fuzz run in seconds.
#[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT_SECS", value_name = "TIMEOUT_SECS")]
pub fuzz_timeout_secs: Option<u64>,

/// Allow timeouts to not be reported as failures.
#[arg(long, env = "FOUNDRY_FUZZ_ALLOW_TIMEOUTS", value_name = "ALLOW_TIMEOUTS")]
pub fuzz_allow_timeouts: bool,
#[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")]
pub fuzz_timeout: Option<u64>,

/// File to rerun fuzz failures from.
#[arg(long)]
Expand Down Expand Up @@ -878,11 +874,8 @@ impl Provider for TestArgs {
if let Some(fuzz_runs) = self.fuzz_runs {
fuzz_dict.insert("runs".to_string(), fuzz_runs.into());
}
if let Some(fuzz_timeout_secs) = self.fuzz_timeout_secs {
fuzz_dict.insert("timeout_secs".to_string(), fuzz_timeout_secs.into());
}
if self.fuzz_allow_timeouts {
fuzz_dict.insert("allow_timeouts".to_string(), true.into());
if let Some(fuzz_timeout) = self.fuzz_timeout {
fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into());
}
if let Some(fuzz_input_file) = self.fuzz_input_file.clone() {
fuzz_dict.insert("failure_persist_file".to_string(), fuzz_input_file.into());
Expand Down

0 comments on commit b2a5f8d

Please sign in to comment.