//! Contains various tests for `forge test`.

use crate::utils::assert_debug_dump_identifies_contract;
use alloy_primitives::{Address, U256};
use anvil::{NodeConfig, spawn};
use foundry_config::{CompilationRestrictions, SettingsOverrides, filter::GlobMatcher};
use foundry_test_utils::{
    TestCommand,
    rpc::{self, rpc_endpoints},
    str,
    util::{OTHER_SOLC_VERSION, OutputExt, SOLC_VERSION},
};
use similar_asserts::assert_eq;
#[cfg(unix)]
use std::fs;
use std::{io::Write, path::PathBuf, str::FromStr};

mod brutalize;
mod core;
mod fuzz;
mod invariant;
mod logs;
mod mutation;
mod repros;
mod showmap;
mod spec;
mod symbolic;
mod symbolic_calls;
mod symbolic_cheatcodes;
mod symbolic_conformance;
mod symbolic_creates;
mod symbolic_engine_capabilities;
mod symbolic_false_pass_prevention;
mod symbolic_helpers;
mod symbolic_invariant;
mod symbolic_limits;
mod symbolic_memory;
mod symbolic_opcodes;
mod symbolic_parity;
mod symbolic_precompiles;
mod symbolic_storage;
mod table;
mod trace;

/// Sets up a [`TestCommand`] to run `forge test` on the `/testdata` directory with RPC
/// endpoints written to a `.env` file.
fn setup_testdata_cmd(cmd: &mut TestCommand) {
    let testdata =
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata").canonicalize().unwrap();
    cmd.current_dir(&testdata);

    let mut dotenv = std::fs::File::create(testdata.join(".env")).unwrap();
    for (name, endpoint) in rpc_endpoints().iter() {
        if let Some(url) = endpoint.endpoint.as_url() {
            let key = format!("RPC_{}", name.to_uppercase());
            writeln!(dotenv, "{key}={url}").unwrap();
        }
    }
    drop(dotenv);
}

fn collect_debug_dump_values<'a, T>(
    value: &'a serde_json::Value,
    collected: &mut Vec<T>,
    collect_from_object: &mut impl FnMut(&'a serde_json::Map<String, serde_json::Value>, &mut Vec<T>),
) {
    match value {
        serde_json::Value::Array(values) => {
            for value in values {
                collect_debug_dump_values(value, collected, collect_from_object);
            }
        }
        serde_json::Value::Object(map) => {
            collect_from_object(map, collected);
            for value in map.values() {
                collect_debug_dump_values(value, collected, collect_from_object);
            }
        }
        _ => {}
    }
}

fn collect_debug_dump_internal_calls<'a>(
    value: &'a serde_json::Value,
    calls: &mut Vec<&'a serde_json::Value>,
) {
    collect_debug_dump_values(value, calls, &mut |map, calls| {
        if let Some(call) = map
            .get("InternalCall")
            .and_then(|value| value.as_array())
            .and_then(|values| values.first())
        {
            calls.push(call);
        }
    });
}

fn collect_debug_dump_decoded_lines<'a>(value: &'a serde_json::Value, lines: &mut Vec<&'a str>) {
    collect_debug_dump_values(value, lines, &mut |map, lines| {
        if let Some(line) = map.get("Line").and_then(|value| value.as_str()) {
            lines.push(line);
        }
    });
}

fn collect_debug_dump_storage_changes<'a>(
    value: &'a serde_json::Value,
    changes: &mut Vec<&'a serde_json::Value>,
) {
    collect_debug_dump_values(value, changes, &mut |map, changes| {
        if let Some(change) = map.get("storage_change")
            && !change.is_null()
        {
            changes.push(change);
        }
    });
}

/// Contracts excluded from the main `testdata` run because they depend on flaky external RPCs.
/// These are run separately by the `flaky_testdata` test below.
/// Format: pipe-separated regex alternation, e.g. `"Foo|Bar|Baz"`.
const FLAKY_TESTDATA_CONTRACTS: &str = "Issue4640Test|Issue14212Test";

// Issue14212Test depends on Base transaction lookups that are not reliably served by the public
// Base RPC endpoint used in CI.
const FLAKY_TESTDATA_RUN_CONTRACTS: &str = "Issue4640Test";

// Run `forge test` on `/testdata`.
forgetest!(testdata, |_prj, cmd| {
    setup_testdata_cmd(&mut cmd);

    let mut args = vec!["test"];
    let nmc_isolate = format!(
        "--nmc=(LastCallGasDefaultTest|MockFunctionTest|WithSeed|StateDiff|GetStorageSlotsTest|RecordAccount|{FLAKY_TESTDATA_CONTRACTS})",
    );
    args.push(&nmc_isolate);

    let orig_assert = cmd.args(args).assert();
    if orig_assert.get_output().status.success() {
        return;
    }
    let stdout = orig_assert.get_output().stdout_lossy();
    if let Some(i) = stdout.rfind("Suite result:") {
        test_debug!("--- short stdout ---\n\n{}\n\n---", &stdout[i..]);
    }

    // Retry failed tests.
    cmd.args(["--rerun"]);
    let n = 3;
    for i in 1..=n {
        test_debug!("retrying failed tests... ({i}/{n})");
        let assert = cmd.assert();
        if assert.get_output().status.success() {
            return;
        }
    }

    orig_assert.success();
});

// Run flaky testdata contracts excluded from the main `testdata` test above.
// Picked up by the nightly `test-flaky` workflow via `cargo nextest run --profile flaky`.
forgetest!(flaky_testdata, |_prj, cmd| {
    setup_testdata_cmd(&mut cmd);
    let mc = format!("--mc=({FLAKY_TESTDATA_RUN_CONTRACTS})");
    cmd.args(["test", &mc]).assert_success();
});

// tests that test filters are handled correctly
forgetest!(can_set_filter_values, |prj, cmd| {
    let patt = regex::Regex::new("test*").unwrap();
    let glob = globset::Glob::from_str("foo/bar/baz*").unwrap();

    // explicitly set patterns
    prj.update_config(|config| {
        config.test_pattern = Some(patt.clone().into());
        config.test_pattern_inverse = None;
        config.contract_pattern = Some(patt.clone().into());
        config.contract_pattern_inverse = None;
        config.path_pattern = Some(glob.clone());
        config.path_pattern_inverse = None;
        config.coverage_pattern_inverse = None;
    });

    let config = cmd.config();

    assert_eq!(config.test_pattern.unwrap().as_str(), patt.as_str());
    assert_eq!(config.test_pattern_inverse, None);
    assert_eq!(config.contract_pattern.unwrap().as_str(), patt.as_str());
    assert_eq!(config.contract_pattern_inverse, None);
    assert_eq!(config.path_pattern.unwrap(), glob);
    assert_eq!(config.path_pattern_inverse, None);
    assert_eq!(config.coverage_pattern_inverse, None);
});

fn dummy_test_filter(cmd: &mut TestCommand) {
    cmd.args(["test", "--match-test", "testA.*", "--no-match-test", "testB.*"]);
    cmd.args(["--match-contract", "TestC.*", "--no-match-contract", "TestD.*"]);
    cmd.args(["--match-path", "*TestE*", "--no-match-path", "*TestF*"]);
}

