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

Conversation

Azleal
Copy link
Contributor

@Azleal Azleal commented Jul 8, 2024

Motivation

As discussed in #3844 , when using fuzz test, console.log doesn't work as expected(display console log info). This PR added a new fuzz config show_execution_logs, with which user could decide if the full console log should be displayed. by defaut, the value is false, so it won't affect any fuzz test prior to this change will be made.

Solution

A logs field is added to CaseOutcome to keep track of logs that generated in a single fuzz test. And collect logs in FuzzTestData after each fuzz test finished. then use the config show_logs in FuzzConfig to decide if full console log output should be displayed. if not, the console.log behavior is the same as what it is now.

Also, you can use /// forge-config: default.fuzz.show-logs = true to enable fuzz test console log per test.

4 test cases is added, to test if the new config works as expected.

@Azleal
Copy link
Contributor Author

Azleal commented Jul 8, 2024

hi @grandizzy , please review this, there's a failed test case in test integration, and i believe it has nothing to do with my pr. btw, I think the docs should be updated if the pr is merged. but I didn't find where to update the docs. please tell me if this work needs to be done, and i'll update it.

@Azleal Azleal changed the title fuze console log & test cases fuzz console log & test cases Jul 8, 2024
@mattsse mattsse requested a review from grandizzy July 8, 2024 13:38
Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, nice progress!

I was thinking more along the lines of #3844 requirement, that is to print logs during the test rather than after the test is completed (good for long campaigns where one would like to get feedback during execution). So instead storing logs for all fuzz cases (which would also require more resources for executing these test) we can print logs right after the executor call (if show-execution-logs enabled), smth like adding

        if self.config.show_execution_logs {
            for log in decode_console_logs(&call.logs) {
                println!("  {log}");
            }
        }

right after

