diff --git a/Cargo.lock b/Cargo.lock index 9138a000ee0d..aaa5f5ef207a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4056,6 +4056,7 @@ dependencies = [ "regex", "serde_json", "similar-asserts", + "snapbox", "tracing", "tracing-subscriber", "walkdir", @@ -5689,6 +5690,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "normalize-path" version = "0.2.1" @@ -7965,6 +7972,30 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "snapbox" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699c824ef8c2061c39efb3af4f334310b3acbfb2a50c6d1f867e4d95dcff94be" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "serde", + "serde_json", + "similar", + "snapbox-macros", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" +dependencies = [ + "anstream", +] + [[package]] name = "socket2" version = "0.5.7" diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 7d7e338811ec..8349f6d3ec5d 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -4,6 +4,7 @@ use alloy_primitives::{address, b256, Address, B256}; use foundry_test_utils::{ casttest, rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}, + str, util::OutputExt, }; use std::{fs, io::Write, path::Path, str::FromStr}; @@ -103,9 +104,10 @@ casttest!(wallet_sign_message_hex_data, |_prj, cmd| { "--private-key", "0x0000000000000000000000000000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000000000000000000000000000000", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x23a42ca5616ee730ff3735890c32fc7b9491a9f633faca9434797f2c845f5abf4d9ba23bd7edb8577acebaa3644dc5a4995296db420522bb40060f1693c33c9b1c"); + ]).assert_success().stdout_eq(str![[r#" +0x23a42ca5616ee730ff3735890c32fc7b9491a9f633faca9434797f2c845f5abf4d9ba23bd7edb8577acebaa3644dc5a4995296db420522bb40060f1693c33c9b1c + +"#]]); }); // tests that `cast wallet sign typed-data` outputs the expected signature, given a JSON string @@ -117,9 +119,10 @@ casttest!(wallet_sign_typed_data_string, |_prj, cmd| { "0x0000000000000000000000000000000000000000000000000000000000000001", "--data", "{\"types\": {\"EIP712Domain\": [{\"name\": \"name\",\"type\": \"string\"},{\"name\": \"version\",\"type\": \"string\"},{\"name\": \"chainId\",\"type\": \"uint256\"},{\"name\": \"verifyingContract\",\"type\": \"address\"}],\"Message\": [{\"name\": \"data\",\"type\": \"string\"}]},\"primaryType\": \"Message\",\"domain\": {\"name\": \"example.metamask.io\",\"version\": \"1\",\"chainId\": \"1\",\"verifyingContract\": \"0x0000000000000000000000000000000000000000\"},\"message\": {\"data\": \"Hello!\"}}", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b"); + ]).assert_success().stdout_eq(str![[r#" +0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b + +"#]]); }); // tests that `cast wallet sign typed-data` outputs the expected signature, given a JSON file @@ -137,9 +140,10 @@ casttest!(wallet_sign_typed_data_file, |_prj, cmd| { .into_string() .unwrap() .as_str(), - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b"); + ]).assert_success().stdout_eq(str![[r#" +0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b + +"#]]); }); // tests that `cast wallet list` outputs the local accounts @@ -177,9 +181,12 @@ casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| { "private-key", "test test test test test test test test test test test junk", "1", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +"#]]); }); // tests that `cast wallet private-key` with options outputs the private key diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 93b313742678..acbb54560361 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -1,8 +1,6 @@ -use foundry_common::fs::read_json_file; use foundry_config::Config; -use foundry_test_utils::forgetest; +use foundry_test_utils::{file, forgetest, str}; use globset::Glob; -use std::{collections::BTreeMap, path::PathBuf}; // tests that json is printed when --json is passed forgetest!(compile_json, |prj, cmd| { @@ -20,26 +18,14 @@ contract Dummy { .unwrap(); // set up command - cmd.args(["compile", "--format-json"]); - - // Exclude build_infos from output as IDs depend on root dir and are not deterministic. - let mut output: BTreeMap = - serde_json::from_str(&cmd.stdout_lossy()).unwrap(); - output.remove("build_infos"); - - let expected: BTreeMap = read_json_file( - &PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"), - ) - .unwrap(); - - similar_asserts::assert_eq!(output, expected); + cmd.args(["compile", "--format-json"]) + .assert() + .stdout_eq(file!["../fixtures/compile_json.stdout": Json]); }); // tests build output is as expected forgetest_init!(exact_build_output, |prj, cmd| { - cmd.args(["build", "--force"]); - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("Compiling"), "\n{stdout}"); + cmd.args(["build", "--force"]).assert_success().stdout_eq(str!["Compiling[..]\n..."]); }); // tests build output is as expected diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index a5c9b12b81af..6aaa4f536c44 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -9,10 +9,10 @@ use anvil::{spawn, NodeConfig}; use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; use foundry_config::Config; use foundry_test_utils::{ - forgetest, forgetest_async, - util::{OutputExt, TestCommand, TestProject}, + forgetest, forgetest_async, str, + util::{TestCommand, TestProject}, }; -use std::{path::PathBuf, str::FromStr}; +use std::str::FromStr; /// This will insert _dummy_ contract that uses a library /// @@ -150,15 +150,22 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { pk.as_str(), ]); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract.stdout"), - ); + cmd.assert().stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +Transaction hash: [..] - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract-2nd.stdout"), - ); +"#]]); + + cmd.assert().stdout_eq(str![[r#" +No files changed, compilation skipped +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Transaction hash: [..] + +"#]]); }); // tests that we can deploy the template contract @@ -183,15 +190,21 @@ forgetest_async!(can_create_using_unlocked, |prj, cmd| { "--unlocked", ]); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked.stdout"), - ); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked-2nd.stdout"), - ); + cmd.assert().stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +Transaction hash: [..] + +"#]]); + cmd.assert().stdout_eq(str![[r#" +No files changed, compilation skipped +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Transaction hash: [..] + +"#]]); }); // tests that we can deploy with constructor args @@ -221,21 +234,26 @@ contract ConstructorContract { ) .unwrap(); - cmd.forge_fuse().args([ - "create", - "./src/ConstructorContract.sol:ConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "My Constructor", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_constructor_args.stdout"), - ); + cmd.forge_fuse() + .args([ + "create", + "./src/ConstructorContract.sol:ConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "My Constructor", + ]) + .assert_success() + .stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +Transaction hash: [..] + +"#]]); prj.add_source( "TupleArrayConstructorContract", @@ -252,21 +270,26 @@ contract TupleArrayConstructorContract { ) .unwrap(); - cmd.forge_fuse().args([ - "create", - "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "[(1,2), (2,3), (3,4)]", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_tuple_constructor_args.stdout"), - ); + cmd.forge_fuse() + .args([ + "create", + "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "[(1,2), (2,3), (3,4)]", + ]) + .assert() + .stdout_eq(str![[r#" +... +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +Transaction hash: [..] + +"#]]); }); // diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index dbbd348d302b..01db02fddc30 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -548,7 +548,7 @@ contract Dummy { .unwrap(); cmd.args(["test", "--match-path", "src/dummy.sol"]); - cmd.assert_success() + cmd.assert_success(); }); forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| { diff --git a/crates/forge/tests/fixtures/can_create_template_contract-2nd.stdout b/crates/forge/tests/fixtures/can_create_template_contract-2nd.stdout deleted file mode 100644 index c04ae5bd577d..000000000000 --- a/crates/forge/tests/fixtures/can_create_template_contract-2nd.stdout +++ /dev/null @@ -1,4 +0,0 @@ -No files changed, compilation skipped -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -Transaction hash: 0x3d78b08c411f05d5e79adc92a4c814e0f818d1a09c111b0ab688270f35a07ae7 diff --git a/crates/forge/tests/fixtures/can_create_template_contract.stdout b/crates/forge/tests/fixtures/can_create_template_contract.stdout deleted file mode 100644 index 533c92727501..000000000000 --- a/crates/forge/tests/fixtures/can_create_template_contract.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 2.27s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x4c3d9f7c4cc26876b43a11ba7ff218374471786a8ae8bf5574deb1d97fc1e851 diff --git a/crates/forge/tests/fixtures/can_create_using_unlocked-2nd.stdout b/crates/forge/tests/fixtures/can_create_using_unlocked-2nd.stdout deleted file mode 100644 index c04ae5bd577d..000000000000 --- a/crates/forge/tests/fixtures/can_create_using_unlocked-2nd.stdout +++ /dev/null @@ -1,4 +0,0 @@ -No files changed, compilation skipped -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -Transaction hash: 0x3d78b08c411f05d5e79adc92a4c814e0f818d1a09c111b0ab688270f35a07ae7 diff --git a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout b/crates/forge/tests/fixtures/can_create_using_unlocked.stdout deleted file mode 100644 index 1f8b60d6f40e..000000000000 --- a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 1.95s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x4c3d9f7c4cc26876b43a11ba7ff218374471786a8ae8bf5574deb1d97fc1e851 diff --git a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout b/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout deleted file mode 100644 index 299ad2f2d85f..000000000000 --- a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 2.82s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x294df85109c991ec2760cd51e5ddc869bf5dc3b249b296305ffcd1a0563b2eea diff --git a/crates/forge/tests/fixtures/can_create_with_tuple_constructor_args.stdout b/crates/forge/tests/fixtures/can_create_with_tuple_constructor_args.stdout deleted file mode 100644 index a0a574c959db..000000000000 --- a/crates/forge/tests/fixtures/can_create_with_tuple_constructor_args.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 26.44ms -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -Transaction hash: 0x69625b76d83634603a9dbc5b836ef89bafdd9fc7c180fc6d636c5088353cf501 diff --git a/crates/forge/tests/fixtures/compile_json.stdout b/crates/forge/tests/fixtures/compile_json.stdout index eff78e60c8d7..2a794cc4a8a9 100644 --- a/crates/forge/tests/fixtures/compile_json.stdout +++ b/crates/forge/tests/fixtures/compile_json.stdout @@ -15,5 +15,6 @@ "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n --> src/jsonError.sol:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n" } ], - "sources": {} + "sources": {}, + "build_infos": ["{...}"] } \ No newline at end of file diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 9b762039acb0..e3cb1f5429d4 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -33,6 +33,7 @@ tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } walkdir.workspace = true rand.workspace = true +snapbox = { version = "0.6.9", features = ["json"] } [features] # feature for integration tests that test external projects diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 28bb4def123e..3782aba7d502 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -28,6 +28,8 @@ pub use script::{ScriptOutcome, ScriptTester}; // re-exports for convenience pub use foundry_compilers; +pub use snapbox::{file, str}; + /// Initializes tracing for tests. pub fn init_tracing() { let _ = tracing_subscriber::FmtSubscriber::builder() diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index b897aacb6340..56d492ebdff8 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -12,6 +12,7 @@ use foundry_config::Config; use once_cell::sync::Lazy; use parking_lot::Mutex; use regex::Regex; +use snapbox::cmd::OutputAssert; use std::{ env, ffi::OsStr, @@ -921,8 +922,8 @@ impl TestCommand { /// Runs the command and asserts that it resulted in success #[track_caller] - pub fn assert_success(&mut self) { - self.output(); + pub fn assert_success(&mut self) -> OutputAssert { + self.assert().success() } /// Executes command, applies stdin function and returns output @@ -1055,6 +1056,10 @@ stderr: lossy_string(&out.stderr), ) } + + pub fn assert(&mut self) -> OutputAssert { + OutputAssert::new(self.execute()) + } } /// Extension trait for [`Output`].