From 44f3196b93d8ab5b322fbff2f648a05855488d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:24:01 +0100 Subject: [PATCH] Get features from all targets (#2570) Closes #2568 Closes #2567 ## Introduced changes - Fixed features support for optimized compilation - Changed the starknet artifacts loading logic so they work with custom test targets. - Artifacts are now loaded from all test targets - Artifacts are now compiled to casm concurrently - Refactored the scarb-api crate ## Checklist - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md` --------- Co-authored-by: Piotr Magiera <56825108+piotmag769@users.noreply.github.com> --- .github/workflows/ci.yml | 5 +- CHANGELOG.md | 14 +- Cargo.lock | 2 + crates/cheatnet/tests/common/mod.rs | 8 +- crates/forge/src/run_tests/package.rs | 9 +- crates/forge/src/run_tests/workspace.rs | 4 +- crates/forge/src/scarb.rs | 26 +- crates/forge/test_utils/src/runner.rs | 23 +- .../benchmarks/declare_deploy_interact.cairo | 2 +- .../tests/data/contracts/hello_starknet.cairo | 4 +- .../l1_handler_execute_checker.cairo | 2 +- .../contracts/response_with_2_felts.cairo | 2 +- .../tests/data/hello_workspaces/Scarb.toml | 3 + .../simple_package/src/hello_starknet.cairo | 4 +- .../data/targets/custom_target/Scarb.toml | 25 ++ .../data/targets/custom_target/src/lib.cairo | 55 +++ .../targets/custom_target/tests/tests.cairo | 28 ++ .../custom_target_custom_names/Scarb.toml | 25 ++ .../custom_target_custom_names/src/lib.cairo | 55 +++ .../tests/tests.cairo | 28 ++ .../custom_target_only_integration/Scarb.toml | 21 + .../src/lib.cairo | 55 +++ .../tests/tests.cairo | 28 ++ .../data/targets/only_integration/Scarb.toml | 14 + .../targets/only_integration/src/lib.cairo | 43 ++ .../only_integration/tests/tests.cairo | 28 ++ .../targets/only_lib_integration/Scarb.toml | 14 + .../only_lib_integration/src/lib.cairo | 43 ++ .../only_lib_integration/tests/lib.cairo | 1 + .../only_lib_integration/tests/tests.cairo | 28 ++ .../tests/data/targets/only_unit/Scarb.toml | 14 + .../data/targets/only_unit/src/lib.cairo | 55 +++ .../targets/unit_and_integration/Scarb.toml | 14 + .../unit_and_integration/src/lib.cairo | 55 +++ .../unit_and_integration/tests/tests.cairo | 28 ++ .../unit_and_lib_integration/Scarb.toml | 14 + .../unit_and_lib_integration/src/lib.cairo | 55 +++ .../unit_and_lib_integration/tests/lib.cairo | 1 + .../tests/tests.cairo | 28 ++ .../data/targets/with_features/Scarb.toml | 17 + .../data/targets/with_features/src/lib.cairo | 58 +++ .../targets/with_features/tests/tests.cairo | 28 ++ crates/forge/tests/e2e/contract_artifacts.rs | 285 ++++++++++++ crates/forge/tests/integration/declare.rs | 2 +- crates/scarb-api/Cargo.toml | 2 + crates/scarb-api/src/artifacts.rs | 94 ++++ .../scarb-api/src/artifacts/deserialized.rs | 39 ++ .../scarb-api/src/artifacts/representation.rs | 83 ++++ crates/scarb-api/src/lib.rs | 422 ++++++++---------- crates/sncast/src/helpers/scarb_utils.rs | 18 +- .../src/simple_contract.cairo | 2 +- .../crates/using_cheatcodes/src/lib.cairo | 2 +- 52 files changed, 1611 insertions(+), 304 deletions(-) create mode 100644 crates/forge/tests/data/targets/custom_target/Scarb.toml create mode 100644 crates/forge/tests/data/targets/custom_target/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/custom_target/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/custom_target_custom_names/Scarb.toml create mode 100644 crates/forge/tests/data/targets/custom_target_custom_names/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/custom_target_custom_names/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/custom_target_only_integration/Scarb.toml create mode 100644 crates/forge/tests/data/targets/custom_target_only_integration/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/custom_target_only_integration/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/only_integration/Scarb.toml create mode 100644 crates/forge/tests/data/targets/only_integration/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/only_integration/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/only_lib_integration/Scarb.toml create mode 100644 crates/forge/tests/data/targets/only_lib_integration/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/only_lib_integration/tests/lib.cairo create mode 100644 crates/forge/tests/data/targets/only_lib_integration/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/only_unit/Scarb.toml create mode 100644 crates/forge/tests/data/targets/only_unit/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/unit_and_integration/Scarb.toml create mode 100644 crates/forge/tests/data/targets/unit_and_integration/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/unit_and_integration/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/unit_and_lib_integration/Scarb.toml create mode 100644 crates/forge/tests/data/targets/unit_and_lib_integration/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/unit_and_lib_integration/tests/lib.cairo create mode 100644 crates/forge/tests/data/targets/unit_and_lib_integration/tests/tests.cairo create mode 100644 crates/forge/tests/data/targets/with_features/Scarb.toml create mode 100644 crates/forge/tests/data/targets/with_features/src/lib.cairo create mode 100644 crates/forge/tests/data/targets/with_features/tests/tests.cairo create mode 100644 crates/scarb-api/src/artifacts.rs create mode 100644 crates/scarb-api/src/artifacts/deserialized.rs create mode 100644 crates/scarb-api/src/artifacts/representation.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a15074eaf4..edb642c204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: needs: build-test-forge-e2e-nextest-archive strategy: matrix: - partition: [1, 2, 3, 4, 5, 6, 7, 8] + partition: [ 1, 2, 3, 4, 5, 6, 7, 8 ] steps: - name: Extract branch name if: github.event_name != 'pull_request' @@ -119,6 +119,9 @@ jobs: - uses: software-mansion/setup-universal-sierra-compiler@v1 - run: | cargo test --package forge --features scarb_2_8_3 e2e::contract_artifacts + - run: | + cargo test --package forge --features scarb_2_8_3 e2e::features + - run: | cargo test --package scarb-api --features scarb_2_8_3 get_starknet_artifacts_path test-forge-runner: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e77f97dcc..333f36e633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `snforge test --build-profile -- --show-inlined-functions` - You can't use now `--coverage` and `--build-profile` flags at the same time. If you want to use both, you need to run `snforge test` twice with different flags. +- Contract artifacts are compiled to CASM concurrently. +- Starknet artifacts are now loaded from all tests targets +- Cairo Edition in `snforge init` template set to `2024_07` + +#### Fixed + +- Scarb features work with optimized compilation +- Custom test targets are now supported with optimized compilation ## [0.32.0] - 2024-10-16 @@ -40,12 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - When using test name filter with `--exact` flag, forge will try to compile only the selected test. -### Forge - -#### Changed - -- Cairo Edition in `snforge init` template set to `2024_07` - ## [0.31.0] - 2024-09-26 ### Cast diff --git a/Cargo.lock b/Cargo.lock index 7797e8f7ea..030cbb2a0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4295,6 +4295,8 @@ dependencies = [ "assert_fs", "camino", "indoc", + "itertools 0.12.1", + "rayon", "regex", "scarb-metadata", "scarb-ui", diff --git a/crates/cheatnet/tests/common/mod.rs b/crates/cheatnet/tests/common/mod.rs index 33f8ce2a55..b1e2dd8400 100644 --- a/crates/cheatnet/tests/common/mod.rs +++ b/crates/cheatnet/tests/common/mod.rs @@ -26,7 +26,9 @@ use conversions::string::TryFromHexStr; use conversions::IntoConv; use runtime::starknet::context::build_context; use scarb_api::metadata::MetadataCommandExt; -use scarb_api::{get_contracts_artifacts_and_source_sierra_paths, ScarbCommand}; +use scarb_api::{ + get_contracts_artifacts_and_source_sierra_paths, target_dir_for_workspace, ScarbCommand, +}; use starknet::core::utils::get_selector_from_name; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; use starknet_api::deprecated_contract_class::EntryPointType; @@ -72,12 +74,12 @@ pub fn get_contracts() -> ContractsData { .manifest_path("tests/contracts/Scarb.toml") .run() .unwrap(); + let target_dir = target_dir_for_workspace(&scarb_metadata).join("dev"); let package = scarb_metadata.packages.first().unwrap(); let contracts = - get_contracts_artifacts_and_source_sierra_paths(&scarb_metadata, &package.id, None, false) - .unwrap(); + get_contracts_artifacts_and_source_sierra_paths(&target_dir, package, false).unwrap(); ContractsData::try_from(contracts).unwrap() } diff --git a/crates/forge/src/run_tests/package.rs b/crates/forge/src/run_tests/package.rs index e5ffdc203f..7f6f8e9058 100644 --- a/crates/forge/src/run_tests/package.rs +++ b/crates/forge/src/run_tests/package.rs @@ -47,15 +47,14 @@ impl RunForPackageArgs { scarb_metadata: &Metadata, args: &TestArgs, cache_dir: &Utf8PathBuf, - snforge_target_dir_path: &Utf8Path, + artifacts_dir: &Utf8Path, versioned_programs_dir: Utf8PathBuf, ) -> Result { - let raw_test_targets = load_test_artifacts(snforge_target_dir_path, &package)?; + let raw_test_targets = load_test_artifacts(artifacts_dir, &package)?; let contracts = get_contracts_artifacts_and_source_sierra_paths( - scarb_metadata, - &package.id, - None, + artifacts_dir, + &package, !should_compile_starknet_contract_target( &scarb_metadata.app_version_info.version, args.no_optimization, diff --git a/crates/forge/src/run_tests/workspace.rs b/crates/forge/src/run_tests/workspace.rs index bac7fc7171..40712b2d39 100644 --- a/crates/forge/src/run_tests/workspace.rs +++ b/crates/forge/src/run_tests/workspace.rs @@ -35,7 +35,7 @@ pub async fn run_for_workspace(args: TestArgs) -> Result { warn_if_snforge_std_not_compatible(&scarb_metadata)?; - let snforge_target_dir_path = + let artifacts_dir_path = target_dir_for_workspace(&scarb_metadata).join(&scarb_metadata.current_profile); let packages: Vec = args @@ -76,7 +76,7 @@ pub async fn run_for_workspace(args: TestArgs) -> Result { &scarb_metadata, &args, &cache_dir, - &snforge_target_dir_path, + &artifacts_dir_path, versioned_programs_dir.clone(), )?; diff --git a/crates/forge/src/scarb.rs b/crates/forge/src/scarb.rs index 0215cb2e4c..57fcbcac25 100644 --- a/crates/forge/src/scarb.rs +++ b/crates/forge/src/scarb.rs @@ -5,11 +5,10 @@ use camino::Utf8Path; use configuration::PackageConfig; use forge_runner::package_tests::raw::TestTargetRaw; use forge_runner::package_tests::TestTargetLocation; -use scarb_api::ScarbCommand; -use scarb_metadata::{PackageMetadata, TargetMetadata}; +use scarb_api::{test_targets_by_name, ScarbCommand}; +use scarb_metadata::PackageMetadata; use scarb_ui::args::{FeaturesSpec, PackagesFilter}; use semver::Version; -use std::collections::HashMap; use std::fs::read_to_string; use std::io::ErrorKind; @@ -76,27 +75,6 @@ fn build_test_artifacts_with_scarb(filter: PackagesFilter, features: FeaturesSpe Ok(()) } -/// collecting by name allow us to dedup targets -/// we do it because they use same sierra and we display them without distinction anyway -fn test_targets_by_name(package: &PackageMetadata) -> HashMap { - fn test_target_name(target: &TargetMetadata) -> String { - // this is logic copied from scarb: https://github.com/software-mansion/scarb/blob/90ab01cb6deee48210affc2ec1dc94d540ab4aea/extensions/scarb-cairo-test/src/main.rs#L115 - target - .params - .get("group-id") // by unit tests grouping - .and_then(|v| v.as_str()) - .map(ToString::to_string) - .unwrap_or(target.name.clone()) // else by integration test name - } - - package - .targets - .iter() - .filter(|target| target.kind == "test") - .map(|target| (test_target_name(target), target)) - .collect() -} - pub fn load_test_artifacts( target_dir: &Utf8Path, package: &PackageMetadata, diff --git a/crates/forge/test_utils/src/runner.rs b/crates/forge/test_utils/src/runner.rs index 9f0e45c425..82ab019c32 100644 --- a/crates/forge/test_utils/src/runner.rs +++ b/crates/forge/test_utils/src/runner.rs @@ -13,8 +13,8 @@ use forge_runner::{ }; use indoc::formatdoc; use scarb_api::{ - get_contracts_artifacts_and_source_sierra_paths, metadata::MetadataCommandExt, ScarbCommand, - StarknetContractArtifacts, + get_contracts_artifacts_and_source_sierra_paths, metadata::MetadataCommandExt, + target_dir_for_workspace, ScarbCommand, StarknetContractArtifacts, }; use shared::command::CommandExt; use std::{ @@ -96,17 +96,14 @@ impl Contract { .iter() .find(|package| package.name == "contract") .unwrap(); - - let contract = get_contracts_artifacts_and_source_sierra_paths( - &scarb_metadata, - &package.id, - None, - false, - ) - .unwrap() - .remove(&self.name) - .ok_or(anyhow!("there is no contract with name {}", self.name))? - .0; + let artifacts_dir = target_dir_for_workspace(&scarb_metadata).join("dev"); + + let contract = + get_contracts_artifacts_and_source_sierra_paths(&artifacts_dir, package, false) + .unwrap() + .remove(&self.name) + .ok_or(anyhow!("there is no contract with name {}", self.name))? + .0; Ok((contract.sierra, contract.casm)) } diff --git a/crates/forge/tests/data/contracts/benchmarks/declare_deploy_interact.cairo b/crates/forge/tests/data/contracts/benchmarks/declare_deploy_interact.cairo index 260933940f..9f542ef593 100644 --- a/crates/forge/tests/data/contracts/benchmarks/declare_deploy_interact.cairo +++ b/crates/forge/tests/data/contracts/benchmarks/declare_deploy_interact.cairo @@ -12,7 +12,7 @@ mod HelloStarknet { balance: felt252, } - // Increases the balance by the given amount. + // Increases the balance by the given amount #[external(v0)] fn increase_balance(ref self: ContractState, amount: felt252) { self.balance.write(self.balance.read() + amount); diff --git a/crates/forge/tests/data/contracts/hello_starknet.cairo b/crates/forge/tests/data/contracts/hello_starknet.cairo index c2310cb9a1..2ab543f589 100644 --- a/crates/forge/tests/data/contracts/hello_starknet.cairo +++ b/crates/forge/tests/data/contracts/hello_starknet.cairo @@ -18,12 +18,12 @@ mod HelloStarknet { #[abi(embed_v0)] impl IHelloStarknetImpl of super::IHelloStarknet { - // Increases the balance by the given amount. + // Increases the balance by the given amount fn increase_balance(ref self: ContractState, amount: felt252) { self.balance.write(self.balance.read() + amount); } - // Returns the current balance. + // Returns the current balance fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } diff --git a/crates/forge/tests/data/contracts/l1_handler_execute_checker.cairo b/crates/forge/tests/data/contracts/l1_handler_execute_checker.cairo index 6ce9b1d270..000dd51cd0 100644 --- a/crates/forge/tests/data/contracts/l1_handler_execute_checker.cairo +++ b/crates/forge/tests/data/contracts/l1_handler_execute_checker.cairo @@ -29,7 +29,7 @@ mod l1_handler_executor { #[abi(embed_v0)] impl IBalanceTokenImpl of super::IBalanceToken { - // Returns the current balance. + // Returns the current balance fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } diff --git a/crates/forge/tests/data/contracts/response_with_2_felts.cairo b/crates/forge/tests/data/contracts/response_with_2_felts.cairo index 980f4a612b..8551936986 100644 --- a/crates/forge/tests/data/contracts/response_with_2_felts.cairo +++ b/crates/forge/tests/data/contracts/response_with_2_felts.cairo @@ -19,7 +19,7 @@ mod ResponseWith2Felts { #[abi(embed_v0)] impl IResponseWith2FeltsImpl of super::IResponseWith2Felts { - // Increases the balance by the given amount. + // Increases the balance by the given amount fn get(self: @ContractState) -> Response { Response { a: 8, b: 43 } } diff --git a/crates/forge/tests/data/hello_workspaces/Scarb.toml b/crates/forge/tests/data/hello_workspaces/Scarb.toml index bb1688e7c4..b3642ca182 100644 --- a/crates/forge/tests/data/hello_workspaces/Scarb.toml +++ b/crates/forge/tests/data/hello_workspaces/Scarb.toml @@ -30,5 +30,8 @@ starknet.workspace = true fibonacci = { path = "crates/fibonacci" } addition = { path = "crates/addition" } +[dev-dependencies] +snforge_std.workspace = true + [[target.starknet-contract]] sierra = true diff --git a/crates/forge/tests/data/simple_package/src/hello_starknet.cairo b/crates/forge/tests/data/simple_package/src/hello_starknet.cairo index 89e4430bb4..259b8de4c7 100644 --- a/crates/forge/tests/data/simple_package/src/hello_starknet.cairo +++ b/crates/forge/tests/data/simple_package/src/hello_starknet.cairo @@ -17,12 +17,12 @@ mod HelloStarknet { #[abi(embed_v0)] impl IHelloStarknetImpl of super::IHelloStarknet { - // Increases the balance by the given amount. + // Increases the balance by the given amount fn increase_balance(ref self: ContractState, amount: felt252) { self.balance.write(self.balance.read() + amount); } - // Returns the current balance. + // Returns the current balance fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } diff --git a/crates/forge/tests/data/targets/custom_target/Scarb.toml b/crates/forge/tests/data/targets/custom_target/Scarb.toml new file mode 100644 index 0000000000..d1a59bd3bc --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target/Scarb.toml @@ -0,0 +1,25 @@ +[package] +name = "custom_target" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[[test]] +name = "custom_target_integrationtest" +kind = "test" +source-path = "./tests/tests.cairo" +test-type = "integration" + +[[test]] +name = "custom_target_unittest" +kind = "test" +test-type = "unit" + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/custom_target/src/lib.cairo b/crates/forge/tests/data/targets/custom_target/src/lib.cairo new file mode 100644 index 0000000000..192ee633fd --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target/src/lib.cairo @@ -0,0 +1,55 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/custom_target/tests/tests.cairo b/crates/forge/tests/data/targets/custom_target/tests/tests.cairo new file mode 100644 index 0000000000..04a6e0c29d --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use custom_target::IHelloStarknetDispatcher; +use custom_target::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/custom_target_custom_names/Scarb.toml b/crates/forge/tests/data/targets/custom_target_custom_names/Scarb.toml new file mode 100644 index 0000000000..895a6f6275 --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target_custom_names/Scarb.toml @@ -0,0 +1,25 @@ +[package] +name = "custom_target_custom_names" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + + +[[test]] +name = "custom_first" +kind = "my_kind" +source-path = "./tests/tests.cairo" +test-type = "integration" +build-external-contracts = ["custom_target_custom_names::*"] + +[[test]] +name = "custom_second" +kind = "my_other_kind" +test-type = "unit" + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/custom_target_custom_names/src/lib.cairo b/crates/forge/tests/data/targets/custom_target_custom_names/src/lib.cairo new file mode 100644 index 0000000000..192ee633fd --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target_custom_names/src/lib.cairo @@ -0,0 +1,55 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/custom_target_custom_names/tests/tests.cairo b/crates/forge/tests/data/targets/custom_target_custom_names/tests/tests.cairo new file mode 100644 index 0000000000..9462eaafd4 --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target_custom_names/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use custom_target_custom_names::IHelloStarknetDispatcher; +use custom_target_custom_names::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/custom_target_only_integration/Scarb.toml b/crates/forge/tests/data/targets/custom_target_only_integration/Scarb.toml new file mode 100644 index 0000000000..f389ff840e --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target_only_integration/Scarb.toml @@ -0,0 +1,21 @@ +[package] +name = "custom_target_only_integration" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[[test]] +name = "custom_first" +kind = "test" +source-path = "./tests/tests.cairo" +test-type = "integration" +build-external-contracts = ["custom_target_only_integration::*"] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/custom_target_only_integration/src/lib.cairo b/crates/forge/tests/data/targets/custom_target_only_integration/src/lib.cairo new file mode 100644 index 0000000000..192ee633fd --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target_only_integration/src/lib.cairo @@ -0,0 +1,55 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/custom_target_only_integration/tests/tests.cairo b/crates/forge/tests/data/targets/custom_target_only_integration/tests/tests.cairo new file mode 100644 index 0000000000..a3e60136ae --- /dev/null +++ b/crates/forge/tests/data/targets/custom_target_only_integration/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use custom_target_only_integration::IHelloStarknetDispatcher; +use custom_target_only_integration::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/only_integration/Scarb.toml b/crates/forge/tests/data/targets/only_integration/Scarb.toml new file mode 100644 index 0000000000..43acb099db --- /dev/null +++ b/crates/forge/tests/data/targets/only_integration/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "only_integration" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/only_integration/src/lib.cairo b/crates/forge/tests/data/targets/only_integration/src/lib.cairo new file mode 100644 index 0000000000..259b8de4c7 --- /dev/null +++ b/crates/forge/tests/data/targets/only_integration/src/lib.cairo @@ -0,0 +1,43 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} diff --git a/crates/forge/tests/data/targets/only_integration/tests/tests.cairo b/crates/forge/tests/data/targets/only_integration/tests/tests.cairo new file mode 100644 index 0000000000..4b83d1243e --- /dev/null +++ b/crates/forge/tests/data/targets/only_integration/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use only_integration::IHelloStarknetDispatcher; +use only_integration::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/only_lib_integration/Scarb.toml b/crates/forge/tests/data/targets/only_lib_integration/Scarb.toml new file mode 100644 index 0000000000..2c6ec0dcc3 --- /dev/null +++ b/crates/forge/tests/data/targets/only_lib_integration/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "only_lib_integration" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/only_lib_integration/src/lib.cairo b/crates/forge/tests/data/targets/only_lib_integration/src/lib.cairo new file mode 100644 index 0000000000..259b8de4c7 --- /dev/null +++ b/crates/forge/tests/data/targets/only_lib_integration/src/lib.cairo @@ -0,0 +1,43 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} diff --git a/crates/forge/tests/data/targets/only_lib_integration/tests/lib.cairo b/crates/forge/tests/data/targets/only_lib_integration/tests/lib.cairo new file mode 100644 index 0000000000..14f00389d0 --- /dev/null +++ b/crates/forge/tests/data/targets/only_lib_integration/tests/lib.cairo @@ -0,0 +1 @@ +mod tests; diff --git a/crates/forge/tests/data/targets/only_lib_integration/tests/tests.cairo b/crates/forge/tests/data/targets/only_lib_integration/tests/tests.cairo new file mode 100644 index 0000000000..1958326629 --- /dev/null +++ b/crates/forge/tests/data/targets/only_lib_integration/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use only_lib_integration::IHelloStarknetDispatcher; +use only_lib_integration::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/only_unit/Scarb.toml b/crates/forge/tests/data/targets/only_unit/Scarb.toml new file mode 100644 index 0000000000..05aa51c5f0 --- /dev/null +++ b/crates/forge/tests/data/targets/only_unit/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "only_unit" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/only_unit/src/lib.cairo b/crates/forge/tests/data/targets/only_unit/src/lib.cairo new file mode 100644 index 0000000000..192ee633fd --- /dev/null +++ b/crates/forge/tests/data/targets/only_unit/src/lib.cairo @@ -0,0 +1,55 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/unit_and_integration/Scarb.toml b/crates/forge/tests/data/targets/unit_and_integration/Scarb.toml new file mode 100644 index 0000000000..b6b75d51f2 --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_integration/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "unit_and_integration" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/unit_and_integration/src/lib.cairo b/crates/forge/tests/data/targets/unit_and_integration/src/lib.cairo new file mode 100644 index 0000000000..192ee633fd --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_integration/src/lib.cairo @@ -0,0 +1,55 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/unit_and_integration/tests/tests.cairo b/crates/forge/tests/data/targets/unit_and_integration/tests/tests.cairo new file mode 100644 index 0000000000..faf56499f6 --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_integration/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use unit_and_integration::IHelloStarknetDispatcher; +use unit_and_integration::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/unit_and_lib_integration/Scarb.toml b/crates/forge/tests/data/targets/unit_and_lib_integration/Scarb.toml new file mode 100644 index 0000000000..da25a6d044 --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_lib_integration/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "unit_and_lib_integration" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/targets/unit_and_lib_integration/src/lib.cairo b/crates/forge/tests/data/targets/unit_and_lib_integration/src/lib.cairo new file mode 100644 index 0000000000..192ee633fd --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_lib_integration/src/lib.cairo @@ -0,0 +1,55 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[starknet::contract] +mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/unit_and_lib_integration/tests/lib.cairo b/crates/forge/tests/data/targets/unit_and_lib_integration/tests/lib.cairo new file mode 100644 index 0000000000..14f00389d0 --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_lib_integration/tests/lib.cairo @@ -0,0 +1 @@ +mod tests; diff --git a/crates/forge/tests/data/targets/unit_and_lib_integration/tests/tests.cairo b/crates/forge/tests/data/targets/unit_and_lib_integration/tests/tests.cairo new file mode 100644 index 0000000000..8da57b44ec --- /dev/null +++ b/crates/forge/tests/data/targets/unit_and_lib_integration/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use unit_and_lib_integration::IHelloStarknetDispatcher; +use unit_and_lib_integration::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/data/targets/with_features/Scarb.toml b/crates/forge/tests/data/targets/with_features/Scarb.toml new file mode 100644 index 0000000000..2a63cd115b --- /dev/null +++ b/crates/forge/tests/data/targets/with_features/Scarb.toml @@ -0,0 +1,17 @@ +[package] +name = "with_features" +version = "0.1.0" + +[dependencies] +starknet = "2.7.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false + +[features] +enable_for_tests = [] diff --git a/crates/forge/tests/data/targets/with_features/src/lib.cairo b/crates/forge/tests/data/targets/with_features/src/lib.cairo new file mode 100644 index 0000000000..7a7026266e --- /dev/null +++ b/crates/forge/tests/data/targets/with_features/src/lib.cairo @@ -0,0 +1,58 @@ +#[starknet::interface] +trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn do_a_panic(self: @TContractState); + fn do_a_panic_with(self: @TContractState, panic_data: Array); +} + +#[cfg(feature: 'enable_for_tests')] +mod dummy { + #[starknet::contract] + mod HelloStarknet { + use array::ArrayTrait; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of with_features::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + // Panics + fn do_a_panic(self: @ContractState) { + let mut arr = ArrayTrait::new(); + arr.append('PANIC'); + arr.append('DAYTAH'); + panic(arr); + } + + // Panics with given array data + fn do_a_panic_with(self: @ContractState, panic_data: Array) { + panic(panic_data); + } + } + } +} + +#[cfg(test)] +mod tests { + use snforge_std::{declare, ContractClassTrait}; + use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + + #[test] + fn declare_contract_from_lib() { + let _ = declare("HelloStarknet").unwrap().contract_class(); + assert(2 == 2, 'Should declare'); + } +} diff --git a/crates/forge/tests/data/targets/with_features/tests/tests.cairo b/crates/forge/tests/data/targets/with_features/tests/tests.cairo new file mode 100644 index 0000000000..825dd00901 --- /dev/null +++ b/crates/forge/tests/data/targets/with_features/tests/tests.cairo @@ -0,0 +1,28 @@ +use array::ArrayTrait; +use result::ResultTrait; +use option::OptionTrait; +use traits::TryInto; +use starknet::ContractAddress; +use starknet::Felt252TryIntoContractAddress; + +use snforge_std::{declare, ContractClassTrait}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; + +use with_features::IHelloStarknetDispatcher; +use with_features::IHelloStarknetDispatcherTrait; + +#[test] +fn declare_and_call_contract_from_lib() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance = dispatcher.get_balance(); + assert(balance == 0, 'balance == 0'); + + dispatcher.increase_balance(100); + + let balance = dispatcher.get_balance(); + assert(balance == 100, 'balance != 100'); +} diff --git a/crates/forge/tests/e2e/contract_artifacts.rs b/crates/forge/tests/e2e/contract_artifacts.rs index 31ab461de6..202eae78a0 100644 --- a/crates/forge/tests/e2e/contract_artifacts.rs +++ b/crates/forge/tests/e2e/contract_artifacts.rs @@ -5,6 +5,291 @@ use shared::test_utils::output_assert::assert_stdout_contains; use std::fs; use toml_edit::DocumentMut; +#[test] +fn unit_and_integration() { + let temp = setup_package("targets/unit_and_integration"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 2 test(s) from unit_and_integration package + Running 1 test(s) from tests/ + [PASS] unit_and_integration_integrationtest::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 1 test(s) from src/ + [PASS] unit_and_integration::tests::declare_contract_from_lib (gas: ~1) + Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +fn unit_and_lib_integration() { + let temp = setup_package("targets/unit_and_lib_integration"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 2 test(s) from unit_and_lib_integration package + Running 1 test(s) from tests/ + [PASS] unit_and_lib_integration_tests::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 1 test(s) from src/ + [PASS] unit_and_lib_integration::tests::declare_contract_from_lib (gas: ~1) + Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +fn only_integration() { + let temp = setup_package("targets/only_integration"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 1 test(s) from only_integration package + Running 1 test(s) from tests/ + [PASS] only_integration_integrationtest::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 0 test(s) from src/ + Tests: 1 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +fn only_unit() { + let temp = setup_package("targets/only_unit"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 1 test(s) from only_unit package + Running 1 test(s) from src/ + [PASS] only_unit::tests::declare_contract_from_lib (gas: ~1) + Tests: 1 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +#[cfg_attr(not(feature = "scarb_2_8_3"), ignore)] +fn only_lib_integration() { + let temp = setup_package("targets/only_lib_integration"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 1 test(s) from only_lib_integration package + Running 1 test(s) from tests/ + [PASS] only_lib_integration_tests::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 0 test(s) from src/ + Tests: 1 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +fn with_features() { + let temp = setup_package("targets/with_features"); + let output = test_runner(&temp) + .arg("--features") + .arg("enable_for_tests") + .assert() + .code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 2 test(s) from with_features package + Running 1 test(s) from tests/ + [PASS] with_features_integrationtest::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 1 test(s) from src/ + [PASS] with_features::tests::declare_contract_from_lib (gas: ~1) + Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +fn with_features_fails_without_flag() { + let temp = setup_package("targets/with_features"); + let output = test_runner(&temp).assert().code(1); + + assert_stdout_contains( + output, + indoc! {r#" + [..]Compiling[..] + [..]Finished[..] + + + Collected 2 test(s) from with_features package + Running 1 test(s) from tests/ + [FAIL] with_features_integrationtest::tests::declare_and_call_contract_from_lib + + Failure data: + "Failed to get contract artifact for name = HelloStarknet." + + Running 1 test(s) from src/ + [FAIL] with_features::tests::declare_contract_from_lib + + Failure data: + "Failed to get contract artifact for name = HelloStarknet." + + Tests: 0 passed, 2 failed, 0 skipped, 0 ignored, 0 filtered out + + Failures: + with_features_integrationtest::tests::declare_and_call_contract_from_lib + with_features::tests::declare_contract_from_lib + "#}, + ); +} + +#[test] +// Case: We define custom test target for both unit and integration test types +// We do not define `build-external-contracts = ["targets::*"]` for `integration` target +// The test still passes because contracts are collected from `unit` target which includes +// the contracts from package by the default +fn custom_target() { + let temp = setup_package("targets/custom_target"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 2 test(s) from custom_target package + Running 1 test(s) from tests/ + [PASS] custom_target_integrationtest::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 1 test(s) from src/ + [PASS] custom_target::tests::declare_contract_from_lib (gas: ~1) + Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +// Case: We define custom test target for both unit and integration test types +// We do not define `build-external-contracts = ["targets::*"]` for `integration` target +// The test still passes because contracts are collected from `unit` target which includes +// the contracts from package by the default +#[cfg_attr(not(feature = "scarb_2_8_3"), ignore)] +fn custom_target_custom_names() { + let temp = setup_package("targets/custom_target_custom_names"); + let output = test_runner(&temp).assert().code(0); + + // Scarb will use the name of the package for unit tests even if custom + // name for the unit test target is defined + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 2 test(s) from custom_target_custom_names package + Running 1 test(s) from tests/ + [PASS] custom_first::tests::declare_and_call_contract_from_lib (gas: ~172) + Running 1 test(s) from src/ + [PASS] custom_target_custom_names::tests::declare_contract_from_lib (gas: ~1) + Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +// Case: We define custom test target for both unit and integration test types +// We must `build-external-contracts = ["targets::*"]` for `integration` target otherwise +// they will not be built and included for declaring. +fn custom_target_only_integration() { + let temp = setup_package("targets/custom_target_only_integration"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 1 test(s) from custom_target_only_integration package + Running 1 test(s) from tests/ + [PASS] custom_first::tests::declare_and_call_contract_from_lib (gas: ~172) + Tests: 1 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out + "}, + ); +} + +#[test] +// Case: We define custom test target for integration test type +// We delete `build-external-contracts = ["targets::*"]` for `integration` so the test fails +#[cfg_attr(not(feature = "scarb_2_8_3"), ignore)] +fn custom_target_only_integration_without_external() { + let temp = setup_package("targets/custom_target_only_integration"); + + // Remove `build-external-contracts` from `[[test]]` target + let manifest_path = temp.child("Scarb.toml"); + let mut scarb_toml = fs::read_to_string(&manifest_path) + .unwrap() + .parse::() + .unwrap(); + let test_target = scarb_toml["test"].as_array_of_tables_mut().unwrap(); + assert_eq!(test_target.len(), 1); + let test_target = test_target.get_mut(0).unwrap(); + test_target.remove("build-external-contracts").unwrap(); + manifest_path.write_str(&scarb_toml.to_string()).unwrap(); + + let output = test_runner(&temp).assert().code(1); + + assert_stdout_contains( + output, + indoc! {r#" + [..]Compiling[..] + [..]Finished[..] + + + Collected 1 test(s) from custom_target_only_integration package + Running 1 test(s) from tests/ + [FAIL] custom_first::tests::declare_and_call_contract_from_lib + + Failure data: + "Failed to get contract artifact for name = HelloStarknet." + + Tests: 0 passed, 1 failed, 0 skipped, 0 ignored, 0 filtered out + "#}, + ); +} + #[test] #[cfg_attr(not(feature = "scarb_2_8_3"), ignore)] fn simple_package_no_starknet_contract_target() { diff --git a/crates/forge/tests/integration/declare.rs b/crates/forge/tests/integration/declare.rs index 5c36afcb28..d582fa3764 100644 --- a/crates/forge/tests/integration/declare.rs +++ b/crates/forge/tests/integration/declare.rs @@ -41,7 +41,7 @@ fn simple_declare() { balance: felt252, } - // Increases the balance by the given amount. + // Increases the balance by the given amount #[abi(embed_v0)] impl HelloStarknetImpl of super::IHelloStarknet { fn increase_balance(ref self: ContractState, amount: felt252) { diff --git a/crates/scarb-api/Cargo.toml b/crates/scarb-api/Cargo.toml index 77ad12172d..e2d811603a 100644 --- a/crates/scarb-api/Cargo.toml +++ b/crates/scarb-api/Cargo.toml @@ -18,6 +18,8 @@ thiserror.workspace = true which.workspace = true semver.workspace = true regex.workspace = true +rayon.workspace = true +itertools.workspace = true universal-sierra-compiler-api = { path = "../universal-sierra-compiler-api" } [dev-dependencies] diff --git a/crates/scarb-api/src/artifacts.rs b/crates/scarb-api/src/artifacts.rs new file mode 100644 index 0000000000..3619408243 --- /dev/null +++ b/crates/scarb-api/src/artifacts.rs @@ -0,0 +1,94 @@ +use anyhow::Result; + +use crate::artifacts::representation::StarknetArtifactsRepresentation; +use camino::{Utf8Path, Utf8PathBuf}; +use itertools::Itertools; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use std::collections::HashMap; +use std::fs; +use universal_sierra_compiler_api::{compile_sierra_at_path, SierraType}; + +mod deserialized; +mod representation; + +/// Contains compiled Starknet artifacts +#[derive(Debug, PartialEq, Clone)] +pub struct StarknetContractArtifacts { + /// Compiled sierra code + pub sierra: String, + /// Compiled casm code + pub casm: String, +} + +#[derive(PartialEq, Debug)] +pub(crate) struct StarknetArtifactsFiles { + base: Utf8PathBuf, + other: Vec, +} + +impl StarknetArtifactsFiles { + pub(crate) fn new(base_file: Utf8PathBuf, other_files: Vec) -> Self { + Self { + base: base_file, + other: other_files, + } + } + + // TODO(#2625) add unit tests + pub(crate) fn load_contracts_artifacts( + self, + ) -> Result> { + // TODO(#2626) handle duplicates + let mut base_artifacts: HashMap = + compile_artifacts( + StarknetArtifactsRepresentation::try_from_path(self.base.as_path())?.artifacts(), + )?; + + let other_artifact_representations: Vec = self + .other + .iter() + .map(|path| StarknetArtifactsRepresentation::try_from_path(path.as_path())) + .collect::>()?; + + let other_artifacts: Vec<(String, Utf8PathBuf)> = + unique_artifacts(other_artifact_representations, &base_artifacts); + + let compiled_artifacts = compile_artifacts(other_artifacts)?; + + base_artifacts.extend(compiled_artifacts); + + Ok(base_artifacts) + } +} + +// TODO(#2625) add unit tests +fn unique_artifacts( + artifact_representations: Vec, + current_artifacts: &HashMap, +) -> Vec<(String, Utf8PathBuf)> { + artifact_representations + .into_iter() + .flat_map(StarknetArtifactsRepresentation::artifacts) + .unique_by(|(name, _)| name.to_string()) + .filter(|(name, _)| !current_artifacts.contains_key(name)) + .collect() +} + +fn compile_artifacts( + artifacts: Vec<(String, Utf8PathBuf)>, +) -> Result> { + artifacts + .into_par_iter() + .map(|(name, path)| { + compile_artifact_at_path(&path).map(|artifact| (name.to_string(), (artifact, path))) + }) + .collect::>() +} + +fn compile_artifact_at_path(path: &Utf8Path) -> Result { + let sierra = fs::read_to_string(path)?; + + let casm = compile_sierra_at_path(path.as_str(), None, &SierraType::Contract)?; + + Ok(StarknetContractArtifacts { sierra, casm }) +} diff --git a/crates/scarb-api/src/artifacts/deserialized.rs b/crates/scarb-api/src/artifacts/deserialized.rs new file mode 100644 index 0000000000..a299265a14 --- /dev/null +++ b/crates/scarb-api/src/artifacts/deserialized.rs @@ -0,0 +1,39 @@ +use anyhow::{Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use serde::Deserialize; +use std::fs; + +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub struct StarknetArtifacts { + pub version: u32, + pub contracts: Vec, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub struct StarknetContract { + pub id: String, + pub package_name: String, + pub contract_name: String, + pub artifacts: StarknetContractArtifactPaths, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub struct StarknetContractArtifactPaths { + pub sierra: Utf8PathBuf, +} + +/// Get deserialized contents of `starknet_artifacts.json` file generated by Scarb +/// +/// # Arguments +/// +/// * `path` - A path to `starknet_artifacts.json` file. +pub fn artifacts_for_package(path: &Utf8Path) -> Result { + let starknet_artifacts = + fs::read_to_string(path).with_context(|| format!("Failed to read {path:?} contents"))?; + let starknet_artifacts: StarknetArtifacts = + serde_json::from_str(starknet_artifacts.as_str()) + .with_context(|| format!("Failed to parse starknet artifacts from path = {path:?}."))?; + Ok(starknet_artifacts) +} diff --git a/crates/scarb-api/src/artifacts/representation.rs b/crates/scarb-api/src/artifacts/representation.rs new file mode 100644 index 0000000000..31d61c5e9b --- /dev/null +++ b/crates/scarb-api/src/artifacts/representation.rs @@ -0,0 +1,83 @@ +use crate::artifacts::deserialized::{artifacts_for_package, StarknetArtifacts}; +use anyhow::anyhow; +use camino::{Utf8Path, Utf8PathBuf}; + +pub struct StarknetArtifactsRepresentation { + base_path: Utf8PathBuf, + artifacts: StarknetArtifacts, +} + +impl StarknetArtifactsRepresentation { + pub fn try_from_path(artifacts_path: &Utf8Path) -> anyhow::Result { + let artifacts = artifacts_for_package(artifacts_path)?; + let path = artifacts_path + .parent() + .ok_or_else(|| anyhow!("Failed to get parent for path = {}", &artifacts_path))? + .to_path_buf(); + + Ok(Self { + base_path: path, + artifacts, + }) + } + + pub fn artifacts(self) -> Vec<(String, Utf8PathBuf)> { + self.artifacts + .contracts + .into_iter() + .map(|contract| { + ( + contract.contract_name, + self.base_path.join(contract.artifacts.sierra.as_path()), + ) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use crate::ScarbCommand; + use assert_fs::fixture::{FileTouch, FileWriteStr, PathChild, PathCopy}; + use assert_fs::TempDir; + use camino::Utf8PathBuf; + + use super::*; + + #[test] + fn parsing_starknet_artifacts() { + let temp = crate::tests::setup_package("basic_package"); + + ScarbCommand::new_with_stdio() + .current_dir(temp.path()) + .arg("build") + .run() + .unwrap(); + + let artifacts_path = temp + .path() + .join("target/dev/basic_package.starknet_artifacts.json"); + let artifacts_path = Utf8PathBuf::from_path_buf(artifacts_path).unwrap(); + + let artifacts = artifacts_for_package(&artifacts_path).unwrap(); + + assert!(!artifacts.contracts.is_empty()); + } + + #[test] + fn parsing_starknet_artifacts_on_invalid_file() { + let temp = TempDir::new().unwrap(); + temp.copy_from("../../", &[".tool-versions"]).unwrap(); + let path = temp.child("wrong.json"); + path.touch().unwrap(); + path.write_str("\"aa\": {}").unwrap(); + let artifacts_path = Utf8PathBuf::from_path_buf(path.to_path_buf()).unwrap(); + + let result = artifacts_for_package(&artifacts_path); + let err = result.unwrap_err(); + + assert!(err.to_string().contains(&format!( + "Failed to parse starknet artifacts from path = {artifacts_path:?}." + ))); + } +} diff --git a/crates/scarb-api/src/lib.rs b/crates/scarb-api/src/lib.rs index 2082356ad1..0b8c2ba88e 100644 --- a/crates/scarb-api/src/lib.rs +++ b/crates/scarb-api/src/lib.rs @@ -1,181 +1,128 @@ -use anyhow::{anyhow, Context, Result}; +use crate::artifacts::StarknetArtifactsFiles; +use anyhow::{anyhow, Result}; use camino::{Utf8Path, Utf8PathBuf}; -use scarb_metadata::{CompilationUnitMetadata, Metadata, PackageId}; +pub use command::*; +use scarb_metadata::{Metadata, PackageId, PackageMetadata, TargetMetadata}; use semver::VersionReq; -use serde::Deserialize; +use shared::print::print_as_warning; use std::collections::HashMap; -use std::fs; -use universal_sierra_compiler_api::{compile_sierra_at_path, SierraType}; - -pub use command::*; +use std::str::FromStr; +mod artifacts; mod command; pub mod metadata; pub mod version; -#[derive(Deserialize, Debug, PartialEq, Clone)] -struct StarknetArtifacts { - version: u32, - contracts: Vec, -} +pub use crate::artifacts::StarknetContractArtifacts; -#[allow(dead_code)] -#[derive(Deserialize, Debug, PartialEq, Clone)] -struct StarknetContract { - id: String, - package_name: String, - contract_name: String, - artifacts: StarknetContractArtifactPaths, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug, PartialEq, Clone)] -struct StarknetContractArtifactPaths { - sierra: Utf8PathBuf, -} +const INTEGRATION_TEST_TYPE: &str = "integration"; -/// Contains compiled Starknet artifacts -#[derive(Debug, PartialEq, Clone)] -pub struct StarknetContractArtifacts { - /// Compiled sierra code - pub sierra: String, - /// Compiled casm code - pub casm: String, -} +/// Constructs `StarknetArtifactsFiles` from contracts built using test target. +/// +/// If artifacts with `test_type` of `INTEGRATION_TEST_TYPE` are present, we use them base path +/// and extend with paths to other artifacts. +/// If `INTEGRATION_TEST_TYPE` is not present, we take first artifacts found. +fn get_starknet_artifacts_paths_from_test_targets( + target_dir: &Utf8Path, + test_targets: &HashMap, +) -> Option { + let artifact = + |name: &str, metadata: &TargetMetadata| -> Option<(Utf8PathBuf, Option)> { + let path = format!("{name}.test.starknet_artifacts.json"); + let path = target_dir.join(&path); + let path = if path.exists() { + Some(path) + } else { + print_as_warning(&anyhow!( + "File = {path} missing when it should be existing, perhaps due to Scarb problem." + )); + None + }; + + let test_type = metadata + .params + .get("test-type") + .and_then(|value| value.as_str()) + .map(ToString::to_string); + + path.map(|path| (Utf8PathBuf::from_str(path.as_str()).unwrap(), test_type)) + }; + + let artifacts = test_targets + .iter() + .filter_map(|(target_name, metadata)| artifact(target_name, metadata)) + .collect::>(); -impl StarknetContractArtifacts { - fn from_scarb_contract_artifact( - starknet_contract: &StarknetContract, - base_path: &Utf8Path, - ) -> Result { - let sierra_path = base_path.join(starknet_contract.artifacts.sierra.clone()); - let sierra = fs::read_to_string(sierra_path)?; - - let casm = compile_sierra_at_path( - starknet_contract.artifacts.sierra.as_str(), - Some(base_path.as_std_path()), - &SierraType::Contract, - )?; - - Ok(Self { sierra, casm }) + let base_artifact_path = artifacts + .iter() + .find(|(_, test_type)| test_type.as_deref() == Some(INTEGRATION_TEST_TYPE)) + .cloned() + .or_else(|| artifacts.first().cloned()); + + if let Some(base_artifact) = base_artifact_path { + let other_artifacts_paths = artifacts + .into_iter() + .filter(|artifact| artifact != &base_artifact) + .map(|(path, _)| path) + .collect(); + let (base_artifact_path, _) = base_artifact; + + Some(StarknetArtifactsFiles::new( + base_artifact_path, + other_artifacts_paths, + )) + } else { + None } } -/// Get deserialized contents of `starknet_artifacts.json` file generated by Scarb -/// -/// # Arguments -/// -/// * `path` - A path to `starknet_artifacts.json` file. -fn artifacts_for_package(path: &Utf8Path) -> Result { - let starknet_artifacts = - fs::read_to_string(path).with_context(|| format!("Failed to read {path:?} contents"))?; - let starknet_artifacts: StarknetArtifacts = - serde_json::from_str(starknet_artifacts.as_str()) - .with_context(|| format!("Failed to parse {path:?} contents. Make sure you have enabled sierra code generation in Scarb.toml"))?; - Ok(starknet_artifacts) -} - -/// Try getting the path to `starknet_artifacts.json` file that is generated by `scarb build` or `scarb build --test` commands. -/// If contract artifacts are produced as part of the test target and exist in both `unittest` and `integrationtest`, then the path to `integrationtest` will be returned. +/// Try getting the path to `starknet_artifacts.json` related to `starknet-contract` target. This file that is generated by `scarb build` command. /// If the file is not present, `None` is returned. fn get_starknet_artifacts_path( target_dir: &Utf8Path, target_name: &str, - current_profile: &str, - use_test_target_contracts: bool, -) -> Option { - let starknet_artifacts_file_name = |test_type: Option<&str>| -> String { - if let Some(test_type) = test_type { - format!("{target_name}_{test_type}test.test.starknet_artifacts.json") - } else { - format!("{target_name}.starknet_artifacts.json") - } - }; - - let check_path = |file_name: &str| -> Option { - let path = target_dir.join(current_profile).join(file_name); - - if path.exists() { - Some(path) - } else { - None - } +) -> Option { + let path = format!("{target_name}.starknet_artifacts.json"); + let path = target_dir.join(&path); + let path = if path.exists() { + Some(path) + } else { + print_as_warning(&anyhow!( + "File = {path} missing. \ + This is most likely caused by `[[target.starknet-contract]]` being undefined in Scarb.toml \ + No contracts will be available for deployment" + )); + None }; - if use_test_target_contracts { - ["integration", "unit"] - .iter() - .find_map(|test_type| check_path(&starknet_artifacts_file_name(Some(test_type)))) - } else { - check_path(&starknet_artifacts_file_name(None)) - } + path.map(|path| StarknetArtifactsFiles::new(path, vec![])) } /// Get the map with `StarknetContractArtifacts` for the given package pub fn get_contracts_artifacts_and_source_sierra_paths( - metadata: &Metadata, - package: &PackageId, - profile: Option<&str>, + artifacts_dir: &Utf8Path, + package: &PackageMetadata, use_test_target_contracts: bool, ) -> Result> { - let target_name = target_name_for_package(metadata, package)?; - let target_dir = target_dir_for_workspace(metadata); - let maybe_contracts_path = get_starknet_artifacts_path( - &target_dir, - &target_name, - profile.unwrap_or(metadata.current_profile.as_str()), - use_test_target_contracts, - ); - - let map = match maybe_contracts_path { - Some(contracts_path) => load_contracts_artifacts_and_source_sierra_paths(&contracts_path)?, - None => HashMap::default(), + let starknet_artifact_files = if use_test_target_contracts { + let test_targets = test_targets_by_name(package); + get_starknet_artifacts_paths_from_test_targets(artifacts_dir, &test_targets) + } else { + let starknet_target_name = package + .targets + .iter() + .find(|target| target.kind == "starknet-contract") + .map(|target| target.name.clone()); + starknet_target_name.and_then(|starknet_target_name| { + get_starknet_artifacts_path(artifacts_dir, starknet_target_name.as_str()) + }) }; - Ok(map) -} - -fn load_contracts_artifacts_and_source_sierra_paths( - contracts_path: &Utf8PathBuf, -) -> Result> { - let base_path = contracts_path - .parent() - .ok_or_else(|| anyhow!("Failed to get parent for path = {}", &contracts_path))?; - let artifacts = artifacts_for_package(contracts_path)?; - let mut map = HashMap::new(); - - for ref contract in artifacts.contracts { - let name = contract.contract_name.clone(); - let contract_artifacts = - StarknetContractArtifacts::from_scarb_contract_artifact(contract, base_path)?; - - let sierra_path = base_path.join(contract.artifacts.sierra.clone()); - - map.insert(name.clone(), (contract_artifacts, sierra_path)); + if let Some(starknet_artifact_files) = starknet_artifact_files { + starknet_artifact_files.load_contracts_artifacts() + } else { + Ok(HashMap::default()) } - Ok(map) -} - -fn compilation_unit_for_package<'a>( - metadata: &'a Metadata, - package: &PackageId, -) -> Result<&'a CompilationUnitMetadata> { - metadata - .compilation_units - .iter() - .filter(|unit| unit.package == *package) - .min_by_key(|unit| match unit.target.kind.as_str() { - name @ "starknet-contract" => (0, name), - name @ "lib" => (1, name), - name => (2, name), - }) - .ok_or_else(|| anyhow!("Failed to find metadata for package = {package}")) -} - -/// Get the target name for the given package -pub fn target_name_for_package(metadata: &Metadata, package: &PackageId) -> Result { - let compilation_unit = compilation_unit_for_package(metadata, package)?; - Ok(compilation_unit.target.name.clone()) } #[must_use] @@ -213,18 +160,40 @@ pub fn package_matches_version_requirement( } } +/// collecting by name allow us to dedup targets +/// we do it because they use same sierra and we display them without distinction anyway +#[must_use] +pub fn test_targets_by_name(package: &PackageMetadata) -> HashMap { + fn test_target_name(target: &TargetMetadata) -> String { + // this is logic copied from scarb: https://github.com/software-mansion/scarb/blob/90ab01cb6deee48210affc2ec1dc94d540ab4aea/extensions/scarb-cairo-test/src/main.rs#L115 + target + .params + .get("group-id") // by unit tests grouping + .and_then(|v| v.as_str()) + .map(ToString::to_string) + .unwrap_or(target.name.clone()) // else by integration test name + } + + package + .targets + .iter() + .filter(|target| target.kind == "test") + .map(|target| (test_target_name(target), target)) + .collect() +} + #[cfg(test)] mod tests { use super::*; use crate::metadata::MetadataCommandExt; use assert_fs::fixture::{FileWriteStr, PathChild, PathCopy}; - use assert_fs::prelude::FileTouch; use assert_fs::TempDir; use camino::Utf8PathBuf; use indoc::{formatdoc, indoc}; + use std::fs; use std::str::FromStr; - fn setup_package(package_name: &str) -> TempDir { + pub(crate) fn setup_package(package_name: &str) -> TempDir { let temp = TempDir::new().unwrap(); temp.copy_from( format!("tests/data/{package_name}"), @@ -288,17 +257,21 @@ mod tests { .unwrap(); let path = get_starknet_artifacts_path( - &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target")).unwrap(), + &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target").join("dev")).unwrap(), "basic_package", - "dev", - false, ) .unwrap(); assert_eq!( path, - temp.path() - .join("target/dev/basic_package.starknet_artifacts.json") + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( + temp.path() + .join("target/dev/basic_package.starknet_artifacts.json") + ) + .unwrap(), + vec![] + ) ); } @@ -314,18 +287,33 @@ mod tests { .run() .unwrap(); - let path = get_starknet_artifacts_path( - &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target")).unwrap(), - "basic_package", - "dev", - true, + let metadata = ScarbCommand::metadata() + .current_dir(temp.path()) + .run() + .unwrap(); + + let package = metadata + .packages + .iter() + .find(|p| p.name == "basic_package") + .unwrap(); + + let path = get_starknet_artifacts_paths_from_test_targets( + &Utf8PathBuf::from_path_buf(temp.join("target").join("dev")).unwrap(), + &test_targets_by_name(package), ) .unwrap(); assert_eq!( path, - temp.path() - .join("target/dev/basic_package_unittest.test.starknet_artifacts.json") + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( + temp.path() + .join("target/dev/basic_package_unittest.test.starknet_artifacts.json") + ) + .unwrap(), + vec![], + ) ); } @@ -354,18 +342,38 @@ mod tests { .run() .unwrap(); - let path = get_starknet_artifacts_path( - &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target")).unwrap(), - "basic_package", - "dev", - true, + let metadata = ScarbCommand::metadata() + .current_dir(temp.path()) + .run() + .unwrap(); + + let package = metadata + .packages + .iter() + .find(|p| p.name == "basic_package") + .unwrap(); + + let path = get_starknet_artifacts_paths_from_test_targets( + &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target").join("dev")).unwrap(), + &test_targets_by_name(package), ) .unwrap(); assert_eq!( path, - temp.path() - .join("target/dev/basic_package_integrationtest.test.starknet_artifacts.json") + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( + temp.path().join( + "target/dev/basic_package_integrationtest.test.starknet_artifacts.json" + ) + ) + .unwrap(), + vec![Utf8PathBuf::from_path_buf( + temp.path() + .join("target/dev/basic_package_unittest.test.starknet_artifacts.json") + ) + .unwrap(),] + ), ); } @@ -456,16 +464,20 @@ mod tests { .unwrap(); let path = get_starknet_artifacts_path( - &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target")).unwrap(), + &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target").join("dev")).unwrap(), "essa", - "dev", - false, ) .unwrap(); assert_eq!( path, - temp.path().join("target/dev/essa.starknet_artifacts.json") + StarknetArtifactsFiles::new( + Utf8PathBuf::from_path_buf( + temp.path().join("target/dev/essa.starknet_artifacts.json") + ) + .unwrap(), + vec![] + ) ); } @@ -491,10 +503,8 @@ mod tests { .unwrap(); let path = get_starknet_artifacts_path( - &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target")).unwrap(), + &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target").join("dev")).unwrap(), "empty_lib", - "dev", - false, ); assert!(path.is_none()); } @@ -504,49 +514,12 @@ mod tests { let temp = setup_package("basic_package"); let path = get_starknet_artifacts_path( - &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target")).unwrap(), + &Utf8PathBuf::from_path_buf(temp.to_path_buf().join("target").join("dev")).unwrap(), "basic_package", - "dev", - false, ); assert!(path.is_none()); } - #[test] - fn parsing_starknet_artifacts() { - let temp = setup_package("basic_package"); - - ScarbCommand::new_with_stdio() - .current_dir(temp.path()) - .arg("build") - .run() - .unwrap(); - - let artifacts_path = temp - .path() - .join("target/dev/basic_package.starknet_artifacts.json"); - let artifacts_path = Utf8PathBuf::from_path_buf(artifacts_path).unwrap(); - - let artifacts = artifacts_for_package(&artifacts_path).unwrap(); - - assert!(!artifacts.contracts.is_empty()); - } - - #[test] - fn parsing_starknet_artifacts_on_invalid_file() { - let temp = TempDir::new().unwrap(); - temp.copy_from("../../", &[".tool-versions"]).unwrap(); - let path = temp.child("wrong.json"); - path.touch().unwrap(); - path.write_str("\"aa\": {}").unwrap(); - let artifacts_path = Utf8PathBuf::from_path_buf(path.to_path_buf()).unwrap(); - - let result = artifacts_for_package(&artifacts_path); - let err = result.unwrap_err(); - - assert!(err.to_string().contains(&format!("Failed to parse {artifacts_path:?} contents. Make sure you have enabled sierra code generation in Scarb.toml"))); - } - #[test] fn get_contracts() { let temp = setup_package("basic_package"); @@ -563,9 +536,11 @@ mod tests { .run() .unwrap(); + let target_dir = target_dir_for_workspace(&metadata).join("dev"); let package = metadata.packages.first().unwrap(); + let contracts = - get_contracts_artifacts_and_source_sierra_paths(&metadata, &package.id, None, false) + get_contracts_artifacts_and_source_sierra_paths(target_dir.as_path(), package, false) .unwrap(); assert!(contracts.contains_key("ERC20")); @@ -602,19 +577,4 @@ mod tests { assert_eq!(&package_name, "basic_package"); } - - #[test] - fn get_target_name_for_package() { - let temp = setup_package("basic_package"); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); - - let target_name = - target_name_for_package(&scarb_metadata, &scarb_metadata.workspace.members[0]).unwrap(); - - assert_eq!(target_name, "basic_package"); - } } diff --git a/crates/sncast/src/helpers/scarb_utils.rs b/crates/sncast/src/helpers/scarb_utils.rs index 52d69eb576..d179b31244 100644 --- a/crates/sncast/src/helpers/scarb_utils.rs +++ b/crates/sncast/src/helpers/scarb_utils.rs @@ -3,7 +3,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use scarb_api::{ get_contracts_artifacts_and_source_sierra_paths, metadata::{Metadata, MetadataCommand, PackageMetadata}, - ScarbCommand, ScarbCommandError, StarknetContractArtifacts, + target_dir_for_workspace, ScarbCommand, ScarbCommandError, StarknetContractArtifacts, }; use scarb_ui::args::PackagesFilter; use shared::{command::CommandExt, print::print_as_warning}; @@ -161,13 +161,14 @@ pub fn build_and_load_artifacts( .map_err(|e| anyhow!(format!("Failed to build using scarb; {e}")))?; let metadata = get_scarb_metadata_with_deps(&config.scarb_toml_path)?; + let target_dir = target_dir_for_workspace(&metadata); + if metadata.profiles.contains(&config.profile) { Ok(get_contracts_artifacts_and_source_sierra_paths( - &metadata, - &package.id, - Some(&config.profile), + &target_dir.join(&config.profile), + package, false, - )? + ).context("Failed to load artifacts. Make sure you have enabled sierra code generation in Scarb.toml")? .into_iter() .map(|(name, (artifacts, _))| (name, artifacts)) .collect()) @@ -177,11 +178,10 @@ pub fn build_and_load_artifacts( "Profile {profile} does not exist in scarb, using '{default_profile}' profile." )); Ok(get_contracts_artifacts_and_source_sierra_paths( - &metadata, - &package.id, - Some(default_profile), + &target_dir.join(default_profile), + package, false, - )? + ).context("Failed to load artifacts. Make sure you have enabled sierra code generation in Scarb.toml")? .into_iter() .map(|(name, (artifacts, _))| (name, artifacts)) .collect()) diff --git a/docs/listings/snforge_overview/crates/testing_smart_contracts/src/simple_contract.cairo b/docs/listings/snforge_overview/crates/testing_smart_contracts/src/simple_contract.cairo index a14456f55b..af38fd767e 100644 --- a/docs/listings/snforge_overview/crates/testing_smart_contracts/src/simple_contract.cairo +++ b/docs/listings/snforge_overview/crates/testing_smart_contracts/src/simple_contract.cairo @@ -13,7 +13,7 @@ pub mod SimpleContract { #[abi(embed_v0)] pub impl SimpleContractImpl of super::ISimpleContract { - // Increases the balance by the given amount. + // Increases the balance by the given amount fn increase_balance(ref self: ContractState, amount: felt252) { self.balance.write(self.balance.read() + amount); } diff --git a/docs/listings/snforge_overview/crates/using_cheatcodes/src/lib.cairo b/docs/listings/snforge_overview/crates/using_cheatcodes/src/lib.cairo index 1938925ce2..b6c20ffcad 100644 --- a/docs/listings/snforge_overview/crates/using_cheatcodes/src/lib.cairo +++ b/docs/listings/snforge_overview/crates/using_cheatcodes/src/lib.cairo @@ -28,7 +28,7 @@ pub mod CheatcodeChecker { #[abi(embed_v0)] impl ICheatcodeCheckerImpl of super::ICheatcodeChecker { - // Increases the balance by the given amount. + // Increases the balance by the given amount fn increase_balance(ref self: ContractState, amount: felt252) { assert_is_allowed_user(); self.balance.write(self.balance.read() + amount);