pub fn single_fuzz(
&self,
address: Address,
should_fail: bool,
calldata: alloy_primitives::Bytes,
) -> Result<FuzzOutcome, TestCaseError> {
let mut call = self
.executor
.call_raw(self.sender, address, calldata.clone(), U256::ZERO)
.map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?;

Would be nice to extend this to invariant testing as well (would probably require the config to be a general one like show_fuzz_logs and apply to both fuzz and invariant) but can be handled in a separate PR.

crates/evm/evm/src/executors/fuzz/mod.rs Outdated Show resolved Hide resolved
@grandizzy
Copy link
Collaborator

hi @grandizzy , please review this, there's a failed test case in test integration, and i believe it has nothing to do with my pr. btw, I think the docs should be updated if the pr is merged. but I didn't find where to update the docs. please tell me if this work needs to be done, and i'll update it.

Yep, would be great to follow up with docs update PR after this one is merged. The repo with book is https://github.com/foundry-rs/book

@Azleal
Copy link
Contributor Author

Azleal commented Jul 8, 2024

Thank you, nice progress!

I was thinking more along the lines of #3844 requirement, that is to print logs during the test rather than after the test is completed (good for long campaigns where one would like to get feedback during execution). So instead storing logs for all fuzz cases (which would also require more resources for executing these test) we can print logs right after the executor call (if show-execution-logs enabled), smth like adding

        if self.config.show_execution_logs {
            for log in decode_console_logs(&call.logs) {
                println!("  {log}");
            }
        }

right after

pub fn single_fuzz(
&self,
address: Address,
should_fail: bool,
calldata: alloy_primitives::Bytes,
) -> Result<FuzzOutcome, TestCaseError> {
let mut call = self
.executor
.call_raw(self.sender, address, calldata.clone(), U256::ZERO)
.map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?;

Would be nice to extend this to invariant testing as well (would probably require the config to be a general one like show_fuzz_logs and apply to both fuzz and invariant) but can be handled in a separate PR.

ok, I got your points. Although show-execution-logs is under fuzz config section, it's clearer to use show_fuzz_logs and this convention could also be applied to invariant tests. I'm on this.

@Azleal
Copy link
Contributor Author

Azleal commented Jul 8, 2024

Thank you, nice progress!

I was thinking more along the lines of #3844 requirement, that is to print logs during the test rather than after the test is completed (good for long campaigns where one would like to get feedback during execution). So instead storing logs for all fuzz cases (which would also require more resources for executing these test) we can print logs right after the executor call (if show-execution-logs enabled), smth like adding

        if self.config.show_execution_logs {
            for log in decode_console_logs(&call.logs) {
                println!("  {log}");
            }
        }

right after

pub fn single_fuzz(
&self,
address: Address,
should_fail: bool,
calldata: alloy_primitives::Bytes,
) -> Result<FuzzOutcome, TestCaseError> {
let mut call = self
.executor
.call_raw(self.sender, address, calldata.clone(), U256::ZERO)
.map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?;

Would be nice to extend this to invariant testing as well (would probably require the config to be a general one like show_fuzz_logs and apply to both fuzz and invariant) but can be handled in a separate PR.

hi @grandizzy
After walking though the execution process. I don't think we could print the fuzz console logs right after the executor call. here's why:

  1. the console.log should also be controlled by verbosity, only verbosity >= 2 is allowed to display logs.
  2. all logs should be printed under respective test results, which means that we have to keep the logs until it should be displayed.

related code:

if verbosity >= 2 {
// We only decode logs from Hardhat and DS-style console events
let console_logs = decode_console_logs(&result.logs);
if !console_logs.is_empty() {
println!("Logs:");
for log in console_logs {
println!(" {log}");
}
println!();
}
}

wdyt

@Azleal Azleal force-pushed the fuzz-console-log branch from 7487b8e to fc02af8 Compare July 8, 2024 16:13
@grandizzy
Copy link
Collaborator

grandizzy commented Jul 8, 2024

hi @grandizzy After walking though the execution process. I don't think we could print the fuzz console logs right after the executor call. here's why:

1. the console.log should also be controlled by `verbosity`, only `verbosity >= 2` is allowed to display logs.

Yeah, I think the show fuzz logs config could supersede the verbosity, so don't think this is a biggie

2. all logs should be printed under respective test results, which means that we have to keep the logs until it should be displayed.

Good point, if we're not displaying under their own test then running multiple fuzz tests at the same time could become a mess. So let's stick with the impl of gather logs and displaying them at the end, let's keep for fuzz only for now and extend to invariants after. There's FuzzTestData that's already ref celled, so pls add that to it (instead declaring the logs: RefCell<Vec<Log>>)

/// Contains data collected during fuzz test runs.
#[derive(Default)]
pub struct FuzzTestData {
// Stores the first fuzz case.
pub first_case: Option<FuzzCase>,
// Stored gas usage per fuzz case.
pub gas_by_case: Vec<(u64, u64)>,
// Stores the result and calldata of the last failed call, if any.
pub counterexample: (Bytes, RawCallResult),
// Stores up to `max_traces_to_collect` traces.
pub traces: Vec<CallTraceArena>,
// Stores breakpoints for the last fuzz case.
pub breakpoints: Option<Breakpoints>,
// Stores coverage information for all fuzz cases.
pub coverage: Option<HitMaps>,
}

@Azleal
Copy link
Contributor Author

Azleal commented Jul 8, 2024

hi @grandizzy After walking though the execution process. I don't think we could print the fuzz console logs right after the executor call. here's why:

1. the console.log should also be controlled by `verbosity`, only `verbosity >= 2` is allowed to display logs.

Yeah, I think the show fuzz logs config could supersede the verbosity, so don't think this is a biggie

2. all logs should be printed under respective test results, which means that we have to keep the logs until it should be displayed.

Good point, if we're not displaying under their own test then running multiple fuzz tests at the same time could become a mess. So let's stick with the impl of gather logs and displaying them at the end, let's keep for fuzz only for now and extend to invariants after. There's FuzzTestData that's already ref celled, so pls add that to it (instead declaring the logs: RefCell<Vec<Log>>)

/// Contains data collected during fuzz test runs.
#[derive(Default)]
pub struct FuzzTestData {
// Stores the first fuzz case.
pub first_case: Option<FuzzCase>,
// Stored gas usage per fuzz case.
pub gas_by_case: Vec<(u64, u64)>,
// Stores the result and calldata of the last failed call, if any.
pub counterexample: (Bytes, RawCallResult),
// Stores up to `max_traces_to_collect` traces.
pub traces: Vec<CallTraceArena>,
// Stores breakpoints for the last fuzz case.
pub breakpoints: Option<Breakpoints>,
// Stores coverage information for all fuzz cases.
pub coverage: Option<HitMaps>,
}

do you mean that I should define a new field like logs in FuzzTestData to store logs, or I could make use of logs in counterexample or traces?

@grandizzy
Copy link
Collaborator

grandizzy commented Jul 8, 2024

hi @grandizzy After walking though the execution process. I don't think we could print the fuzz console logs right after the executor call. here's why:

1. the console.log should also be controlled by `verbosity`, only `verbosity >= 2` is allowed to display logs.

Yeah, I think the show fuzz logs config could supersede the verbosity, so don't think this is a biggie

2. all logs should be printed under respective test results, which means that we have to keep the logs until it should be displayed.

Good point, if we're not displaying under their own test then running multiple fuzz tests at the same time could become a mess. So let's stick with the impl of gather logs and displaying them at the end, let's keep for fuzz only for now and extend to invariants after. There's FuzzTestData that's already ref celled, so pls add that to it (instead declaring the logs: RefCell<Vec<Log>>)

/// Contains data collected during fuzz test runs.
#[derive(Default)]
pub struct FuzzTestData {
// Stores the first fuzz case.
pub first_case: Option<FuzzCase>,
// Stored gas usage per fuzz case.
pub gas_by_case: Vec<(u64, u64)>,
// Stores the result and calldata of the last failed call, if any.
pub counterexample: (Bytes, RawCallResult),
// Stores up to `max_traces_to_collect` traces.
pub traces: Vec<CallTraceArena>,
// Stores breakpoints for the last fuzz case.
pub breakpoints: Option<Breakpoints>,
// Stores coverage information for all fuzz cases.
pub coverage: Option<HitMaps>,
}

do you mean that I should define a new field like logs in FuzzTestData to store logs, or I could make use of logs in counterexample or traces?

Yeah, I mean new field in struct instead
https://github.com/foundry-rs/foundry/pull/8387/files#diff-12784ea10f5753f595ab88ff54a8d09ecdd044d6a19676229f601a1bffe702e6R96

@Azleal Azleal force-pushed the fuzz-console-log branch from 9b864dc to cc84b89 Compare July 9, 2024 01:31
@Azleal
Copy link
Contributor Author

Azleal commented Jul 9, 2024

Thank you, nice progress!

I was thinking more along the lines of #3844 requirement, that is to print logs during the test rather than after the test is completed (good for long campaigns where one would like to get feedback during execution). So instead storing logs for all fuzz cases (which would also require more resources for executing these test) we can print logs right after the executor call (if show-execution-logs enabled), smth like adding

        if self.config.show_execution_logs {
            for log in decode_console_logs(&call.logs) {
                println!("  {log}");
            }
        }

right after

pub fn single_fuzz(
&self,
address: Address,
should_fail: bool,
calldata: alloy_primitives::Bytes,
) -> Result<FuzzOutcome, TestCaseError> {
let mut call = self
.executor
.call_raw(self.sender, address, calldata.clone(), U256::ZERO)
.map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?;

Would be nice to extend this to invariant testing as well (would probably require the config to be a general one like show_fuzz_logs and apply to both fuzz and invariant) but can be handled in a separate PR.

@grandizzy Can we just use show_logs in FuzzConfig? Because I noticed that, there are runs in both FuzzConfig and InvariantConfig, and share the same meaning as runs for fuzz test or invariants test. wdyt

And do I need to add something like fuzz_show_logs here👇🏻 for users to use environment FOUNDRY_FUZZ_SHOW_LOGS?

/// Set seed used to generate randomness during your fuzz runs.
#[arg(long)]
pub fuzz_seed: Option<U256>,
#[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")]
pub fuzz_runs: Option<u64>,
/// File to rerun fuzz failures from.
#[arg(long)]
pub fuzz_input_file: Option<String>,

@Azleal Azleal force-pushed the fuzz-console-log branch from cc84b89 to d0fed8d Compare July 9, 2024 02:15
@grandizzy
Copy link
Collaborator

@grandizzy Can we just use show_logs in FuzzConfig? Because I noticed that, there are runs in both FuzzConfig and InvariantConfig, and share the same meaning as runs for fuzz test or invariants test. wdyt

yes, that should work

And do I need to add something like fuzz_show_logs here👇🏻 for users to use environment FOUNDRY_FUZZ_SHOW_LOGS?

nope, that's not needed

Azleal added 4 commits July 9, 2024 13:25
test fuzz console.log
rename to show_fuzz_logs
add logs field in FuzzTestData

add logs field in FuzzTestData
@Azleal Azleal force-pushed the fuzz-console-log branch from 9ae2095 to fdf165b Compare July 9, 2024 05:29
@Azleal
Copy link
Contributor Author

Azleal commented Jul 9, 2024

@grandizzy Can we just use show_logs in FuzzConfig? Because I noticed that, there are runs in both FuzzConfig and InvariantConfig, and share the same meaning as runs for fuzz test or invariants test. wdyt

yes, that should work

well, I think this is my final commit. please review this to see if any change should be made. @grandizzy

@Azleal Azleal requested a review from grandizzy July 9, 2024 05:56
Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, think we can simplify a little bit, pls check

crates/evm/evm/src/executors/fuzz/mod.rs Outdated Show resolved Hide resolved
crates/evm/evm/src/executors/fuzz/mod.rs Outdated Show resolved Hide resolved
@Azleal Azleal force-pushed the fuzz-console-log branch from 1458958 to b73b815 Compare July 9, 2024 06:38
@Azleal Azleal requested a review from grandizzy July 9, 2024 07:29
Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thank you! clippy failure not related

@Azleal
Copy link
Contributor Author

Azleal commented Jul 9, 2024

lgtm, thank you! clippy failure not related

Thank you, @grandizzy, for your patience and guidance throughout this PR. You're so meticulous. I'm new to Rust, and every time I thought there was nothing to optimize, your review showed me a better way. 😄👍🏻

Hopefully, this PR will not be reverted again.

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty

@mattsse mattsse enabled auto-merge (squash) July 9, 2024 10:34
@mattsse mattsse merged commit f557626 into foundry-rs:master Jul 9, 2024
20 checks passed
@Azleal Azleal deleted the fuzz-console-log branch July 9, 2024 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants