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

fuzz console log & test cases #8387

Merged
merged 8 commits into from
Jul 9, 2024
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
8 changes: 7 additions & 1 deletion crates/config/src/fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Configuration for fuzz testing.

use crate::inline::{
parse_config_u32, InlineConfigParser, InlineConfigParserError, INLINE_CONFIG_FUZZ_KEY,
parse_config_bool, parse_config_u32, InlineConfigParser, InlineConfigParserError,
INLINE_CONFIG_FUZZ_KEY,
};
use alloy_primitives::U256;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -29,6 +30,8 @@ pub struct FuzzConfig {
pub failure_persist_dir: Option<PathBuf>,
/// Name of the file to record fuzz failures, defaults to `failures`.
pub failure_persist_file: Option<String>,
/// show `console.log` in fuzz test, defaults to `false`
pub show_logs: bool,
}

impl Default for FuzzConfig {
Expand All @@ -41,6 +44,7 @@ impl Default for FuzzConfig {
gas_report_samples: 256,
failure_persist_dir: None,
failure_persist_file: None,
show_logs: false,
}
}
}
Expand All @@ -56,6 +60,7 @@ impl FuzzConfig {
gas_report_samples: 256,
failure_persist_dir: Some(cache_dir),
failure_persist_file: Some("failures".to_string()),
show_logs: false,
}
}
}
Expand Down Expand Up @@ -84,6 +89,7 @@ impl InlineConfigParser for FuzzConfig {
conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)?
}
"failure-persist-file" => conf_clone.failure_persist_file = Some(value),
"show-logs" => conf_clone.show_logs = parse_config_bool(key, value)?,
_ => Err(InlineConfigParserError::InvalidConfigProperty(key))?,
}
}
Expand Down
19 changes: 11 additions & 8 deletions crates/evm/evm/src/executors/fuzz/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
use crate::executors::{Executor, RawCallResult};
use alloy_dyn_abi::JsonAbiExt;
use alloy_json_abi::Function;
use alloy_primitives::{Address, Bytes, U256};
use alloy_primitives::{Address, Bytes, Log, U256};
use eyre::Result;
use foundry_common::evm::Breakpoints;
use foundry_config::FuzzConfig;
use foundry_evm_core::{
constants::MAGIC_ASSUME,
decode::{decode_console_logs, RevertDecoder},
};
use foundry_evm_core::{constants::MAGIC_ASSUME, decode::RevertDecoder};
use foundry_evm_coverage::HitMaps;
use foundry_evm_fuzz::{
strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
Expand Down Expand Up @@ -37,6 +34,8 @@ pub struct FuzzTestData {
pub breakpoints: Option<Breakpoints>,
// Stores coverage information for all fuzz cases.
pub coverage: Option<HitMaps>,
// Stores logs for all fuzz cases
pub logs: Vec<Log>,
}

/// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`].
Expand Down Expand Up @@ -90,6 +89,7 @@ impl FuzzedExecutor {
];
// We want to collect at least one trace which will be displayed to user.
let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize;
let show_logs = self.config.show_logs;

let run_result = self.runner.clone().run(&strat, |calldata| {
let fuzz_res = self.single_fuzz(address, should_fail, calldata)?;
Expand All @@ -113,7 +113,9 @@ impl FuzzedExecutor {
data.traces.push(call_traces);
data.breakpoints.replace(case.breakpoints);
}

if show_logs {
data.logs.extend(case.logs);
}
// Collect and merge coverage if `forge snapshot` context.
match &mut data.coverage {
Some(prev) => prev.merge(case.coverage.unwrap()),
Expand All @@ -133,6 +135,7 @@ impl FuzzedExecutor {
// to run at least one more case to find a minimal failure
// case.
let reason = rd.maybe_decode(&outcome.1.result, Some(status));
execution_data.borrow_mut().logs.extend(outcome.1.logs.clone());
execution_data.borrow_mut().counterexample = outcome;
// HACK: we have to use an empty string here to denote `None`.
Err(TestCaseError::fail(reason.unwrap_or_default()))
Expand All @@ -156,8 +159,7 @@ impl FuzzedExecutor {
success: run_result.is_ok(),
reason: None,
counterexample: None,
decoded_logs: decode_console_logs(&call.logs),
logs: call.logs,
logs: fuzz_result.logs,
labeled_addresses: call.labels,
traces: last_run_traces,
breakpoints: last_run_breakpoints,
Expand Down Expand Up @@ -229,6 +231,7 @@ impl FuzzedExecutor {
traces: call.traces,
coverage: call.coverage,
breakpoints,
logs: call.logs,
}))
} else {
Ok(FuzzOutcome::CounterExample(CounterExampleOutcome {
Expand Down
4 changes: 3 additions & 1 deletion crates/evm/evm/src/executors/fuzz/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::executors::RawCallResult;
use alloy_primitives::Bytes;
use alloy_primitives::{Bytes, Log};
use foundry_common::evm::Breakpoints;
use foundry_evm_coverage::HitMaps;
use foundry_evm_fuzz::FuzzCase;
Expand All @@ -17,6 +17,8 @@ pub struct CaseOutcome {
pub coverage: Option<HitMaps>,
/// Breakpoints char pc map
pub breakpoints: Breakpoints,
/// logs of a single fuzz test case
pub logs: Vec<Log>,
}

/// Returned by a single fuzz when a counterexample has been discovered
Expand Down
3 changes: 0 additions & 3 deletions crates/evm/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,6 @@ pub struct FuzzTestResult {
/// be printed to the user.
pub logs: Vec<Log>,

/// The decoded DSTest logging events and Hardhat's `console.log` from [logs](Self::logs).
pub decoded_logs: Vec<String>,

/// Labeled addresses
pub labeled_addresses: HashMap<Address, String>,

Expand Down
1 change: 1 addition & 0 deletions crates/forge/tests/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ forgetest!(can_extract_config_values, |prj, cmd| {
seed: Some(U256::from(1000)),
failure_persist_dir: Some("test-cache/fuzz".into()),
failure_persist_file: Some("failures".to_string()),
show_logs: false,
..Default::default()
},
invariant: InvariantConfig {
Expand Down
125 changes: 125 additions & 0 deletions crates/forge/tests/cli/test_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,3 +728,128 @@ contract PrecompileLabelsTest is Test {
assert!(output.contains("Blake2F: [0x0000000000000000000000000000000000000009]"));
assert!(output.contains("PointEvaluation: [0x000000000000000000000000000000000000000A]"));
});

// tests that `forge test` with config `show_logs: true` for fuzz tests will
// display `console.log` info
forgetest_init!(should_show_logs_when_fuzz_test, |prj, cmd| {
prj.wipe_contracts();

// run fuzz test 3 times
let config = Config {
fuzz: { FuzzConfig { runs: 3, show_logs: true, ..Default::default() } },
..Default::default()
};
prj.write_config(config);
let config = cmd.config();
assert_eq!(config.fuzz.runs, 3);

prj.add_test(
"ContractFuzz.t.sol",
r#"pragma solidity 0.8.24;
import {Test, console2} from "forge-std/Test.sol";
contract ContractFuzz is Test {
function testFuzzConsoleLog(uint256 x) public pure {
console2.log("inside fuzz test, x is:", x);
}
}
"#,
)
.unwrap();
cmd.args(["test", "-vv"]);
let stdout = cmd.stdout_lossy();
assert!(stdout.contains("inside fuzz test, x is:"), "\n{stdout}");
});

// tests that `forge test` with inline config `show_logs = true` for fuzz tests will
// display `console.log` info
forgetest_init!(should_show_logs_when_fuzz_test_inline_config, |prj, cmd| {
prj.wipe_contracts();

// run fuzz test 3 times
let config =
Config { fuzz: { FuzzConfig { runs: 3, ..Default::default() } }, ..Default::default() };
prj.write_config(config);
let config = cmd.config();
assert_eq!(config.fuzz.runs, 3);

prj.add_test(
"ContractFuzz.t.sol",
r#"pragma solidity 0.8.24;
import {Test, console2} from "forge-std/Test.sol";
contract ContractFuzz is Test {

/// forge-config: default.fuzz.show-logs = true
function testFuzzConsoleLog(uint256 x) public pure {
console2.log("inside fuzz test, x is:", x);
}
}
"#,
)
.unwrap();
cmd.args(["test", "-vv"]);
let stdout = cmd.stdout_lossy();
assert!(stdout.contains("inside fuzz test, x is:"), "\n{stdout}");
});

// tests that `forge test` with config `show_logs: false` for fuzz tests will not display
// `console.log` info
forgetest_init!(should_not_show_logs_when_fuzz_test, |prj, cmd| {
prj.wipe_contracts();

// run fuzz test 3 times
let config = Config {
fuzz: { FuzzConfig { runs: 3, show_logs: false, ..Default::default() } },
..Default::default()
};
prj.write_config(config);
let config = cmd.config();
assert_eq!(config.fuzz.runs, 3);

prj.add_test(
"ContractFuzz.t.sol",
r#"pragma solidity 0.8.24;
import {Test, console2} from "forge-std/Test.sol";
contract ContractFuzz is Test {

function testFuzzConsoleLog(uint256 x) public pure {
console2.log("inside fuzz test, x is:", x);
}
}
"#,
)
.unwrap();
cmd.args(["test", "-vv"]);
let stdout = cmd.stdout_lossy();
assert!(!stdout.contains("inside fuzz test, x is:"), "\n{stdout}");
});

// tests that `forge test` with inline config `show_logs = false` for fuzz tests will not
// display `console.log` info
forgetest_init!(should_not_show_logs_when_fuzz_test_inline_config, |prj, cmd| {
prj.wipe_contracts();

// run fuzz test 3 times
let config =
Config { fuzz: { FuzzConfig { runs: 3, ..Default::default() } }, ..Default::default() };
prj.write_config(config);
let config = cmd.config();
assert_eq!(config.fuzz.runs, 3);

prj.add_test(
"ContractFuzz.t.sol",
r#"pragma solidity 0.8.24;
import {Test, console2} from "forge-std/Test.sol";
contract ContractFuzz is Test {

/// forge-config: default.fuzz.show-logs = false
function testFuzzConsoleLog(uint256 x) public pure {
console2.log("inside fuzz test, x is:", x);
}
}
"#,
)
.unwrap();
cmd.args(["test", "-vv"]);
let stdout = cmd.stdout_lossy();
assert!(!stdout.contains("inside fuzz test, x is:"), "\n{stdout}");
});
1 change: 1 addition & 0 deletions crates/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl ForgeTestProfile {
gas_report_samples: 256,
failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()),
failure_persist_file: Some("testfailure".to_string()),
show_logs: false,
})
.invariant(InvariantConfig {
runs: 256,
Expand Down
Loading