Skip to content

Commit 882fc9d

Browse files
authored
Fix #[test_case] not adding required config_run if (#3900)
<!-- Reference any GitHub issues resolved by this PR --> Closes #3885 ## Introduced changes <!-- A brief description of the changes --> - Add better handling in `test_case` with removing duplication of `#[__internal_config_statement]` and adding it if necessary, with tests ## Checklist <!-- Make sure all of these are complete --> - [x] Linked relevant issue - [ ] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [ ] Added changes to `CHANGELOG.md`
1 parent 74a947a commit 882fc9d

File tree

8 files changed

+153
-9
lines changed

8 files changed

+153
-9
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
- Gas values in fuzzing test output are now displayed as whole numbers without fractional parts
1515

16+
#### Fixed
17+
18+
- A bug that prevented the `#[test_case]` attribute from being used on its own with cheatcodes
19+
1620
## [0.51.2] - 2025-10-31
1721

1822
### Cast

crates/forge/tests/data/test_case/src/lib.cairo

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
1+
#[starknet::interface]
2+
pub trait IValueStorage<TContractState> {
3+
fn set_value(ref self: TContractState, value: u128);
4+
fn get_value(self: @TContractState) -> u128;
5+
}
6+
17
#[starknet::contract]
2-
pub mod HelloStarknet {
8+
pub mod ValueStorage {
9+
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
10+
311
#[storage]
4-
struct Storage {}
5-
}
12+
struct Storage {
13+
stored_value: u128,
14+
}
15+
16+
#[abi(embed_v0)]
17+
impl ValueStorageImpl of super::IValueStorage<ContractState> {
18+
fn set_value(ref self: ContractState, value: u128) {
19+
self.stored_value.write(value);
20+
}
621

22+
fn get_value(self: @ContractState) -> u128 {
23+
self.stored_value.read()
24+
}
25+
}
26+
}
727

