//! Contains various tests for checking forge's commands

use crate::constants::*;
use foundry_compilers::artifacts::{ConfigurableContractArtifact, Metadata, remappings::Remapping};
use foundry_config::{
    BasicConfig, Chain, Config, DenyLevel, FuzzConfig, InvariantConfig, SolidityErrorCode,
    parse_with_profile,
};
use foundry_test_utils::{
    foundry_compilers::PathStyle,
    rpc::next_etherscan_api_key,
    snapbox::IntoData,
    util::{OutputExt, read_string},
};
use std::{
    fs,
    path::Path,
    process::{Command, Stdio},
    str::FromStr,
};

// tests `--help` is printed to std out
forgetest!(print_help, |_prj, cmd| {
    cmd.arg("--help").assert_success().stdout_eq(str![[r#"
Build, test, fuzz, debug and deploy Solidity contracts

Usage: forge[..] <COMMAND>

Commands:
...

Options:
  -h, --help
          Print help (see a summary with '-h')

  -j, --threads <THREADS>
          Number of threads to use. Specifying 0 defaults to the number of logical cores
          
          [alias: --jobs]

      --profile <PROFILE>
          The configuration profile to use

  -V, --version
          Print version

Display options:
      --color <COLOR>
          The color of the log messages

          Possible values:
          - auto:   Intelligently guess whether to use color output (default)
          - always: Force color output
          - never:  Force disable color output

      --json
          Format log messages as JSON

      --md
          Format log messages as Markdown

  -q, --quiet
          Do not print log messages

  -v, --verbosity...
          Verbosity level of the log messages.
          
          Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv).
          
          Depending on the context the verbosity levels have different meanings.
          
          For example, the verbosity levels of the EVM are:
          - 2 (-vv): Print logs for all tests.
          - 3 (-vvv): Print execution traces for failing tests.
          - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests.
          - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes
          and
            backtraces with line numbers.

Find more information in the book: https://getfoundry.sh/forge/overview

"#]]);
});

// checks that `clean` can be invoked even if out and cache don't exist
forgetest!(can_clean_non_existing, |prj, cmd| {
    cmd.arg("clean");
    cmd.assert_empty_stdout();
    prj.assert_cleaned();
});

// checks that `clean` doesn't output warnings
forgetest_init!(can_clean_without_warnings, |prj, cmd| {
    prj.add_source(
        "Simple.sol",
        r#"
pragma solidity ^0.8.5;

contract Simple {
    uint public value = 42;
}
"#,
    );

    prj.create_file(
        "foundry.toml",
        r#"
[default]
evm_version = "cancun"
solc = "0.8.5"
"#,
    );
    // `forge build` warns
    cmd.forge_fuse().arg("build").assert_success().stderr_eq(str![[r#"
Warning: Found unknown config section in foundry.toml: [default]
This notation for profiles has been deprecated and may result in the profile not being registered in future versions.
Please use [profile.default] instead or run `forge config --fix`.
note[could-be-constant]: state variable could be declared constant
  [FILE]:6:17
  │
6 │     uint public value = 42;
  │                 ━━━━━
  │
  ╰ help: https://getfoundry.sh/forge/linting/could-be-constant


"#]]);
    // `forge clear` should not warn
    cmd.forge_fuse().arg("clean").assert_success().stderr_eq(str![[r#"

"#]]);
});

// checks that `cache ls` can be invoked and displays the foundry cache
forgetest!(
    #[ignore]
    can_cache_ls,
    |_prj, cmd| {
        let chain = Chain::mainnet();
        let block1 = 100;
        let block2 = 101;

        let block1_cache_dir = Config::foundry_block_cache_dir(chain, block1).unwrap();
        let block1_file = Config::foundry_block_cache_file(chain, block1).unwrap();
        let block2_cache_dir = Config::foundry_block_cache_dir(chain, block2).unwrap();
        let block2_file = Config::foundry_block_cache_file(chain, block2).unwrap();
        let etherscan_cache_dir = Config::foundry_etherscan_chain_cache_dir(chain).unwrap();
        fs::create_dir_all(block1_cache_dir).unwrap();
        fs::write(block1_file, "{}").unwrap();
        fs::create_dir_all(block2_cache_dir).unwrap();
        fs::write(block2_file, "{}").unwrap();
        fs::create_dir_all(etherscan_cache_dir).unwrap();

        let output = cmd.args(["cache", "ls"]).assert_success().get_output().stderr_lossy();
        let output_lines = output.split('\n').collect::<Vec<_>>();
        println!("{output}");

        assert_eq!(output_lines.len(), 6);
        assert!(output_lines[0].starts_with("-️ mainnet ("));
        assert!(output_lines[1].starts_with("\t-️ Block Explorer ("));
        assert_eq!(output_lines[2], "");
        assert!(output_lines[3].starts_with("\t-️ Block 100 ("));
        assert!(output_lines[4].starts_with("\t-️ Block 101 ("));
        assert_eq!(output_lines[5], "");

        Config::clean_foundry_cache().unwrap();
    }
);

// checks that `cache clean` can be invoked and cleans the foundry cache
// this test is not isolated and modifies ~ so it is ignored
forgetest!(
    #[ignore]
    can_cache_clean,
    |_prj, cmd| {
        let cache_dir = Config::foundry_cache_dir().unwrap();
        let path = cache_dir.as_path();
        fs::create_dir_all(path).unwrap();
        cmd.args(["cache", "clean"]);
        cmd.assert_empty_stdout();

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

// checks that `cache clean --etherscan` can be invoked and only cleans the foundry etherscan cache
// this test is not isolated and modifies ~ so it is ignored
forgetest!(
    #[ignore]
    can_cache_clean_etherscan,
    |_prj, cmd| {
        let cache_dir = Config::foundry_cache_dir().unwrap();
        let etherscan_cache_dir = Config::foundry_etherscan_cache_dir().unwrap();
        let path = cache_dir.as_path();
        let etherscan_path = etherscan_cache_dir.as_path();
        fs::create_dir_all(etherscan_path).unwrap();
        cmd.args(["cache", "clean", "--etherscan"]);
        cmd.assert_empty_stdout();

        assert!(path.exists());
        assert!(!etherscan_path.exists());

        Config::clean_foundry_cache().unwrap();
    }
);

// checks that `cache clean all --etherscan` can be invoked and only cleans the foundry etherscan
// cache. This test is not isolated and modifies ~ so it is ignored
forgetest!(
    #[ignore]
    can_cache_clean_all_etherscan,
    |_prj, cmd| {
        let rpc_cache_dir = Config::foundry_rpc_cache_dir().unwrap();
        let etherscan_cache_dir = Config::foundry_etherscan_cache_dir().unwrap();
        let rpc_path = rpc_cache_dir.as_path();
        let etherscan_path = etherscan_cache_dir.as_path();
        fs::create_dir_all(rpc_path).unwrap();
        fs::create_dir_all(etherscan_path).unwrap();
        cmd.args(["cache", "clean", "all", "--etherscan"]);
        cmd.assert_empty_stdout();

        assert!(rpc_path.exists());
        assert!(!etherscan_path.exists());

        Config::clean_foundry_cache().unwrap();
    }
);

// checks that `cache clean <chain>` can be invoked and cleans the chain cache
// this test is not isolated and modifies ~ so it is ignored
forgetest!(
    #[ignore]
    can_cache_clean_chain,
    |_prj, cmd| {
        let chain = Chain::mainnet();
        let cache_dir = Config::foundry_chain_cache_dir(chain).unwrap();
        let etherscan_cache_dir = Config::foundry_etherscan_chain_cache_dir(chain).unwrap();
        let path = cache_dir.as_path();
        let etherscan_path = etherscan_cache_dir.as_path();
        fs::create_dir_all(path).unwrap();
        fs::create_dir_all(etherscan_path).unwrap();
        cmd.args(["cache", "clean", "mainnet"]);
        cmd.assert_empty_stdout();

        assert!(!path.exists());
        assert!(!etherscan_path.exists());

        Config::clean_foundry_cache().unwrap();
    }
);

// checks that `cache clean <chain> --blocks 100,101` can be invoked and cleans the chain block
// caches this test is not isolated and modifies ~ so it is ignored
forgetest!(
    #[ignore]
    can_cache_clean_blocks,
    |_prj, cmd| {
        let chain = Chain::mainnet();
        let block1 = 100;
        let block2 = 101;
        let block3 = 102;
        let block1_cache_dir = Config::foundry_block_cache_dir(chain, block1).unwrap();
        let block2_cache_dir = Config::foundry_block_cache_dir(chain, block2).unwrap();
        let block3_cache_dir = Config::foundry_block_cache_dir(chain, block3).unwrap();
        let etherscan_cache_dir = Config::foundry_etherscan_chain_cache_dir(chain).unwrap();
        let block1_path = block1_cache_dir.as_path();
        let block2_path = block2_cache_dir.as_path();
        let block3_path = block3_cache_dir.as_path();
        let etherscan_path = etherscan_cache_dir.as_path();
        fs::create_dir_all(block1_path).unwrap();
        fs::create_dir_all(block2_path).unwrap();
        fs::create_dir_all(block3_path).unwrap();
        fs::create_dir_all(etherscan_path).unwrap();
        cmd.args(["cache", "clean", "mainnet", "--blocks", "100,101"]);
        cmd.assert_empty_stdout();

        assert!(!block1_path.exists());
        assert!(!block2_path.exists());
        assert!(block3_path.exists());
        assert!(etherscan_path.exists());

        Config::clean_foundry_cache().unwrap();
    }
);

// checks that `cache clean <chain> --etherscan` can be invoked and cleans the etherscan chain cache
// this test is not isolated and modifies ~ so it is ignored
forgetest!(
    #[ignore]
    can_cache_clean_chain_etherscan,
    |_prj, cmd| {
        let cache_dir = Config::foundry_chain_cache_dir(Chain::mainnet()).unwrap();
        let etherscan_cache_dir =
            Config::foundry_etherscan_chain_cache_dir(Chain::mainnet()).unwrap();
        let path = cache_dir.as_path();
        let etherscan_path = etherscan_cache_dir.as_path();
        fs::create_dir_all(path).unwrap();
        fs::create_dir_all(etherscan_path).unwrap();
        cmd.args(["cache", "clean", "mainnet", "--etherscan"]);
        cmd.assert_empty_stdout();

        assert!(path.exists());
        assert!(!etherscan_path.exists());

        Config::clean_foundry_cache().unwrap();
    }
);

// checks that init works
forgetest!(can_init_repo_with_config, |prj, cmd| {
    let foundry_toml = prj.root().join(Config::FILE_NAME);
    assert!(!foundry_toml.exists());

    cmd.args(["init", "--force"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let s = read_string(&foundry_toml);
    let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1;
});

// Checks that a forge project fails to initialise if dir is already git repo and dirty
forgetest!(can_detect_dirty_git_status_on_init, |prj, cmd| {
    prj.wipe();

    // initialize new git repo
    cmd.git_init();

    std::fs::write(prj.root().join("untracked.text"), "untracked").unwrap();

    // create nested dir and execute init in nested dir
    let nested = prj.root().join("nested");
    fs::create_dir_all(&nested).unwrap();

    cmd.current_dir(&nested);
    cmd.args(["init", "--commit"]).assert_failure().stderr_eq(str![[r#"
Error: The target directory is a part of or on its own an already initialized git repository,
and it requires clean working and staging areas, including no untracked files.

Check the current git repository's status with `git status`.
Then, you can track files with `git add ...` and then commit them with `git commit`,
ignore them in the `.gitignore` file.

"#]]);

    // ensure nothing was emitted, dir is empty
    assert!(!nested.read_dir().map(|mut i| i.next().is_some()).unwrap_or_default());
});

// Checks that a forge project can be initialized without creating a git repository
forgetest!(can_init_no_git, |prj, cmd| {
    prj.wipe();

    cmd.arg("init").arg(prj.root()).arg("--no-git").assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );
    prj.assert_config_exists();

    assert!(!prj.root().join(".git").exists());
    assert!(prj.root().join("lib/forge-std").exists());
    assert!(!prj.root().join("lib/forge-std/.git").exists());
});

// Checks that `--no-commit` is accepted as a noop backwards-compatibility flag
forgetest!(can_init_with_no_commit, |prj, cmd| {
    prj.wipe();

    cmd.arg("init")
        .arg(prj.root())
        .arg("--no-commit")
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]]);
    prj.assert_config_exists();
});

// Checks that quiet mode does not print anything
forgetest!(can_init_quiet, |prj, cmd| {
    prj.wipe();

    cmd.arg("init").arg(prj.root()).arg("-q").assert_empty_stdout();
});

// `forge init foobar` works with dir argument
forgetest!(can_init_with_dir, |prj, cmd| {
    prj.create_file("README.md", "non-empty dir");
    cmd.args(["init", "foobar"]);

    cmd.assert_success();
    assert!(prj.root().join("foobar").exists());
});

// `forge init foobar --template [template]` works with dir argument
forgetest!(can_init_with_dir_and_template, |prj, cmd| {
    cmd.args(["init", "foobar", "--template", "foundry-rs/forge-template"])
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..] from https://github.com/foundry-rs/forge-template...
...
    Initialized forge project

"#]]);

    assert!(prj.root().join("foobar/.git").exists());
    assert!(prj.root().join("foobar/foundry.toml").exists());
    assert!(prj.root().join("foobar/lib/forge-std").exists());
    // assert that gitmodules were correctly initialized
    assert!(prj.root().join("foobar/.git/modules").exists());
    assert!(prj.root().join("foobar/src").exists());
    assert!(prj.root().join("foobar/test").exists());
});

// `forge init foobar --template [template] --branch [branch]` works with dir argument
forgetest!(can_init_with_dir_and_template_and_branch, |prj, cmd| {
    cmd.args([
        "init",
        "foobar",
        "--template",
        "foundry-rs/forge-template",
        "--branch",
        "test/deployments",
    ])
    .assert_success()
    .stdout_eq(str![""])
    .stderr_eq(str![[r#"
Initializing [..] from https://github.com/foundry-rs/forge-template...
...
    Initialized forge project

"#]]);

    assert!(prj.root().join("foobar/.dapprc").exists());
    assert!(prj.root().join("foobar/lib/ds-test").exists());
    // assert that gitmodules were correctly initialized
    assert!(prj.root().join("foobar/.git/modules").exists());
    assert!(prj.root().join("foobar/src").exists());
    assert!(prj.root().join("foobar/scripts").exists());
});

// `forge init --force` works on non-empty dirs
forgetest!(can_init_non_empty, |prj, cmd| {
    prj.create_file("README.md", "non-empty dir");
    cmd.arg("init").arg(prj.root()).assert_failure().stderr_eq(str![[r#"
Error: Cannot run `init` on a non-empty directory.
Run with the `--force` flag to initialize regardless.

"#]]);

    cmd.arg("--force").assert_success().stdout_eq(str![""]).stderr_eq(str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]]);

    assert!(prj.root().join(".git").exists());
    assert!(prj.root().join("lib/forge-std").exists());
});

// `forge init --force` works on already initialized git repository
forgetest!(can_init_in_empty_repo, |prj, cmd| {
    let root = prj.root();

    // initialize new git repo
    let status = Command::new("git")
        .arg("init")
        .current_dir(root)
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .expect("could not run git init");
    assert!(status.success());
    assert!(root.join(".git").exists());

    cmd.arg("init").arg(root).assert_failure().stderr_eq(str![[r#"
Error: Cannot run `init` on a non-empty directory.
Run with the `--force` flag to initialize regardless.

"#]]);

    cmd.arg("--force").assert_success().stdout_eq(str![""]).stderr_eq(str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]]);

    assert!(root.join("lib/forge-std").exists());
});

// `forge init --force` works on already initialized git repository
forgetest!(can_init_in_non_empty_repo, |prj, cmd| {
    let root = prj.root();

    // initialize new git repo
    let status = Command::new("git")
        .arg("init")
        .current_dir(root)
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .expect("could not run git init");
    assert!(status.success());
    assert!(root.join(".git").exists());

    prj.create_file("README.md", "non-empty dir");
    prj.create_file(".gitignore", "not foundry .gitignore");

    cmd.arg("init").arg(root).assert_failure().stderr_eq(str![[r#"
Error: Cannot run `init` on a non-empty directory.
Run with the `--force` flag to initialize regardless.

"#]]);

    cmd.arg("--force").assert_success().stdout_eq(str![""]).stderr_eq(str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]]);

    assert!(root.join("lib/forge-std").exists());

    // not overwritten
    let gitignore = root.join(".gitignore");
    let gitignore = fs::read_to_string(gitignore).unwrap();
    assert_eq!(gitignore, "not foundry .gitignore");
});

// `forge init --use-parent-git` works on already initialized git repository
forgetest!(can_init_using_parent_repo, |prj, cmd| {
    let root = prj.root();

    // initialize new git repo
    let status = Command::new("git")
        .arg("init")
        .current_dir(root)
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .expect("could not run git init");
    assert!(status.success());
    assert!(root.join(".git").exists());

    prj.create_file("README.md", "non-empty dir");
    prj.create_file(".gitignore", "not foundry .gitignore");

    let folder = "foundry-folder";
    cmd.arg("init")
        .arg(folder)
        .arg("--force")
        .arg("--use-parent-git")
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]]);

    assert!(root.join(folder).join("lib/forge-std").exists());

    // not overwritten
    let gitignore = root.join(".gitignore");
    let gitignore = fs::read_to_string(gitignore).unwrap();
    assert_eq!(gitignore, "not foundry .gitignore");

    // submodules are registered at root
    let gitmodules = root.join(".gitmodules");
    let gitmodules = fs::read_to_string(gitmodules).unwrap();
    assert!(gitmodules.contains(
        "
	path = foundry-folder/lib/forge-std
	url = https://github.com/foundry-rs/forge-std
"
    ));
});

// Checks that remappings.txt and .vscode/settings.json is generated
forgetest!(can_init_vscode, |prj, cmd| {
    prj.wipe();

    cmd.arg("init").arg(prj.root()).arg("--vscode").assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let settings = prj.root().join(".vscode/settings.json");
    assert!(settings.is_file());
    let settings: serde_json::Value = foundry_compilers::utils::read_json_file(&settings).unwrap();
    assert_eq!(
        settings,
        serde_json::json!({
            "solidity.packageDefaultDependenciesContractsDirectory": "src",
            "solidity.packageDefaultDependenciesDirectory": "lib"
        })
    );

    let remappings = prj.root().join("remappings.txt");
    assert!(remappings.is_file());
    let content = std::fs::read_to_string(remappings).unwrap();
    assert_eq!(content, "forge-std/=lib/forge-std/src/",);
});

// checks that forge can init with template
forgetest!(can_init_template, |prj, cmd| {
    prj.wipe();

    cmd.args(["init", "--template", "foundry-rs/forge-template"])
        .arg(prj.root())
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..] from https://github.com/foundry-rs/forge-template...
...
    Initialized forge project

"#]]);

    assert!(prj.root().join(".git").exists());
    assert!(prj.root().join("foundry.toml").exists());
    assert!(prj.root().join("lib/forge-std").exists());
    // assert that gitmodules were correctly initialized
    assert!(prj.root().join(".git/modules").exists());
    assert!(prj.root().join("src").exists());
    assert!(prj.root().join("test").exists());
});

// checks that forge can init with template and branch
forgetest!(can_init_template_with_branch, |prj, cmd| {
    prj.wipe();
    cmd.args(["init", "--template", "foundry-rs/forge-template", "--branch", "test/deployments"])
        .arg(prj.root())
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..] from https://github.com/foundry-rs/forge-template...
...
    Initialized forge project

"#]]);

    assert!(prj.root().join(".git").exists());
    assert!(prj.root().join(".dapprc").exists());
    assert!(prj.root().join("lib/ds-test").exists());
    // assert that gitmodules were correctly initialized
    assert!(prj.root().join(".git/modules").exists());
    assert!(prj.root().join("src").exists());
    assert!(prj.root().join("scripts").exists());
});

// checks that init fails when the provided template doesn't exist
forgetest!(fail_init_nonexistent_template, |prj, cmd| {
    prj.wipe();
    cmd.args(["init", "--template", "a"]).arg(prj.root()).assert_failure().stderr_eq(str![[r#"
Initializing [..] from https://github.com/a...
remote: Not Found
fatal: repository 'https://github.com/a/' not found
Error: git fetch exited with code 128

"#]]);
});

// checks that `forge init --template [template] works by default i.e without committing
forgetest!(can_init_template_with_no_commit, |prj, cmd| {
    prj.wipe();
    cmd.args(["init", "--template", "foundry-rs/forge-template"])
        .arg(prj.root())
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..] from https://github.com/foundry-rs/forge-template...
...
    Initialized forge project

"#]]);

    // show the latest commit message was not changed
    let output = Command::new("git")
        .args(["log", "-1", "--pretty=%s"]) // Get the latest commit message
        .output()
        .expect("Failed to execute git command");

    let commit_message = String::from_utf8_lossy(&output.stdout);
    assert!(
        !commit_message.starts_with("chore: init from foundry-rs/forge-template"),
        "Commit message should not start with 'chore: init from foundry-rs/forge-template'"
    );
});

// checks that clone works
forgetest!(flaky_can_clone, |prj, cmd| {
    prj.wipe();

    let foundry_toml = prj.root().join(Config::FILE_NAME);
    assert!(!foundry_toml.exists());

    cmd.args([
        "clone",
        "--etherscan-api-key",
        next_etherscan_api_key().as_str(),
        "0x044b75f554b886A065b9567891e45c79542d7357",
    ])
    .arg(prj.root())
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]])
    .stderr_eq(str![[r#"
Downloading the source code of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan...
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project
Collecting the creation information of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan...

"#]]);

    let s = read_string(&foundry_toml);
    let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1;
});

// Checks that clone follows the SparkLend USDS proxy to its AToken implementation.
forgetest!(flaky_can_clone_proxy_implementation, |prj, cmd| {
    prj.wipe();

    cmd.args([
        "clone",
        "--etherscan-api-key",
        next_etherscan_api_key().as_str(),
        "--implementation",
        "0xC02aB1A5eaA8d1B114EF786D9bde108cD4364359",
    ])
    .arg(prj.root())
    .assert_success();

    let metadata: serde_json::Value =
        serde_json::from_str(&read_string(prj.root().join(".clone.meta"))).unwrap();
    assert_eq!(metadata["targetContract"], "AToken");
    assert_eq!(metadata["address"], "0x6175ddec3b9b38c88157c10a01ed4a3fa8639cc6");
    assert!(prj.root().join(metadata["path"].as_str().unwrap()).exists());
});

// Checks that `--no-commit` is accepted as a noop backwards-compatibility flag for clone
forgetest!(flaky_can_clone_with_no_commit, |prj, cmd| {
    prj.wipe();

    cmd.args([
        "clone",
        "--etherscan-api-key",
        next_etherscan_api_key().as_str(),
        "--no-commit",
        "0x044b75f554b886A065b9567891e45c79542d7357",
    ])
    .arg(prj.root())
    .assert_success();
});

// Checks that quiet mode does not print anything for clone
forgetest!(flaky_can_clone_quiet, |prj, cmd| {
    prj.wipe();

    cmd.args([
        "clone",
        "--etherscan-api-key",
        next_etherscan_api_key().as_str(),
        "--quiet",
        "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec",
    ])
    .arg(prj.root())
    .assert_empty_stdout();
});

// checks that clone works with sourcify
forgetest!(flaky_can_clone_sourcify, |prj, cmd| {
    prj.wipe();

    let foundry_toml = prj.root().join(Config::FILE_NAME);
    assert!(!foundry_toml.exists());

    cmd.args(["clone", "--source", "sourcify", "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec"])
        .arg(prj.root())
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]])
        .stderr_eq(str![[r#"
Downloading the source code of 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec from Sourcify...
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project
Collecting the creation information of 0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec from Sourcify...

"#]]);

    let s = read_string(&foundry_toml);
    let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1;
});

// checks that clone works with --no-remappings-txt
forgetest!(flaky_can_clone_no_remappings_txt, |prj, cmd| {
    prj.wipe();

    let foundry_toml = prj.root().join(Config::FILE_NAME);
    assert!(!foundry_toml.exists());

    cmd.args([
        "clone",
        "--etherscan-api-key",
        next_etherscan_api_key().as_str(),
        "--no-remappings-txt",
        "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf",
    ])
    .arg(prj.root())
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]])
    .stderr_eq(str![[r#"
Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan...
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project
Collecting the creation information of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan...

"#]]);

    let s = read_string(&foundry_toml);
    let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1;
});

// checks that clone works with --keep-directory-structure
forgetest!(flaky_can_clone_keep_directory_structure, |prj, cmd| {
    prj.wipe();

    let foundry_toml = prj.root().join(Config::FILE_NAME);
    assert!(!foundry_toml.exists());

    let output = cmd
        .forge_fuse()
        .args([
            "clone",
            "--etherscan-api-key",
            next_etherscan_api_key().as_str(),
            "--keep-directory-structure",
            "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf",
        ])
        .arg(prj.root())
        .assert_success()
        .get_output()
        .stdout_lossy();

    if output.contains("502 Bad Gateway") {
        // etherscan nginx proxy issue, skip this test:
        //
        // stdout:
        // Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from
        // Etherscan... 2024-07-05T11:40:11.801765Z ERROR etherscan: Failed to deserialize
        // response: expected value at line 1 column 1 res="<html>\r\n<head><title>502 Bad
        // Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad
        // Gateway</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n"

        eprintln!("Skipping test due to 502 Bad Gateway");
        return;
    }

    let s = read_string(&foundry_toml);
    let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1;
});

// checks that `forge init` works.
forgetest!(can_init_project, |prj, cmd| {
    prj.wipe();

    cmd.args(["init"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(str![[r#"
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]]);

    assert!(prj.root().join("foundry.toml").exists());
    assert!(prj.root().join("lib/forge-std").exists());

    assert!(prj.root().join("src").exists());
    assert!(prj.root().join("src").join("Counter.sol").exists());

    assert!(prj.root().join("test").exists());
    assert!(prj.root().join("test").join("Counter.t.sol").exists());

    assert!(prj.root().join("script").exists());
    assert!(prj.root().join("script").join("Counter.s.sol").exists());

    assert!(prj.root().join(".github").join("workflows").exists());
    assert!(prj.root().join(".github").join("workflows").join("test.yml").exists());
});

// checks that `forge init --vyper` works.
forgetest!(can_init_vyper_project, |prj, cmd| {
    prj.wipe();

    cmd.args(["init", "--vyper"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    assert!(prj.root().join("foundry.toml").exists());
    assert!(prj.root().join("lib/forge-std").exists());

    assert!(prj.root().join("src").exists());
    assert!(prj.root().join("src").join("Counter.vy").exists());
    assert!(prj.root().join("src").join("ICounter.sol").exists());

    assert!(prj.root().join("test").exists());
    assert!(prj.root().join("test").join("Counter.t.sol").exists());

    assert!(prj.root().join("script").exists());
    assert!(prj.root().join("script").join("Counter.s.sol").exists());

    assert!(prj.root().join(".github").join("workflows").exists());
    assert!(prj.root().join(".github").join("workflows").join("test.yml").exists());
});

// checks that `forge init --network tempo` works.
forgetest!(can_init_tempo_project, |prj, cmd| {
    prj.wipe();

    cmd.args(["init", "--network", "tempo"])
        .arg(prj.root())
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..]...
...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
Installing tempo-std in [..] (url: https://github.com/tempoxyz/tempo-std, tag: None)
...
    Installed tempo-std[..]
    Initialized forge project

"#]]);

    assert!(prj.root().join("foundry.toml").exists());

    // Verify foundry.toml contains `network = "tempo"` so subsequent commands auto-detect the
    // network.
    let foundry_toml = std::fs::read_to_string(prj.root().join("foundry.toml")).unwrap();
    assert!(
        foundry_toml.contains("network = \"tempo\""),
        "foundry.toml should contain `network = \"tempo\"`, got:\n{foundry_toml}"
    );
    assert!(
        foundry_toml.contains("[rpc_endpoints]")
            && foundry_toml.contains("tempo = \"https://rpc.mpp.tempo.xyz\"")
            && foundry_toml.contains("moderato = \"https://rpc.mpp.moderato.tempo.xyz\""),
        "foundry.toml should contain tempo rpc_endpoints, got:\n{foundry_toml}"
    );

    assert!(prj.root().join("lib/forge-std").exists());
    assert!(prj.root().join("lib/tempo-std").exists());

    assert!(prj.root().join("src").exists());
    assert!(prj.root().join("src").join("Mail.sol").exists());

    assert!(prj.root().join("test").exists());
    assert!(prj.root().join("test").join("Mail.t.sol").exists());

    assert!(prj.root().join("script").exists());
    assert!(prj.root().join("script").join("Mail.s.sol").exists());

    assert!(prj.root().join(".github").join("workflows").exists());
    assert!(prj.root().join(".github").join("workflows").join("test.yml").exists());

    assert!(prj.root().join("README.md").exists());
});

// checks that `forge init --network tempo` correctly setup network key in config
forgetest!(can_execute_test_and_script_with_default_tempo_config, |prj, cmd| {
    prj.wipe();

    // Initialize a Tempo project.
    cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success();

    // Run tests, Tempo EVM selection is made by reading foundry.toml config
    cmd.forge_fuse().arg("test").arg("--root").arg(prj.root()).assert_success();

    // Same for script
    cmd.forge_fuse()
        .arg("script")
        .arg("script/Mail.s.sol")
        .arg("temposalt")
        .arg("--tempo")
        .arg("--root")
        .arg(prj.root())
        .assert_success();
});

// checks that `forge test --tempo` and `forge test -n tempo` are equivalent
forgetest!(network_flag_tempo_equivalent_to_legacy_tempo, |prj, cmd| {
    prj.wipe();
    cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success();

    // --network tempo (new flag)
    cmd.forge_fuse()
        .args(["test", "--network", "tempo"])
        .arg("--root")
        .arg(prj.root())
        .assert_success();

    // -n tempo (short form)
    cmd.forge_fuse().args(["test", "-n", "tempo"]).arg("--root").arg(prj.root()).assert_success();

    // --tempo (legacy flag)
    cmd.forge_fuse().args(["test", "--tempo"]).arg("--root").arg(prj.root()).assert_success();
});

// checks that clone works with raw src containing `node_modules`
// <https://github.com/foundry-rs/foundry/issues/10115>
forgetest!(flaky_can_clone_with_node_modules, |prj, cmd| {
    prj.wipe();

    let foundry_toml = prj.root().join(Config::FILE_NAME);
    assert!(!foundry_toml.exists());

    cmd.args([
        "clone",
        "--etherscan-api-key",
        next_etherscan_api_key().as_str(),
        "0xA3E217869460bEf59A1CfD0637e2875F9331e823",
    ])
    .arg(prj.root())
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]])
    .stderr_eq(str![[r#"
Downloading the source code of 0xA3E217869460bEf59A1CfD0637e2875F9331e823 from Etherscan...
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project
Collecting the creation information of 0xA3E217869460bEf59A1CfD0637e2875F9331e823 from Etherscan...

"#]]);
});

// checks that `clean` removes dapptools style paths
forgetest!(can_clean, |prj, cmd| {
    prj.assert_create_dirs_exists();
    prj.assert_style_paths_exist(PathStyle::Dapptools);
    cmd.arg("clean");
    cmd.assert_empty_stdout();
    prj.assert_cleaned();
});

// checks that `clean` removes hardhat style paths
forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj, cmd| {
    prj.assert_create_dirs_exists();
    prj.assert_style_paths_exist(PathStyle::HardHat);
    cmd.arg("clean");
    cmd.assert_empty_stdout();
    prj.assert_cleaned();
});

// checks that `clean` also works with the "out" value set in Config
forgetest_init!(can_clean_config, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.update_config(|config| config.out = "custom-out".into());
    cmd.arg("build").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    // default test contract is written in custom out directory
    let artifact = prj.root().join(format!("custom-out/{TEMPLATE_TEST_CONTRACT_ARTIFACT_JSON}"));
    assert!(artifact.exists());

    cmd.forge_fuse().arg("clean").assert_empty_stdout();
    assert!(!artifact.exists());
});

// checks that `clean` removes fuzz and invariant cache dirs
forgetest_init!(can_clean_test_cache, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.update_config(|config| {
        config.fuzz = FuzzConfig::new("cache/fuzz".into());
        config.invariant = InvariantConfig::new("cache/invariant".into());
    });
    // default test contract is written in custom out directory
    let fuzz_cache_dir = prj.root().join("cache/fuzz");
    let _ = fs::create_dir(fuzz_cache_dir.clone());
    let invariant_cache_dir = prj.root().join("cache/invariant");
    let _ = fs::create_dir(invariant_cache_dir.clone());
    let frontier_cache_dir = prj.root().join("cache/frontiers");
    let _ = fs::create_dir(frontier_cache_dir.clone());
    prj.update_config(|config| {
        config.fuzz.corpus.frontier_dir = Some("cache/frontiers".into());
    });

    assert!(fuzz_cache_dir.exists());
    assert!(invariant_cache_dir.exists());
    assert!(frontier_cache_dir.exists());

    cmd.forge_fuse().arg("clean").assert_empty_stdout();
    assert!(!fuzz_cache_dir.exists());
    assert!(!invariant_cache_dir.exists());
    assert!(!frontier_cache_dir.exists());
});

// checks that extra output works
forgetest_init!(can_emit_extra_output, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.clear();

    cmd.args(["build", "--extra-output", "metadata"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    let artifact_path = prj.paths().artifacts.join(TEMPLATE_CONTRACT_ARTIFACT_JSON);
    let artifact: ConfigurableContractArtifact =
        foundry_compilers::utils::read_json_file(&artifact_path).unwrap();
    assert!(artifact.metadata.is_some());

    cmd.forge_fuse()
        .args(["build", "--extra-output-files", "metadata", "--force"])
        .root_arg()
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    let metadata_path =
        prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.metadata.json"));
    let _artifact: Metadata = foundry_compilers::utils::read_json_file(&metadata_path).unwrap();
});

// checks that extra output works
forgetest_init!(can_emit_multiple_extra_output, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args([
        "build",
        "--extra-output",
        "metadata",
        "legacyAssembly",
        "ir-optimized",
        "--extra-output",
        "ir",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    let artifact_path = prj.paths().artifacts.join(TEMPLATE_CONTRACT_ARTIFACT_JSON);
    let artifact: ConfigurableContractArtifact =
        foundry_compilers::utils::read_json_file(&artifact_path).unwrap();
    assert!(artifact.metadata.is_some());
    assert!(artifact.legacy_assembly.is_some());
    assert!(artifact.ir.is_some());
    assert!(artifact.ir_optimized.is_some());

    cmd.forge_fuse()
        .args([
            "build",
            "--extra-output-files",
            "metadata",
            "ir-optimized",
            "evm.bytecode.sourceMap",
            "evm.legacyAssembly",
            "--force",
        ])
        .root_arg()
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    let metadata_path =
        prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.metadata.json"));
    let _artifact: Metadata = foundry_compilers::utils::read_json_file(&metadata_path).unwrap();

    let iropt = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.iropt"));
    std::fs::read_to_string(iropt).unwrap();

    let sourcemap =
        prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.sourcemap"));
    std::fs::read_to_string(sourcemap).unwrap();

    let legacy_assembly = prj
        .paths()
        .artifacts
        .join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.legacyAssembly.json"));
    std::fs::read_to_string(legacy_assembly).unwrap();
});

forgetest!(can_print_warnings, |prj, cmd| {
    prj.add_source(
        "Foo",
        r"
contract Greeter {
    function foo(uint256 a) public {
        uint256 x = 1;
    }
}
   ",
    );

    cmd.arg("build").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (5667): Unused function parameter. Remove or comment out the variable name to silence this warning.
 [FILE]:5:18:
  |
5 |     function foo(uint256 a) public {
  |                  ^^^^^^^^^

Warning (2072): Unused local variable.
 [FILE]:6:9:
  |
6 |         uint256 x = 1;
  |         ^^^^^^^^^

Warning (2018): Function state mutability can be restricted to pure
 [FILE]:5:5:
  |
5 |     function foo(uint256 a) public {
  |     ^ (Relevant source part starts here and spans across multiple lines).


"#]]);
});

// Tests that direct import paths are handled correctly
forgetest!(can_handle_direct_imports_into_src, |prj, cmd| {
    prj.add_source(
        "Foo",
        r#"
import {FooLib} from "src/FooLib.sol";
struct Bar {
    uint8 x;
}
contract Foo {
    mapping(uint256 => Bar) bars;
    function checker(uint256 id) external {
        Bar memory b = bars[id];
        FooLib.check(b);
    }
    function checker2() external {
        FooLib.check2(this);
    }
}
   "#,
    );

    prj.add_source(
        "FooLib",
        r#"
import {Foo, Bar} from "src/Foo.sol";
library FooLib {
    function check(Bar memory b) public {}
    function check2(Foo f) public {}
}
   "#,
    );

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

"#]]);
});

// tests that the `inspect` command works correctly
forgetest!(can_execute_inspect_command, |prj, cmd| {
    let contract_name = "Foo";
    let path = prj.add_source(
        contract_name,
        r#"
contract Foo {
    event log_string(string);
    function run() external {
        emit log_string("script ran");
    }
}
    "#,
    );

    cmd.arg("inspect").arg(contract_name).arg("bytecode").assert_success().stdout_eq(str![[r#"
0x60806040[..]

"#]]);

    let info = format!("src/{}:{}", path.file_name().unwrap().to_string_lossy(), contract_name);
    cmd.forge_fuse().arg("inspect").arg(info).arg("bytecode").assert_success().stdout_eq(str![[
        r#"
0x60806040[..]

"#
    ]]);

    // `--json` wraps the bytecode hex as a JSON string
    cmd.forge_fuse()
        .args(["inspect", contract_name, "bytecode", "--json"])
        .assert_success()
        .stdout_eq(str![[r#"
"0x60806040[..]"

"#]]);
});

// tests that `forge inspect <contract> transientStorageLayout` works
forgetest!(can_inspect_transient_storage_layout, |prj, cmd| {
    prj.add_source(
        "Transient.sol",
        r#"
contract Transient {
    uint256 transient counter;
    address transient owner;
}
    "#,
    );

    cmd.arg("inspect").args(["Transient", "transientStorageLayout"]).assert_success().stdout_eq(
        str![[r#"

╭---------+---------+------+--------+-------+-----------------------------╮
| Name    | Type    | Slot | Offset | Bytes | Contract                    |
+=========================================================================+
| counter | uint256 | 0    | 0      | 32    | src/Transient.sol:Transient |
|---------+---------+------+--------+-------+-----------------------------|
| owner   | address | 1    | 0      | 20    | src/Transient.sol:Transient |
╰---------+---------+------+--------+-------+-----------------------------╯


"#]],
    );

    // `--json` prints the raw storage layout object.
    cmd.forge_fuse()
        .args(["inspect", "Transient", "transientStorageLayout", "--json"])
        .assert_success()
        .stdout_eq(
            str![[r#"
{
  "storage": [
    {
      "astId": "{...}",
      "contract": "src/Transient.sol:Transient",
      "label": "counter",
      "offset": 0,
      "slot": "0",
      "type": "t_uint256"
    },
    {
      "astId": "{...}",
      "contract": "src/Transient.sol:Transient",
      "label": "owner",
      "offset": 0,
      "slot": "1",
      "type": "t_address"
    }
  ],
  "types": {
    "t_address": {
      "encoding": "inplace",
      "label": "address",
      "numberOfBytes": "20"
    },
    "t_uint256": {
      "encoding": "inplace",
      "label": "uint256",
      "numberOfBytes": "32"
    }
  }
}

"#]]
            .is_json(),
        );
});

forgetest!(can_inspect_linearization_markdown, |prj, cmd| {
    prj.add_source("A.sol", "contract A {}");
    prj.add_source("B.sol", r#"import {A} from "./A.sol"; contract B is A {}"#);
    prj.add_source("C.sol", r#"import {B} from "./B.sol"; contract C is B {}"#);

    cmd.forge_fuse().args(["inspect", "C", "linearization", "--md"]).assert_success().stdout_eq(
        str![[r#"

| Order | Source    | Contract |
|-------|-----------|----------|
| 0     | src/C.sol | C        |
| 1     | src/B.sol | B        |
| 2     | src/A.sol | A        |


"#]],
    );
});

forgetest!(can_inspect_linearization_json, |prj, cmd| {
    prj.add_source("A.sol", "contract A {}");
    prj.add_source("B.sol", r#"import {A} from "./A.sol"; contract B is A {}"#);
    prj.add_source("C.sol", r#"import {B} from "./B.sol"; contract C is B {}"#);

    cmd.forge_fuse().args(["inspect", "C", "linearization", "--json"]).assert_success().stdout_eq(
        str![[r#"
[
  {
    "order": 0,
    "source": "src/C.sol",
    "contract": "C"
  },
  {
    "order": 1,
    "source": "src/B.sol",
    "contract": "B"
  },
  {
    "order": 2,
    "source": "src/A.sol",
    "contract": "A"
  }
]

"#]],
    );
});

forgetest!(can_inspect_linearization_path_qualified_contract, |prj, cmd| {
    prj.add_source("one/Base.sol", "contract Base {}");
    prj.add_source(
        "one/Target.sol",
        r#"import {Base} from "./Base.sol"; contract Target is Base {}"#,
    );

    prj.add_source("two/Base.sol", "contract Base {}");
    prj.add_source(
        "two/Target.sol",
        r#"import {Base} from "./Base.sol"; contract Target is Base {}"#,
    );

    cmd.forge_fuse()
        .args(["inspect", "src/two/Target.sol:Target", "linearization", "--json"])
        .assert_success()
        .stdout_eq(str![[r#"
[
  {
    "order": 0,
    "source": "src/two/Target.sol",
    "contract": "Target"
  },
  {
    "order": 1,
    "source": "src/two/Base.sol",
    "contract": "Base"
  }
]

"#]]);
});

forgetest!(cannot_inspect_linearization_non_solidity_target, |prj, cmd| {
    prj.create_file("src/NotSol.vy", "x: uint256");

    cmd.forge_fuse()
        .args(["inspect", "src/NotSol.vy", "linearization"])
        .assert_failure()
        .stderr_eq(str![[r#"
Error: linearization inspection is only supported for Solidity contracts (.sol targets)

"#]]);
});

// test that `forge snapshot` commands work
forgetest!(can_check_snapshot, |prj, cmd| {
    prj.insert_ds_test();

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

    cmd.args(["snapshot"]).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] 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)

"#]]);

    cmd.arg("--check").assert_success().stdout_eq(str![[r#"
No files changed, compilation skipped

Ran 1 test for src/ATest.t.sol:ATest
[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!(snapshot_expands_invariant_campaign_predicates, |prj, cmd| {
    prj.add_test(
        "InvariantSnapshot.t.sol",
        r#"
contract InvariantSnapshotHandler {
    uint256 public count;

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

contract InvariantSnapshotTest {
    InvariantSnapshotHandler internal handler;

    function setUp() public {
        handler = new InvariantSnapshotHandler();
    }

    /// forge-config: default.invariant.runs = 2
    /// forge-config: default.invariant.depth = 4
    function invariant_count_is_non_negative() public view {
        require(handler.count() >= 0);
    }

    /// forge-config: default.invariant.runs = 2
    /// forge-config: default.invariant.depth = 4
    function invariant_count_is_not_max() public view {
        require(handler.count() != type(uint256).max);
    }
}
   "#,
    );

    cmd.args(["snapshot"]).assert_success();

    let snapshot = read_string(prj.root().join(".gas-snapshot"));
    assert_eq!(
        snapshot.lines().filter(|line| line.starts_with("InvariantSnapshotTest:")).count(),
        2
    );
    assert!(snapshot.contains("InvariantSnapshotTest:invariant_count_is_non_negative()"));
    assert!(snapshot.contains("InvariantSnapshotTest:invariant_count_is_not_max()"));
});

// tests that `forge snapshot --diff` keeps per-test diff rows on stdout but moves summary to stderr
forgetest!(snapshot_diff_summary_on_stderr, |prj, cmd| {
    prj.insert_ds_test();

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

    // Baseline snapshot.
    cmd.args(["snapshot"]).assert_success();

    // Re-run with --diff so the per-test diff row is emitted.
    let assert = cmd.forge_fuse().args(["snapshot", "--diff"]).assert_success();
    let output = assert.get_output();
    let stdout = output.stdout_lossy();
    let stderr = output.stderr_lossy();
    assert!(
        stdout.contains("ATest::testExample()"),
        "expected diff row on stdout, got stdout: {stdout}"
    );
    assert!(
        stderr.contains("Total tests:") && stderr.contains("Overall gas change:"),
        "expected summary on stderr, got stderr: {stderr}"
    );
    assert!(
        !stdout.contains("Total tests:") && !stdout.contains("Overall gas change:"),
        "summary leaked to stdout: {stdout}"
    );
});

// tests that `forge snapshot --check` reports mismatches on stderr
forgetest!(snapshot_check_writes_diff_to_stderr, |prj, cmd| {
    prj.insert_ds_test();

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

    // Pre-seed an empty snapshot so `--check` triggers the "No matching snapshot entry" path.
    fs::write(prj.root().join(".gas-snapshot"), "").unwrap();

    let assert = cmd.args(["snapshot", "--check"]).assert_failure();
    let output = assert.get_output();
    let stdout = output.stdout_lossy();
    let stderr = output.stderr_lossy();
    assert!(
        stderr.contains("No matching snapshot entry found for \"ATest::testExample()\""),
        "expected diff on stderr, got stderr: {stderr}\nstdout: {stdout}"
    );
    assert!(
        !stdout.contains("No matching snapshot entry"),
        "diff prose leaked to stdout: {stdout}"
    );
});

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

    prj.add_source(
        "FailingSnapshot.t.sol",
        r#"
import "./test.sol";
contract FailingSnapshotTest is DSTest {
    function testSnapshotFailure() public {
        assertTrue(false);
    }
}
   "#,
    );

    let assert = cmd.args(["snapshot", "--snap", "failed.snap"]).assert_failure();
    let output = assert.get_output();
    assert!(output.stderr_lossy().contains(
        "Error: gas snapshot file \"failed.snap\" was not written because the test run failed"
    ));
    assert!(!prj.root().join("failed.snap").exists());
});

// test that `forge build` does not print `(with warnings)` if file path is ignored
forgetest!(can_compile_without_warnings_ignored_file_paths, |prj, cmd| {
    // Ignoring path and setting empty error_codes as default would set some error codes
    prj.update_config(|config| {
        config.ignored_file_paths = vec![Path::new("src").to_path_buf()];
        config.ignored_error_codes = vec![];
    });

    prj.add_raw_source(
        "src/example.sol",
        r"
pragma solidity *;
contract A {
    function testExample() public {}
}
",
    );

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

"#]]);

    // Reconfigure without ignored paths or error codes and check for warnings
    prj.update_config(|config| config.ignored_file_paths = vec![]);

    cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
[FILE]


"#]]);
});

// test that `forge build` does not print `(with warnings)` if there aren't any
forgetest!(can_compile_without_warnings, |prj, cmd| {
    prj.update_config(|config| {
        config.ignored_error_codes = vec![SolidityErrorCode::SpdxLicenseNotProvided];
    });
    prj.add_raw_source(
        "A",
        r"
pragma solidity *;
contract A {
    function testExample() public {}
}
   ",
    );

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

"#]]);

    // don't ignore errors
    prj.update_config(|config| {
        config.ignored_error_codes = vec![];
    });

    cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
[FILE]


"#]]);
});

// test that `forge build` compiles when severity set to error, fails when set to warning, and
// handles ignored error codes as an exception
forgetest!(can_fail_compile_with_warnings, |prj, cmd| {
    prj.update_config(|config| {
        config.ignored_error_codes = vec![];
        config.deny = DenyLevel::Never;
    });
    prj.add_raw_source(
        "A",
        r"
pragma solidity *;
contract A {
    event Example();

    function testExample() public {
        emit Example();
    }
}
   ",
    );

    // there are no errors
    cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
[FILE]


"#]]);

    // warning fails to compile
    prj.update_config(|config| {
        config.ignored_error_codes = vec![];
        config.deny = DenyLevel::Warnings;
    });

    cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#"
Error: Compiler run failed:
Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
[FILE]

"#]]);

    // ignores error code and compiles
    prj.update_config(|config| {
        config.ignored_error_codes = vec![SolidityErrorCode::SpdxLicenseNotProvided];
        config.deny = DenyLevel::Warnings;
    });

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

"#]]);
});

// test that `forge build` ignores error codes only from matching path prefixes
forgetest!(can_compile_without_warnings_ignored_error_codes_from, |prj, cmd| {
    let contract = r"
pragma solidity *;
contract A {}
    ";
    prj.add_raw_source("A", contract);
    prj.add_raw_test("A", contract);

    // suppressed for both src and test
    prj.update_config(|config| {
        config.ignored_error_codes = vec![];
        config.ignored_error_codes_from = vec![
            (std::path::PathBuf::from("src"), vec![SolidityErrorCode::SpdxLicenseNotProvided]),
            (std::path::PathBuf::from("test"), vec![SolidityErrorCode::SpdxLicenseNotProvided]),
        ];
    });

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

"#]]);

    // suppressed only for test, src warning still shows
    prj.update_config(|config| {
        config.ignored_error_codes_from = vec![(
            std::path::PathBuf::from("test"),
            vec![SolidityErrorCode::SpdxLicenseNotProvided],
        )];
    });

    cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
[FILE]


"#]]);
});

// test that a failing `forge build` does not impact followup builds
forgetest!(can_build_after_failure, |prj, cmd| {
    prj.insert_ds_test();

    // Disable linting during build to avoid linting output interfering with test assertions
    prj.update_config(|config| {
        config.lint.lint_on_build = false;
    });

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

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

"#]]);

    prj.assert_cache_exists();
    prj.assert_artifacts_dir_exists();

    let syntax_err = r#"
import "./test.sol";
contract CTest is DSTest {
    function testExample() public {
        THIS WILL CAUSE AN ERROR
    }
}
   "#;

    // introduce contract with syntax error
    prj.add_source("CTest.t.sol", syntax_err);

    // `forge build --force` which should fail
    cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#"
Error: Compiler run failed:
Error (2314): Expected ';' but got identifier
 [FILE]:7:19:
  |
7 |         THIS WILL CAUSE AN ERROR
  |                   ^^^^^

"#]]);

    // but ensure this cleaned cache and artifacts
    assert!(!prj.paths().artifacts.exists());
    assert!(!prj.cache().exists());

    // still errors
    cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#"
Error: Compiler run failed:
Error (2314): Expected ';' but got identifier
 [FILE]:7:19:
  |
7 |         THIS WILL CAUSE AN ERROR
  |                   ^^^^^

"#]]);

    // resolve the error by replacing the file
    prj.add_source(
        "CTest.t.sol",
        r#"
import "./test.sol";
contract CTest is DSTest {
    function testExample() public {
         assertTrue(true);
    }
}
   "#,
    );

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

"#]]);

    prj.assert_cache_exists();
    prj.assert_artifacts_dir_exists();

    // ensure cache is unchanged after error
    let cache = fs::read_to_string(prj.cache()).unwrap();

    // introduce the error again but building without force
    prj.add_source("CTest.t.sol", syntax_err);
    cmd.forge_fuse().arg("build").assert_failure().stderr_eq(str![[r#"
Error: Compiler run failed:
Error (2314): Expected ';' but got identifier
 [FILE]:7:19:
  |
7 |         THIS WILL CAUSE AN ERROR
  |                   ^^^^^

"#]]);

    // ensure unchanged cache file
    let cache_after = fs::read_to_string(prj.cache()).unwrap();
    assert_eq!(cache, cache_after);
});

const GAS_REPORT_CONTRACTS: &str = r#"
//SPDX-license-identifier: MIT

import "./test.sol";

contract ContractOne {
    int public i;

    constructor() {
        i = 0;
    }

    function foo() public{
        while(i<5){
            i++;
        }
    }
}

contract ContractOneTest is DSTest {
    ContractOne c1;

    function setUp() public {
        c1 = new ContractOne();
    }

    function testFoo() public {
        c1.foo();
    }
}


contract ContractTwo {
    int public i;

    constructor() {
        i = 0;
    }

    function bar() public{
        while(i<50){
            i++;
        }
    }
}

contract ContractTwoTest is DSTest {
    ContractTwo c2;

    function setUp() public {
        c2 = new ContractTwo();
    }

    function testBar() public {
        c2.bar();
    }
}

contract ContractThree {
    int public i;

    constructor() {
        i = 0;
    }

    function baz() public{
        while(i<500){
            i++;
        }
    }
}

contract ContractThreeTest is DSTest {
    ContractThree c3;

    function setUp() public {
        c3 = new ContractThree();
    }

    function testBaz() public {
        c3.baz();
    }
}
"#;

forgetest!(gas_report_all_contracts, |prj, cmd| {
    prj.insert_ds_test();
    prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS);

    // report for all
    prj.update_config(|config| {
        config.gas_reports = vec!["*".to_string()];
        config.gas_reports_ignore = vec![];
    });

    cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯

╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
        .is_json(),
    );

    prj.update_config(|config| config.gas_reports = vec![]);
    cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯

╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
        .is_json(),
    );

    prj.update_config(|config| config.gas_reports = vec!["*".to_string()]);
    cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯

╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
        .is_json(),
    );

    prj.update_config(|config| {
        config.gas_reports =
            vec!["ContractOne".to_string(), "ContractTwo".to_string(), "ContractThree".to_string()];
    });
    cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯

╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
        .is_json(),
    );
});

forgetest!(gas_report_some_contracts, |prj, cmd| {
    prj.insert_ds_test();
    prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS);

    // report for One
    prj.update_config(|config| config.gas_reports = vec!["ContractOne".to_string()]);
    cmd.forge_fuse();
    cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  }
]
"#]]
        .is_json(),
    );

    // report for Two
    prj.update_config(|config| config.gas_reports = vec!["ContractTwo".to_string()]);
    cmd.forge_fuse();
    cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
        .is_json(),
    );

    // report for Three
    prj.update_config(|config| config.gas_reports = vec!["ContractThree".to_string()]);
    cmd.forge_fuse();
    cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  }
]
"#]]
        .is_json(),
    );
});

forgetest!(gas_report_ignore_some_contracts, |prj, cmd| {
    prj.insert_ds_test();
    prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS);

    // ignore ContractOne
    prj.update_config(|config| {
        config.gas_reports = vec!["*".to_string()];
        config.gas_reports_ignore = vec!["ContractOne".to_string()];
    });
    cmd.forge_fuse();
    cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯

╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
        .is_json(),
    );

    // ignore ContractTwo
    cmd.forge_fuse();
    prj.update_config(|config| {
        config.gas_reports = vec![];
        config.gas_reports_ignore = vec!["ContractTwo".to_string()];
    });
    cmd.forge_fuse();
    cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  }
]
"#]]
        .is_json(),
    );

    // If the user listed the contract in 'gas_reports' (the foundry.toml field) a
    // report for the contract is generated even if it's listed in the ignore
    // list. This is addressed this way because getting a report you don't expect is
    // preferable than not getting one you expect. A warning is printed to stderr
    // indicating the "double listing".
    cmd.forge_fuse();
    prj.update_config(|config| {
        config.gas_reports =
            vec!["ContractOne".to_string(), "ContractTwo".to_string(), "ContractThree".to_string()];
        config.gas_reports_ignore = vec!["ContractThree".to_string()];
    });
    cmd.forge_fuse();
    cmd.arg("test")
        .arg("--gas-report")
        .assert_success()
        .stdout_eq(str![[r#"
...
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractOne Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| foo                                    |           45656 | 45656 |  45656 | 45656 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+--------+--------+--------+---------╮
| src/Contracts.sol:ContractThree Contract |                 |        |        |        |         |
+=================================================================================================+
| Deployment Cost                          | Deployment Size |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                   133219 |             395 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
|                                          |                 |        |        |        |         |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                            | Min             | Avg    | Median | Max    | # Calls |
|------------------------------------------+-----------------+--------+--------+--------+---------|
| baz                                      |          287711 | 287711 | 287711 | 287711 |       1 |
╰------------------------------------------+-----------------+--------+--------+--------+---------╯

╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/Contracts.sol:ContractTwo Contract |                 |       |        |       |         |
+=============================================================================================+
| Deployment Cost                        | Deployment Size |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                 133015 |             394 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
|                                        |                 |       |        |       |         |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                          | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| bar                                    |           67683 | 67683 |  67683 | 67683 |       1 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯


Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests)

"#]])
        .stderr_eq(str![[r#"
...
Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'.
...
"#]]);
    cmd.forge_fuse()
        .arg("test")
        .arg("--gas-report")
        .arg("--json")
        .assert_success()
        .stdout_eq(
            str![[r#"
[
  {
    "contract": "src/Contracts.sol:ContractOne",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "foo()": {
        "calls": 1,
        "min": 45656,
        "mean": 45656,
        "median": 45656,
        "max": 45656
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractThree",
    "deployment": {
      "gas": 133219,
      "size": 395
    },
    "functions": {
      "baz()": {
        "calls": 1,
        "min": 287711,
        "mean": 287711,
        "median": 287711,
        "max": 287711
      }
    }
  },
  {
    "contract": "src/Contracts.sol:ContractTwo",
    "deployment": {
      "gas": 133015,
      "size": 394
    },
    "functions": {
      "bar()": {
        "calls": 1,
        "min": 67683,
        "mean": 67683,
        "median": 67683,
        "max": 67683
      }
    }
  }
]
"#]]
            .is_json(),
        )
        .stderr_eq(str![[r#"
...
Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'.
...
"#]]);
});

forgetest!(gas_report_flatten_multiple_selectors, |prj, cmd| {
    prj.insert_ds_test();
    prj.add_source(
        "Counter.sol",
        r#"
contract Counter {
    uint256 public a;
    int256 public b;

    function setNumber(uint256 x) public {
        a = x;
    }

    function setNumber(int256 x) public {
        b = x;
    }
}
"#,
    );

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

contract CounterTest is DSTest {
    Counter public counter;

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

    function test_Increment() public {
        counter.setNumber(uint256(counter.a() + 1));
        counter.setNumber(int256(counter.b() + 1));
    }
}
"#,
    );

    cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#"
...
╭----------------------------------+-----------------+-------+--------+-------+---------╮
| src/Counter.sol:Counter Contract |                 |       |        |       |         |
+=======================================================================================+
| Deployment Cost                  | Deployment Size |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
|                           172107 |             578 |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
|                                  |                 |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                    | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------+-----------------+-------+--------+-------+---------|
| a                                |            2402 |  2402 |   2402 |  2402 |       1 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| b                                |            2447 |  2447 |   2447 |  2447 |       1 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| setNumber(int256)                |           23851 | 33807 |  33807 | 43763 |       2 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| setNumber(uint256)               |           23806 | 33762 |  33762 | 43718 |       2 |
╰----------------------------------+-----------------+-------+--------+-------+---------╯


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

"#]]);
    cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq(
        str![[r#"
[
  {
    "contract": "src/Counter.sol:Counter",
    "deployment": {
      "gas": 172107,
      "size": 578
    },
    "functions": {
      "a()": {
        "calls": 1,
        "min": 2402,
        "mean": 2402,
        "median": 2402,
        "max": 2402
      },
      "b()": {
        "calls": 1,
        "min": 2447,
        "mean": 2447,
        "median": 2447,
        "max": 2447
      },
      "setNumber(int256)": {
        "calls": 2,
        "min": 23851,
        "mean": 33807,
        "median": 33807,
        "max": 43763
      },
      "setNumber(uint256)": {
        "calls": 2,
        "min": 23806,
        "mean": 33762,
        "median": 33762,
        "max": 43718
      }
    }
  }
]
"#]]
        .is_json(),
    );
});

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

contract ProxiedContract {
    uint256 public amount;

    function deposit(uint256 aba) external {
        amount = amount * 2;
    }

    function deposit() external {
    }
}

contract DelegateProxy {
    address internal implementation;

    constructor(address counter) {
        implementation = counter;
    }

    function deposit() external {
    }

    fallback() external payable {
        address addr = implementation;

        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), addr, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

contract GasReportFallbackTest is Test {
    function test_fallback_gas_report() public {
        ProxiedContract proxied = ProxiedContract(address(new DelegateProxy(address(new ProxiedContract()))));
        proxied.deposit(100);
        proxied.deposit();
    }
}
"#,
    );

    cmd.args(["test", "--mt", "test_fallback_gas_report", "-vvvv", "--gas-report"])
        .assert_success()
        .stdout_eq(str![[r#"
...
╭---------------------------------------------------+-----------------+-------+--------+-------+---------╮
| test/DelegateProxyTest.sol:DelegateProxy Contract |                 |       |        |       |         |
+========================================================================================================+
| Deployment Cost                                   | Deployment Size |       |        |       |         |
|---------------------------------------------------+-----------------+-------+--------+-------+---------|
|                                            117171 |             471 |       |        |       |         |
|---------------------------------------------------+-----------------+-------+--------+-------+---------|
|                                                   |                 |       |        |       |         |
|---------------------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                                     | Min             | Avg   | Median | Max   | # Calls |
|---------------------------------------------------+-----------------+-------+--------+-------+---------|
| deposit                                           |           21185 | 21185 |  21185 | 21185 |       1 |
|---------------------------------------------------+-----------------+-------+--------+-------+---------|
| fallback                                          |           29758 | 29758 |  29758 | 29758 |       1 |
╰---------------------------------------------------+-----------------+-------+--------+-------+---------╯

╭-----------------------------------------------------+-----------------+------+--------+------+---------╮
| test/DelegateProxyTest.sol:ProxiedContract Contract |                 |      |        |      |         |
+========================================================================================================+
| Deployment Cost                                     | Deployment Size |      |        |      |         |
|-----------------------------------------------------+-----------------+------+--------+------+---------|
|                                              153519 |             494 |      |        |      |         |
|-----------------------------------------------------+-----------------+------+--------+------+---------|
|                                                     |                 |      |        |      |         |
|-----------------------------------------------------+-----------------+------+--------+------+---------|
| Function Name                                       | Min             | Avg  | Median | Max  | # Calls |
|-----------------------------------------------------+-----------------+------+--------+------+---------|
| deposit                                             |            3661 | 3661 |   3661 | 3661 |       1 |
╰-----------------------------------------------------+-----------------+------+--------+------+---------╯


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

"#]]);

    cmd.forge_fuse()
        .args(["test", "--mt", "test_fallback_gas_report", "--gas-report", "--json"])
        .assert_success()
        .stdout_eq(
            str![[r#"
[
  {
    "contract": "test/DelegateProxyTest.sol:DelegateProxy",
    "deployment": {
      "gas": 117171,
      "size": 471
    },
    "functions": {
      "deposit()": {
        "calls": 1,
        "min": 21185,
        "mean": 21185,
        "median": 21185,
        "max": 21185
      },
      "fallback()": {
        "calls": 1,
        "min": 29758,
        "mean": 29758,
        "median": 29758,
        "max": 29758
      }
    }
  },
  {
    "contract": "test/DelegateProxyTest.sol:ProxiedContract",
    "deployment": {
      "gas": 153519,
      "size": 494
    },
    "functions": {
      "deposit(uint256)": {
        "calls": 1,
        "min": 3661,
        "mean": 3661,
        "median": 3661,
        "max": 3661
      }
    }
  }
]
"#]]
            .is_json(),
        );
});

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

contract CounterWithFallback {
    uint256 public number;

    function increment() public {
        number++;
    }

    fallback() external {
        number++;
    }
}

contract CounterWithFallbackTest is Test {
    CounterWithFallback public counter;

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

    function test_fallback_with_calldata() public {
        (bool success,) = address(counter).call("hello");
        require(success);
    }
}
"#,
    );

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

Ran 1 test for test/FallbackWithCalldataTest.sol:CounterWithFallbackTest
[PASS] test_fallback_with_calldata() ([GAS])
Traces:
  [48777] CounterWithFallbackTest::test_fallback_with_calldata()
    ├─ [43461] CounterWithFallback::fallback(0x68656c6c6f)
    │   └─ ← [Stop]
    └─ ← [Stop]

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

╭----------------------------------------------------------------+-----------------+-------+--------+-------+---------╮
| test/FallbackWithCalldataTest.sol:CounterWithFallback Contract |                 |       |        |       |         |
+=====================================================================================================================+
| Deployment Cost                                                | Deployment Size |       |        |       |         |
|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|                                                         132471 |             396 |       |        |       |         |
|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
|                                                                |                 |       |        |       |         |
|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                                                  | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
| fallback                                                       |           43461 | 43461 |  43461 | 43461 |       1 |
╰----------------------------------------------------------------+-----------------+-------+--------+-------+---------╯


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

"#]]);

    cmd.forge_fuse()
        .args(["test", "--mt", "test_fallback_with_calldata", "--gas-report", "--json"])
        .assert_success()
        .stdout_eq(
            str![[r#"
[
  {
    "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback",
    "deployment": {
      "gas": 132471,
      "size": 396
    },
    "functions": {
      "fallback()": {
        "calls": 1,
        "min": 43461,
        "mean": 43461,
        "median": 43461,
        "max": 43461
      }
    }
  }
]
"#]]
            .is_json(),
        );
});

// <https://github.com/foundry-rs/foundry/issues/9300>
forgetest_init!(gas_report_size_for_nested_create, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "NestedDeployTest.sol",
        r#"
import {Test} from "forge-std/Test.sol";
contract Child {
    AnotherChild public child;
    constructor() {
        child = new AnotherChild();
    }
    function w() external {
        child.w();
    }
}
contract AnotherChild {
    function w() external {}
}
contract Parent {
    Child public immutable child;
    constructor() {
        child = new Child();
    }
}
contract NestedDeploy is Test {
    function test_nested_create_gas_report() external {
        Parent p = new Parent();
        p.child().child().w();
    }
}
"#,
    );

    cmd.args(["test", "--mt", "test_nested_create_gas_report", "--gas-report"])
        .assert_success()
        .stdout_eq(str![[r#"
...
╭-------------------------------------------------+-----------------+-------+--------+-------+---------╮
| test/NestedDeployTest.sol:AnotherChild Contract |                 |       |        |       |         |
+======================================================================================================+
| Deployment Cost                                 | Deployment Size |       |        |       |         |
|-------------------------------------------------+-----------------+-------+--------+-------+---------|
|                                               0 |             132 |       |        |       |         |
|-------------------------------------------------+-----------------+-------+--------+-------+---------|
|                                                 |                 |       |        |       |         |
|-------------------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                                   | Min             | Avg   | Median | Max   | # Calls |
|-------------------------------------------------+-----------------+-------+--------+-------+---------|
| w                                               |           21185 | 21185 |  21185 | 21185 |       1 |
╰-------------------------------------------------+-----------------+-------+--------+-------+---------╯

╭------------------------------------------+-----------------+------+--------+------+---------╮
| test/NestedDeployTest.sol:Child Contract |                 |      |        |      |         |
+=============================================================================================+
| Deployment Cost                          | Deployment Size |      |        |      |         |
|------------------------------------------+-----------------+------+--------+------+---------|
|                                        0 |             731 |      |        |      |         |
|------------------------------------------+-----------------+------+--------+------+---------|
|                                          |                 |      |        |      |         |
|------------------------------------------+-----------------+------+--------+------+---------|
| Function Name                            | Min             | Avg  | Median | Max  | # Calls |
|------------------------------------------+-----------------+------+--------+------+---------|
| child                                    |            2681 | 2681 |   2681 | 2681 |       1 |
╰------------------------------------------+-----------------+------+--------+------+---------╯

╭-------------------------------------------+-----------------+-----+--------+-----+---------╮
| test/NestedDeployTest.sol:Parent Contract |                 |     |        |     |         |
+============================================================================================+
| Deployment Cost                           | Deployment Size |     |        |     |         |
|-------------------------------------------+-----------------+-----+--------+-----+---------|
|                                    328961 |            1163 |     |        |     |         |
|-------------------------------------------+-----------------+-----+--------+-----+---------|
|                                           |                 |     |        |     |         |
|-------------------------------------------+-----------------+-----+--------+-----+---------|
| Function Name                             | Min             | Avg | Median | Max | # Calls |
|-------------------------------------------+-----------------+-----+--------+-----+---------|
| child                                     |             525 | 525 |    525 | 525 |       1 |
╰-------------------------------------------+-----------------+-----+--------+-----+---------╯


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

"#]]);

    cmd.forge_fuse()
        .args(["test", "--mt", "test_nested_create_gas_report", "--gas-report", "--json"])
        .assert_success()
        .stdout_eq(
            str![[r#"
[
  {
    "contract": "test/NestedDeployTest.sol:AnotherChild",
    "deployment": {
      "gas": 0,
      "size": 132
    },
    "functions": {
      "w()": {
        "calls": 1,
        "min": 21185,
        "mean": 21185,
        "median": 21185,
        "max": 21185
      }
    }
  },
  {
    "contract": "test/NestedDeployTest.sol:Child",
    "deployment": {
      "gas": 0,
      "size": 731
    },
    "functions": {
      "child()": {
        "calls": 1,
        "min": 2681,
        "mean": 2681,
        "median": 2681,
        "max": 2681
      }
    }
  },
  {
    "contract": "test/NestedDeployTest.sol:Parent",
    "deployment": {
      "gas": 328961,
      "size": 1163
    },
    "functions": {
      "child()": {
        "calls": 1,
        "min": 525,
        "mean": 525,
        "median": 525,
        "max": 525
      }
    }
  }
]
"#]]
            .is_json(),
        );
});

forgetest_init!(can_use_absolute_imports, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.update_config(|config| {
        let remapping = prj.paths().libraries[0].join("myDependency");
        config.remappings = vec![
            Remapping::from_str(&format!("myDependency/={}", remapping.display())).unwrap().into(),
        ];
    });

    prj.add_lib(
        "myDependency/src/interfaces/IConfig.sol",
        r"

    interface IConfig {}
   ",
    );

    prj.add_lib(
        "myDependency/src/Config.sol",
        r#"
        import {IConfig} from "myDependency/src/interfaces/IConfig.sol";

    contract Config is IConfig {}
   "#,
    );

    prj.add_source(
        "Greeter",
        r#"
        import {Config} from "myDependency/src/Config.sol";

    contract Greeter {
        Config config;
    }
   "#,
    );

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

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/3440>
forgetest_init!(can_use_absolute_imports_from_test_and_script, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_script(
        "IMyScript.sol",
        r"
interface IMyScript {}
        ",
    );

    prj.add_script(
        "MyScript.sol",
        r#"
import "script/IMyScript.sol";

contract MyScript is IMyScript {}
        "#,
    );

    prj.add_test(
        "IMyTest.sol",
        r"
interface IMyTest {}
        ",
    );

    prj.add_test(
        "MyTest.sol",
        r#"
import "test/IMyTest.sol";

contract MyTest is IMyTest {}
    "#,
    );

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

"#]]);
});

// checks `forge inspect <contract> irOptimized works
forgetest_init!(can_inspect_ir_optimized, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["inspect", TEMPLATE_CONTRACT, "irOptimized"]);
    cmd.assert_success().stdout_eq(str![[r#"
/// @use-src 0:"src/Counter.sol"
object "Counter_21" {
    code {
        {
            /// @src 0:65:257  "contract Counter {..."
            mstore(64, memoryguard(0x80))
...
"#]]);

    // check inspect with strip comments
    cmd.forge_fuse().args(["inspect", TEMPLATE_CONTRACT, "irOptimized", "-s"]);
    cmd.assert_success().stdout_eq(str![[r#"
object "Counter_21" {
    code {
        {
            mstore(64, memoryguard(0x80))
            if callvalue()
...
"#]]);
});

// checks `forge inspect <contract> irOptimized works
forgetest_init!(can_inspect_ir, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["inspect", TEMPLATE_CONTRACT, "ir"]);
    cmd.assert_success().stdout_eq(str![[r#"

/// @use-src 0:"src/Counter.sol"
object "Counter_21" {
    code {
        /// @src 0:65:257  "contract Counter {..."
        mstore(64, memoryguard(128))
...
"#]]);

    // check inspect with strip comments
    cmd.forge_fuse().args(["inspect", TEMPLATE_CONTRACT, "ir", "-s"]);
    cmd.assert_success().stdout_eq(str![[r#"

object "Counter_21" {
    code {
        mstore(64, memoryguard(128))
        if callvalue() { revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() }
...
"#]]);

    // `--json` wraps the IR source as a JSON string (one quoted line)
    let json_out = cmd
        .forge_fuse()
        .args(["inspect", TEMPLATE_CONTRACT, "ir", "--json"])
        .assert_success()
        .get_output()
        .stdout_lossy();
    let json_out = json_out.trim();
    assert!(json_out.starts_with('"'), "expected quoted JSON string, got {json_out:?}");
    assert!(json_out.ends_with('"'), "expected quoted JSON string, got {json_out:?}");
    let _: String =
        serde_json::from_str(json_out).expect("ir --json stdout should be a valid JSON string");
});

// checks forge bind works correctly on the default project
forgetest_init!(can_bind, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.clear();

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

"#]])
        .stderr_eq(str![[r#"
Generating bindings for [..] contracts
Bindings have been generated to [..]

"#]]);
});

// checks that extra output works
forgetest_init!(can_build_skip_contracts, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.clear();

    // Only builds the single template contract `src/*`
    cmd.args(["build", "--skip", "tests", "--skip", "scripts"]).assert_success().stdout_eq(str![[
        r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#
    ]]);

    // Expect compilation to be skipped as no files have changed
    cmd.arg("build").assert_success().stdout_eq(str![[r#"
No files changed, compilation skipped

"#]]);
});

forgetest_init!(can_build_skip_glob, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_test(
        "Foo",
        r"
contract TestDemo {
function test_run() external {}
}",
    );

    // only builds the single template contract `src/*` even if `*.t.sol` or `.s.sol` is absent
    prj.clear();
    cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**", "--force"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    cmd.forge_fuse()
        .args(["build", "--skip", "./test/**", "--skip", "./script/**", "--force"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);
});

forgetest!(can_build_specific_paths, |prj, cmd| {
    prj.add_source(
        "Counter.sol",
        r"
contract Counter {
function count() external {}
}",
    );
    prj.add_test(
        "Foo.sol",
        r"
contract Foo {
function test_foo() external {}
}",
    );
    prj.add_test(
        "Bar.sol",
        r"
contract Bar {
function test_bar() external {}
}",
    );

    // Build 2 files within test dir
    prj.clear();
    cmd.args(["build", "test", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    // Build one file within src dir
    prj.clear();
    cmd.forge_fuse();
    cmd.args(["build", "src", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    // Build 3 files from test and src dirs
    prj.clear();
    cmd.forge_fuse();
    cmd.args(["build", "src", "test", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    // Build single test file
    prj.clear();
    cmd.forge_fuse();
    cmd.args(["build", "test/Bar.sol", "--force"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]]);

    // Fail if no source file found.
    prj.clear();
    cmd.forge_fuse();
    cmd.args(["build", "test/Dummy.sol", "--force"]).assert_failure().stderr_eq(str![[r#"
Error: No source files found in specified build paths.

"#]]);
});

// checks that build --sizes includes all contracts even if unchanged
forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.clear_cache();

    cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#"
...
╭----------+------------------+-------------------+--------------------+---------------------╮
| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) |
+============================================================================================+
| Counter  | 481              | 509               | 24,095             | 48,643              |
╰----------+------------------+-------------------+--------------------+---------------------╯


"#]]);

    cmd.forge_fuse().args(["build", "--sizes", "--md"]).assert_success().stdout_eq(str![[r#"
...
| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) |
|----------|------------------|-------------------|--------------------|---------------------|
| Counter  | 481              | 509               | 24,095             | 48,643              |


"#]]);

    cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_success().stdout_eq(
        str![[r#"
{
  "Counter": {
    "runtime_size": 481,
    "init_size": 509,
    "runtime_margin": 24095,
    "init_margin": 48643
  }
}
"#]]
        .is_json(),
    );
});

// checks that build --names includes all contracts even if unchanged
forgetest_init!(can_build_names_repeatedly, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.clear_cache();

    cmd.args(["build", "--names"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
  compiler version: [..]
    - [..]
...

"#]]);

    cmd.forge_fuse()
        .args(["build", "--names", "--json"])
        .assert_success()
        .stdout_eq(str![[r#""{...}""#]].is_json());
});

forgetest_init!(can_inspect_counter_pretty, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["inspect", "src/Counter.sol:Counter", "abi"]).assert_success().stdout_eq(str![[r#"

╭----------+---------------------------------+------------╮
| Type     | Signature                       | Selector   |
+=========================================================+
| function | increment() nonpayable          | 0xd09de08a |
|----------+---------------------------------+------------|
| function | number() view returns (uint256) | 0x8381f58a |
|----------+---------------------------------+------------|
| function | setNumber(uint256) nonpayable   | 0x3fb5c1cb |
╰----------+---------------------------------+------------╯


"#]]);
});

const CUSTOM_COUNTER: &str = r#"
    contract Counter {
    uint256 public number;
    uint64 public count;
    struct MyStruct {
        uint64 count;
    }
    struct ErrWithMsg {
        string message;
    }

    event Incremented(uint256 newValue);
    event Decremented(uint256 newValue);

    error NumberIsZero();
    error CustomErr(ErrWithMsg e);

    constructor(uint256 _number) {
        number = _number;
    }

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

    function increment() external {
        number++;
    }

    function decrement() public payable {
        if (number == 0) {
            return;
        }
        number--;
    }

    function square() public {
        number = number * number;
    }

    fallback() external payable {
        ErrWithMsg memory err = ErrWithMsg("Fallback function is not allowed");
        revert CustomErr(err);
    }

    receive() external payable {
        count++;
    }

    function setStruct(MyStruct memory s, uint32 b) public {
        count = s.count;
    }
}
    "#;

const ANOTHER_COUNTER: &str = r#"
    contract AnotherCounter is Counter {
        constructor(uint256 _number) Counter(_number) {}
    }
"#;
forgetest!(inspect_custom_counter_abi, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER);

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

╭-------------+-----------------------------------------------+--------------------------------------------------------------------╮
| Type        | Signature                                     | Selector                                                           |
+==================================================================================================================================+
| event       | Decremented(uint256)                          | 0xc9118d86370931e39644ee137c931308fa3774f6c90ab057f0c3febf427ef94a |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| event       | Incremented(uint256)                          | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| error       | CustomErr(Counter.ErrWithMsg)                 | 0x0625625a                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| error       | NumberIsZero()                                | 0xde5d32ac                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | count() view returns (uint64)                 | 0x06661abd                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | decrement() payable                           | 0x2baeceb7                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | increment() nonpayable                        | 0xd09de08a                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | number() view returns (uint256)               | 0x8381f58a                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | setNumber(uint256) nonpayable                 | 0x3fb5c1cb                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | setStruct(Counter.MyStruct,uint32) nonpayable | 0x08ef7366                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| function    | square() nonpayable                           | 0xd742cb01                                                         |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| constructor | constructor(uint256) nonpayable               |                                                                    |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| fallback    | fallback() payable                            |                                                                    |
|-------------+-----------------------------------------------+--------------------------------------------------------------------|
| receive     | receive() payable                             |                                                                    |
╰-------------+-----------------------------------------------+--------------------------------------------------------------------╯


"#]]);
});

forgetest!(inspect_abi_does_not_write_artifacts, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER);

    let artifact_path = prj.paths().artifacts.join("Counter.sol/Counter.json");

    cmd.args(["inspect", "Counter", "abi", "--json"]).assert_success();
    assert!(!artifact_path.exists());

    cmd.forge_fuse().arg("build").assert_success();
    let built = std::fs::read(&artifact_path).unwrap();
    let artifact: serde_json::Value =
        foundry_compilers::utils::read_json_file(&artifact_path).unwrap();
    let bytecode = artifact["bytecode"]["object"]
        .as_str()
        .expect("build artifact should include creation bytecode");
    assert!(bytecode.starts_with("0x"));
    assert!(bytecode.len() > 2);

    cmd.forge_fuse().args(["inspect", "Counter", "abi", "--json"]).assert_success();
    let inspected = std::fs::read(&artifact_path).unwrap();
    assert_eq!(built, inspected);
});

forgetest!(inspect_custom_counter_events, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER);

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

╭----------------------+--------------------------------------------------------------------╮
| Event                | Topic                                                              |
+===========================================================================================+
| Decremented(uint256) | 0xc9118d86370931e39644ee137c931308fa3774f6c90ab057f0c3febf427ef94a |
|----------------------+--------------------------------------------------------------------|
| Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 |
╰----------------------+--------------------------------------------------------------------╯


"#]]);
});

forgetest!(inspect_custom_counter_errors, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER);

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

╭-------------------------------+----------╮
| Error                         | Selector |
+==========================================+
| CustomErr(Counter.ErrWithMsg) | 0625625a |
|-------------------------------+----------|
| NumberIsZero()                | de5d32ac |
╰-------------------------------+----------╯


"#]]);
});

forgetest!(inspect_anonymous_event_has_no_selector, |prj, cmd| {
    prj.add_source(
        "Counter.sol",
        r#"
contract Counter {
    event FakeEvent(uint256 indexed topic0) anonymous;
    event NormalEvent(uint256 indexed value);

    function setNumber(uint256 newNumber) public {
        emit FakeEvent(newNumber);
        emit NormalEvent(newNumber);
    }
}
    "#,
    );

    // `abi`: anonymous event must render with an empty selector and an `anonymous` marker.
    cmd.args(["inspect", "Counter", "abi"]).assert_success().stdout_eq(str![[r#"

╭----------+-------------------------------+--------------------------------------------------------------------╮
| Type     | Signature                     | Selector                                                           |
+===============================================================================================================+
| event    | FakeEvent(uint256) anonymous  |                                                                    |
|----------+-------------------------------+--------------------------------------------------------------------|
| event    | NormalEvent(uint256)          | 0x2e58689f6eed514b3003e0a132a1284ba04042a9d59d11c5db973fdb520d10c1 |
|----------+-------------------------------+--------------------------------------------------------------------|
| function | setNumber(uint256) nonpayable | 0x3fb5c1cb                                                         |
╰----------+-------------------------------+--------------------------------------------------------------------╯


"#]]);

    // `events`: anonymous event must render with an empty topic.
    cmd.forge_fuse().args(["inspect", "Counter", "events"]).assert_success().stdout_eq(str![[r#"

╭----------------------+--------------------------------------------------------------------╮
| Event                | Topic                                                              |
+===========================================================================================+
| FakeEvent(uint256)   |                                                                    |
|----------------------+--------------------------------------------------------------------|
| NormalEvent(uint256) | 0x2e58689f6eed514b3003e0a132a1284ba04042a9d59d11c5db973fdb520d10c1 |
╰----------------------+--------------------------------------------------------------------╯


"#]]);

    // `events --json`: anonymous event must serialize as `null`, not a fake hash.
    cmd.forge_fuse().args(["inspect", "Counter", "events", "--json"]).assert_success().stdout_eq(
        str![[r#"
{
  "FakeEvent(uint256)": null,
  "NormalEvent(uint256)": "0x2e58689f6eed514b3003e0a132a1284ba04042a9d59d11c5db973fdb520d10c1"
}

"#]],
    );
});

forgetest!(inspect_path_only_identifier, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER);

    cmd.args(["inspect", "src/Counter.sol", "errors"]).assert_success().stdout_eq(str![[r#"

╭-------------------------------+----------╮
| Error                         | Selector |
+==========================================+
| CustomErr(Counter.ErrWithMsg) | 0625625a |
|-------------------------------+----------|
| NumberIsZero()                | de5d32ac |
╰-------------------------------+----------╯


"#]]);
});

forgetest!(test_inspect_contract_with_same_name, |prj, cmd| {
    let source = format!("{CUSTOM_COUNTER}\n{ANOTHER_COUNTER}");
    prj.add_source("Counter.sol", &source);

    cmd.args(["inspect", "src/Counter.sol", "errors"]).assert_failure().stderr_eq(str![[r#"Error: Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>[..]"#]]);

    cmd.forge_fuse().args(["inspect", "Counter", "errors"]).assert_success().stdout_eq(str![[r#"

╭-------------------------------+----------╮
| Error                         | Selector |
+==========================================+
| CustomErr(Counter.ErrWithMsg) | 0625625a |
|-------------------------------+----------|
| NumberIsZero()                | de5d32ac |
╰-------------------------------+----------╯


"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/10531>
forgetest!(inspect_multiple_contracts_with_different_paths, |prj, cmd| {
    prj.add_source(
        "Source.sol",
        r#"
    contract Source {
        function foo() public {}
    }
    "#,
    );

    prj.add_source(
        "another/Source.sol",
        r#"
    contract Source {
        function bar() public {}
    }
    "#,
    );

    cmd.args(["inspect", "src/another/Source.sol:Source", "methodIdentifiers"])
        .assert_success()
        .stdout_eq(str![[r#"

╭--------+------------╮
| Method | Identifier |
+=====================+
| bar()  | febb0f7e   |
╰--------+------------╯


"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/11146>
forgetest!(inspect_contracts_by_exact_input_path, |prj, cmd| {
    prj.add_test(
        "InspectTarget.t.sol",
        r#"
contract InspectTarget {
    function testValue() external pure returns (uint256) {
        return 1;
    }
}
"#,
    );
    prj.create_file(
        "node_modules/example/Dependency.sol",
        r#"
pragma solidity ^0.8.0;

contract Dependency {
    function dependencyValue() external pure returns (uint256) {
        return 2;
    }
}
"#,
    );
    prj.update_config(|config| config.libs.push("node_modules".into()));

    for (target, function) in [
        ("test/InspectTarget.t.sol:InspectTarget", "testValue"),
        ("node_modules/example/Dependency.sol:Dependency", "dependencyValue"),
    ] {
        let stdout = cmd
            .forge_fuse()
            .args(["inspect", target, "abi", "--json"])
            .assert_success()
            .get_output()
            .stdout_lossy();
        let abi: serde_json::Value = serde_json::from_str(&stdout).unwrap();
        assert!(abi.as_array().unwrap().iter().any(|item| item["name"] == function));
    }
});

forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER);

    cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#"

╭----------------------------+------------╮
| Method                     | Identifier |
+=========================================+
| count()                    | 06661abd   |
|----------------------------+------------|
| decrement()                | 2baeceb7   |
|----------------------------+------------|
| increment()                | d09de08a   |
|----------------------------+------------|
| number()                   | 8381f58a   |
|----------------------------+------------|
| setNumber(uint256)         | 3fb5c1cb   |
|----------------------------+------------|
| setStruct((uint64),uint32) | 08ef7366   |
|----------------------------+------------|
| square()                   | d742cb01   |
╰----------------------------+------------╯


"#]]);
});

const CUSTOM_COUNTER_HUGE_METHOD_IDENTIFIERS: &str = r#"
contract Counter {
    struct BigStruct {
        uint256 a;
        uint256 b;
        uint256 c;
        uint256 d;
        uint256 e;
        uint256 f;
    }

    struct NestedBigStruct {
        BigStruct a;
        BigStruct b;
        BigStruct c;
    }

    function hugeIdentifier(NestedBigStruct[] calldata _bigStructs, NestedBigStruct calldata _bigStruct) external {}
}
"#;

forgetest!(inspect_custom_counter_very_huge_method_identifiers_unwrapped, |prj, cmd| {
    prj.add_source("Counter.sol", CUSTOM_COUNTER_HUGE_METHOD_IDENTIFIERS);

    cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#"

╭-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╮
| Method                                                                                                                                                                                                                                                                                                                            | Identifier |
+================================================================================================================================================================================================================================================================================================================================================+
| hugeIdentifier(((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))[],((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))) | f38dafbb   |
╰-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------╯


"#]]);

    cmd.forge_fuse().args(["inspect", "Counter", "method-identifiers", "--md"]).assert_success().stdout_eq(str![[r#"

| Method                                                                                                                                                                                                                                                                                                                            | Identifier |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|
| hugeIdentifier(((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))[],((uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256),(uint256,uint256,uint256,uint256,uint256,uint256))) | f38dafbb   |


"#]]);
});

forgetest_init!(can_inspect_standard_json, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["inspect", "src/Counter.sol:Counter", "standard-json"]).assert_success().stdout_eq(str![[r#"
{
  "language": "Solidity",
  "sources": {
    "src/Counter.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED/npragma solidity ^0.8.13;/n/ncontract Counter {/n    uint256 public number;/n/n    function setNumber(uint256 newNumber) public {/n        number = newNumber;/n    }/n/n    function increment() public {/n        number++;/n    }/n}/n"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/"
    ],
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "abi",
          "evm.bytecode.object",
          "evm.bytecode.sourceMap",
          "evm.bytecode.linkReferences",
          "evm.deployedBytecode.object",
          "evm.deployedBytecode.sourceMap",
          "evm.deployedBytecode.linkReferences",
          "evm.deployedBytecode.immutableReferences",
          "evm.methodIdentifiers",
          "metadata"
        ]
      }
    },
    "evmVersion": "osaka",
    "viaIR": false,
    "viaSSACFG": false,
    "experimental": false,
    "libraries": {}
  }
}

"#]]);
});

forgetest!(can_inspect_artifact_json, |prj, cmd| {
    prj.add_source(
        "Counter.sol",
        r#"
contract Counter {
    uint256 public number;

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

    let stdout =
        cmd.args(["inspect", "Counter", "artifact"]).assert_success().get_output().stdout_lossy();
    let artifact: serde_json::Value =
        serde_json::from_str(stdout.trim()).expect("artifact stdout should be valid JSON");

    let abi = artifact["abi"].as_array().expect("artifact should include an ABI array");
    assert!(abi.iter().any(|item| {
        item.get("type").and_then(|value| value.as_str()) == Some("function")
            && item.get("name").and_then(|value| value.as_str()) == Some("increment")
    }));

    let bytecode =
        artifact["bytecode"]["object"].as_str().expect("artifact should include creation bytecode");
    assert!(bytecode.starts_with("0x"));

    let deployed_bytecode = artifact["deployedBytecode"]["object"]
        .as_str()
        .expect("artifact should include deployed bytecode");
    assert!(deployed_bytecode.starts_with("0x"));
});

forgetest!(can_inspect_artifact_json_with_custom_out_and_duplicate_contract_names, |prj, cmd| {
    prj.update_config(|config| config.out = "custom-out".into());

    prj.add_source(
        "Source.sol",
        r#"
contract Source {
    function foo() public {}
}
    "#,
    );
    prj.add_source(
        "another/Source.sol",
        r#"
contract Source {
    function bar() public {}
}
    "#,
    );

    let stdout = cmd
        .args(["inspect", "src/another/Source.sol:Source", "output"])
        .assert_success()
        .get_output()
        .stdout_lossy();
    let artifact: serde_json::Value =
        serde_json::from_str(stdout.trim()).expect("artifact stdout should be valid JSON");

    let abi = artifact["abi"].as_array().expect("artifact should include an ABI array");
    assert!(abi.iter().any(|item| {
        item.get("type").and_then(|value| value.as_str()) == Some("function")
            && item.get("name").and_then(|value| value.as_str()) == Some("bar")
    }));
    assert!(!abi.iter().any(|item| {
        item.get("type").and_then(|value| value.as_str()) == Some("function")
            && item.get("name").and_then(|value| value.as_str()) == Some("foo")
    }));
    assert!(prj.root().join("custom-out/Source.sol/Source.json").exists());
});

forgetest_init!(can_inspect_libraries, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.add_source(
        "Source.sol",
        r#"
    import "./Lib.sol";

    library Lib2 {
        function foo() public {}
    }

    contract Source {
        function foo() public {
            Lib.foo();
            Lib2.foo();
        }
    }"#,
    );

    prj.add_source(
        "Lib.sol",
        r#"
    library Lib {
        function foo() public {}
    }
    "#,
    );

    cmd.args(["inspect", "Source", "libraries"])
        .assert_success()
        .stdout_eq(str![[r#"
src/Lib.sol:Lib
src/Source.sol:Lib2

"#]])
        .stderr_eq(str![[r#"
Dynamically linked libraries:

"#]]);

    cmd.forge_fuse().args(["inspect", "Source", "libraries", "--json"]).assert_success().stdout_eq(
        str![[r#"
[
  "src/Lib.sol:Lib",
  "src/Source.sol:Lib2"
]

"#]],
    );
});

// checks that `clean` also works with the "out" value set in Config
forgetest_init!(gas_report_include_tests, |prj, cmd| {
    prj.initialize_default_contracts();
    prj.update_config(|config| {
        config.gas_reports_include_tests = true;
        config.fuzz.runs = 1;
    });

    cmd.args([
        "test",
        "--match-test",
        "test_Increment",
        "--gas-report",
        "--no-dynamic-test-linking",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
...
╭----------------------------------+-----------------+-------+--------+-------+---------╮
| src/Counter.sol:Counter Contract |                 |       |        |       |         |
+=======================================================================================+
| Deployment Cost                  | Deployment Size |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
|                           156801 |             509 |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
|                                  |                 |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                    | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------+-----------------+-------+--------+-------+---------|
| increment                        |           43482 | 43482 |  43482 | 43482 |       1 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| number                           |            2424 |  2424 |   2424 |  2424 |       1 |
|----------------------------------+-----------------+-------+--------+-------+---------|
| setNumber                        |           23784 | 23784 |  23784 | 23784 |       1 |
╰----------------------------------+-----------------+-------+--------+-------+---------╯

╭-----------------------------------------+-----------------+--------+--------+--------+---------╮
| test/Counter.t.sol:CounterTest Contract |                 |        |        |        |         |
+================================================================================================+
| Deployment Cost                         | Deployment Size |        |        |        |         |
|-----------------------------------------+-----------------+--------+--------+--------+---------|
|                                 1544498 |            7573 |        |        |        |         |
|-----------------------------------------+-----------------+--------+--------+--------+---------|
|                                         |                 |        |        |        |         |
|-----------------------------------------+-----------------+--------+--------+--------+---------|
| Function Name                           | Min             | Avg    | Median | Max    | # Calls |
|-----------------------------------------+-----------------+--------+--------+--------+---------|
| setUp                                   |          218890 | 218890 | 218890 | 218890 |       1 |
|-----------------------------------------+-----------------+--------+--------+--------+---------|
| test_Increment                          |           51847 |  51847 |  51847 |  51847 |       1 |
╰-----------------------------------------+-----------------+--------+--------+--------+---------╯


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

"#]]);

    cmd.forge_fuse()
        .args([
            "test",
            "--match-test",
            "test_Increment",
            "--gas-report",
            "--md",
            "--no-dynamic-test-linking",
        ])
        .assert_success()
        .stdout_eq(str![[r#"
...
| src/Counter.sol:Counter Contract |                 |       |        |       |         |
|----------------------------------|-----------------|-------|--------|-------|---------|
| Deployment Cost                  | Deployment Size |       |        |       |         |
|                           156801 |             509 |       |        |       |         |
|                                  |                 |       |        |       |         |
| Function Name                    | Min             | Avg   | Median | Max   | # Calls |
| increment                        |           43482 | 43482 |  43482 | 43482 |       1 |
| number                           |            2424 |  2424 |   2424 |  2424 |       1 |
| setNumber                        |           23784 | 23784 |  23784 | 23784 |       1 |

| test/Counter.t.sol:CounterTest Contract |                 |        |        |        |         |
|-----------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost                         | Deployment Size |        |        |        |         |
|                                 1544498 |            7573 |        |        |        |         |
|                                         |                 |        |        |        |         |
| Function Name                           | Min             | Avg    | Median | Max    | # Calls |
| setUp                                   |          218890 | 218890 | 218890 | 218890 |       1 |
| test_Increment                          |           51847 |  51847 |  51847 |  51847 |       1 |


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

"#]]);

    cmd.forge_fuse()
        .args([
            "test",
            "--mt",
            "test_Increment",
            "--gas-report",
            "--json",
            "--no-dynamic-test-linking",
        ])
        .assert_success()
        .stdout_eq(
            str![[r#"
[
  {
    "contract": "src/Counter.sol:Counter",
    "deployment": {
      "gas": 156801,
      "size": 509
    },
    "functions": {
      "increment()": {
        "calls": 1,
        "min": 43482,
        "mean": 43482,
        "median": 43482,
        "max": 43482
      },
      "number()": {
        "calls": 1,
        "min": 2424,
        "mean": 2424,
        "median": 2424,
        "max": 2424
      },
      "setNumber(uint256)": {
        "calls": 1,
        "min": 23784,
        "mean": 23784,
        "median": 23784,
        "max": 23784
      }
    }
  },
  {
    "contract": "test/Counter.t.sol:CounterTest",
    "deployment": {
      "gas": 1544498,
      "size": 7573
    },
    "functions": {
      "setUp()": {
        "calls": 1,
        "min": 218890,
        "mean": 218890,
        "median": 218890,
        "max": 218890
      },
      "test_Increment()": {
        "calls": 1,
        "min": 51847,
        "mean": 51847,
        "median": 51847,
        "max": 51847
      }
    }
  }
]
"#]]
            .is_json(),
        );
});

forgetest_async!(gas_report_fuzz_invariant, |prj, cmd| {
    // speed up test by running with depth of 15
    prj.update_config(|config| config.invariant.depth = 15);

    prj.insert_ds_test();
    prj.add_source(
        "Contracts.sol",
        r#"
import "./test.sol";

contract Foo {
    function foo() public {}
}

contract Bar {
    function bar() public {}
}

contract FooBarTest is DSTest {
    Foo public targetContract;

    function setUp() public {
        targetContract = new Foo();
    }

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

    function testFuzz_bar(uint256 _val) public {
        (new Bar()).bar();
    }
}
    "#,
    );

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

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

    prj.add_source(
        "Enum.sol",
        r#"
    contract Enum {
        enum MyEnum { A, B, C }
    }
    "#,
    );

    prj.add_source(
        "UseEnum.sol",
        r#"
    import "./Enum.sol";
    contract UseEnum {
        Enum.MyEnum public myEnum;
    }"#,
    );

    cmd.args(["bind", "--select", "^Enum$"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]])
        .stderr_eq(str![[r#"
Generating bindings for 1 contracts
Bindings have been generated to [..]

"#]]);
});

// forge bind e2e
forgetest_init!(can_bind_e2e, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["bind"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

"#]])
        .stderr_eq(str![[r#"
Generating bindings for 2 contracts
Bindings have been generated to [..]

"#]]);

    let bindings_path = prj.root().join("out/bindings");

    assert!(bindings_path.exists(), "Bindings directory should exist");
    let out = Command::new("cargo")
        .arg("build")
        .current_dir(&bindings_path)
        .output()
        .expect("Failed to run cargo build");

    assert!(out.status.success(), "Cargo build should succeed");
});

// `forge flatten -o <path>` writes the file and emits its status string to stderr,
// keeping stdout empty so agents can pipe the command without diagnostics.
forgetest!(flatten_output_writes_status_to_stderr, |prj, cmd| {
    prj.add_source(
        "Counter",
        r#"contract Counter { uint256 public n; function inc() external { n += 1; } }"#,
    );

    let out = prj.root().join("flat.sol");
    cmd.args(["flatten", "src/Counter.sol", "-o"])
        .arg(&out)
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Flattened file written at [..]flat.sol

"#]]);

    assert!(out.exists(), "flattened file should have been written");
});

// `forge generate test` writes the scaffolded file and emits its status string to stderr,
// keeping stdout empty so agents can pipe the command without diagnostics.
forgetest!(generate_test_writes_status_to_stderr, |prj, cmd| {
    cmd.args(["generate", "test", "--contract-name", "Counter"])
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Warning: `forge generate` is deprecated and will be removed in a future version
Generated test file: test/Counter.t.sol

"#]]);

    assert!(prj.root().join("test/Counter.t.sol").exists(), "scaffolded test file should exist");
});

// `forge init` writes its status prose to stderr and keeps stdout empty so agents
// can pipe the command without diagnostics. Uses `--offline` + `--no-git` to skip
// network and git side-effects that would otherwise add prose to stdout.
forgetest!(init_status_on_stderr, |prj, cmd| {
    prj.wipe();

    cmd.args(["init", "--offline", "--no-git"])
        .arg(prj.root())
        .assert_success()
        .stdout_eq(str![""])
        .stderr_eq(str![[r#"
Initializing [..]...
    Initialized forge project

"#]]);

    assert!(prj.root().join("foundry.toml").exists());
});