// tests that a warning is displayed when there are no tests in project, regardless of filters
forgetest!(warn_no_tests, |prj, cmd| {
    // Must add at least one source to not fail earlier.
    prj.add_source(
        "dummy",
        r"
contract Dummy {}
",
    );

    cmd.arg("test").assert_success().stderr_eq(str![[r#"
Warning: No tests found in project! Forge looks for functions that start with `test`

"#]]);

    cmd.forge_fuse();
    dummy_test_filter(&mut cmd);
    cmd.assert_success().stderr_eq(str![[r#"
Warning: No tests found in project! Forge looks for functions that start with `test`

"#]]);
});

// tests that a warning is displayed if there are tests but none match a non-empty filter
forgetest!(suggest_when_no_tests_match, |prj, cmd| {
    prj.add_source(
        "TestE.t.sol",
        r"
contract TestC {
    function test1() public {
    }
}
   ",
    );

    dummy_test_filter(&mut cmd);
    cmd.assert_success().stderr_eq(str![[r#"
Warning: no tests match the provided pattern:
	match-test: `testA.*`
	no-match-test: `testB.*`
	match-contract: `TestC.*`
	no-match-contract: `TestD.*`
	match-path: `*TestE*`
	no-match-path: `*TestF*`

Did you mean `test1`?

"#]]);
});

// tests that direct import paths are handled correctly
forgetest!(can_fuzz_array_params, |prj, cmd| {
    prj.insert_ds_test();

    prj.add_source(
        "ATest.t.sol",
        r#"
import "./test.sol";
contract ATest is DSTest {
    function testArray(uint64[2] calldata) external {
        assertTrue(true);
    }
}
   "#,
    );

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/ATest.t.sol:ATest
[PASS] testArray(uint64[2]) (runs: 256, [AVG_GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests that `bytecode_hash` will be sanitized
forgetest!(can_test_pre_bytecode_hash, |prj, cmd| {
    prj.insert_ds_test();

    prj.add_source(
        "ATest.t.sol",
        r#"
// pre bytecode hash version, was introduced in 0.6.0
pragma solidity 0.5.17;
import "./test.sol";
contract ATest is DSTest {
    function testArray(uint64[2] calldata) external {
        assertTrue(true);
    }
}
   "#,
    );

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
...
Compiler run successful!

Ran 1 test for src/ATest.t.sol:ATest
[PASS] testArray(uint64[2]) (runs: 256, [AVG_GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests that using the --match-path option only runs files matching the path
forgetest!(can_test_with_match_path, |prj, cmd| {
    prj.insert_ds_test();

    prj.add_source(
        "ATest.t.sol",
        r#"
import "./test.sol";
contract ATest is DSTest {
    function testPass() external {
        assertTrue(true);
    }
}
   "#,
    );

    prj.add_source(
        "FailTest.t.sol",
        r#"
import "./test.sol";
contract FailTest is DSTest {
    function testNothing() external {
        assertTrue(false);
    }
}
   "#,
    );

    cmd.args(["test", "--match-path", "*src/ATest.t.sol"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/ATest.t.sol:ATest
[PASS] testPass() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests that using the --match-path option works with absolute paths
forgetest!(can_test_with_match_path_absolute, |prj, cmd| {
    prj.insert_ds_test();

    prj.add_source(
        "ATest.t.sol",
        r#"
import "./test.sol";
contract ATest is DSTest {
    function testPass() external {
        assertTrue(true);
    }
}
   "#,
    );

    prj.add_source(
        "FailTest.t.sol",
        r#"
import "./test.sol";
contract FailTest is DSTest {
    function testNothing() external {
        assertTrue(false);
    }
}
   "#,
    );

    let test_path = prj.root().join("src/ATest.t.sol");
    let test_path = test_path.to_string_lossy();

    cmd.args(["test", "--match-path", test_path.as_ref()]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/ATest.t.sol:ATest
[PASS] testPass() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

const SIMPLE_CONTRACT: &str = r#"
import "./test.sol";
import "./console.sol";

contract SimpleContract {
    uint256 public num;

    function setValues(uint256 _num) public {
        num = _num;
    }
}

contract SimpleContractTest is DSTest {
    function test() public {
        SimpleContract c = new SimpleContract();
        c.setValues(100);
        console.logUint(100);
    }
}
   "#;

forgetest!(can_run_test_with_json_output_verbose, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_console();

    prj.add_source("Simple.t.sol", SIMPLE_CONTRACT);

    // Assert that with verbose output the json output includes the traces
    cmd.args(["test", "-vvvvv", "--json"])
        .assert_success()
        .stdout_eq(file!["../../fixtures/SimpleContractTestVerbose.json": Json]);
});

forgetest!(test_json_trace_depth_removes_nested_nodes, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_console();
    prj.add_source("Simple.t.sol", SIMPLE_CONTRACT);

    let output = cmd
        .args(["test", "-vvvvv", "--json", "--trace-depth", "0"])
        .assert_success()
        .get_output()
        .stdout
        .clone();
    let output: serde_json::Value = serde_json::from_slice(&output).unwrap();
    let traces = output["src/Simple.t.sol:SimpleContractTest"]["test_results"]["test()"]["traces"]
        .as_array()
        .unwrap();
    assert!(!traces.is_empty());

    for trace in traces {
        let arena = trace[1]["arena"].as_array().unwrap();
        assert_eq!(arena.len(), 1);
        assert!(arena[0]["children"].as_array().unwrap().is_empty());
    }
});

forgetest!(can_run_test_with_json_output_non_verbose, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_console();

    prj.add_source("Simple.t.sol", SIMPLE_CONTRACT);

    // Assert that without verbose output the json output does not include the traces
    cmd.args(["test", "--json"])
        .assert_success()
        .stdout_eq(file!["../../fixtures/SimpleContractTestNonVerbose.json": Json]);
});

forgetest!(can_write_json_results_without_changing_stdout, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_console();
    prj.add_source("Simple.t.sol", SIMPLE_CONTRACT);

    let json_path = prj.root().join("test-results.json");
    cmd.forge_fuse().args(["test", "--json-file"]).arg(&json_path).assert_success().stdout_eq(
        str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/Simple.t.sol:SimpleContractTest
[PASS] test() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]],
    );

    let results: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&json_path).unwrap()).unwrap();
    let result = &results["src/Simple.t.sol:SimpleContractTest"]["test_results"]["test()"];
    assert_eq!(result["status"], "Success");
    assert_eq!(result["logs"], serde_json::json!([]));

    let json_stdout = cmd
        .forge_fuse()
        .args(["test", "-vvvv", "--json"])
        .assert_success()
        .get_output()
        .stdout
        .clone();
    cmd.forge_fuse().args(["test", "-vvvv", "--json-file"]).arg(&json_path).assert_success();
    let mut stdout_results: serde_json::Value = serde_json::from_slice(&json_stdout).unwrap();
    let mut file_results: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&json_path).unwrap()).unwrap();
    for results in [&mut stdout_results, &mut file_results] {
        let suite = &mut results["src/Simple.t.sol:SimpleContractTest"];
        suite.as_object_mut().unwrap().remove("duration");
        suite["test_results"]["test()"].as_object_mut().unwrap().remove("duration");
    }
    assert_eq!(file_results, stdout_results);

    prj.add_test(
        "Failing.t.sol",
        r#"
contract FailingTest {
    function testFail() public pure {
        require(false, "boom");
    }
}
"#,
    );
    cmd.forge_fuse()
        .args(["test", "--match-test", "testFail", "--json-file"])
        .arg(&json_path)
        .assert_failure();
    let failed_results: serde_json::Value =
        serde_json::from_slice(&std::fs::read(json_path).unwrap()).unwrap();
    assert_eq!(
        failed_results["test/Failing.t.sol:FailingTest"]["test_results"]["testFail()"]["status"],
        "Failure"
    );
});

forgetest!(json_file_fail_fast_preserves_completed_suites, |prj, cmd| {
    prj.add_test(
        "Failing.t.sol",
        r#"
interface VmFail {
    function sleep(uint256 milliseconds) external;
}

contract FailingTest {
    VmFail constant vm = VmFail(address(uint160(uint256(keccak256("hevm cheat code")))));

    function testBreaks() public {
        // Let both suites start, but finish this one first.
        vm.sleep(100);
        require(false, "boom");
    }
}
"#,
    );
    prj.add_test(
        "Passing.t.sol",
        r#"
interface VmPass {
    function sleep(uint256 milliseconds) external;
}

contract PassingTest {
    VmPass constant vm = VmPass(address(uint160(uint256(keccak256("hevm cheat code")))));

    function testPass() public {
        // Complete after the failing suite has stopped console output.
        vm.sleep(500);
    }
}
"#,
    );

    let json_path = prj.root().join("test-results.json");
    let output = cmd
        .args(["test", "--fail-fast", "-j", "2", "--json-file"])
        .arg(&json_path)
        .assert_failure();

    assert!(
        json_path.exists(),
        "stdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.get_output().stdout),
        String::from_utf8_lossy(&output.get_output().stderr)
    );
    let stdout = String::from_utf8_lossy(&output.get_output().stdout);
    assert!(!stdout.contains("Ran 1 test for test/Passing.t.sol:PassingTest"));
    assert!(stdout.contains("Encountered a total of 1 failing tests, 0 tests succeeded"));

    let results: serde_json::Value =
        serde_json::from_slice(&std::fs::read(json_path).unwrap()).unwrap();
    assert_eq!(
        results["test/Failing.t.sol:FailingTest"]["test_results"]["testBreaks()"]["status"],
        "Failure"
    );
    assert_eq!(
        results["test/Passing.t.sol:PassingTest"]["test_results"]["testPass()"]["status"],
        "Success"
    );
});

// tests that `forge test` will pick up tests that are stored in the `test = <path>` config value
forgetest!(can_run_test_in_custom_test_folder, |prj, cmd| {
    prj.insert_ds_test();

    // explicitly set the test folder
    prj.update_config(|config| config.test = "nested/forge-tests".into());

    let config = cmd.config();
    assert_eq!(config.test, PathBuf::from("nested/forge-tests"));

    prj.add_source(
        "nested/forge-tests/MyTest.t.sol",
        r#"
import "../../test.sol";
contract MyTest is DSTest {
    function testTrue() public {
        assertTrue(true);
    }
}
   "#,
    );

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/nested/forge-tests/MyTest.t.sol:MyTest
[PASS] testTrue() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// checks that forge test repeatedly produces the same output
forgetest_init!(can_test_repeatedly, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.clear();

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS])
[PASS] test_Increment() ([GAS])
Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)

"#]]);

    for _ in 0..5 {
        cmd.assert_success().stdout_eq(str![[r#"
No files changed, compilation skipped

Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS])
[PASS] test_Increment() ([GAS])
Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)

"#]]);
    }
});

// tests that `forge test` will run a test only once after changing the version
forgetest!(runs_tests_exactly_once_with_changed_versions, |prj, cmd| {
    prj.insert_ds_test();

    prj.add_source(
        "Contract.t.sol",
        r#"
pragma solidity *;

import "./test.sol";

contract ContractTest is DSTest {
    function setUp() public {}

    function testExample() public {
        assertTrue(true);
    }
}
   "#,
    );

    // pin version
    prj.update_config(|config| {
        config.solc = Some(SOLC_VERSION.into());
    });

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/Contract.t.sol:ContractTest
[PASS] testExample() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);

    // pin version
    prj.update_config(|config| {
        config.solc = Some(OTHER_SOLC_VERSION.into());
    });

    cmd.forge_fuse().arg("test").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/Contract.t.sol:ContractTest
[PASS] testExample() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

forgetest_init!(links_library_artifacts_across_versions, |prj, cmd| {
    prj.wipe_contracts();
    prj.update_config(|config| config.solc = None);

    prj.add_source(
        "Lib.sol",
        r#"
pragma solidity >=0.8.0;

library Lib {
    function identity(uint256 value) external pure returns (uint256) {
        return value;
    }
}
"#,
    );
    prj.add_test(
        "New.t.sol",
        &format!(
            r#"
pragma solidity {SOLC_VERSION};

import "src/Lib.sol";

contract NewTest {{
    function testIdentity() public {{
        require(Lib.identity(1) == 1);
    }}
}}
"#
        ),
    );
    prj.add_test(
        "Old.t.sol",
        &format!(
            r#"
pragma solidity {OTHER_SOLC_VERSION};

import "src/Lib.sol";

contract OldTest {{
    function testIdentity() public {{
        require(Lib.identity(1) == 1);
    }}
}}
"#
        ),
    );

    cmd.arg("test").assert_success();

    prj.update_config(|config| config.create2_deployer = Address::ZERO);
    cmd.forge_fuse().arg("test").assert_success();
});

forgetest_init!(links_library_artifacts_across_compiler_profiles, |prj, cmd| {
    prj.wipe_contracts();
    prj.add_source(
        "Lib.sol",
        r#"
pragma solidity >=0.8.0;

library Lib {
    function identity(uint256 value) external pure returns (uint256) {
        return value;
    }
}
"#,
    );
    prj.add_source(
        "Prod.sol",
        r#"
pragma solidity >=0.8.0;

import "src/Lib.sol";

contract Prod {
    function identity(uint256 value) external view returns (uint256) {
        return Lib.identity(value);
    }
}
"#,
    );
    prj.add_test(
        "Profiles.t.sol",
        r#"
pragma solidity >=0.8.0;

import "src/Lib.sol";
import "src/Prod.sol";

contract ProfilesTest {
    function testProfiles() public {
        require(Lib.identity(1) == 1);
        require(new Prod().identity(2) == 2);
    }
}
"#,
    );
    prj.update_config(|config| {
        config.additional_compiler_profiles = vec![SettingsOverrides {
            name: "prod".to_string(),
            via_ir: Some(true),
            evm_version: None,
            optimizer: Some(true),
            optimizer_runs: Some(1),
            bytecode_hash: None,
        }];
        config.compilation_restrictions = vec![CompilationRestrictions {
            paths: GlobMatcher::from_str("src/Prod.sol").unwrap(),
            version: None,
            via_ir: Some(true),
            bytecode_hash: None,
            min_optimizer_runs: None,
            optimizer_runs: Some(1),
            max_optimizer_runs: None,
            min_evm_version: None,
            evm_version: None,
            max_evm_version: None,
        }];
    });

    cmd.arg("test").assert_success();
    assert!(prj.artifacts().join("Lib.sol/Lib.json").exists());
    assert!(prj.artifacts().join("Lib.sol/Lib.prod.json").exists());

    prj.update_config(|config| config.create2_deployer = Address::ZERO);
    cmd.forge_fuse().arg("test").assert_success();
});

#[cfg(unix)]
forgetest_init!(links_libraries_through_workspace_symlinks, |prj, cmd| {
    let workspace = prj.root().join("workspace");
    let airdrops = workspace.join("airdrops");
    let libraries = workspace.join("library/src/libraries");
    let package_scope = workspace.join("node_modules/@workspace");
    fs::create_dir_all(airdrops.join("src")).unwrap();
    fs::create_dir_all(airdrops.join("test")).unwrap();
    fs::create_dir_all(&libraries).unwrap();
    fs::create_dir_all(&package_scope).unwrap();
    std::os::unix::fs::symlink("../../library", package_scope.join("library")).unwrap();

    fs::write(
        airdrops.join("foundry.toml"),
        r#"
[profile.default]
allow_paths = ["../"]
src = "src"
test = "test"
out = "out"
remappings = ["@workspace/=../node_modules/@workspace/"]
"#,
    )
    .unwrap();
    fs::write(
        libraries.join("Math.sol"),
        r#"
pragma solidity >=0.8.0;

library Math {
    function increment(uint256 value) external pure returns (uint256) {
        return value + 1;
    }
}
"#,
    )
    .unwrap();
    fs::write(
        libraries.join("Helpers.sol"),
        r#"
pragma solidity >=0.8.0;

import {Math} from "./Math.sol";

library Helpers {
    function addTwo(uint256 value) external pure returns (uint256) {
        return Math.increment(Math.increment(value));
    }
}
"#,
    )
    .unwrap();
    fs::write(
        airdrops.join("src/Consumer.sol"),
        r#"
pragma solidity >=0.8.0;

import {Helpers} from "@workspace/library/src/libraries/Helpers.sol";

contract Consumer {
    function addTwo(uint256 value) external pure returns (uint256) {
        return Helpers.addTwo(value);
    }
}
"#,
    )
    .unwrap();
    fs::write(
        airdrops.join("test/Consumer.t.sol"),
        r#"
pragma solidity >=0.8.0;

import {Consumer} from "../src/Consumer.sol";

contract ConsumerTest {
    function testAddTwo() external {
        Consumer consumer = new Consumer();
        require(consumer.addTwo(1) == 3);
    }
}
"#,
    )
    .unwrap();
    cmd.current_dir(&airdrops).arg("build").assert_success();
    cmd.forge_fuse().current_dir(&airdrops).arg("test").assert_success();
    cmd.forge_fuse()
        .current_dir(&airdrops)
        .args(["test", "--create2-deployer", "0x0000000000000000000000000000000000000000"])
        .assert_success();
    writeln!(
        fs::OpenOptions::new().append(true).open(airdrops.join("foundry.toml")).unwrap(),
        "libraries = [\"../library/src/libraries/Helpers.sol:Helpers:0x1111111111111111111111111111111111111111\"]"
    )
    .unwrap();
    let stdout = cmd
        .forge_fuse()
        .current_dir(&airdrops)
        .args(["test", "-vvvv"])
        .assert_failure()
        .get_output()
        .stdout_lossy();
    assert!(stdout.contains("0x1111111111111111111111111111111111111111"), "{stdout}");
});

forgetest_init!(create2_factory_is_installed_after_constructor_when_no_libraries, |prj, cmd| {
    prj.wipe_contracts();
    prj.add_test(
        "Factory.t.sol",
        r#"
pragma solidity >=0.8.0;

contract FactoryTest {
    address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;

    constructor() {
        require(CREATE2_FACTORY.code.length == 0, "factory installed before constructor");
    }

    function testFactoryInstalledAfterConstructor() public view {
        require(CREATE2_FACTORY.code.length > 0, "factory not installed after constructor");
    }
}
"#,
    );

    cmd.arg("test").assert_success();
});

// tests that libraries are handled correctly in multiforking mode
forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| {
    prj.add_source(
        "Contract.sol",
        r"
library Library {
    function f(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b;
    }
}

contract Contract {
    uint256 c;

    constructor() {
        c = Library.f(1, 2);
    }
}
   ",
    );

    let endpoint = rpc::next_http_archive_rpc_url();

    prj.add_test(
        "Contract.t.sol",
        &r#"
import "forge-std/Test.sol";
import "src/Contract.sol";

contract ContractTest is Test {
    function setUp() public {
        vm.createSelectFork("<url>");
    }

    function test() public {
        new Contract();
    }
}
   "#
        .replace("<url>", &endpoint),
    );

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Contract.t.sol:ContractTest
[PASS] test() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

static FAILING_TEST: &str = r#"
import "forge-std/Test.sol";

contract FailingTest is Test {
    function testShouldFail() public {
        assertTrue(false);
    }
}
"#;

forgetest_init!(exit_code_error_on_fail_fast, |prj, cmd| {
    prj.add_source("failing_test", FAILING_TEST);

    cmd.args(["test", "--fail-fast"]);

    cmd.assert_empty_stderr();
});

forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| {
    prj.add_source("failing_test", FAILING_TEST);
    cmd.args(["test", "--fail-fast", "--json"]);

    cmd.assert_empty_stderr();
});

// Verify that --show-progress doesn't stop tests after first failure
forgetest_init!(show_progress_runs_all_tests, |prj, cmd| {
    prj.add_test(
        "MultiTest.t.sol",
        r#"
import "forge-std/Test.sol";

contract MultiTest is Test {
    function test_1_Fail() public {
        assertTrue(false);
    }

    function test_2_Pass() public {
        assertTrue(true);
    }

    function test_3_Pass() public {
        assertTrue(true);
    }
}
"#,
    );

    // With --show-progress, all 3 tests should run despite first one failing
    let output = cmd.args(["test", "--show-progress", "-j1"]).assert_failure();
    let stdout = String::from_utf8_lossy(&output.get_output().stdout);

    // Verify all 3 tests were executed
    assert!(stdout.contains("test_1_Fail"), "test_1_Fail should run");
    assert!(stdout.contains("test_2_Pass"), "test_2_Pass should run");
    assert!(stdout.contains("test_3_Pass"), "test_3_Pass should run");
    assert!(stdout.contains("2 passed; 1 failed"), "Should show 2 passed and 1 failed");
});

// Verify that --show-progress with --fail-fast DOES stop after first failure
forgetest_init!(show_progress_with_fail_fast_exits_early, |prj, cmd| {
    prj.add_test(
        "MultiTest.t.sol",
        r#"
import "forge-std/Test.sol";

contract MultiTest is Test {
    function test_1_Fail() public {
        assertTrue(false);
    }

    function test_2_SlowPass() public {
        vm.sleep(60000); // Sleep for 60 seconds to ensure fail-fast stops before this completes
        assertTrue(true);
    }

    function test_3_SlowPass() public {
        vm.sleep(60000); // Sleep for 60 seconds to ensure fail-fast stops before this completes
        assertTrue(true);
    }
}
"#,
    );

    // With both --show-progress and --fail-fast, should stop after first failure
    let output = cmd.args(["test", "--show-progress", "--fail-fast", "-j1"]).assert_failure();
    let stdout = String::from_utf8_lossy(&output.get_output().stdout);

    // Verify first test ran and failed
    assert!(stdout.contains("test_1_Fail"), "test_1_Fail should run");

    // With -j1 (sequential execution) and fail-fast, the slow tests should not run
    // since test_1_Fail will fail first
    let slow_tests_count = (if stdout.contains("test_2_SlowPass") { 1 } else { 0 })
        + (if stdout.contains("test_3_SlowPass") { 1 } else { 0 });

    assert!(
        slow_tests_count < 2,
        "With --fail-fast and sequential execution, not all slow tests should run after first failure"
    );
});

// Regression: a `setUp()` failure in one suite must trip the global fail-fast flag so that
// long-running invariant campaigns in sibling suites (dispatched in parallel via rayon) exit
// at their next run boundary instead of running to their configured timeout.
forgetest_init!(fail_fast_cancels_invariant_after_setup_failure, |prj, cmd| {
    // Need at least 2 worker threads so the two suites run concurrently.
    if std::thread::available_parallelism().map_or(1, |n| n.get()) < 2 {
        return;
    }

    // Use a very large invariant timeout so the with-fix vs without-fix behavior is
    // dramatically different: with the fix the campaign sees `should_stop()` and exits within
    // one fuzz sequence (~seconds); without the fix it would run for the full timeout. Same
    // canary pattern as `test_fail_fast_config`.
    prj.update_config(|config| {
        config.invariant.timeout = Some(3600);
    });

    prj.add_test(
        "BrokenSetup.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract BrokenSetupTest is Test {
    function setUp() public pure {
        require(false, "setUp failure");
    }

    function test_Noop() public pure {}
}
"#,
    );

    prj.add_test(
        "LongInvariant.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Target {
    uint256 public x;
    function bump() external { x += 1; }
}

contract LongInvariantTest is Test {
    Target internal target;

    function setUp() public {
        target = new Target();
        targetContract(address(target));
    }

    function invariant_alwaysTrue() public view {
        require(true);
    }
}
"#,
    );

    let started = std::time::Instant::now();
    cmd.args(["test", "--fail-fast", "--mc", "BrokenSetupTest|LongInvariantTest"]).assert_failure();
    let elapsed = started.elapsed();

    // The two suites run in parallel; the broken `setUp()` reverts within milliseconds and must
    // cancel the in-flight invariant campaign almost immediately. The 1h invariant timeout above
    // gives us a huge margin: with the fix elapsed is ~seconds; without it elapsed approaches
    // 1h. 60s is generous slack for CI but still catches the regression unambiguously.
    assert!(
        elapsed < std::time::Duration::from_secs(60),
        "fail-fast did not cancel the in-flight invariant campaign: elapsed {elapsed:?}",
    );
});

// https://github.com/foundry-rs/foundry/pull/6531
forgetest_init!(fork_traces, |prj, cmd| {
    let endpoint = rpc::next_http_archive_rpc_url();

    prj.add_test(
        "Contract.t.sol",
        &r#"
import {Test} from "forge-std/Test.sol";

interface IERC20 {
    function name() external view returns (string memory);
}

contract USDTCallingTest is Test {
    function test() public {
        vm.createSelectFork("<url>");
        IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).name();
    }
}
   "#
        .replace("<url>", &endpoint),
    );

    cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Contract.t.sol:USDTCallingTest
[PASS] test() ([GAS])
Traces:
  [..] USDTCallingTest::test()
    ├─ [0] VM::createSelectFork("[..]")
    │   └─ ← [Return] 0
    ├─ [3110] 0xdAC17F958D2ee523a2206206994597C13D831ec7::name() [staticcall]
    │   └─ ← [Return] "Tether USD"
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// Validates BPO1 blob gas price calculation during fork transaction replay.
// Block 24127158 has a blob tx at index 0, target tx at index 1.
// Forking at the target tx replays the blob tx with correct BPO1 blob base fee calculation.
forgetest_init!(fork_tx_replay_bpo1_blob_base_fee, |prj, cmd| {
    let endpoint = rpc::next_http_archive_rpc_url();

    prj.add_test(
        "BlobFork.t.sol",
        &r#"
import {Test} from "forge-std/Test.sol";

contract BlobForkTest is Test {
    function test_fork_with_blob_replay() public {
        // Fork at tx index 1 in block 24127158, which replays blob tx at index 0
        bytes32 txHash = 0xa0f349b16e0f338ee760a9954ff5dbf2a402cff3320f3fe2c3755aee8babc335;
        vm.createSelectFork("<url>", txHash);
        // If we get here, blob tx replay succeeded
        assertTrue(true);
    }
}
    "#
        .replace("<url>", &endpoint),
    );

    cmd.args(["test", "-vvvv"]).assert_success();
});

// https://github.com/foundry-rs/foundry/issues/10689
forgetest_init!(flaky_roll_fork_arbitrum_priority_fee_above_max_fee, |prj, cmd| {
    let endpoint = "https://arb-mainnet.g.alchemy.com/public";

    prj.add_test(
        "ArbitrumFork.t.sol",
        &r#"
import {Test} from "forge-std/Test.sol";

contract ArbitrumForkTest is Test {
    function test_rollFork() public {
        uint256 forkId = vm.createFork("<url>");
        bytes32 txHash = 0x2e43e9ececcbb9cd08ce061edc3b4d39ca2b0ba480034e5f4650ba0065bf6b62;
        vm.rollFork(forkId, txHash);
    }
}
    "#
        .replace("<url>", endpoint),
    );

    cmd.arg("test").assert_success();
});

// https://github.com/foundry-rs/foundry/issues/6579
forgetest_init!(include_custom_types_in_traces, |prj, cmd| {
    prj.add_test(
        "Contract.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

error PoolNotInitialized();
event MyEvent(uint256 a);

contract CustomTypesTest is Test {
    function testErr() public pure {
       revert PoolNotInitialized();
    }
    function testEvent() public {
       emit MyEvent(100);
    }
}
   "#,
    );

    cmd.args(["test", "-vvvvv"]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/Contract.t.sol:CustomTypesTest
[FAIL: PoolNotInitialized()] testErr() ([GAS])
Traces:
  [247] CustomTypesTest::testErr()
    └─ ← [Revert] PoolNotInitialized()

Backtrace:
  at CustomTypesTest.testErr (test/Contract.t.sol:[..]:[..])

[PASS] testEvent() ([GAS])
Traces:
  [1524] CustomTypesTest::testEvent()
    ├─ emit MyEvent(a: 100)
    └─ ← [Stop]

Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests)

Failing tests:
Encountered 1 failing test in test/Contract.t.sol:CustomTypesTest
[FAIL: PoolNotInitialized()] testErr() ([GAS])

Encountered a total of 1 failing tests, 1 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

forgetest_init!(can_test_transient_storage_with_isolation, |prj, cmd| {
    prj.add_test(
        "Contract.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract TransientTester {
    function locked() public view returns (bool isLocked) {
        assembly {
            isLocked := tload(0)
        }
    }

    modifier lock() {
        require(!locked(), "locked");
        assembly {
            tstore(0, 1)
        }
        _;
    }

    function maybeReentrant(address target, bytes memory data) public lock {
        (bool success, bytes memory ret) = target.call(data);
        if (!success) {
            // forwards revert reason
            assembly {
                let ret_size := mload(ret)
                revert(add(32, ret), ret_size)
            }
        }
    }
}

contract TransientTest is Test {
    function test() public {
        TransientTester t = new TransientTester();
        vm.expectRevert(bytes("locked"));
        t.maybeReentrant(address(t), abi.encodeCall(TransientTester.maybeReentrant, (address(0), new bytes(0))));

        t.maybeReentrant(address(0), new bytes(0));
        assertEq(t.locked(), false);
    }
}
   "#,
    );

    cmd.args(["test", "-vvvv", "--isolate", "--evm-version", "cancun"]).assert_success();
});

forgetest_init!(eip2935_history_storage_in_prague_tests, |prj, cmd| {
    prj.add_test(
        "EIP2935.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract EIP2935Test is Test {
    address constant HISTORY = 0x0000F90827F1C53a10cb7A02335B175320002935;

    function firstAccessGas(address target) internal view returns (uint256 used, bytes32 codehash) {
        assembly {
            let gasBefore := gas()
            codehash := extcodehash(target)
            used := sub(gasBefore, gas())
        }
    }

    function historyReadGas(uint256 blockNumber) internal view returns (uint256 used) {
        bytes memory data = abi.encodePacked(bytes32(blockNumber));
        bool ok;
        bytes memory ret;
        uint256 gasBefore = gasleft();
        (ok, ret) = HISTORY.staticcall(data);
        used = gasBefore - gasleft();
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
    }

    function testHistoryContractDeployed() public {
        assertGt(HISTORY.code.length, 0, "history contract missing");
    }

    function testInitialHistoryWindowSeeded() public {
        bytes32 expected = blockhash(99);

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(99))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertEq(bytes32(ret), expected, "initial history hash");
    }

    function testRollStoresParentBlockHash() public {
        vm.roll(1000);
        bytes32 expected = keccak256("parent");
        vm.setBlockhash(1000, expected);

        vm.roll(1001);

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(1000))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertEq(bytes32(ret), expected, "stored parent hash");
    }

    function testSetBlockhashPopulatesHistory() public {
        vm.roll(2000);
        bytes32 expected = keccak256("history");
        vm.setBlockhash(1999, expected);

        assertEq(blockhash(1999), expected);

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(1999))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertEq(bytes32(ret), expected, "stored history hash");
    }

    function testSetBlockhashUpdatesCachedHistorySlot() public {
        vm.roll(2000);
        bytes32 expected = keccak256("cached history");

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(1998))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertNotEq(bytes32(ret), expected, "history precondition");

        vm.setBlockhash(1998, expected);

        (ok, ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(1998))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertEq(bytes32(ret), expected, "cached history hash");
    }

    function testLargeRollBackfillsHistoryWindow() public {
        vm.roll(1000);
        vm.setBlockhash(1000, keccak256("block 1000"));

        vm.roll(2000);

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(1000))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertEq(bytes32(ret), keccak256("block 1000"), "block 1000 hash");
    }

    function testRollDoesNotWarmHistoryAccountOrSlot() public {
        vm.roll(3000);

        (uint256 accountGas, bytes32 codehash) = firstAccessGas(HISTORY);
        assertNotEq(codehash, bytes32(0), "history code hash");
        assertGt(accountGas, 2000, "history account warm after roll");
        uint256 firstRead = historyReadGas(2999);
        assertGt(firstRead, 2000, "history slot warm after roll");
    }

    function testSetBlockhashDoesNotCorruptHistoryRing() public {
        vm.roll(20000);
        bytes32 ancient = keccak256("ancient");
        vm.setBlockhash(0, ancient);

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(16382))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertNotEq(bytes32(ret), ancient, "ring collision");
    }

    function testSetBlockhashCurrentBlockDoesNotCorruptOldestSlot() public {
        vm.roll(8192);
        bytes32 oldest = keccak256("oldest");
        bytes32 current = keccak256("current");
        vm.setBlockhash(1, oldest);

        vm.setBlockhash(8192, current);

        (bool ok, bytes memory ret) = HISTORY.staticcall(abi.encodePacked(bytes32(uint256(1))));
        assertTrue(ok, "history call failed");
        assertEq(ret.length, 32, "history return length");
        assertEq(bytes32(ret), oldest, "oldest slot corrupted");
    }

    function testSetBlockhashDoesNotWarmHistoryAccountOrSlot() public {
        vm.roll(4000);
        vm.setBlockhash(3999, keccak256("warmth"));

        (uint256 accountGas, bytes32 codehash) = firstAccessGas(HISTORY);
        assertNotEq(codehash, bytes32(0), "history code hash");
        assertGt(accountGas, 2000, "history account warm after setBlockhash");
        uint256 firstRead = historyReadGas(3999);
        assertGt(firstRead, 2000, "history slot warm after setBlockhash");
    }

    function testRollDoesNotPopulateReplacedHistoryContract() public {
        vm.etch(HISTORY, hex"00");

        uint256 parent = 3000;
        vm.roll(parent + 1);

        assertEq(vm.load(HISTORY, bytes32(parent % 8191)), bytes32(0), "replaced history slot");
    }

    function testRollDoesNotExposeSeededHistorySlotAfterEtch() public {
        vm.etch(HISTORY, hex"00");

        uint256 parent = 8191 * 1000 + 12;
        vm.roll(parent + 1);

        assertEq(vm.load(HISTORY, bytes32(parent % 8191)), bytes32(0), "replaced seeded history slot");
    }

    function testSetBlockhashDoesNotPopulateReplacedHistoryContract() public {
        vm.etch(HISTORY, hex"00");

        uint256 blockNumber = 3999;
        vm.roll(blockNumber + 1);
        vm.setBlockhash(blockNumber, keccak256("replaced"));

        assertEq(blockhash(blockNumber), keccak256("replaced"));
        assertEq(vm.load(HISTORY, bytes32(blockNumber % 8191)), bytes32(0), "replaced history slot");
    }
}
"#,
    );

    cmd.args(["test", "--evm-version", "prague", "--block-number", "100"]).assert_success();
});

forgetest_init!(eip2935_history_storage_not_deployed_before_prague, |prj, cmd| {
    prj.add_test(
        "EIP2935.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract EIP2935Test is Test {
    address constant HISTORY = 0x0000F90827F1C53a10cb7A02335B175320002935;

    function testHistoryContractNotDeployed() public {
        assertEq(HISTORY.code.length, 0, "history contract deployed before Prague");
    }
}
"#,
    );

    cmd.args(["test", "--evm-version", "cancun"]).assert_success();
});

forgetest_init!(setup_selfdestruct_deletes_same_tx_created_contracts, |prj, cmd| {
    prj.add_test(
        "SetupSelfdestruct.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract A {
    function codeLength() public view returns (uint256) {
        return address(this).code.length;
    }

    function kill() public {
        selfdestruct(payable(address(0)));
    }
}

contract B {
    uint256 private x;

    constructor(uint256 x_) {
        x = x_;
    }
}

contract Factory {
    function helloA() public returns (address) {
        return address(new A());
    }

    function helloB() public returns (address) {
        return address(new B(1337));
    }

    function kill() public {
        selfdestruct(payable(address(0)));
    }
}

contract SetupSelfdestructTest is Test {
    A private a;
    B private b;
    Factory private factory;

    function setUp() public {
        factory = new Factory{salt: keccak256(abi.encode("evil"))}();
        a = A(factory.helloA());

        (bool success, bytes memory data) = address(a).staticcall(abi.encodeCall(A.codeLength, ()));
        assertTrue(success);
        assertEq(abi.decode(data, (uint256)), address(a).code.length);

        a.kill();
        factory.kill();
    }

    function testMorphingContract() public {
        assertEq(address(a).code.length, 0);
        assertEq(address(factory).code.length, 0);

        factory = new Factory{salt: keccak256(abi.encode("evil"))}();
        b = B(factory.helloB());
        assertEq(address(a), address(b));
    }
}
   "#,
    );

    cmd.args(["test", "--match-path", "test/SetupSelfdestruct.t.sol", "--evm-version", "cancun"])
        .assert_success();
});

forgetest_init!(selfdestruct_keeps_setup_created_contract_in_test_body, |prj, cmd| {
    prj.add_test(
        "SetupThenTestSelfdestruct.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract A {
    function kill() public {
        selfdestruct(payable(address(0)));
    }
}

contract SetupThenTestSelfdestructTest is Test {
    A private a;

    function setUp() public {
        a = new A();
    }

    function testCodePersistsAcrossSetupBoundary() public {
        a.kill();
        assertGt(address(a).code.length, 0);
    }
}
   "#,
    );

    cmd.args([
        "test",
        "--match-path",
        "test/SetupThenTestSelfdestruct.t.sol",
        "--evm-version",
        "cancun",
    ])
    .assert_success();
});

forgetest_init!(
    #[ignore = "Too slow"]
    can_disable_block_gas_limit,
    |prj, cmd| {
        let endpoint = rpc::next_http_archive_rpc_url();

        prj.add_test(
            "Contract.t.sol",
            &r#"
import {Test} from "forge-std/Test.sol";

contract C is Test {}

contract GasWaster {
    function waste() public {
        for (uint256 i = 0; i < 100; i++) {
            new C();
        }
    }
}

contract GasLimitTest is Test {
    function test() public {
        vm.createSelectFork("<rpc>");

        GasWaster waster = new GasWaster();
        waster.waste();
    }
}
   "#
            .replace("<rpc>", &endpoint),
        );

        cmd.args(["test", "-vvvv", "--isolate", "--disable-block-gas-limit"]).assert_success();
    }
);

forgetest!(test_match_path, |prj, cmd| {
    prj.add_source(
        "dummy",
        r"
contract Dummy {
    function testDummy() public {}
}
",
    );

    cmd.args(["test", "--match-path", "src/dummy.sol"]);
    cmd.assert_success();
});

forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| {
    // deterministic test so we always have 54 runs until test fails with overflow
    prj.update_config(|config| {
        config.fuzz.runs = 256;
        config.fuzz.seed = Some(U256::from(100));
    });

    prj.add_test(
        "CounterFuzz.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Counter {
    uint256 public number = 0;

    function addOne(uint256 x) external pure returns (uint256) {
        return x + 100_000_000;
    }
}

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
    }

    function testAddOne(uint256 x) public view {
        assertEq(counter.addOne(x), x + 100_000_000);
    }
}
     "#,
    );

    // make sure there are only 61 runs (with proptest shrinking same test results in 298 runs)
    cmd.args(["test"]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/CounterFuzz.t.sol:CounterTest
[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=[..] args=[..]] testAddOne(uint256) (runs: [..], [AVG_GAS])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/CounterFuzz.t.sol:CounterTest
[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=[..] args=[..]] testAddOne(uint256) (runs: [..], [AVG_GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

[SEED] (use `--fuzz-seed` to reproduce)

"#]]);
});

forgetest_init!(should_exit_early_on_invariant_failure, |prj, cmd| {
    prj.add_test(
        "CounterInvariant.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Counter {
    uint256 public number = 0;

    function inc() external {
        number += 1;
    }
}

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
    }

    function invariant_early_exit() public view {
        assertTrue(counter.number() == 10, "wrong count");
    }
}
     "#,
    );

    // make sure a pre-run invariant failure exits after the first campaign call and keeps the
    // predicate failure attribution.
    cmd.args(["test"]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/CounterInvariant.t.sol:CounterTest
[FAIL: wrong count] invariant_early_exit() (runs: 1, calls: 1, reverts: 0)

╭----------+----------+-------+---------+----------╮
| Contract | Selector | Calls | Reverts | Discards |
+==================================================+
| Counter  | inc      | 1     | 0       | 0        |
╰----------+----------+-------+---------+----------╯

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/CounterInvariant.t.sol:CounterTest
[FAIL: wrong count] invariant_early_exit() (runs: 1, calls: 1, reverts: 0)

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test

[SEED] (use `--fuzz-seed` to reproduce)

"#]]);
});