828
pub fn add(a: felt252, b: felt252) -> felt252 {
929
a + b
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use snforge_std::{ContractClassTrait, DeclareResultTrait, declare};
2+
use test_case::{IValueStorageDispatcher, IValueStorageDispatcherTrait};
3+
4+
#[test]
5+
#[test_case(100)]
6+
#[test_case(42)]
7+
#[test_case(0)]
8+
fn with_contract_deploy(value: u128) {
9+
let contract = declare("ValueStorage").unwrap().contract_class();
10+
let (contract_address, _) = contract.deploy(@array![]).unwrap();
11+
let dispatcher = IValueStorageDispatcher { contract_address };
12+
13+
dispatcher.set_value(value);
14+
assert!(dispatcher.get_value() == value, "Value mismatch");
15+
}
16+
17+
#[test]
18+
#[fuzzer]
19+
#[test_case(123)]
20+
#[test_case(0)]
21+
fn with_fuzzer_and_contract_deploy(value: u128) {
22+
let contract = declare("ValueStorage").unwrap().contract_class();
23+
let (contract_address, _) = contract.deploy(@array![]).unwrap();
24+
let dispatcher = IValueStorageDispatcher { contract_address };
25+
26+
dispatcher.set_value(value);
27+
assert!(dispatcher.get_value() == value, "FAIL");
28+
}
29+

crates/forge/tests/e2e/test_case.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,66 @@ fn addition_with_name_arg() {
144144
"},
145145
);
146146
}
147+
148+
#[test]
149+
#[cfg_attr(
150+
feature = "skip_test_for_only_latest_scarb",
151+
ignore = "Plugin checks skipped"
152+
)]
153+
fn with_contract_deploy() {
154+
let temp = setup_package("test_case");
155+
156+
let output = test_runner(&temp)
157+
.arg("with_contract_deploy")
158+
.assert()
159+
.code(0);
160+
161+
assert_stdout_contains(
162+
output,
163+
indoc! {r"
164+
[..]Compiling[..]
165+
[..]Finished[..]
166+
167+
168+
Collected 3 test(s) from test_case package
169+
Running 3 test(s) from tests/
170+
[PASS] test_case_integrationtest::with_deploy::with_contract_deploy_100 [..]
171+
[PASS] test_case_integrationtest::with_deploy::with_contract_deploy_42 [..]
172+
[PASS] test_case_integrationtest::with_deploy::with_contract_deploy_0 [..]
173+
Running 0 test(s) from src/
174+
Tests: 3 passed, 0 failed, 0 ignored, [..] filtered out
175+
"},
176+
);
177+
}
178+
179+
#[test]
180+
#[cfg_attr(
181+
feature = "skip_test_for_only_latest_scarb",
182+
ignore = "Plugin checks skipped"
183+
)]
184+
fn with_fuzzer_and_contract_deploy() {
185+
let temp = setup_package("test_case");
186+
187+
let output = test_runner(&temp)
188+
.arg("with_fuzzer_and_contract_deploy")
189+
.assert()
190+
.code(0);
191+
192+
assert_stdout_contains(
193+
output,
194+
indoc! {r"
195+
[..]Compiling[..]
196+
[..]Finished[..]
197+
198+
199+
Collected 3 test(s) from test_case package
200+
Running 3 test(s) from tests/
201+
[PASS] test_case_integrationtest::with_deploy::with_fuzzer_and_contract_deploy_123 [..]
202+
[PASS] test_case_integrationtest::with_deploy::with_fuzzer_and_contract_deploy_0 [..]
203+
[PASS] test_case_integrationtest::with_deploy::with_fuzzer_and_contract_deploy [..]
204+
Running 0 test(s) from src/
205+
Tests: 3 passed, 0 failed, 0 ignored, [..] filtered out
206+
Fuzzer seed: [..]
207+
"},
208+
);
209+
}

crates/snforge-scarb-plugin/src/attributes/test_case.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use crate::args::Arguments;
22
use crate::args::unnamed::UnnamedArgs;
3+
use crate::attributes::internal_config_statement::InternalConfigStatementCollector;
34
use crate::attributes::test::{TestCollector, test_func_with_attrs};
45
use crate::attributes::test_case::name::test_case_name;
56
use crate::attributes::{AttributeInfo, ErrorExt};
6-
use crate::common::{has_fuzzer_attribute, into_proc_macro_result, with_parsed_values};
7-
use crate::format_ident;
7+
use crate::common::{
8+
has_fuzzer_attribute, has_test_attribute, into_proc_macro_result, with_parsed_values,
9+
};
810
use crate::utils::SyntaxNodeUtils;
11+
use crate::{create_single_token, format_ident};
912
use cairo_lang_macro::{Diagnostic, Diagnostics, ProcMacroResult, TokenStream, quote};
1013
use cairo_lang_parser::utils::SimpleParserDatabase;
1114
use cairo_lang_syntax::node::ast::FunctionWithBody;
@@ -40,7 +43,7 @@ fn test_case_internal(
4043

4144
let func_name = func.declaration(db).name(db);
4245
let case_fn_name = test_case_name(&func_name.text(db), &args, args_db)?;
43-
let filtered_fn_attrs = collect_attrs_excluding_test_without_fuzzer(func, db);
46+
let filtered_fn_attrs = collect_preserved_attributes_for_test_case(func, db);
4447

4548
let signature = func.declaration(db).signature(db).as_syntax_node();
4649
let signature = SyntaxNodeWithDb::new(&signature, db);
@@ -57,8 +60,20 @@ fn test_case_internal(
5760

5861
let test_func_with_attrs = test_func_with_attrs(&case_fn_name, &func_name, &call_args);
5962

63+
// If the function has both `#[fuzzer]` and `#[test]` attributes, we do not need to add
64+
// `#[__internal_config_statement]` attribute, since it will be handled by `#[test]`.
65+
let skip_internal_config = has_fuzzer_attribute(db, func) && has_test_attribute(db, func);
66+
let internal_config_attr = if skip_internal_config {
67+
TokenStream::empty()
68+
} else {
69+
let internal_config = create_single_token(InternalConfigStatementCollector::ATTR_NAME);
70+
quote!(#[#internal_config])
71+
};
72+
6073
let func_ident = quote!(
6174
#filtered_fn_attrs
75+
76+
#internal_config_attr
6277
fn #func_name #signature
6378
#func_body
6479
);
@@ -112,23 +127,27 @@ fn ensure_params_valid(
112127
Ok(())
113128
}
114129

115-
fn collect_attrs_excluding_test_without_fuzzer(
130+
fn collect_preserved_attributes_for_test_case(
116131
func: &FunctionWithBody,
117132
func_db: &SimpleParserDatabase,
118133
) -> TokenStream {
119134
let attr_list = func.attributes(func_db);
120135
let has_fuzzer = has_fuzzer_attribute(func_db, func);
121136

122-
// We do not want to copy the `#[test]` attribute if there is no `#[fuzzer]`
137+
// We do not want to copy the `#[test]` attribute unless the function has `#[fuzzer]`.
138+
// We also do not want to copy `#[__internal_config_statement]` because it will be added later.
123139
attr_list
124140
.elements(func_db)
125141
.filter(|attr| {
126142
let test_attr_text = format!("#[{}]", TestCollector::ATTR_NAME);
143+
let internal_config_attr_text =
144+
format!("#[{}]", InternalConfigStatementCollector::ATTR_NAME);
127145
let attr_text = attr.as_syntax_node().get_text(func_db);
128146
let attr_text = attr_text.trim();
129147
let is_test_attr = attr_text == test_attr_text;
148+
let is_internal_config_attr = attr_text == internal_config_attr_text;
130149

131-
!is_test_attr || has_fuzzer
150+
(!is_test_attr || has_fuzzer) && !is_internal_config_attr
132151
})
133152
.map(|attr| attr.to_token_stream(func_db))
134153
.fold(TokenStream::empty(), |mut acc, token| {

crates/snforge-scarb-plugin/src/common.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{
33
attributes::{
44
AttributeInfo,
55
fuzzer::{FuzzerCollector, FuzzerConfigCollector, wrapper::FuzzerWrapperCollector},
6+
test::TestCollector,
67
test_case::TestCaseCollector,
78
},
89
parse::{parse, parse_args},
@@ -92,3 +93,8 @@ pub fn has_test_case_attribute(db: &SimpleParserDatabase, func: &FunctionWithBod
9293
const TEST_CASE_ATTRIBUTES: [&str; 1] = [TestCaseCollector::ATTR_NAME];
9394
has_any_attribute(db, func, &TEST_CASE_ATTRIBUTES)
9495
}
96+
97+
pub fn has_test_attribute(db: &SimpleParserDatabase, func: &FunctionWithBody) -> bool {
98+
const TEST_ATTRIBUTES: [&str; 1] = [TestCollector::ATTR_NAME];
99+
has_any_attribute(db, func, &TEST_ATTRIBUTES)
100+
}

crates/snforge-scarb-plugin/tests/integration/multiple_attributes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ fn works_with_test_fuzzer_and_test_case() {
544544
545545
#[__fuzzer_config]
546546
#[__fuzzer_wrapper]
547+
#[__internal_config_statement]
547548
fn test_add(x: i128, y: i128, expected: i128) {}
548549
",
549550
);

crates/snforge-scarb-plugin/tests/integration/single_attributes/test_case.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ fn works_with_args() {
3232
let mut arr = ArrayTrait::new();
3333
core::array::ArrayTrait::span(@arr)
3434
}
35+
#[__internal_config_statement]
3536
fn test_add(x: i128, y: i128, expected: i128) {}
3637
",
3738
);
@@ -61,6 +62,7 @@ fn works_with_name_and_args() {
6162
let mut arr = ArrayTrait::new();
6263
core::array::ArrayTrait::span(@arr)
6364
}
65+
#[__internal_config_statement]
6466
fn test_add(x: i128, y: i128, expected: i128) {}
6567
",
6668
);

0 commit comments

Comments
 (0)