forgetest_init!(should_replay_failures_only, |prj, cmd| {
    prj.add_test(
        "ReplayFailures.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract ReplayFailuresTest is Test {
    function testA() public pure {
        require(2 > 1);
    }

    function testB() public pure {
        require(1 > 2, "testB failed");
    }

    function testC() public pure {
        require(2 > 1);
    }

    function testD() public pure {
        require(1 > 2, "testD failed");
    }
}
     "#,
    );

    cmd.args(["test"]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 4 tests for test/ReplayFailures.t.sol:ReplayFailuresTest
[PASS] testA() ([GAS])
[FAIL: testB failed] testB() ([GAS])
[PASS] testC() ([GAS])
[FAIL: testD failed] testD() ([GAS])
Suite result: FAILED. 2 passed; 2 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 2 tests passed, 2 failed, 0 skipped (4 total tests)

Failing tests:
Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest
[FAIL: testB failed] testB() ([GAS])
[FAIL: testD failed] testD() ([GAS])

Encountered a total of 2 failing tests, 2 tests succeeded

Tip: Run `forge test --rerun` to retry only the 2 failed tests
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);

    // Test failure filter should be persisted.
    assert!(prj.root().join("cache/test-failures").exists());

    // Perform only the 2 failing tests from last run.
    cmd.forge_fuse().args(["test", "--rerun"]).assert_failure().stdout_eq(str![[r#"
No files changed, compilation skipped

Ran 2 tests for test/ReplayFailures.t.sol:ReplayFailuresTest
[FAIL: testB failed] testB() ([GAS])
[FAIL: testD failed] testD() ([GAS])
Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests)

Failing tests:
Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest
[FAIL: testB failed] testB() ([GAS])
[FAIL: testD failed] testD() ([GAS])

Encountered a total of 2 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 2 failed tests
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/9285>
forgetest_init!(should_not_record_setup_failures, |prj, cmd| {
    prj.add_test(
        "ReplayFailures.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract SetupFailureTest is Test {
    function setUp() public {
        require(2 > 1);
    }

    function testA() public pure {
    }
}
     "#,
    );

    cmd.args(["test"]).assert_success();
    // Test failure filter should not be persisted if `setUp` failed.
    assert!(!prj.root().join("cache/test-failures").exists());
});

// https://github.com/foundry-rs/foundry/issues/7530
forgetest_init!(should_show_precompile_labels, |prj, cmd| {
    prj.add_test(
        "Contract.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract PrecompileLabelsTest is Test {
    function testPrecompileLabels() public {
        vm.deal(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D), 1 ether);
        vm.deal(address(0x000000000000000000636F6e736F6c652e6c6f67), 1 ether);
        vm.deal(address(0x4e59b44847b379578588920cA78FbF26c0B4956C), 1 ether);
        vm.deal(address(0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38), 1 ether);
        vm.deal(address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496), 1 ether);
        vm.deal(address(1), 1 ether);
        vm.deal(address(2), 1 ether);
        vm.deal(address(3), 1 ether);
        vm.deal(address(4), 1 ether);
        vm.deal(address(5), 1 ether);
        vm.deal(address(6), 1 ether);
        vm.deal(address(7), 1 ether);
        vm.deal(address(8), 1 ether);
        vm.deal(address(9), 1 ether);
        vm.deal(address(10), 1 ether);
    }
}
   "#,
    );

    cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Contract.t.sol:PrecompileLabelsTest
[PASS] testPrecompileLabels() ([GAS])
Traces:
  [..] PrecompileLabelsTest::testPrecompileLabels()
    ├─ [0] VM::deal(VM: [0x7109709ECfa91a80626fF3989D68f67F5b1DD12D], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(console: [0x000000000000000000636F6e736F6c652e6c6f67], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(Create2Deployer: [0x4e59b44847b379578588920cA78FbF26c0B4956C], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(PrecompileLabelsTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(ECRecover: [0x0000000000000000000000000000000000000001], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(RIPEMD-160: [0x0000000000000000000000000000000000000003], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(Identity: [0x0000000000000000000000000000000000000004], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(ModExp: [0x0000000000000000000000000000000000000005], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(ECAdd: [0x0000000000000000000000000000000000000006], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(ECMul: [0x0000000000000000000000000000000000000007], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(ECPairing: [0x0000000000000000000000000000000000000008], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(Blake2F: [0x0000000000000000000000000000000000000009], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    ├─ [0] VM::deal(PointEvaluation: [0x000000000000000000000000000000000000000A], 1000000000000000000 [1e18])
    │   └─ ← [Return]
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// 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| {
    // run fuzz test 3 times
    prj.update_config(|config| {
        config.fuzz.runs = 3;
        config.fuzz.show_logs = true;
    });
    let config = cmd.config();
    assert_eq!(config.fuzz.runs, 3);

    prj.add_test(
        "ContractFuzz.t.sol",
        r#"
import {Test, console} from "forge-std/Test.sol";

contract ContractFuzz is Test {
    function testFuzzConsoleLog(uint256 x) public pure {
        console.log("inside fuzz test, x is:", x);
    }
}
    "#,
    );
    cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz
[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS])
Logs:
  inside fuzz test, x is: [..]
  inside fuzz test, x is: [..]
  inside fuzz test, x is: [..]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// 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| {
    // run fuzz test 3 times
    prj.update_config(|config| {
        config.fuzz.runs = 3;
    });
    let config = cmd.config();
    assert_eq!(config.fuzz.runs, 3);

    prj.add_test(
        "ContractFuzz.t.sol",
        r#"
import {Test, console} from "forge-std/Test.sol";

contract ContractFuzz is Test {
    /// forge-config: default.fuzz.show-logs = true
    function testFuzzConsoleLog(uint256 x) public pure {
        console.log("inside fuzz test, x is:", x);
    }
}
    "#,
    );
    cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz
[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS])
Logs:
  inside fuzz test, x is: [..]
  inside fuzz test, x is: [..]
  inside fuzz test, x is: [..]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests that `forge test` with config `show_logs: false` for fuzz tests will
// still display `console.log` from the last run at verbosity >= 2 (issue #11039)
forgetest_init!(should_not_show_logs_when_fuzz_test, |prj, cmd| {
    // run fuzz test 3 times
    prj.update_config(|config| {
        config.fuzz.runs = 3;
        config.fuzz.show_logs = false;
    });
    let config = cmd.config();
    assert_eq!(config.fuzz.runs, 3);

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

      function testFuzzConsoleLog(uint256 x) public pure {
        console.log("inside fuzz test, x is:", x);
      }
    }
     "#,
    );
    // At verbosity >= 2, logs from the last run should be shown even when show_logs is false
    cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz
[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS])
Logs:
  inside fuzz test, x is: [..]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

forgetest_init!(failed_fuzz_test_shows_only_failure_logs, |prj, cmd| {
    let persist_dir = prj.cache().parent().unwrap().join("persist");
    prj.update_config(|config| {
        config.fuzz.runs = 5;
        config.fuzz.show_logs = false;
        config.fuzz.failure_persist_dir = Some(persist_dir);
    });
    prj.add_test(
        "FuzzLogs.t.sol",
        r#"
contract FuzzLogsTest {
    event log_named_uint(string key, uint256 value);

    function testFuzzLogs(uint256 value) public {
        if (value == 154) {
            emit log_named_uint("FAILING CASE", value);
            revert("target value");
        }
        emit log_named_uint("SUCCESSFUL CASE", value);
    }
}
"#,
    );

    cmd.args([
        "test",
        "--match-test",
        "testFuzzLogs",
        "--fuzz-seed",
        "1",
        "--threads",
        "1",
        "-vv",
    ])
    .assert_failure()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/FuzzLogs.t.sol:FuzzLogsTest
[FAIL: target value; counterexample: calldata=[..] args=[154]] testFuzzLogs(uint256) (runs: 3, [AVG_GAS])
Logs:
  FAILING CASE: 154

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/FuzzLogs.t.sol:FuzzLogsTest
[FAIL: target value; counterexample: calldata=[..] args=[154]] testFuzzLogs(uint256) (runs: 3, [AVG_GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

[SEED] (use `--fuzz-seed` to reproduce)

"#]]);

    prj.update_config(|config| config.fuzz.runs = 128);
    prj.add_test(
        "FuzzLogs.t.sol",
        r#"
interface Vm {
    function sleep(uint256 duration) external;
}

contract FuzzLogsTest {
    Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

    event log_named_uint(string key, uint256 value);

    function testFuzzLogs(uint256 value) public {
        if (value == 154) {
            emit log_named_uint("FAILING CASE", value);
            vm.sleep(100);
            revert("target value");
        }
        emit log_named_uint("SUCCESSFUL CASE", value);
    }
}
"#,
    );

    cmd.forge_fuse()
        .args([
            "test",
            "--match-test",
            "testFuzzLogs",
            "--fuzz-seed",
            "1",
            "--threads",
            "2",
            "-vv",
        ])
        .assert_failure()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/FuzzLogs.t.sol:FuzzLogsTest
[FAIL: target value; counterexample: calldata=[..] args=[154]] testFuzzLogs(uint256) (runs: [..], [AVG_GAS])
Logs:
  FAILING CASE: 154

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/FuzzLogs.t.sol:FuzzLogsTest
[FAIL: target value; counterexample: calldata=[..] args=[154]] testFuzzLogs(uint256) (runs: [..], [AVG_GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

[SEED] (use `--fuzz-seed` to reproduce)

"#]]);
});

// tests that `forge test` with inline config `show_logs = false` for fuzz tests will
// still display `console.log` from the last run at verbosity >= 2 (issue #11039)
forgetest_init!(should_not_show_logs_when_fuzz_test_inline_config, |prj, cmd| {
    // run fuzz test 3 times
    prj.update_config(|config| {
        config.fuzz.runs = 3;
    });
    let config = cmd.config();
    assert_eq!(config.fuzz.runs, 3);

    prj.add_test(
        "ContractFuzz.t.sol",
        r#"
import {Test, console} from "forge-std/Test.sol";

contract ContractFuzz is Test {
    /// forge-config: default.fuzz.show-logs = false
    function testFuzzConsoleLog(uint256 x) public pure {
        console.log("inside fuzz test, x is:", x);
    }
}
     "#,
    );
    // At verbosity >= 2, logs from the last run should be shown even when show_logs is false
    cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz
[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS])
Logs:
  inside fuzz test, x is: [..]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests internal functions trace
forgetest_init!(internal_functions_trace, |prj, cmd| {
    prj.clear();

    prj.add_test(
        "Simple",
        r#"
import {Test, console} from "forge-std/Test.sol";

contract SimpleContract {
    uint256 public num;
    address public addr;

    function _setNum(uint256 _num) internal returns(uint256 prev) {
        prev = num;
        num = _num;
    }

    function _setAddr(address _addr) internal returns(address prev) {
        prev = addr;
        addr = _addr;
    }

    function increment() public {
        _setNum(num + 1);
    }

    function setValues(uint256 _num, address _addr) public {
        _setNum(_num);
        _setAddr(_addr);
    }
}

contract SimpleContractTest is Test {
    function test() public {
        SimpleContract c = new SimpleContract();
        c.increment();
        c.setValues(100, address(0x123));
    }
}
     "#,
    );
    cmd.args(["test", "-vvvv", "--decode-internal"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Simple.sol:SimpleContractTest
[PASS] test() ([GAS])
Traces:
  [..] SimpleContractTest::test()
    ├─ [231452] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 826 bytes of code
    ├─ [43694] SimpleContract::increment()
    │   ├─ [20147] SimpleContract::_setNum(uint256)(1)
    │   │   └─ ← 0
    │   └─ ← [Stop]
    ├─ [49360] SimpleContract::setValues(100, 0x0000000000000000000000000000000000000123)
    │   ├─ [5047] SimpleContract::_setNum(uint256)(100)
    │   │   └─ ← 1
    │   ├─ [22336] SimpleContract::_setAddr(address)(0x0000000000000000000000000000000000000123)
    │   │   └─ ← 0x0000000000000000000000000000000000000000
    │   └─ ← [Stop]
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests internal functions trace with memory decoding
forgetest_init!(internal_functions_trace_memory, |prj, cmd| {
    prj.clear();

    prj.add_test(
        "Simple",
        r#"
import {Test, console} from "forge-std/Test.sol";

contract SimpleContract {
    string public str = "initial value";

    function _setStr(string memory _str) internal returns(string memory prev) {
        prev = str;
        str = _str;
    }

    function setStr(string memory _str) public {
        _setStr(_str);
    }
}

contract SimpleContractTest is Test {
    function test() public {
        SimpleContract c = new SimpleContract();
        c.setStr("new value");
    }
}
     "#,
    );
    cmd.args(["test", "-vvvv", "--decode-internal"]).assert_success().stdout_eq(str![[r#"
...
Traces:
  [..] SimpleContractTest::test()
    ├─ [..] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] [..] bytes of code
    ├─ [..] SimpleContract::setStr("new value")
    │   ├─ [..] SimpleContract::_setStr(string)("new value")
    │   │   └─ ← "initial value"
    │   └─ ← [Stop]
    └─ ← [Stop]
...
"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/15224>
forgetest_init!(internal_functions_trace_calldata_bytes, |prj, cmd| {
    prj.clear();

    prj.add_test(
        "DecodeInternalCalldata",
        r#"
contract DecodeInternalCalldataTraceTest {
    function test_decodeInternalCalldataBytes() public {
        DecodeInternalTarget target = new DecodeInternalTarget();

        bytes[] memory wrappedSignatures = new bytes[](1);
        wrappedSignatures[0] = hex"11223344556677889900";

        bytes32 expectedDigest = keccak256("digest that should appear in the internal trace");

        bool ok = target.outer(wrappedSignatures, expectedDigest);
        require(ok, "target returned false");
    }
}

contract DecodeInternalTarget {
    function outer(bytes[] calldata signatures, bytes32 digest) external pure returns (bool) {
        bytes calldata signature = signatures[0];
        return _internalValidate(digest, signature);
    }

    function _internalValidate(bytes32 digest, bytes calldata signature)
        internal
        pure
        returns (bool)
    {
        return digest == keccak256("digest that should appear in the internal trace")
            && signature.length == 10;
    }
}
     "#,
    );

    let stdout = cmd
        .args(["test", "-vvvv", "--decode-internal"])
        .assert_success()
        .get_output()
        .stdout_lossy();
    assert!(
        stdout.contains(
            "DecodeInternalTarget::_internalValidate(bytes32,bytes)(\
             0x9a707a44f6e6038d2b03f3947e82f6403612c5d5b923868f6a759e9c38e16a28, \
             0x11223344556677889900)"
        ),
        "{stdout}"
    );
});

// tests that `forge test` with a seed produces deterministic random values for uint and addresses.
forgetest_init!(deterministic_randomness_with_seed, |prj, cmd| {
    prj.add_test(
        "DeterministicRandomnessTest.t.sol",
        r#"
import {Test, console} from "forge-std/Test.sol";

contract DeterministicRandomnessTest is Test {

    function testDeterministicRandomUint() public {
        console.log(vm.randomUint());
        console.log(vm.randomUint());
        console.log(vm.randomUint());
    }

    function testDeterministicRandomUintRange() public {
        uint256 min = 0;
        uint256 max = 1000000000;
        console.log(vm.randomUint(min, max));
        console.log(vm.randomUint(min, max));
        console.log(vm.randomUint(min, max));
    }

    function testDeterministicRandomAddress() public {
        console.log(vm.randomAddress());
        console.log(vm.randomAddress());
        console.log(vm.randomAddress());
    }
}
"#,
    );

    // Extracts the test result section from the DeterministicRandomnessTest contract output.
    fn extract_test_result(out: &str) -> &str {
        let start = out
            .find("for test/DeterministicRandomnessTest.t.sol:DeterministicRandomnessTest")
            .unwrap();
        let end = out.find("Suite result: ok.").unwrap();
        &out[start..end]
    }

    // Run the test twice with the same seed and verify the outputs are the same.
    let seed1 = "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2";
    let out1 = cmd
        .args(["test", "--fuzz-seed", seed1, "-vv"])
        .assert_success()
        .get_output()
        .stdout_lossy();
    let res1 = extract_test_result(&out1);

    let out2 = cmd
        .forge_fuse()
        .args(["test", "--fuzz-seed", seed1, "-vv"])
        .assert_success()
        .get_output()
        .stdout_lossy();
    let res2 = extract_test_result(&out2);

    assert_eq!(res1, res2);

    // Run the test with another seed and verify the output differs.
    let seed2 = "0xb1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2";
    let out3 = cmd
        .forge_fuse()
        .args(["test", "--fuzz-seed", seed2, "-vv"])
        .assert_success()
        .get_output()
        .stdout_lossy();
    let res3 = extract_test_result(&out3);
    assert_ne!(res3, res1);

    // Run the test without a seed and verify the outputs differs once again.
    cmd.forge_fuse();
    let out4 = cmd.args(["test", "-vv"]).assert_success().get_output().stdout_lossy();
    let res4 = extract_test_result(&out4);
    assert_ne!(res4, res1);
    assert_ne!(res4, res3);
});

// Tests that `pauseGasMetering` used at the end of test does not produce meaningless values.
// https://github.com/foundry-rs/foundry/issues/5491
forgetest_init!(gas_metering_pause_last_call, |prj, cmd| {
    prj.add_test(
        "ATest.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract ATest is Test {
    function testWeirdGas1() public {
        vm.pauseGasMetering();
    }

    function testWeirdGas2() public {
        uint256 a = 1;
        uint256 b = a + 1;
        require(b == 2, "b is not 2");
        vm.pauseGasMetering();
    }

    function testNormalGas() public {
        vm.pauseGasMetering();
        vm.resumeGasMetering();
    }

    function testWithAssembly() public {
        vm.pauseGasMetering();
        assembly {
            return(0, 0)
        }
    }
}
   "#,
    );

    cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#"
...
[PASS] testNormalGas() (gas: 3148)
[PASS] testWeirdGas1() (gas: 2986)
[PASS] testWeirdGas2() (gas: 3213)
[PASS] testWithAssembly() (gas: 3029)
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/5564
forgetest_init!(gas_metering_expect_revert, |prj, cmd| {
    prj.add_test(
        "ATest.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";
contract ATest is Test {
    error MyError();
    function testSelfMeteringRevert() public {
        vm.pauseGasMetering();
        vm.expectRevert(MyError.selector);
        this.selfReverts();
    }
    function selfReverts() external {
        vm.resumeGasMetering();
        revert MyError();
    }
}
   "#,
    );

    cmd.args(["test"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/ATest.t.sol:ATest
[PASS] testSelfMeteringRevert() ([GAS])
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/4523
forgetest_init!(gas_metering_gasleft, |prj, cmd| {
    prj.add_test(
        "ATest.t.sol",
        r#"
import "forge-std/Test.sol";

contract ATest is Test {
    mapping(uint256 => bytes32) map;

    function test_GasMeter() public {
        vm.pauseGasMetering();
        consumeGas();
        vm.resumeGasMetering();

        consumeGas();
    }

    function test_GasLeft() public {
        consumeGas();

        uint256 start = gasleft();
        consumeGas();
        console.log("Gas cost:", start - gasleft());
    }

    function consumeGas() private {
        for (uint256 i = 0; i < 100; i++) {
            map[i] = keccak256(abi.encode(i));
        }
    }
}
   "#,
    );

    // Log and test gas cost should be similar.
    cmd.args(["test", "-vvvv"]).with_no_redact().assert_success().stdout_eq(str![[r#"
...
Logs:
  Gas cost: 50068

Traces:
  [2303684] ATest::test_GasLeft()
    ├─ [0] console::log("Gas cost:", 50068 [5.006e4]) [staticcall]
    │   └─ ← [Stop]
    └─ ← [Stop]

[PASS] test_GasMeter() (gas: 53097)
Traces:
  [53097] ATest::test_GasMeter()
    ├─ [0] VM::pauseGasMetering()
    │   └─ ← [Return]
    ├─ [0] VM::resumeGasMetering()
    │   └─ ← [Return]
    └─ ← [Stop]
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/4370
forgetest_init!(pause_gas_metering_with_delete, |prj, cmd| {
    prj.add_test(
        "ATest.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";
contract ATest is Test {
    uint a;
    function test_negativeGas () public {
        vm.pauseGasMetering();
        a = 100;
        vm.resumeGasMetering();
        delete a;
    }
}
   "#,
    );

    cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#"
...
[PASS] test_negativeGas() (gas: 96)
...
"#]]);
});

// tests `pauseTracing` and `resumeTracing` functions
forgetest_init!(pause_tracing, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.add_source(
        "Pause.t.sol",
        r#"
import {Vm} from "./Vm.sol";
import {DSTest} from "./test.sol";
contract TraceGenerator is DSTest {
    Vm vm = Vm(HEVM_ADDRESS);
    event DummyEvent(uint256 i);
    function call(uint256 i) public {
        emit DummyEvent(i);
    }
    function generate() public {
        for (uint256 i = 0; i < 10; i++) {
            if (i == 3) {
                vm.pauseTracing();
            }
            this.call(i);
            if (i == 7) {
                vm.resumeTracing();
            }
        }
    }
}
contract PauseTracingTest is DSTest {
    Vm vm = Vm(HEVM_ADDRESS);
    event DummyEvent(uint256 i);
    function setUp() public {
        emit DummyEvent(1);
        vm.pauseTracing();
        emit DummyEvent(2);
    }
    function test() public {
        emit DummyEvent(3);
        TraceGenerator t = new TraceGenerator();
        vm.resumeTracing();
        t.generate();
    }
}
     "#,
    );
    cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#"
...
Traces:
  [7757] PauseTracingTest::setUp()
    ├─ emit DummyEvent(i: 1)
    ├─ [0] VM::pauseTracing() [staticcall]
    │   └─ ← [Return]
    └─ ← [Stop]

  [558945] PauseTracingTest::test()
    ├─ [0] VM::resumeTracing() [staticcall]
    │   └─ ← [Return]
    ├─ [48460] TraceGenerator::generate()
    │   ├─ [1589] TraceGenerator::call(0)
    │   │   ├─ emit DummyEvent(i: 0)
    │   │   └─ ← [Stop]
    │   ├─ [1589] TraceGenerator::call(1)
    │   │   ├─ emit DummyEvent(i: 1)
    │   │   └─ ← [Stop]
    │   ├─ [1589] TraceGenerator::call(2)
    │   │   ├─ emit DummyEvent(i: 2)
    │   │   └─ ← [Stop]
    │   ├─ [0] VM::pauseTracing() [staticcall]
    │   │   └─ ← [Return]
    │   ├─ [0] VM::resumeTracing() [staticcall]
    │   │   └─ ← [Return]
    │   ├─ [1589] TraceGenerator::call(8)
    │   │   ├─ emit DummyEvent(i: 8)
    │   │   └─ ← [Stop]
    │   ├─ [1589] TraceGenerator::call(9)
    │   │   ├─ emit DummyEvent(i: 9)
    │   │   └─ ← [Stop]
    │   └─ ← [Stop]
    └─ ← [Stop]
...
"#]]);
});

forgetest_init!(gas_metering_reset, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.add_source(
        "ATest.t.sol",
        r#"
import {Vm} from "./Vm.sol";
import {DSTest} from "./test.sol";
contract B {
    function a() public returns (uint256) {
        return 100;
    }
}
contract ATest is DSTest {
    Vm vm = Vm(HEVM_ADDRESS);
    B b;
    uint256 a;

    function testResetGas() public {
        vm.resetGasMetering();
    }

    function testResetGas1() public {
        vm.resetGasMetering();
        b = new B();
        vm.resetGasMetering();
    }

    function testResetGas2() public {
        b = new B();
        b = new B();
        vm.resetGasMetering();
    }

    function testResetGas3() public {
        vm.resetGasMetering();
        b = new B();
        b = new B();
    }

    function testResetGas4() public {
        vm.resetGasMetering();
        b = new B();
        vm.resetGasMetering();
        b = new B();
    }

    function testResetGas5() public {
        vm.resetGasMetering();
        b = new B();
        vm.resetGasMetering();
        b = new B();
        vm.resetGasMetering();
    }

    function testResetGas6() public {
        vm.resetGasMetering();
        b = new B();
        b = new B();
        _reset();
        vm.resetGasMetering();
    }

    function testResetGas7() public {
        vm.resetGasMetering();
        b = new B();
        b = new B();
        _reset();
    }

    function testResetGas8() public {
        this.resetExternal();
    }

    function testResetGas9() public {
        this.resetExternal();
        vm.resetGasMetering();
    }

    function testResetNegativeGas() public {
        a = 100;
        vm.resetGasMetering();

        delete a;
    }

    function _reset() internal {
        vm.resetGasMetering();
    }

    function resetExternal() external {
        b = new B();
        b = new B();
        vm.resetGasMetering();
    }

    // https://github.com/foundry-rs/foundry/issues/12474
    function testMemoryOnReset(uint8[1] memory x) public {
        uint8[1] memory z;
        z[0] = x[0];
        assertEq(z[0], x[0]);
        vm.resetGasMetering();
        assertEq(x[0], z[0]);
    }
}
     "#,
    );

    cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#"
...
[PASS] testResetGas() (gas: 96)
[PASS] testResetGas1() (gas: 96)
[PASS] testResetGas2() (gas: 96)
[PASS] testResetGas3() (gas: [..])
[PASS] testResetGas4() (gas: [..])
[PASS] testResetGas5() (gas: 96)
[PASS] testResetGas6() (gas: 96)
[PASS] testResetGas7() (gas: 96)
[PASS] testResetGas8() (gas: [..])
[PASS] testResetGas9() (gas: 96)
[PASS] testResetNegativeGas() (gas: 96)
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/8705
forgetest_init!(test_expect_revert_decode, |prj, cmd| {
    prj.add_test(
        "Counter.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";
contract Counter {
    uint256 public number;
    error NumberNotEven(uint256 number);
    error RandomError();
    function setNumber(uint256 newNumber) public {
        if (newNumber % 2 != 0) {
            revert NumberNotEven(newNumber);
        }
        number = newNumber;
    }
}
contract CounterTest is Test {
    Counter public counter;
    function setUp() public {
        counter = new Counter();
        counter.setNumber(0);
    }
    function test_decode() public {
        vm.expectRevert(Counter.RandomError.selector);
        counter.setNumber(1);
    }
    function test_decode_with_args() public {
        vm.expectRevert(abi.encodePacked(Counter.NumberNotEven.selector, uint(2)));
        counter.setNumber(1);
    }
}
   "#,
    );

    cmd.args(["test"]).assert_failure().stdout_eq(str![[r#"
...
[FAIL: Error != expected error: NumberNotEven(1) != RandomError()] test_decode() ([GAS])
[FAIL: Error != expected error: NumberNotEven(1) != NumberNotEven(2)] test_decode_with_args() ([GAS])
...
"#]]);
});

// Tests that `expectPartialRevert` cheatcode partially matches revert data.
forgetest_init!(test_expect_partial_revert, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.add_source(
        "Counter.t.sol",
        r#"
import {Vm} from "./Vm.sol";
import {DSTest} from "./test.sol";
contract Counter {
    error WrongNumber(uint256 number);
    function count() public pure {
        revert WrongNumber(0);
    }
}
contract CounterTest is DSTest {
    Vm vm = Vm(HEVM_ADDRESS);
    function testExpectPartialRevertWithSelector() public {
        Counter counter = new Counter();
        vm.expectPartialRevert(Counter.WrongNumber.selector);
        counter.count();
    }
    function testExpectPartialRevertWith4Bytes() public {
        Counter counter = new Counter();
        vm.expectPartialRevert(bytes4(0x238ace70));
        counter.count();
    }
    function testExpectRevert() public {
        Counter counter = new Counter();
        vm.expectRevert(Counter.WrongNumber.selector);
        counter.count();
    }
}
     "#,
    );

    cmd.args(["test"]).assert_failure().stdout_eq(str![[r#"
...
[PASS] testExpectPartialRevertWith4Bytes() ([GAS])
[PASS] testExpectPartialRevertWithSelector() ([GAS])
[FAIL: Error != expected error: WrongNumber(0) != custom error 0x238ace70] testExpectRevert() ([GAS])
...
"#]]);
});

forgetest_init!(test_assume_no_revert, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.update_config(|config| {
        config.fuzz.runs = 100;
        config.fuzz.seed = Some(U256::from(100));
    });

    prj.add_source(
        "Counter.t.sol",
        r#"
import {Vm} from "./Vm.sol";
import {DSTest} from "./test.sol";
contract CounterWithRevert {
    error CountError();
    error CheckError();

    function count(uint256 a) public pure returns (uint256) {
        if (a > 1000 || a < 10) {
            revert CountError();
        }
        return 99999999;
    }
    function check(uint256 a) public pure {
        if (a == 99999999) {
            revert CheckError();
        }
    }
    function dummy() public pure {}
}

contract CounterRevertTest is DSTest {
    Vm vm = Vm(HEVM_ADDRESS);

    function test_assume_no_revert_pass(uint256 a) public {
        CounterWithRevert counter = new CounterWithRevert();
        vm.assumeNoRevert();
        a = counter.count(a);
        assertEq(a, 99999999);
    }
    function test_assume_no_revert_fail_assert(uint256 a) public {
        CounterWithRevert counter = new CounterWithRevert();
        vm.assumeNoRevert();
        a = counter.count(a);
        // Test should fail on next assertion.
        assertEq(a, 1);
    }
    function test_assume_no_revert_fail_in_2nd_call(uint256 a) public {
        CounterWithRevert counter = new CounterWithRevert();
        vm.assumeNoRevert();
        a = counter.count(a);
        // Test should revert here (not in scope of `assumeNoRevert` cheatcode).
        counter.check(a);
        assertEq(a, 99999999);
    }
    function test_assume_no_revert_fail_in_3rd_call(uint256 a) public {
        CounterWithRevert counter = new CounterWithRevert();
        vm.assumeNoRevert();
        a = counter.count(a);
        // Test `assumeNoRevert` applied to non reverting call should not be available for next reverting call.
        vm.assumeNoRevert();
        counter.dummy();
        // Test will revert here (not in scope of `assumeNoRevert` cheatcode).
        counter.check(a);
        assertEq(a, 99999999);
    }
}
     "#,
    );

    cmd.args(["test"]).with_no_redact().assert_failure().stdout_eq(str![[r#"
...
[FAIL; counterexample: [..]] test_assume_no_revert_fail_assert(uint256) [..]
[FAIL: CheckError(); counterexample: [..]] test_assume_no_revert_fail_in_2nd_call(uint256) [..]
[FAIL: CheckError(); counterexample: [..]] test_assume_no_revert_fail_in_3rd_call(uint256) [..]
[PASS] test_assume_no_revert_pass(uint256) [..]
...
"#]]);
});

forgetest_init!(skip_output, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.add_source(
        "Counter.t.sol",
        r#"
        import {Vm} from "./Vm.sol";
        import {DSTest} from "./test.sol";

        contract Skips is DSTest {
            Vm constant vm = Vm(HEVM_ADDRESS);

            function test_skipUnit() public {
                vm.skip(true);
            }
            function test_skipUnitReason() public {
                vm.skip(true, "unit");
            }

            function test_skipFuzz(uint) public {
                vm.skip(true);
            }
            function test_skipFuzzReason(uint) public {
                vm.skip(true, "fuzz");
            }

            function invariant_skipInvariant() public {
                vm.skip(true);
            }
            function invariant_skipInvariantReason() public {
                vm.skip(true, "invariant");
            }
        }
    "#,
    );

    cmd.arg("test").assert_success().stdout_eq(str![[r#"
...
Ran 6 tests for src/Counter.t.sol:Skips
[SKIP]
Skips invariants:
[SKIP] invariant_skipInvariant
[SKIP: invariant] invariant_skipInvariantReason
 Skips invariants (runs: 1, calls: 1, reverts: 1)
[SKIP] test_skipFuzz(uint256) (runs: 0, [AVG_GAS])
[SKIP: fuzz] test_skipFuzzReason(uint256) (runs: 0, [AVG_GAS])
[SKIP] test_skipUnit() ([GAS])
[SKIP: unit] test_skipUnitReason() ([GAS])
Suite result: ok. 0 passed; 0 failed; 6 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 6 skipped (6 total tests)

"#]]);
});

forgetest_init!(skip_setup, |prj, cmd| {
    prj.add_test(
        "Counter.t.sol",
        r#"
import "forge-std/Test.sol";

contract SkipCounterSetup is Test {

    function setUp() public {
        vm.skip(true, "skip counter test");
    }

    function test_require1() public pure {
        require(1 > 2);
    }

    function test_require2() public pure {
        require(1 > 2);
    }

    function test_require3() public pure {
        require(1 > 2);
    }
}
    "#,
    );

    cmd.args(["test", "--mc", "SkipCounterSetup"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Counter.t.sol:SkipCounterSetup
[SKIP: skipped: skip counter test] setUp() ([GAS])
Suite result: ok. 0 passed; 0 failed; 1 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 1 skipped (1 total tests)

"#]]);
});

forgetest_init!(should_generate_junit_xml_report, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.add_source(
        "JunitReportTest.t.sol",
        r#"
        import {Vm} from "./Vm.sol";
        import {DSTest} from "./test.sol";

        contract AJunitReportTest is DSTest {
            function test_junit_assert_fail() public {
                assert(1 > 2);
            }

            function test_junit_revert_fail() public {
                require(1 > 2, "Revert");
            }
        }

        contract BJunitReportTest is DSTest {
            Vm constant vm = Vm(HEVM_ADDRESS);
            function test_junit_pass() public {
                require(1 < 2, "Revert");
            }

            function test_junit_skip() public {
                vm.skip(true);
            }

            function test_junit_skip_with_message() public {
                vm.skip(true, "skipped test");
            }

            function test_junit_pass_fuzz(uint256 a) public {
            }
        }
   "#,
    );

    cmd.args(["test", "--junit"]).assert_failure().stdout_eq(str![[r#"
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Test run" tests="6" skipped="2" failures="2" errors="0" timestamp="[..]" time="[..]">
    <testsuite name="src/JunitReportTest.t.sol:AJunitReportTest" tests="2" skipped="0" errors="0" failures="2" time="[..]">
        <testcase name="test_junit_assert_fail()" time="[..]">
            <failure message="panic: assertion failed (0x01)"/>
            <system-out>[FAIL: panic: assertion failed (0x01)] test_junit_assert_fail() ([GAS])</system-out>
        </testcase>
        <testcase name="test_junit_revert_fail()" time="[..]">
            <failure message="Revert"/>
            <system-out>[FAIL: Revert] test_junit_revert_fail() ([GAS])</system-out>
        </testcase>
        <system-out>Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED]</system-out>
    </testsuite>
    <testsuite name="src/JunitReportTest.t.sol:BJunitReportTest" tests="4" skipped="2" errors="0" failures="0" time="[..]">
        <testcase name="test_junit_pass()" time="[..]">
            <system-out>[PASS] test_junit_pass() ([GAS])</system-out>
        </testcase>
        <testcase name="test_junit_pass_fuzz(uint256)" time="[..]">
            <system-out>[PASS] test_junit_pass_fuzz(uint256) (runs: 256, [AVG_GAS])</system-out>
        </testcase>
        <testcase name="test_junit_skip()" time="[..]">
            <skipped/>
            <system-out>[SKIP] test_junit_skip() ([GAS])</system-out>
        </testcase>
        <testcase name="test_junit_skip_with_message()" time="[..]">
            <skipped message="skipped test"/>
            <system-out>[SKIP: skipped test] test_junit_skip_with_message() ([GAS])</system-out>
        </testcase>
        <system-out>Suite result: ok. 2 passed; 0 failed; 2 skipped; [ELAPSED]</system-out>
    </testsuite>
</testsuites>


"#]]);
});

forgetest_init!(should_generate_junit_xml_report_with_logs, |prj, cmd| {
    prj.add_source(
        "JunitReportTest.t.sol",
        r#"
import "forge-std/Test.sol";
contract JunitReportTest is Test {
    function test_junit_with_logs() public {
        console.log("Step1");
        console.log("Step2");
        console.log("Step3");
        assert(2 > 1);
    }
}
   "#,
    );

    cmd.args(["test", "--junit", "-vvvv"]).assert_success().stdout_eq(str![[r#"
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Test run" tests="1" skipped="0" failures="0" errors="0" timestamp="[..]" time="[..]">
    <testsuite name="src/JunitReportTest.t.sol:JunitReportTest" tests="1" skipped="0" errors="0" failures="0" time="[..]">
        <testcase name="test_junit_with_logs()" time="[..]">
            <system-out>[PASS] test_junit_with_logs() ([GAS])/nLogs:/n  Step1/n  Step2/n  Step3/n</system-out>
        </testcase>
        <system-out>Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]</system-out>
    </testsuite>
</testsuites>


"#]]);
});

forgetest_init!(
    // Enable this if no cheatcodes are deprecated.
    // #[ignore = "no cheatcodes are deprecated"]
    test_deprecated_cheatcode_warning,
    |prj, cmd| {
        prj.add_test(
            "DeprecatedCheatcodeTest.t.sol",
            r#"
        import "forge-std/Test.sol";
        contract DeprecatedCheatcodeTest is Test {
            function test_deprecated_cheatcode() public view {
                vm.keyExists('{"a": 123}', ".a");
                vm.keyExists('{"a": 123}', ".a");
            }
        }

        contract DeprecatedCheatcodeFuzzTest is Test {
            function test_deprecated_cheatcode(uint256 a) public view {
                vm.keyExists('{"a": 123}', ".a");
            }
        }

        contract Counter {
            uint256 a;

            function count() public {
                a++;
            }
        }

        contract DeprecatedCheatcodeInvariantTest is Test {
            function setUp() public {
                Counter counter = new Counter();
            }

            /// forge-config: default.invariant.runs = 1
            function invariant_deprecated_cheatcode() public {
                vm.keyExists('{"a": 123}', ".a");
            }
        }
   "#,
        );

        // Tests deprecated cheatcode warning for unit tests.
        cmd.args(["test", "--mc", "DeprecatedCheatcodeTest"]).assert_success().stderr_eq(str![[
            r#"
Warning: the following cheatcode(s) are deprecated and will be removed in future versions:
  keyExists(string,string): replaced by `keyExistsJson`

"#
        ]]);

        // Tests deprecated cheatcode warning for fuzz tests.
        cmd.forge_fuse()
            .args(["test", "--mc", "DeprecatedCheatcodeFuzzTest"])
            .assert_success()
            .stderr_eq(str![[r#"
Warning: the following cheatcode(s) are deprecated and will be removed in future versions:
  keyExists(string,string): replaced by `keyExistsJson`

"#]]);

        // Tests deprecated cheatcode warning for invariant tests.
        cmd.forge_fuse()
            .args(["test", "--mc", "DeprecatedCheatcodeInvariantTest"])
            .assert_success()
            .stderr_eq(str![[r#"
Warning: the following cheatcode(s) are deprecated and will be removed in future versions:
  keyExists(string,string): replaced by `keyExistsJson`

"#]]);
    }
);

forgetest_init!(requires_single_test, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["test", "--debug"]).assert_failure().stderr_eq(str![[r#"
Error: 2 tests matched your criteria, but exactly 1 test must match in order to run the debugger.

Matching tests:
  test/Counter.t.sol:CounterTest.testFuzz_SetNumber
  test/Counter.t.sol:CounterTest.test_Increment

Use --match-test <TEST_NAME>, --match-contract, and --match-path to further limit the search.

"#]]);
    cmd.forge_fuse().args(["test", "--flamegraph"]).assert_failure().stderr_eq(str![[r#"
Error: 2 tests matched your criteria, but exactly 1 test must match in order to generate a flamegraph.

Use --match-contract and --match-path to further limit the search.

"#]]);
    cmd.forge_fuse().args(["test", "--flamechart"]).assert_failure().stderr_eq(str![[r#"
Error: 2 tests matched your criteria, but exactly 1 test must match in order to generate a flamechart.

Use --match-contract and --match-path to further limit the search.

"#]]);
    cmd.forge_fuse().args(["test", "--evm-profile"]).assert_failure().stderr_eq(str![[r#"
Error: 2 tests matched your criteria, but exactly 1 test must match in order to generate an EVM profile.

Use --match-contract and --match-path to further limit the search.

"#]]);
});

// Test a script that calls vm.rememberKeys
forgetest_init!(script_testing, |prj, cmd| {
    prj
    .add_source(
        "Foo",
        r#"
import "forge-std/Script.sol";

interface Vm {
function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs);
}

contract WalletScript is Script {
function run() public {
    string memory mnemonic = "test test test test test test test test test test test junk";
    string memory derivationPath = "m/44'/60'/0'/0/";
    address[] memory wallets = Vm(address(vm)).rememberKeys(mnemonic, derivationPath, 3);
    for (uint256 i = 0; i < wallets.length; i++) {
        console.log(wallets[i]);
    }
}
}

contract FooTest {
    WalletScript public script;


    function setUp() public {
        script = new WalletScript();
    }

    function testWalletScript() public {
        script.run();
    }
}

"#,
    );

    cmd.args(["test", "--mt", "testWalletScript", "-vvv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for src/Foo.sol:FooTest
[PASS] testWalletScript() ([GAS])
Logs:
  0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  0x70997970C51812dc3A010C7d01b50e0d17dc79C8
  0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
...
"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/8995>
forgetest_init!(metadata_bytecode_traces, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_source(
        "ParentProxy.sol",
        r#"
import {Counter} from "./Counter.sol";

abstract contract ParentProxy {
    Counter impl;
    bytes data;

    constructor(Counter _implementation, bytes memory _data) {
        impl = _implementation;
        data = _data;
    }
}
   "#,
    );
    prj.add_source(
        "Proxy.sol",
        r#"
import {ParentProxy} from "./ParentProxy.sol";
import {Counter} from "./Counter.sol";

contract Proxy is ParentProxy {
    constructor(Counter _implementation, bytes memory _data)
        ParentProxy(_implementation, _data)
    {}
}
   "#,
    );

    prj.add_test(
        "MetadataTraceTest.t.sol",
        r#"
import {Counter} from "src/Counter.sol";
import {Proxy} from "src/Proxy.sol";

import {Test} from "forge-std/Test.sol";

contract MetadataTraceTest is Test {
    function test_proxy_trace() public {
        Counter counter = new Counter();
        new Proxy(counter, "");
    }
}
   "#,
    );

    cmd.args(["test", "--mt", "test_proxy_trace", "-vvvv", "--no-dynamic-test-linking"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest
[PASS] test_proxy_trace() ([GAS])
Traces:
  [..] MetadataTraceTest::test_proxy_trace()
    ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [..] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b
    │   └─ ← [Return] 62 bytes of code
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);

    // Check consistent traces for running with no metadata.
    cmd.forge_fuse()
        .args([
            "test",
            "--mt",
            "test_proxy_trace",
            "-vvvv",
            "--no-metadata",
            "--no-dynamic-test-linking",
        ])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest
[PASS] test_proxy_trace() ([GAS])
Traces:
  [..] MetadataTraceTest::test_proxy_trace()
    ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 427 bytes of code
    ├─ [..] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b
    │   └─ ← [Return] 8 bytes of code
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// Tests if dump of execution was created.
forgetest!(test_debug_with_dump, |prj, cmd| {
    prj.add_source(
        "dummy",
        r"
contract Dummy {
    function testDummy() public {}
}
",
    );

    let dump_path = prj.root().join("dump.json");

    cmd.args(["test", "--mt", "testDummy", "--debug", "--dump", dump_path.to_str().unwrap()]);
    cmd.assert_success();

    assert!(dump_path.exists());
});

forgetest!(debug_dump_marks_precompile_call_steps, |prj, cmd| {
    prj.add_test(
        "PrecompileDebug.t.sol",
        r#"
contract PrecompileDebugTest {
    function testSha256PrecompileDebug() public {
        (bool ok, bytes memory output) = address(0x02).staticcall(hex"68656c6c6f");
        require(ok, "sha256 precompile failed");
        require(output.length == 32, "bad sha256 output");
    }
}
"#,
    );

    let dump_path = prj.root().join("precompile_dump.json");

    cmd.args([
        "test",
        "--mt",
        "testSha256PrecompileDebug",
        "--debug",
        "--dump",
        dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();

    let dump: serde_json::Value =
        serde_json::from_str(&std::fs::read_to_string(dump_path).unwrap()).unwrap();
    let mut lines = Vec::new();
    collect_debug_dump_decoded_lines(&dump, &mut lines);

    assert!(
        lines.iter().any(|line| {
            *line == "precompile: PRECOMPILES::sha256(0x68656c6c6f) -> 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
        }),
        "missing decoded precompile line in debugger dump: {lines:?}"
    );
});

forgetest!(debug_dump_marks_tempo_precompile_call_steps, |prj, cmd| {
    prj.update_config(|config| {
        config.networks = foundry_evm_networks::NetworkConfigs::with_tempo();
        config.hardfork = Some("tempo:T5".parse::<foundry_config::FoundryHardfork>().unwrap());
    });

    prj.add_test(
        "TempoPrecompileDebug.t.sol",
        r#"
contract TempoPrecompileDebugTest {
    address constant TIP_FEE_MANAGER = 0xfeEC000000000000000000000000000000000000;

    function testTempoFeeManagerPrecompileDebug() public view {
        (bool ok, bytes memory output) = TIP_FEE_MANAGER.staticcall(
            abi.encodeWithSignature("userTokens(address)", address(0))
        );
        require(ok, "FeeManager precompile failed");
        require(output.length == 32, "bad FeeManager output");
    }
}
"#,
    );

    let dump_path = prj.root().join("tempo_precompile_dump.json");

    cmd.args([
        "test",
        "--mt",
        "testTempoFeeManagerPrecompileDebug",
        "--debug",
        "--dump",
        dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();

    let dump: serde_json::Value =
        serde_json::from_str(&std::fs::read_to_string(dump_path).unwrap()).unwrap();
    let mut lines = Vec::new();
    collect_debug_dump_decoded_lines(&dump, &mut lines);

    assert!(
        lines.iter().any(|line| line.contains("precompile: FeeManager::userTokens(")),
        "missing decoded Tempo precompile line in debugger dump: {lines:?}"
    );
});

forgetest!(debug_dump_disambiguates_overloaded_internal_functions, |prj, cmd| {
    prj.add_source(
        "DebugVars",
        r"
contract DebugVarsTest {
    function testOverloadedInternalDebugVars() public {
        uint256 amount = foo(uint256(42));
        address who = foo(address(0x000000000000000000000000000000000000bEEF));

        require(amount == 43, 'bad amount');
        require(who == address(0x000000000000000000000000000000000000bEEF), 'bad address');
    }

    function foo(uint256 amount) internal pure returns (uint256 out) {
        uint256 next = amount + 1;
        return next;
    }

    function foo(address who) internal pure returns (address out) {
        address seen = who;
        return seen;
    }
}
",
    );

    let dump_path = prj.root().join("overloads_dump.json");

    cmd.args([
        "test",
        "--mt",
        "testOverloadedInternalDebugVars",
        "--debug",
        "--dump",
        dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();

    let dump: serde_json::Value =
        serde_json::from_str(&std::fs::read_to_string(dump_path).unwrap()).unwrap();
    let mut calls = Vec::new();
    collect_debug_dump_internal_calls(&dump, &mut calls);

    let uint_call = calls
        .iter()
        .find(|call| call["func_name"] == "DebugVarsTest::foo(uint256)")
        .expect("missing uint256 overload in debugger dump");
    assert_eq!(uint_call["args"], serde_json::json!(["42"]));
    assert_eq!(uint_call["return_data"], serde_json::json!(["43"]));

    let address_call = calls
        .iter()
        .find(|call| call["func_name"] == "DebugVarsTest::foo(address)")
        .expect("missing address overload in debugger dump");
    assert_eq!(
        address_call["args"],
        serde_json::json!(["0x000000000000000000000000000000000000bEEF"])
    );
    assert_eq!(
        address_call["return_data"],
        serde_json::json!(["0x000000000000000000000000000000000000bEEF"])
    );
});

// Progresses <https://github.com/foundry-rs/foundry/issues/927>: debugger storage view.
forgetest!(debug_dump_includes_storage_changes, |prj, cmd| {
    prj.add_source(
        "DebugStorage",
        r"
contract DebugStorageTest {
    uint256 value;
    uint256 untouched;

    function testStorage() public {
        value = 42;
        uint256 loaded = value;
        uint256 coldLoaded = untouched;
        require(loaded == 42 && coldLoaded == 0);
    }
}
",
    );

    let dump_path = prj.root().join("storage_dump.json");

    cmd.args(["test", "--mt", "testStorage", "--debug", "--dump", dump_path.to_str().unwrap()]);
    cmd.assert_success();

    let dump: serde_json::Value =
        serde_json::from_str(&std::fs::read_to_string(dump_path).unwrap()).unwrap();
    let mut storage_changes = Vec::new();
    collect_debug_dump_storage_changes(&dump, &mut storage_changes);

    assert!(
        storage_changes.iter().any(|change| change["reason"] == "SSTORE"),
        "debugger dump should include an SSTORE storage change: {storage_changes:#?}"
    );
    assert!(
        storage_changes.iter().any(|change| change["reason"] == "SLOAD"),
        "debugger dump should include an SLOAD storage access: {storage_changes:#?}"
    );
});

// <https://github.com/foundry-rs/foundry/issues/10322>
forgetest!(test_debug_with_dump_setup_revert, |prj, cmd| {
    prj.add_test(
        "SetupRevertDebug.t.sol",
        r#"
contract SetupRevertDebugTest {
    function setUp() public {
        revert("setUp failed");
    }

    function testDummy() public {}
}
"#,
    );

    let dump_path = prj.root().join("setup_revert_dump.json");

    let out = cmd
        .args(["test", "--mt", "testDummy", "--debug", "--dump", dump_path.to_str().unwrap()])
        .execute();

    assert!(out.status.success(), "debug run with --dump should complete even when setUp reverts");
    assert!(dump_path.exists(), "debugger dump should still be generated");
    assert!(
        !out.stderr_lossy().contains("debug arena is empty"),
        "debugger should not fail with an empty arena on setUp revert"
    );
    assert!(
        out.stdout_lossy().contains("[FAIL: setUp failed] setUp()"),
        "expected setup failure output in forge test report"
    );
});

// <https://github.com/foundry-rs/foundry/issues/10322>
forgetest!(test_debug_with_dump_setup_revert_invariant, |prj, cmd| {
    prj.add_test(
        "SetupRevertInvariantDebug.t.sol",
        r#"
contract SetupRevertInvariantDebugTest {
    function setUp() public {
        revert("setUp failed");
    }

    function invariant_dummy() public {}
}
"#,
    );

    let dump_path = prj.root().join("setup_revert_invariant_dump.json");

    let out = cmd
        .args(["test", "--mt", "invariant_dummy", "--debug", "--dump", dump_path.to_str().unwrap()])
        .execute();

    assert!(
        out.status.success(),
        "debug run with --dump should complete even when invariant setUp reverts"
    );
    assert!(dump_path.exists(), "debugger dump should still be generated");
    assert!(
        !out.stderr_lossy().contains("debug arena is empty"),
        "debugger should not fail with an empty arena on invariant setUp revert"
    );
    assert!(
        out.stdout_lossy().contains("[FAIL: setUp failed] setUp()"),
        "expected setup failure output in forge test report"
    );
});

forgetest_async!(debug_dump_identifies_contracts_loaded_from_selected_fork, |prj, cmd| {
    prj.add_source(
        "ForkDebugTarget.sol",
        r#"
contract ForkDebugTarget {
    uint256 public value;

    function set(uint256 newValue) public {
        value = newValue;
    }
}
"#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    let rpc = handle.http_endpoint();
    let pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

    cmd.forge_fuse()
        .args([
            "create",
            "./src/ForkDebugTarget.sol:ForkDebugTarget",
            "--rpc-url",
            rpc.as_str(),
            "--private-key",
            pk,
            "--broadcast",
        ])
        .assert_success();
    let deployed =
        Address::from_str("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266").unwrap().create(0);
    let deployed = deployed.to_string();

    prj.add_test(
        "ForkDebug.t.sol",
        &format!(
            r#"
interface Vm {{
    function createSelectFork(string calldata url) external returns (uint256 forkId);
}}

interface IForkDebugTarget {{
    function set(uint256 newValue) external;
    function value() external view returns (uint256);
}}

contract ForkDebugTest {{
    Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

    uint256[] public fixtureNewValue = [7];

    function testDebugForkTarget() public {{
        vm.createSelectFork("{rpc}");
        IForkDebugTarget target = IForkDebugTarget({deployed});
        target.set(7);
        require(target.value() == 7, "value");
    }}

    /// forge-config: default.fuzz.runs = 1
    function testDebugForkTargetFuzz(uint256 newValue) public {{
        vm.createSelectFork("{rpc}");
        IForkDebugTarget target = IForkDebugTarget({deployed});
        target.set(newValue);
        require(target.value() == newValue, "value");
    }}

    function tableDebugForkTarget(uint256 newValue) public {{
        vm.createSelectFork("{rpc}");
        IForkDebugTarget target = IForkDebugTarget({deployed});
        target.set(newValue);
        require(target.value() == newValue, "value");
    }}
}}
"#
        ),
    );

    let dump_path = prj.root().join("dump.json");
    cmd.forge_fuse().args([
        "test",
        "--mt",
        "^testDebugForkTarget\\(\\)$",
        "--debug",
        "--dump",
        dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();
    assert_debug_dump_identifies_contract(&dump_path, &deployed, "ForkDebugTarget");

    let fuzz_dump_path = prj.root().join("fuzz_dump.json");
    cmd.forge_fuse().args([
        "test",
        "--mt",
        "^testDebugForkTargetFuzz\\(uint256\\)$",
        "--debug",
        "--dump",
        fuzz_dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();
    assert_debug_dump_identifies_contract(&fuzz_dump_path, &deployed, "ForkDebugTarget");

    let table_dump_path = prj.root().join("table_dump.json");
    cmd.forge_fuse().args([
        "test",
        "--mt",
        "^tableDebugForkTarget\\(uint256\\)$",
        "--debug",
        "--dump",
        table_dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();
    assert_debug_dump_identifies_contract(&table_dump_path, &deployed, "ForkDebugTarget");
});

forgetest_init!(test_assume_no_revert_with_data, |prj, cmd| {
    prj.update_config(|config| {
        config.fuzz.seed = Some(U256::from(111));
        config.fuzz.dictionary.max_fuzz_dictionary_literals = 0;
    });

    prj.add_source(
        "AssumeNoRevertTest.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

interface Vm {
    struct PotentialRevert {
        address reverter;
        bool partialMatch;
        bytes revertData;
    }
    function expectRevert() external;
    function assumeNoRevert() external pure;
    function assumeNoRevert(PotentialRevert calldata revertData) external pure;
    function assumeNoRevert(PotentialRevert[] calldata revertData) external pure;
    function expectRevert(bytes4 revertData, uint64 count) external;
    function assume(bool condition) external pure;
}

contract ReverterB {
    /// @notice has same error selectors as contract below to test the `reverter` param
    error MyRevert();
    error SpecialRevertWithData(uint256 x);

    function revertIf2(uint256 x) public pure returns (bool) {
        if (x == 2) {
            revert MyRevert();
        }
        return true;
    }

    function revertWithData() public pure returns (bool) {
        revert SpecialRevertWithData(2);
    }
}

contract Reverter {
    error MyRevert();
    error RevertWithData(uint256 x);
    error UnusedError();
    error ExpectedRevertCountZero();

    ReverterB public immutable subReverter;

    constructor() {
        subReverter = new ReverterB();
    }

    function myFunction() public pure returns (bool) {
        revert MyRevert();
    }

    function revertIf2(uint256 value) public pure returns (bool) {
        if (value == 2) {
            revert MyRevert();
        }
        return true;
    }

    function revertWithDataIf2(uint256 value) public pure returns (bool) {
        if (value == 2) {
            revert RevertWithData(2);
        }
        return true;
    }

    function twoPossibleReverts(uint256 x) public pure returns (bool) {
        if (x == 2) {
            revert MyRevert();
        } else if (x == 3) {
            revert RevertWithData(3);
        }
        return true;
    }

    function revertIf2Or3ExpectedRevertZero(uint256 x) public pure returns (bool) {
        if (x == 2) {
            revert ExpectedRevertCountZero();
        } else if (x == 3) {
            revert MyRevert();
        }
        return true;
    }
}

contract ReverterTest is Test {
    Reverter reverter;
    Vm _vm = Vm(VM_ADDRESS);

    function setUp() public {
        reverter = new Reverter();
    }

    /// @dev Test that `assumeNoRevert` does not reject an unanticipated error selector
    function testAssume_wrongSelector_fails(uint256 x) public view {
        _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.UnusedError.selector), partialMatch: false, reverter: address(0)}));
        reverter.revertIf2(x);
    }

    /// @dev Test that `assumeNoRevert` does not reject an unanticipated error with extra data
    function testAssume_wrongData_fails(uint256 x) public view {
        _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 3), partialMatch: false, reverter: address(0)}));
        reverter.revertWithDataIf2(x);
    }

    /// @dev Test that `assumeNoRevert` correctly rejects an error selector from a different contract
    function testAssumeWithReverter_fails(uint256 x) public view {
        ReverterB subReverter = (reverter.subReverter());
        _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(reverter)}));
        subReverter.revertIf2(x);
    }

    /// @dev Test that `assumeNoRevert` correctly rejects one of two different error selectors when supplying a specific reverter
    function testMultipleAssumes_OneWrong_fails(uint256 x) public view {
        Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2);
        revertData[0] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(reverter)});
        revertData[1] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 4), partialMatch: false, reverter: address(reverter)});
        _vm.assumeNoRevert(revertData);
        reverter.twoPossibleReverts(x);
    }

    /// @dev Test that `assumeNoRevert` assumptions are cleared after the first non-cheatcode external call
    function testMultipleAssumesClearAfterCall_fails(uint256 x) public view {
        _vm.assume(x != 3);
        Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2);
        revertData[0] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(0)});
        revertData[1] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 4), partialMatch: false, reverter: address(reverter)});
        _vm.assumeNoRevert(revertData);
        reverter.twoPossibleReverts(x);

        reverter.twoPossibleReverts(2);
    }

    /// @dev Test that `assumeNoRevert` correctly rejects a generic assumeNoRevert call after any specific reason is provided
    function testMultipleAssumes_ThrowOnGenericNoRevert_AfterSpecific_fails(bytes4 selector) public view {
        _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encode(selector), partialMatch: false, reverter: address(0)}));
        _vm.assumeNoRevert();
        reverter.twoPossibleReverts(2);
    }

    function testAssumeThenExpectCountZeroFails(uint256 x) public {
        _vm.assumeNoRevert(
            Vm.PotentialRevert({
                revertData: abi.encodeWithSelector(Reverter.MyRevert.selector),
                partialMatch: false,
                reverter: address(0)
            })
        );
        _vm.expectRevert(Reverter.ExpectedRevertCountZero.selector, 0);
        reverter.revertIf2Or3ExpectedRevertZero(x);
    }

    function testExpectCountZeroThenAssumeFails(uint256 x) public {
        _vm.expectRevert(Reverter.ExpectedRevertCountZero.selector, 0);
        _vm.assumeNoRevert(
            Vm.PotentialRevert({
                revertData: abi.encodeWithSelector(Reverter.MyRevert.selector),
                partialMatch: false,
                reverter: address(0)
            })
        );
        reverter.revertIf2Or3ExpectedRevertZero(x);
    }

}"#,
    );
    cmd.args(["test", "--mc", "ReverterTest"]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 8 tests for src/AssumeNoRevertTest.t.sol:ReverterTest
[FAIL: call reverted with 'FOUNDRY::ASSUME' when it was expected not to revert; counterexample: [..] testAssumeThenExpectCountZeroFails(uint256) (runs: [..], [AVG_GAS])
[FAIL: MyRevert(); counterexample: calldata=[..]] testAssumeWithReverter_fails(uint256) (runs: [..], [AVG_GAS])
[FAIL: RevertWithData(2); counterexample: [..]] testAssume_wrongData_fails(uint256) (runs: [..], [AVG_GAS])
[FAIL: MyRevert(); counterexample: [..]] testAssume_wrongSelector_fails(uint256) (runs: [..], [AVG_GAS])
[FAIL: call reverted with 'FOUNDRY::ASSUME' when it was expected not to revert; counterexample: [..]] testExpectCountZeroThenAssumeFails(uint256) (runs: [..], [AVG_GAS])
[FAIL: MyRevert(); counterexample: [..]] testMultipleAssumesClearAfterCall_fails(uint256) (runs: 0, [AVG_GAS])
[FAIL: RevertWithData(3); counterexample: [..]] testMultipleAssumes_OneWrong_fails(uint256) (runs: [..], [AVG_GAS])
[FAIL: vm.assumeNoRevert: you must make another external call prior to calling assumeNoRevert again; counterexample: [..]] testMultipleAssumes_ThrowOnGenericNoRevert_AfterSpecific_fails(bytes4) (runs: [..], [AVG_GAS])
...

"#]]);
});

forgetest_async!(flaky_can_get_broadcast_txs, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    let (_api, handle) = spawn(NodeConfig::test().silent()).await;

    prj.insert_vm();
    prj.insert_ds_test();
    prj.insert_console();

    prj.add_source(
        "Counter.sol",
        r#"
        contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}
    "#,
    );

    prj.add_script(
        "DeployCounter",
        r#"
        import "forge-std/Script.sol";
        import "src/Counter.sol";

        contract DeployCounter is Script {
            function run() public {
                vm.startBroadcast();

                Counter counter = new Counter();

                counter.increment();

                counter.setNumber(10);

                vm.stopBroadcast();
            }
        }
    "#,
    );

    prj.add_script(
        "DeployCounterWithCreate2",
        r#"
        import "forge-std/Script.sol";
        import "src/Counter.sol";

        contract DeployCounterWithCreate2 is Script {
            function run() public {
                vm.startBroadcast();

                bytes32 salt = bytes32(uint256(1337));
                Counter counter = new Counter{salt: salt}();

                counter.increment();

                counter.setNumber(20);

                vm.stopBroadcast();
            }
        }
    "#,
    );

    let test = r#"
        import {Vm} from "../src/Vm.sol";
        import {DSTest} from "../src/test.sol";
        import {console} from "../src/console.sol";

        contract GetBroadcastTest is DSTest {
            Vm constant vm = Vm(HEVM_ADDRESS);

            function test_getLatestBroadcast() external {
                // Gets the latest create
                Vm.BroadcastTxSummary memory broadcast = vm.getBroadcast(
                    "Counter",
                    31337,
                    Vm.BroadcastTxType.Create
                );

                console.log("latest create");
                console.log(broadcast.blockNumber);

                assertEq(broadcast.blockNumber, 1);

                // Gets the latest create2
                Vm.BroadcastTxSummary memory broadcast2 = vm.getBroadcast(
                    "Counter",
                    31337,
                    Vm.BroadcastTxType.Create2
                );

                console.log("latest create2");
                console.log(broadcast2.blockNumber);
                assertEq(broadcast2.blockNumber, 4);

                // Gets the latest call
                Vm.BroadcastTxSummary memory broadcast3 = vm.getBroadcast(
                    "Counter",
                    31337,
                    Vm.BroadcastTxType.Call
                );

                console.log("latest call");
                assertEq(broadcast3.blockNumber, 6);
            }

            function test_getBroadcasts() public {
                // Gets all calls
                Vm.BroadcastTxSummary[] memory broadcasts = vm.getBroadcasts(
                    "Counter",
                    31337,
                    Vm.BroadcastTxType.Call
                );

                assertEq(broadcasts.length, 4);
            }

            function test_getAllBroadcasts() public {
                // Gets all broadcasts
                Vm.BroadcastTxSummary[] memory broadcasts2 = vm.getBroadcasts(
                    "Counter",
                    31337
                );

                assertEq(broadcasts2.length, 6);
            }

            function test_getLatestDeployment() public {
                address deployedAddress = vm.getDeployment(
                    "Counter",
                    31337
                );

                assertGt(uint160(deployedAddress), 0);
            }

            function test_getDeployments() public {
                address[] memory deployments = vm.getDeployments(
                    "Counter",
                    31337
                );

                assertEq(deployments.length, 2);
                // Verify valid addresses returned and they're different (CREATE vs CREATE2)
                assertGt(uint160(deployments[0]), 0);
                assertGt(uint160(deployments[1]), 0);
                assertTrue(deployments[0] != deployments[1]);
            }
}
    "#;

    prj.add_test("GetBroadcast", test);

    let sender = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";

    cmd.args([
        "script",
        "DeployCounter",
        "--rpc-url",
        &handle.http_endpoint(),
        "--sender",
        sender,
        "--unlocked",
        "--broadcast",
        "--slow",
    ])
    .assert_success();

    cmd.forge_fuse()
        .args([
            "script",
            "DeployCounterWithCreate2",
            "--rpc-url",
            &handle.http_endpoint(),
            "--sender",
            sender,
            "--unlocked",
            "--broadcast",
            "--slow",
        ])
        .assert_success();

    let broadcast_path = prj.root().join("broadcast");

    // Check if the broadcast folder exists
    assert!(broadcast_path.exists() && broadcast_path.is_dir());

    cmd.forge_fuse().args(["test", "--mc", "GetBroadcastTest", "-vvv"]).assert_success();
});

// See <https://github.com/foundry-rs/foundry/issues/9297>
forgetest_init!(
    #[ignore = "RPC Service Unavailable"]
    test_roll_scroll_fork_with_cancun,
    |prj, cmd| {
        prj.add_test(
            "ScrollForkTest.t.sol",
            r#"

import {Test} from "forge-std/Test.sol";

contract ScrollForkTest is Test {
    function test_roll_scroll_fork_to_tx() public {
        vm.createSelectFork("https://scroll-mainnet.chainstacklabs.com/");
        bytes32 targetTxHash = 0xf94774a1f69bba76892141190293ffe85dd8d9ac90a0a2e2b114b8c65764014c;
        vm.rollFork(targetTxHash);
    }
}
   "#,
        );

        cmd.args(["test", "--mt", "test_roll_scroll_fork_to_tx", "--evm-version", "cancun"])
            .assert_success();
    }
);

// Test that failed fork errors still surface the provider hostname.
forgetest_init!(test_display_provider_on_error, |prj, cmd| {
    prj.add_test(
        "ForkTest.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract ForkTest is Test {
    function test_fork_err_message() public {
        vm.createSelectFork("https://eth-mainnet.g.alchemy.com/v2/DUMMY_KEY");
    }
}
   "#,
    );

    cmd.args(["test", "--mt", "test_fork_err_message"]).assert_failure().stdout_eq(str![[r#"
...
Ran 1 test for test/ForkTest.t.sol:ForkTest
[FAIL: vm.createSelectFork: could not instantiate forked environment with provider eth-mainnet.g.alchemy.com; HTTP error 401 with body: [..]

...

"#]]);
});

// Tests that test traces display state changes when running with verbosity.
forgetest_init!(should_show_state_changes, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["test", "--mt", "test_Increment", "-vvvvv", "--no-dynamic-test-linking"])
        .assert_success()
        .stdout_eq(str![[r#"
...
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_Increment() ([GAS])
Traces:
  [218890] CounterTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [23784] Counter::setNumber(0)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [51847] CounterTest::test_Increment()
    ├─ [43482] Counter::increment()
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    ├─ [2424] Counter::number() [staticcall]
    │   └─ ← [Return] 1
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

forgetest_init!(tracing_verbosity_shows_state_changes_independently, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.update_config(|config| {
        config.verbosity = 0;
        config.tracing.verbosity = 5;
        config.tracing.labels.insert(
            Address::from_str("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f").unwrap(),
            "ConfiguredCounter".to_string(),
        );
    });

    cmd.args(["test", "--mt", "test_Increment", "--no-dynamic-test-linking"])
        .assert_success()
        .stdout_eq(str![[r#"
...
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_Increment() ([GAS])
Traces:
  [218890] CounterTest::setUp()
    ├─ [156801] → new ConfiguredCounter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [23784] ConfiguredCounter::setNumber(0)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [51847] CounterTest::test_Increment()
    ├─ [43482] ConfiguredCounter::increment()
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    ├─ [2424] ConfiguredCounter::number() [staticcall]
    │   └─ ← [Return] 1
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// Tests that test traces display opcodes when verbosity level is 5
forgetest_init!(should_show_opcodes, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args([
        "test",
        "--mt",
        "test_Increment",
        "-vvvvv",
        "--opcodes",
        "SLOAD,MLOAD",
        "--no-dynamic-test-linking",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
...
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_Increment() ([GAS])
Traces:
  [..] CounterTest::setUp()
    ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [..] Counter::setNumber(0)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [..] CounterTest::test_Increment()
    ├─ [..] SLOAD 0x1f → (0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f01)
    ├─ [..] MLOAD
    ├─ [..] MLOAD
    ├─ [..] Counter::increment()
    │   ├─ [..] SLOAD 0x0 → (0x0)
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    ├─ [..] SLOAD
    ├─ [..] MLOAD
    ├─ [..] MLOAD
    ├─ [..] Counter::number() [staticcall]
    │   ├─ [..] SLOAD 0x0 → (0x1)
    │   ├─ [..] MLOAD
    │   ├─ [..] MLOAD
    │   └─ ← [Return] 1
    ├─ [..] MLOAD
    ├─ [..] MLOAD
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// Tests that --opcodes properly errors (fails early) with wrong opcode names
forgetest_init!(opcodes_invalid_name, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["test", "--mt", "test_Increment", "-vvvvv", "--opcodes", "BOGUS"])
        .assert_failure()
        .stderr_eq(str![[r#"
...
error: invalid value 'BOGUS' for '--opcodes <OPCODES>': invalid opcode: BOGUS

For more information, try '--help'.

"#]]);
});

// Tests that --opcodes errors when not provided with -vvvvv
forgetest_init!(opcodes_not_enough_verbosity, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["test", "--mt", "test_Increment", "-vvv", "--opcodes", "ADD"])
        .assert_failure()
        .stderr_eq(str![[r#"
...
Error: Not enough verbosity. Use -vvvvv to show opcodes.

"#]]);
});

forgetest_init!(opcodes_conflict_with_non_trace_outputs, |prj, cmd| {
    prj.initialize_default_contracts();
    for flag in ["--json", "--junit", "--list", "--debug"] {
        let output =
            cmd.forge_fuse().args(["test", "-vvvvv", "--opcodes", "SLOAD", flag]).assert_failure();
        let stderr = String::from_utf8_lossy(&output.get_output().stderr);
        assert!(stderr.contains("--opcodes"), "stderr should mention --opcodes: {stderr}");
        assert!(stderr.contains(flag), "stderr should mention {flag}: {stderr}");
        assert!(stderr.contains("cannot be used with"), "stderr should explain conflict: {stderr}");
    }
});

// Tests that the test file path is not swallowed by the --opcodes flag
forgetest_init!(opcodes_path_after_flag, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args([
        "test",
        "--mt",
        "test_Increment",
        "-vvvvv",
        "--opcodes",
        "SSTORE",
        "test/Counter.t.sol",
        "--no-dynamic-test-linking",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
...
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_Increment() ([GAS])
Traces:
  [..] CounterTest::setUp()
    ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [..] Counter::setNumber(0)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [..] CounterTest::test_Increment()
    ├─ [..] Counter::increment()
    │   ├─ [..] SSTORE 0x0: 0x0 → 0x1
    │   └─ ← [Stop]
    ├─ [..] Counter::number() [staticcall]
    │   └─ ← [Return] 1
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// Tests that chained errors are properly displayed.
// <https://github.com/foundry-rs/foundry/issues/9161>
forgetest!(displays_chained_error, |prj, cmd| {
    prj.add_test(
        "Foo.t.sol",
        r#"
contract ContractTest {
    function test_anything(uint) public {}
}
   "#,
    );

    cmd.arg("test").arg("--gas-limit=100").assert_failure().stdout_eq(str![[r#"
...
Failing tests:
Encountered 1 failing test in test/Foo.t.sol:ContractTest
[FAIL: EVM error; transaction validation error: call [GAS_COST] exceeds the [GAS_LIMIT]] setUp() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// Tests that `start/stopAndReturn` debugTraceRecording does not panic when running with
// verbosity > 3. <https://github.com/foundry-rs/foundry/issues/9526>
forgetest_init!(should_not_panic_on_debug_trace_verbose, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "DebugTraceRecordingTest.t.sol",
        r#"
import "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract DebugTraceRecordingTest is Test {
    function test_start_stop_recording() public {
        vm.startDebugTraceRecording();
        Counter counter = new Counter();
        counter.increment();
        vm.stopAndReturnDebugTraceRecording();
    }
}
     "#,
    );

    cmd.args(["test", "--mt", "test_start_stop_recording", "-vvvv"]).assert_success().stdout_eq(
        str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/DebugTraceRecordingTest.t.sol:DebugTraceRecordingTest
[PASS] test_start_stop_recording() ([GAS])
Traces:
  [..] DebugTraceRecordingTest::test_start_stop_recording()
...
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]],
    );
});

forgetest_init!(colored_traces, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args([
        "test",
        "--mt",
        "test_Increment",
        "--color",
        "always",
        "-vvvvv",
        "--no-dynamic-test-linking",
    ])
    .assert_success()
    .stdout_eq(file!["../../fixtures/colored_traces.svg": TermSvg]);
});

// Tests that traces for successful tests can be suppressed by using `-s` flag.
// <https://github.com/foundry-rs/foundry/issues/9864>
forgetest_init!(should_only_show_failed_tests_trace, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "SuppressTracesTest.t.sol",
        r#"
import "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract SuppressTracesTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
        counter.setNumber(0);
    }

    function test_increment_success() public {
        console.log("test increment success");
        counter.increment();
        assertEq(counter.number(), 1);
    }

    function test_increment_failure() public {
        console.log("test increment failure");
        counter.increment();
        assertEq(counter.number(), 100);
    }
}
     "#,
    );

    // Show traces and logs for failed test only.
    cmd.args(["test", "--mc", "SuppressTracesTest", "-vvvvv", "-s", "--no-dynamic-test-linking"])
        .assert_failure()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/SuppressTracesTest.t.sol:SuppressTracesTest
[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS])
Logs:
  test increment failure

Traces:
  [218890] SuppressTracesTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [23784] Counter::setNumber(0)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [58264] SuppressTracesTest::test_increment_failure()
    ├─ [0] console::log("test increment failure") [staticcall]
    │   └─ ← [Stop]
    ├─ [43482] Counter::increment()
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    ├─ [2424] Counter::number() [staticcall]
    │   └─ ← [Return] 1
    ├─ [0] VM::assertEq(1, 100) [staticcall]
    │   └─ ← [Revert] assertion failed: 1 != 100
    └─ ← [Revert] assertion failed: 1 != 100

Backtrace:
  at VM.assertEq
  at SuppressTracesTest.test_increment_failure (lib/forge-std/src/StdAssertions.sol:[..]:[..])

[PASS] test_increment_success() ([GAS])
Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests)

Failing tests:
Encountered 1 failing test in test/SuppressTracesTest.t.sol:SuppressTracesTest
[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS])

Encountered a total of 1 failing tests, 1 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);

    // Show traces and logs for all tests.
    cmd.forge_fuse()
        .args(["test", "--mc", "SuppressTracesTest", "-vvvv", "--no-dynamic-test-linking"])
        .assert_failure()
        .stdout_eq(str![[r#"
No files changed, compilation skipped

Ran 2 tests for test/SuppressTracesTest.t.sol:SuppressTracesTest
[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS])
Logs:
  test increment failure

Traces:
  [218890] SuppressTracesTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [23784] Counter::setNumber(0)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [58264] SuppressTracesTest::test_increment_failure()
    ├─ [0] console::log("test increment failure") [staticcall]
    │   └─ ← [Stop]
    ├─ [43482] Counter::increment()
    │   └─ ← [Stop]
    ├─ [2424] Counter::number() [staticcall]
    │   └─ ← [Return] 1
    ├─ [0] VM::assertEq(1, 100) [staticcall]
    │   └─ ← [Revert] assertion failed: 1 != 100
    └─ ← [Revert] assertion failed: 1 != 100

Backtrace:
  at VM.assertEq
  at SuppressTracesTest.test_increment_failure

[PASS] test_increment_success() ([GAS])
Logs:
  test increment success

Traces:
  [55228] SuppressTracesTest::test_increment_success()
    ├─ [0] console::log("test increment success") [staticcall]
    │   └─ ← [Stop]
    ├─ [43482] Counter::increment()
    │   └─ ← [Stop]
    ├─ [2424] Counter::number() [staticcall]
    │   └─ ← [Return] 1
    └─ ← [Stop]

Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests)

Failing tests:
Encountered 1 failing test in test/SuppressTracesTest.t.sol:SuppressTracesTest
[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS])

Encountered a total of 1 failing tests, 1 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

forgetest_init!(catch_test_deployment_failure, |prj, cmd| {
    prj.add_test(
        "TestDeploymentFailure.t.sol",
        r#"
import "forge-std/Test.sol";
contract TestDeploymentFailure is Test {

    constructor() {
        require(false);
    }

    function setUp() public {
        require(true);
    }

    function test_something() public {
        require(1 == 1);
    }
}
    "#,
    );

    cmd.args(["t", "--mt", "test_something"]).assert_failure().stdout_eq(str![[r#"
...
Failing tests:
Encountered 1 failing test in test/TestDeploymentFailure.t.sol:TestDeploymentFailure
[FAIL: EvmError: Revert] constructor() ([GAS])
..."#]]);
});

// <https://github.com/foundry-rs/foundry/issues/10012>
forgetest_init!(state_diff_recording_with_revert, |prj, cmd| {
    prj.add_test(
        "TestStateDiffRevertFailure.t.sol",
        r#"
import "forge-std/Test.sol";
contract StateDiffRevertAtSameDepthTest is Test {
    function test_something() public {
        CounterTestA counter = new CounterTestA();
        counter.doSomething();
    }
}

contract CounterTestA is Test {
    function doSomething() public {
        vm.startStateDiffRecording();
        require(1 > 2);
    }
}
    "#,
    );

    cmd.args(["t", "--mt", "test_something"]).assert_failure();
});

// <https://github.com/foundry-rs/foundry/issues/5521>
forgetest_init!(should_apply_pranks_per_recorded_depth, |prj, cmd| {
    prj.add_test(
        "Counter.t.sol",
        r#"
import "forge-std/Test.sol";
contract CounterTest is Test {
    function test_stackPrank() public {
        address player = makeAddr("player");
        SenderLogger senderLogger = new SenderLogger();
        Contract c = new Contract();

        senderLogger.log(); // Log(ContractTest, DefaultSender)
        vm.startPrank(player, player);
        senderLogger.log(); // Log(player, player)
        c.f(); // vm.startPrank(player)
        senderLogger.log(); // Log(ContractTest, player) <- ContractTest should be player
        vm.stopPrank();
    }
}

contract Contract {
    Vm public constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))));

    function f() public {
        vm.startPrank(msg.sender);
    }
}

contract SenderLogger {
    event Log(address, address);

    function log() public {
        emit Log(msg.sender, tx.origin);
    }
}
    "#,
    );
    // Emits
    // Log(: player: [], : player: []) instead
    // Log(: ContractTest: [], : player: [])
    cmd.args(["test", "--mt", "test_stackPrank", "-vvvv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] test_stackPrank() ([GAS])
Traces:
  [..] CounterTest::test_stackPrank()
    ├─ [..] VM::addr(<pk>) [staticcall]
    │   └─ ← [Return] player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C]
    ├─ [..] VM::label(player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C], "player")
    │   └─ ← [Return]
    ├─ [..] → new SenderLogger@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 255 bytes of code
    ├─ [..] → new Contract@0x2e234DAe75C793f67A35089C9d99245E1C58470b
    │   └─ ← [Return] 542 bytes of code
    ├─ [..] SenderLogger::log()
    │   ├─ emit Log(: CounterTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], : DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
    │   └─ ← [Stop]
    ├─ [..] VM::startPrank(player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C], player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
    │   └─ ← [Return]
    ├─ [..] SenderLogger::log()
    │   ├─ emit Log(: player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C], : player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
    │   └─ ← [Stop]
    ├─ [..] Contract::f()
    │   ├─ [..] VM::startPrank(player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
    │   │   └─ ← [Return]
    │   └─ ← [Stop]
    ├─ [..] SenderLogger::log()
    │   ├─ emit Log(: player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C], : player: [0x44E97aF4418b7a17AABD8090bEA0A471a366305C])
    │   └─ ← [Stop]
    ├─ [..] VM::stopPrank()
    │   └─ ← [Return]
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/10060>
forgetest_init!(should_redact_pk_in_sign_delegation, |prj, cmd| {
    prj.add_test(
        "Counter.t.sol",
        r#"
import "forge-std/Test.sol";
contract CounterTest is Test {
    function testCheckDelegation() external {
        (address alice, uint256 key) = makeAddrAndKey("alice");
        vm.signDelegation(address(0), key);
        vm.signAndAttachDelegation(address(0), key);
    }
}
    "#,
    );

    cmd.args(["test", "--mt", "testCheckDelegation", "-vvvv"]).assert_success().stdout_eq(str![[r#"
...
Ran 1 test for test/Counter.t.sol:CounterTest
[PASS] testCheckDelegation() ([GAS])
Traces:
  [..] CounterTest::testCheckDelegation()
    ├─ [0] VM::addr(<pk>) [staticcall]
    │   └─ ← [Return] alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]
    ├─ [0] VM::label(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], "alice")
    │   └─ ← [Return]
    ├─ [0] VM::signDelegation(0x0000000000000000000000000000000000000000, "<pk>")
    │   └─ ← [Return] (0, 0x3d6ad67cc3dc94101a049f85f96937513a05485ae0f8b27545d25c4f71b12cf9, 0x3c0f2d62834f59d6ef0209e8a935f80a891a236eb18ac0e3700dd8f7ac8ae279, 0, 0x0000000000000000000000000000000000000000)
    ├─ [0] VM::signAndAttachDelegation(0x0000000000000000000000000000000000000000, "<pk>")
    │   └─ ← [Return] (0, 0x3d6ad67cc3dc94101a049f85f96937513a05485ae0f8b27545d25c4f71b12cf9, 0x3c0f2d62834f59d6ef0209e8a935f80a891a236eb18ac0e3700dd8f7ac8ae279, 0, 0x0000000000000000000000000000000000000000)
    └─ ← [Stop]
...

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/10068>
forgetest_init!(flaky_can_upload_selectors_with_path, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_source(
        "CounterV1.sol",
        r#"
contract Counter {
    uint256 public number;

    function setNumberV1(uint256 newNumber) public {
        number = newNumber;
    }

    function incrementV1() public {
        number++;
    }
}
    "#,
    );

    prj.add_source(
        "CounterV2.sol",
        r#"
contract CounterV2 {
    uint256 public number;

    function setNumberV2(uint256 newNumber) public {
        number = newNumber;
    }

    function incrementV2() public {
        number++;
    }
}
    "#,
    );

    // Upload Counter without path fails as there are multiple contracts with same name.
    cmd.args(["selectors", "upload", "Counter"]).assert_failure().stderr_eq(str![[r#"
...
Error: Multiple contracts found with the name `Counter`
...

"#]]);

    // Upload without contract name should fail.
    cmd.forge_fuse().args(["selectors", "upload", "src/Counter.sol"]).assert_failure().stderr_eq(
        str![[r#"
...
Error: No contract name provided.
...

"#]],
    );

    // Upload single CounterV2.
    cmd.forge_fuse()
        .args(["selectors", "upload", "CounterV2"])
        .assert_success()
        .stdout_eq(str![[r#"
...
Selectors successfully uploaded to OpenChain
...

"#]])
        .stderr_eq(str![[r#"
Uploading selectors for CounterV2...

"#]]);

    // Upload CounterV1 with path.
    cmd.forge_fuse()
        .args(["selectors", "upload", "src/CounterV1.sol:Counter"])
        .assert_success()
        .stdout_eq(str![[r#"
...
Selectors successfully uploaded to OpenChain
...

"#]])
        .stderr_eq(str![[r#"
Uploading selectors for Counter...

"#]]);

    // Upload Counter with path.
    cmd.forge_fuse()
        .args(["selectors", "upload", "src/Counter.sol:Counter"])
        .assert_success()
        .stdout_eq(str![[r#"
...
Selectors successfully uploaded to OpenChain
...

"#]])
        .stderr_eq(str![[r#"
Uploading selectors for Counter...

"#]]);
});

forgetest_init!(selectors_collision_does_not_write_artifacts, |prj, cmd| {
    prj.add_source(
        "First.sol",
        r"
contract First {
    function burn(uint256 value) public pure {
        value;
    }
}
   ",
    );
    prj.add_source(
        "Second.sol",
        r"
contract Second {
    function collate_propagate_storage(bytes16 value) public pure {
        value;
    }
}
   ",
    );

    let first_artifact = prj.paths().artifacts.join("First.sol/First.json");
    let second_artifact = prj.paths().artifacts.join("Second.sol/Second.json");

    let output = cmd.args(["selectors", "collision", "First", "Second"]).assert_success();
    let stdout = output.get_output().stdout_lossy();

    assert!(stdout.contains("1 collisions found:"), "unexpected stdout:\n{stdout}");
    assert!(stdout.contains("42966c68"), "unexpected stdout:\n{stdout}");
    assert!(stdout.contains("burn(uint256)"), "unexpected stdout:\n{stdout}");
    assert!(stdout.contains("collate_propagate_storage(bytes16)"), "unexpected stdout:\n{stdout}");
    assert!(!first_artifact.exists());
    assert!(!second_artifact.exists());
});

forgetest_init!(selectors_list_does_not_write_artifacts, |prj, cmd| {
    prj.add_source(
        "Counter.sol",
        r"
contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }
}
   ",
    );

    let artifact = prj.paths().artifacts.join("Counter.sol/Counter.json");
    let output = cmd.args(["selectors", "list", "Counter"]).assert_success();
    let stdout = output.get_output().stdout_lossy();

    assert!(stdout.contains("Counter"), "unexpected stdout:\n{stdout}");
    assert!(stdout.contains("setNumber(uint256)"), "unexpected stdout:\n{stdout}");
    assert!(!artifact.exists());
});

forgetest_init!(selectors_list_cmd, |prj, cmd| {
    prj.add_source(
        "Counter.sol",
        r"
contract Counter {
    uint256 public number;
    event Incremented(uint256 newNumber);
    error IncrementError();

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}
   ",
    );

    prj.add_source(
        "CounterV2.sol",
        r"
contract CounterV2 {
    uint256 public number;

    function setNumberV2(uint256 newNumber) public {
        number = newNumber;
    }

    function incrementV2() public {
        number++;
    }
}
   ",
    );

    cmd.args(["selectors", "list"])
        .assert_success()
        .stdout_eq(str![[r#"
Counter

╭----------+----------------------+--------------------------------------------------------------------╮
| Type     | Signature            | Selector                                                           |
+======================================================================================================+
| Function | increment()          | 0xd09de08a                                                         |
|----------+----------------------+--------------------------------------------------------------------|
| Function | number()             | 0x8381f58a                                                         |
|----------+----------------------+--------------------------------------------------------------------|
| Function | setNumber(uint256)   | 0x3fb5c1cb                                                         |
|----------+----------------------+--------------------------------------------------------------------|
| Event    | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 |
|----------+----------------------+--------------------------------------------------------------------|
| Error    | IncrementError()     | 0x46544c04                                                         |
╰----------+----------------------+--------------------------------------------------------------------╯

CounterV2

╭----------+----------------------+------------╮
| Type     | Signature            | Selector   |
+==============================================+
| Function | incrementV2()        | 0x49365a69 |
|----------+----------------------+------------|
| Function | number()             | 0x8381f58a |
|----------+----------------------+------------|
| Function | setNumberV2(uint256) | 0xb525b68c |
╰----------+----------------------+------------╯

"#]])
        .stderr_eq(str![[r#"
Listing selectors for contracts in the project...

"#]]);

    cmd.forge_fuse()
        .args(["selectors", "list", "--no-group"])
        .assert_success()
        .stdout_eq(str![[r#"

╭----------+----------------------+--------------------------------------------------------------------+-----------╮
| Type     | Signature            | Selector                                                           | Contract  |
+==================================================================================================================+
| Function | increment()          | 0xd09de08a                                                         | Counter   |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Function | number()             | 0x8381f58a                                                         | Counter   |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Function | setNumber(uint256)   | 0x3fb5c1cb                                                         | Counter   |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Event    | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | Counter   |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Error    | IncrementError()     | 0x46544c04                                                         | Counter   |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Function | incrementV2()        | 0x49365a69                                                         | CounterV2 |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Function | number()             | 0x8381f58a                                                         | CounterV2 |
|----------+----------------------+--------------------------------------------------------------------+-----------|
| Function | setNumberV2(uint256) | 0xb525b68c                                                         | CounterV2 |
╰----------+----------------------+--------------------------------------------------------------------+-----------╯

"#]])
        .stderr_eq(str![[r#"
Listing selectors for contracts in the project...

"#]]);
});

forgetest_init!(selectors_list_cmd_md, |prj, cmd| {
    prj.add_source(
        "Counter.sol",
        r"
contract Counter {
    uint256 public number;
    event Incremented(uint256 newNumber);
    error IncrementError();

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}
   ",
    );

    prj.add_source(
        "CounterV2.sol",
        r"
contract CounterV2 {
    uint256 public number;

    function setNumberV2(uint256 newNumber) public {
        number = newNumber;
    }

    function incrementV2() public {
        number++;
    }
}
   ",
    );

    cmd.args(["selectors", "list", "--md"])
        .assert_success()
        .stdout_eq(str![[r#"
Counter

| Type     | Signature            | Selector                                                           |
|----------|----------------------|--------------------------------------------------------------------|
| Function | increment()          | 0xd09de08a                                                         |
| Function | number()             | 0x8381f58a                                                         |
| Function | setNumber(uint256)   | 0x3fb5c1cb                                                         |
| Event    | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 |
| Error    | IncrementError()     | 0x46544c04                                                         |

CounterV2

| Type     | Signature            | Selector   |
|----------|----------------------|------------|
| Function | incrementV2()        | 0x49365a69 |
| Function | number()             | 0x8381f58a |
| Function | setNumberV2(uint256) | 0xb525b68c |

"#]])
        .stderr_eq(str![[r#"
Listing selectors for contracts in the project...

"#]]);

    cmd.forge_fuse()
        .args(["selectors", "list", "--no-group", "--md"])
        .assert_success()
        .stdout_eq(str![[r#"

| Type     | Signature            | Selector                                                           | Contract  |
|----------|----------------------|--------------------------------------------------------------------|-----------|
| Function | increment()          | 0xd09de08a                                                         | Counter   |
| Function | number()             | 0x8381f58a                                                         | Counter   |
| Function | setNumber(uint256)   | 0x3fb5c1cb                                                         | Counter   |
| Event    | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | Counter   |
| Error    | IncrementError()     | 0x46544c04                                                         | Counter   |
| Function | incrementV2()        | 0x49365a69                                                         | CounterV2 |
| Function | number()             | 0x8381f58a                                                         | CounterV2 |
| Function | setNumberV2(uint256) | 0xb525b68c                                                         | CounterV2 |

"#]])
        .stderr_eq(str![[r#"
Listing selectors for contracts in the project...

"#]]);
});

// tests `interceptInitcode` function
forgetest_init!(intercept_initcode, |prj, cmd| {
    prj.insert_ds_test();
    prj.insert_vm();
    prj.clear();

    prj.add_source(
        "InterceptInitcode.t.sol",
        r#"
import {Vm} from "./Vm.sol";
import {DSTest} from "./test.sol";

contract SimpleContract {
    uint256 public value;
    constructor(uint256 _value) {
        value = _value;
    }
}

contract InterceptInitcodeTest is DSTest {
    Vm vm = Vm(HEVM_ADDRESS);

    function testInterceptRegularCreate() public {
        // Set up interception
        vm.interceptInitcode();

        // Try to create a contract - this should revert with the initcode
        bytes memory initcode;
        try new SimpleContract(42) {
            assert(false);
        } catch (bytes memory interceptedInitcode) {
            initcode = interceptedInitcode;
        }

        // Verify the initcode contains the constructor argument
        assertTrue(initcode.length > 0, "initcode should not be empty");

        // The constructor argument is encoded as a 32-byte value at the end of the initcode
        // We need to convert the last 32 bytes to uint256
        uint256 value;
        assembly {
            value := mload(add(add(initcode, 0x20), sub(mload(initcode), 32)))
        }
        assertEq(value, 42, "initcode should contain constructor arg");
    }

    function testInterceptCreate2() public {
        // Set up interception
        vm.interceptInitcode();

        // Try to create a contract with CREATE2 - this should revert with the initcode
        bytes memory initcode;
        try new SimpleContract(1337) {
            assert(false);
        } catch (bytes memory interceptedInitcode) {
            initcode = interceptedInitcode;
        }

        // Verify the initcode contains the constructor argument
        assertTrue(initcode.length > 0, "initcode should not be empty");

        // The constructor argument is encoded as a 32-byte value at the end of the initcode
        uint256 value;
        assembly {
            value := mload(add(add(initcode, 0x20), sub(mload(initcode), 32)))
        }
        assertEq(value, 1337, "initcode should contain constructor arg");
    }

    function testInterceptMultiple() public {
        // First interception
        vm.interceptInitcode();
        bytes memory initcode1;
        try new SimpleContract(1) {
            assert(false);
        } catch (bytes memory interceptedInitcode) {
            initcode1 = interceptedInitcode;
        }

        // Second interception
        vm.interceptInitcode();
        bytes memory initcode2;
        try new SimpleContract(2) {
            assert(false);
        } catch (bytes memory interceptedInitcode) {
            initcode2 = interceptedInitcode;
        }

        // Verify different initcodes
        assertTrue(initcode1.length > 0, "first initcode should not be empty");
        assertTrue(initcode2.length > 0, "second initcode should not be empty");

        // Extract constructor arguments from both initcodes
        uint256 value1;
        uint256 value2;
        assembly {
            value1 := mload(add(add(initcode1, 0x20), sub(mload(initcode1), 32)))
            value2 := mload(add(add(initcode2, 0x20), sub(mload(initcode2), 32)))
        }
        assertEq(value1, 1, "first initcode should contain first arg");
        assertEq(value2, 2, "second initcode should contain second arg");
    }
}
     "#,
    );
    cmd.args(["test", "-vvvvv"]).assert_success();
});

// <https://github.com/foundry-rs/foundry/issues/10296>
// <https://github.com/foundry-rs/foundry/issues/10552>
forgetest_init!(should_preserve_fork_state_setup, |prj, cmd| {
    prj.add_test(
        "Counter.t.sol",
        &r#"
import "forge-std/Test.sol";
import {StdChains} from "forge-std/StdChains.sol";

contract CounterTest is Test {
    struct Domain {
        StdChains.Chain chain;
        uint256 forkId;
    }

    struct Bridge {
        Domain source;
        Domain destination;
        uint256 someVal;
    }

    struct SomeStruct {
        Domain domain;
        Bridge[] bridges;
    }

    mapping(uint256 => SomeStruct) internal data;

    function setUp() public {
        // Temporary workaround for `https://eth.llamarpc.com/` being down
        setChain("mainnet", ChainData({
            name: "mainnet",
            rpcUrl: "<url>",
            chainId: 1
        }));

        StdChains.Chain memory chain1 = getChain("mainnet");
        StdChains.Chain memory chain2 = getChain("base");
        Domain memory domain1 = Domain(chain1, vm.createFork(chain1.rpcUrl, 22253716));
        Domain memory domain2 = Domain(chain2, vm.createFork(chain2.rpcUrl, 28839981));
        data[1].domain = domain1;
        data[2].domain = domain2;

        vm.selectFork(domain1.forkId);

        data[2].bridges.push(Bridge(domain1, domain2, 123));
        vm.selectFork(data[2].domain.forkId);
        vm.selectFork(data[1].domain.forkId);
        data[2].bridges.push(Bridge(domain1, domain2, 456));

        assertEq(data[2].bridges.length, 2);
    }

    function test_assert_storage() public {
        vm.selectFork(data[2].domain.forkId);
        assertEq(data[2].bridges.length, 2);
    }

    function test_modify_and_storage() public {
        data[3].domain = Domain(getChain("base"), vm.createFork(getChain("base").rpcUrl, 28839981));
        data[3].bridges.push(Bridge(data[1].domain, data[2].domain, 123));
        data[3].bridges.push(Bridge(data[1].domain, data[2].domain, 456));

        vm.selectFork(data[2].domain.forkId);
        assertEq(data[3].bridges.length, 2);
    }
}
    "#
        .replace("<url>", &rpc::next_http_archive_rpc_url()),
    );

    cmd.args(["test", "--mc", "CounterTest"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] test_assert_storage() ([GAS])
[PASS] test_modify_and_storage() ([GAS])
Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/10544>
forgetest_init!(flaky_should_not_panic_on_cool, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "Counter.t.sol",
        r#"
import "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract CounterTest is Test {
    Counter counter = new Counter();

    function testCoolPanic() public {
        address alice = makeAddr("alice");
        vm.deal(alice, 10000 ether);
        counter.setNumber(1);
        vm.cool(address(counter));
        vm.prank(alice);
        payable(address(counter)).transfer(1 ether);
    }
}
    "#,
    );

    cmd.args(["test", "--mc", "CounterTest"]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Counter.t.sol:CounterTest
[FAIL: EvmError: Revert] testCoolPanic() ([GAS])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/Counter.t.sol:CounterTest
[FAIL: EvmError: Revert] testCoolPanic() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

forgetest_init!(detailed_revert_when_calling_non_contract_address, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "NonContractCallRevertTest.t.sol",
        r#"
import "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

interface ICounter {
    function increment() external;
    function number() external returns (uint256);
    function random() external returns (uint256);
}

contract NonContractCallRevertTest is Test {
    Counter public counter;
    address constant ADDRESS = 0xdEADBEeF00000000000000000000000000000000;

    function setUp() public {
        counter = new Counter();
        counter.setNumber(1);
    }

    function test_non_supported_selector_call_failure() public {
        console.log("test non supported fn selector call failure");
        ICounter(address(counter)).random();
    }

    function test_non_contract_call_failure() public {
        console.log("test non contract call failure");
        ICounter(ADDRESS).number();
    }

    function test_non_contract_void_call_failure() public {
        console.log("test non contract (void) call failure");
        ICounter(ADDRESS).increment();
    }

    function test_revert_data_exact_diagnostic() public {
        vm.expectRevert(
            abi.encodePacked("call to non-contract address ", vm.toString(ADDRESS))
        );
        this.call_non_contract(ADDRESS);
    }

    function call_non_contract(address target) external {
        ICounter(target).number();
    }
}
     "#,
    );

    cmd.args([
        "test",
        "--mc",
        "NonContractCallRevertTest",
        "--no-match-test",
        "test_revert_data_",
        "-vvvvv",
        "--no-dynamic-test-linking",
    ])
        .assert_failure()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 3 tests for test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest
[FAIL: EvmError: Revert] test_non_contract_call_failure() ([GAS])
Logs:
  test non contract call failure

Traces:
  [238803] NonContractCallRevertTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [43696] Counter::setNumber(1)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [27488] NonContractCallRevertTest::test_non_contract_call_failure()
    ├─ [0] console::log("test non contract call failure") [staticcall]
    │   └─ ← [Stop]
    ├─ [21160] 0xdEADBEeF00000000000000000000000000000000::number()
    │   └─ ← [Stop]
    └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000

Backtrace:
  at NonContractCallRevertTest.test_non_contract_call_failure

[FAIL: EvmError: Revert] test_non_contract_void_call_failure() ([GAS])
Logs:
  test non contract (void) call failure

Traces:
  [238803] NonContractCallRevertTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [43696] Counter::setNumber(1)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [6303] NonContractCallRevertTest::test_non_contract_void_call_failure()
    ├─ [0] console::log("test non contract (void) call failure") [staticcall]
    │   └─ ← [Stop]
    └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000

Backtrace:
  at NonContractCallRevertTest.test_non_contract_void_call_failure (test/NonContractCallRevertTest.t.sol:[..]:[..])

[FAIL: EvmError: Revert] test_non_supported_selector_call_failure() ([GAS])
Logs:
  test non supported fn selector call failure

Traces:
  [238803] NonContractCallRevertTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [43696] Counter::setNumber(1)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [29684] NonContractCallRevertTest::test_non_supported_selector_call_failure()
    ├─ [0] console::log("test non supported fn selector call failure") [staticcall]
    │   └─ ← [Stop]
    ├─ [21209] Counter::random()
    │   └─ ← [Revert] unrecognized function selector 0x5ec01e4d for contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, which has no fallback function.
    └─ ← [Revert] EvmError: Revert

Backtrace:
  at Counter.random (src/Counter.sol:[..]:[..])
  at NonContractCallRevertTest.test_non_supported_selector_call_failure (test/NonContractCallRevertTest.t.sol:[..]:[..])

Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 3 failed, 0 skipped (3 total tests)

Failing tests:
Encountered 3 failing tests in test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest
[FAIL: EvmError: Revert] test_non_contract_call_failure() ([GAS])
[FAIL: EvmError: Revert] test_non_contract_void_call_failure() ([GAS])
[FAIL: EvmError: Revert] test_non_supported_selector_call_failure() ([GAS])

Encountered a total of 3 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 3 failed tests
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);

    cmd.forge_fuse()
        .args([
            "test",
            "--mc",
            "NonContractCallRevertTest",
            "--mt",
            "test_revert_data_exact_diagnostic",
            "-vv",
            "--no-dynamic-test-linking",
        ])
        .assert_failure()
        .stdout_eq(str![[r#"
No files changed, compilation skipped

Ran 1 test for test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest
[FAIL: call reverted as expected, but without data] test_revert_data_exact_diagnostic() ([GAS])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest
[FAIL: call reverted as expected, but without data] test_revert_data_exact_diagnostic() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);

    cmd.forge_fuse()
        .args([
            "test",
            "--mc",
            "NonContractCallRevertTest",
            "--mt",
            "test_revert_data_exact_diagnostic",
            "-vvvv",
            "--no-dynamic-test-linking",
        ])
        .assert_failure()
        .stdout_eq(str![[r#"
No files changed, compilation skipped

Ran 1 test for test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest
[FAIL: call reverted as expected, but without data] test_revert_data_exact_diagnostic() ([GAS])
Traces:
  [238803] NonContractCallRevertTest::setUp()
    ├─ [156801] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 481 bytes of code
    ├─ [43696] Counter::setNumber(1)
    │   └─ ← [Stop]
    └─ ← [Stop]

  [30284] NonContractCallRevertTest::test_revert_data_exact_diagnostic()
    ├─ [0] VM::toString(0xdEADBEeF00000000000000000000000000000000) [staticcall]
    │   └─ ← [Return] "0xdEADBEeF00000000000000000000000000000000"
    ├─ [0] VM::expectRevert(call to non-contract address 0xdEADBEeF00000000000000000000000000000000)
    │   └─ ← [Return]
    ├─ [24558] NonContractCallRevertTest::call_non_contract(0xdEADBEeF00000000000000000000000000000000)
    │   ├─ [0] 0xdEADBEeF00000000000000000000000000000000::number()
    │   │   └─ ← [Stop]
    │   └─ ← [Revert] call to non-contract address 0xdEADBEeF00000000000000000000000000000000
    └─ ← [Revert] call reverted as expected, but without data

Backtrace:
  at NonContractCallRevertTest.call_non_contract
  at NonContractCallRevertTest.test_revert_data_exact_diagnostic

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest
[FAIL: call reverted as expected, but without data] test_revert_data_exact_diagnostic() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);

    let output = cmd
        .forge_fuse()
        .args([
            "test",
            "--mc",
            "NonContractCallRevertTest",
            "--mt",
            "test_revert_data_exact_diagnostic",
            "-vvvv",
            "--json",
            "--no-dynamic-test-linking",
        ])
        .assert_failure();
    let json: serde_json::Value = serde_json::from_slice(&output.get_output().stdout).unwrap();
    let result = &json.as_object().unwrap().values().next().unwrap()["test_results"]["test_revert_data_exact_diagnostic()"];
    let traces = result["traces"].as_array().unwrap();
    assert!(traces.iter().all(|trace| trace[1].get("diagnostics").is_none()));

    let diagnostic_nodes = traces
        .iter()
        .flat_map(|trace| trace[1]["arena"].as_array().unwrap())
        .filter(|node| {
            node["trace"]["decoded"]["return_data"]
                == "call to non-contract address 0xdEADBEeF00000000000000000000000000000000"
        })
        .collect::<Vec<_>>();
    assert_eq!(diagnostic_nodes.len(), 1);
    assert_eq!(diagnostic_nodes[0]["trace"]["output"], "0x");
});

forgetest_init!(detailed_revert_when_delegatecalling_unlinked_library, |prj, cmd| {
    prj.add_test(
        "NonContractDelegateCallRevertTest.t.sol",
        r#"
import "forge-std/Test.sol";

library TestLibrary {
    function foo(uint256 a) public pure returns (uint256) {
        return a * 2;
    }
}

contract LibraryCaller {
    address public lib;

    constructor(address _lib) {
        lib = _lib;
    }

    function foobar(uint256 val) public returns (uint256) {
        (bool success, bytes memory data) = lib.delegatecall(
            abi.encodeWithSelector(TestLibrary.foo.selector, val)
        );

        assert(success);
        return abi.decode(data, (uint256));
    }
}

contract NonContractDelegateCallRevertTest is Test {
    function test_unlinked_library_call_failure() public {
        console.log("Test: Simulating call to unlinked library");
        LibraryCaller caller = new LibraryCaller(0xdEADBEeF00000000000000000000000000000000);

        caller.foobar(10);
    }
}
     "#,
    );

    cmd.args(["test", "--mc", "NonContractDelegateCallRevertTest", "-vvvvv"])
        .assert_failure()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/NonContractDelegateCallRevertTest.t.sol:NonContractDelegateCallRevertTest
[FAIL: EvmError: Revert] test_unlinked_library_call_failure() ([GAS])
Logs:
  Test: Simulating call to unlinked library

Traces:
  [350673] NonContractDelegateCallRevertTest::test_unlinked_library_call_failure()
    ├─ [0] console::log("Test: Simulating call to unlinked library") [staticcall]
    │   └─ ← [Stop]
    ├─ [286930] → new LibraryCaller@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   ├─  storage changes:
    │   │   @ 0: 0 → 0x000000000000000000000000deadbeef00000000000000000000000000000000
    │   └─ ← [Return] 960 bytes of code
    ├─ [27100] LibraryCaller::foobar(10)
    │   ├─ [0] 0xdEADBEeF00000000000000000000000000000000::foo(10) [delegatecall]
    │   │   └─ ← [Stop]
    │   └─ ← [Revert] delegatecall to non-contract address 0xdEADBEeF00000000000000000000000000000000 (usually an unliked library)
    └─ ← [Revert] EvmError: Revert

Backtrace:
  at LibraryCaller.foobar
  at NonContractDelegateCallRevertTest.test_unlinked_library_call_failure (test/NonContractDelegateCallRevertTest.t.sol:[..]:[..])

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/NonContractDelegateCallRevertTest.t.sol:NonContractDelegateCallRevertTest
[FAIL: EvmError: Revert] test_unlinked_library_call_failure() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// This test is a copy of `error_event_decode_with_cache` in cast/tests/cli/selectors.rs
// but it uses `forge build` to check that the project selectors are cached by default.
forgetest_init!(flaky_build_with_selectors_cache, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_source(
        "LocalProjectContract",
        r#"
contract ContractWithCustomError {
    error AnotherValueTooHigh(uint256, address);
    event MyUniqueEventWithinLocalProject(uint256 a, address b);
}
   "#,
    );
    // Build and cache project selectors.
    cmd.forge_fuse().args(["build", "--force"]).assert_success();

    // Assert cast can decode custom error with local cache.
    cmd.cast_fuse()
        .args(["decode-error", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"])
        .assert_success()
        .stdout_eq(str![[r#"
AnotherValueTooHigh(uint256,address)
101
0x0000000000000000000000000000000000D0004F

"#]]);
    // Assert cast can decode event with local cache.
    cmd.cast_fuse()
        .args(["decode-event", "0xbd3699995dcc867b64dbb607be2c33be38df9134bef1178df13bfb9446e73104000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"])
        .assert_success()
        .stdout_eq(str![[r#"
MyUniqueEventWithinLocalProject(uint256,address)
78
0x00000000000000000000000000000DD00000004e

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/11021>
forgetest_init!(revm_27_prank_bug_fix, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "PrankBug.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract PrankTest is Test {
    Counter public counter;

    function setUp() public {
        vm.startPrank(address(0x123));
        counter = new Counter();
        vm.stopPrank();
    }

    function test_Increment() public {
        vm.startPrank(address(0x123));
        counter = new Counter();
        vm.stopPrank();

        counter.increment();
        assertEq(counter.number(), 1);
    }
}
"#,
    );

    cmd.args(["test", "--mc", "PrankTest", "-vvvvv", "--no-dynamic-test-linking"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/PrankBug.t.sol:PrankTest
[PASS] test_Increment() ([GAS])
Traces:
  [..] PrankTest::setUp()
    ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123)
    │   └─ ← [Return]
    ├─ [..] → new Counter@0x6cdBd1b486b8FBD4140e8cd6daAED05bE13eD914
    │   └─ ← [Return] 481 bytes of code
    ├─ [0] VM::stopPrank()
    │   └─ ← [Return]
    └─ ← [Stop]

  [..] PrankTest::test_Increment()
    ├─ [0] VM::startPrank(0x0000000000000000000000000000000000000123)
    │   └─ ← [Return]
    ├─ [..] → new Counter@0xc4B957Cd61beB9b9afD76204b30683EDAaaB51Ec
    │   └─ ← [Return] 481 bytes of code
    ├─ [0] VM::stopPrank()
    │   └─ ← [Return]
    ├─ [..] Counter::increment()
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    ├─ [..] Counter::number() [staticcall]
    │   └─ ← [Return] 1
    ├─  storage changes:
    │   @ 31: 0x00000000000000000000006cdbd1b486b8fbd4140e8cd6daaed05be13ed91401 → 0x0000000000000000000000c4b957cd61beb9b9afd76204b30683edaaab51ec01
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// tests proper reverts in fork mode for contracts with non-existent linked libraries.
// <https://github.com/foundry-rs/foundry/issues/11185>
forgetest_init!(can_fork_test_with_non_existent_linked_library, |prj, cmd| {
    prj.update_config(|config| {
        config.libraries =
            vec!["src/Counter.sol:LibCounter:0x530008d2b058137d9c475b1b7d83984f1fcf1dd0".into()];
    });
    prj.add_source(
        "Counter.sol",
        r"
library LibCounter {
    function dummy() external pure returns (uint) {
        return 1;
    }
}

contract Counter {
    uint256 public number;

    constructor() {
        LibCounter.dummy();
    }

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }

    function dummy() external pure returns (uint) {
        return LibCounter.dummy();
    }
}
   ",
    );

    let endpoint = rpc::next_http_archive_rpc_url();

    prj.add_test(
        "Counter.t.sol",
        &r#"
import "forge-std/Test.sol";
import "src/Counter.sol";

contract CounterTest is Test {
    function test_select_fork() public {
        vm.createSelectFork("<url>");
        new Counter();
    }

    function test_roll_fork() public {
        vm.rollFork(block.number - 100);
        new Counter();
    }
}
   "#
        .replace("<url>", &endpoint),
    );

    cmd.args(["test", "--fork-url", &endpoint]).assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/Counter.t.sol:CounterTest
[FAIL: EvmError: Revert] test_roll_fork() ([GAS])
[FAIL: Contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f does not exist and is not marked as persistent, see `vm.makePersistent()`] test_select_fork() ([GAS])
Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests)

Failing tests:
Encountered 2 failing tests in test/Counter.t.sol:CounterTest
[FAIL: EvmError: Revert] test_roll_fork() ([GAS])
[FAIL: Contract 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f does not exist and is not marked as persistent, see `vm.makePersistent()`] test_select_fork() ([GAS])

Encountered a total of 2 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 2 failed tests
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/11632>
forgetest_init!(invariant_consistent_output, |prj, cmd| {
    prj.update_config(|config| {
        config.fuzz.seed = Some(U256::from(100u32));
        config.invariant.runs = 10;
        config.invariant.depth = 100;
        config.invariant.show_metrics = false;
    });
    prj.add_test(
        "InvariantOutputTest.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract InvariantOutputTest is Test {
    uint256 count;

    function setCond(uint256 cond) public {
        if (cond > type(uint256).max / 2) {
            count++;
        }
    }

    function setUp() public {
        targetContract(address(this));
    }

    function invariant_check_count() public view {
        require(count < 2, "failed invariant");
    }
}
   "#,
    );

    cmd.args(["test", "--mt", "invariant_check_count", "--color", "always"])
        .assert_failure()
        .stdout_eq(file!["../../fixtures/invariant_traces.svg": TermSvg]);
});

forgetest_init!(memory_limit, |prj, cmd| {
    prj.wipe_contracts();
    prj.update_config(|config| {
        config.memory_limit = 500 * 32;
    });
    prj.add_test(
        "MemoryLimit.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Memory {
    function allocate(uint256 n) external pure returns (uint256[] memory) {
        return new uint256[](n);
    }
}

contract MemoryLimitTest is Test {
    Memory public m = new Memory();

    function test_inBounds() public {
        m.allocate(100);
    }

    function test_oom() public {
        m.allocate(1000);
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
Ran 2 tests for test/MemoryLimit.t.sol:MemoryLimitTest
[PASS] test_inBounds() ([GAS])
[FAIL: EvmError: Revert] test_oom() ([GAS])
Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests)

Failing tests:
Encountered 1 failing test in test/MemoryLimit.t.sol:MemoryLimitTest
[FAIL: EvmError: Revert] test_oom() ([GAS])

Encountered a total of 1 failing tests, 1 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

forgetest_init!(zero_runs, |prj, cmd| {
    prj.wipe_contracts();
    prj.add_test(
        "ZeroRuns.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Handler is Test {
    function doSomething(uint256 param) public {
        revert("unreachable");
    }
}

contract ZeroRuns is Test {
    Handler handler = new Handler();

    /// forge-config: default.fuzz.runs = 0
    function test_fuzzZeroRuns(uint256 x) public {
        revert("unreachable");
    }

    /// forge-config: default.invariant.runs = 0
    function invariant_zeroRuns() public {}

    /// forge-config: default.invariant.depth = 0
    function invariant_zeroDepth() public {}
}
"#,
    );

    cmd.args(["test"]).assert_success().stdout_eq(str![[r#"
...
Ran 3 tests for test/ZeroRuns.t.sol:ZeroRuns
[PASS] invariant_zeroDepth() (runs: 256, calls: 0, reverts: 0)
[PASS] invariant_zeroRuns() (runs: 0, calls: 0, reverts: 0)
[PASS] test_fuzzZeroRuns(uint256) (runs: 0, [AVG_GAS])
Suite result: ok. 3 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
});
