diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 11203579b5d..625ae7f1322 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -2041,36 +2041,56 @@ impl PkgTestEntry { let span = decl_ref.span(); let test_function_decl = engines.de().get_function(decl_ref); - let Some(test_attr) = test_function_decl.attributes.test() else { - unreachable!("`test_function_decl` is guaranteed to be a test function and it must have a `#[test]` attribute"); - }; + let test_attr = test_function_decl.attributes.test(); + let fuzz_attr = test_function_decl.attributes.fuzz(); + + // With fixture-based testing, #[test] is mandatory and can coexist with #[fuzz] and #[case] + let has_case = test_function_decl.attributes.cases().next().is_some(); + let has_parameterization = fuzz_attr.is_some() || has_case; + + if test_attr.is_none() && has_parameterization { + bail!( + "Function \"{}\" has parameterization attributes (#[fuzz] or #[case]) but is missing the required #[test] attribute", + test_function_decl.name + ); + } + + if test_attr.is_none() && !has_parameterization { + unreachable!("`test_function_decl` is guaranteed to be a test or fuzz function and it must have a `#[test]` attribute or parameterization attributes"); + } - let pass_condition = match test_attr - .args - .iter() - // Last "should_revert" argument wins ;-) - .rfind(|arg| arg.is_test_should_revert()) - { - Some(should_revert_arg) => { - match should_revert_arg.get_string_opt(&Handler::default()) { - Ok(should_revert_arg_value) => TestPassCondition::ShouldRevert( - should_revert_arg_value - .map(|val| val.parse::()) - .transpose() - .map_err(|_| { - anyhow!(get_invalid_revert_code_error_msg( - &test_function_decl.name, - should_revert_arg - )) - })?, - ), - Err(_) => bail!(get_invalid_revert_code_error_msg( - &test_function_decl.name, - should_revert_arg - )), + let pass_condition = if let Some(test_attr) = test_attr { + // Handle #[test] attributes + match test_attr + .args + .iter() + // Last "should_revert" argument wins ;-) + .rfind(|arg| arg.is_test_should_revert()) + { + Some(should_revert_arg) => { + match should_revert_arg.get_string_opt(&Handler::default()) { + Ok(should_revert_arg_value) => TestPassCondition::ShouldRevert( + should_revert_arg_value + .map(|val| val.parse::()) + .transpose() + .map_err(|_| { + anyhow!(get_invalid_revert_code_error_msg( + &test_function_decl.name, + should_revert_arg + )) + })?, + ), + Err(_) => bail!(get_invalid_revert_code_error_msg( + &test_function_decl.name, + should_revert_arg + )), + } } + None => TestPassCondition::ShouldNotRevert, } - None => TestPassCondition::ShouldNotRevert, + } else { + // Handle #[fuzz] attributes - fuzz tests shouldn't revert by default + TestPassCondition::ShouldNotRevert }; let file_path = diff --git a/sway-ast/src/attribute.rs b/sway-ast/src/attribute.rs index 9f6fbfa61b5..6e94e8e8ed1 100644 --- a/sway-ast/src/attribute.rs +++ b/sway-ast/src/attribute.rs @@ -33,6 +33,12 @@ pub const DOC_COMMENT_ATTRIBUTE_NAME: &str = "doc-comment"; pub const TEST_ATTRIBUTE_NAME: &str = "test"; pub const TEST_SHOULD_REVERT_ARG_NAME: &str = "should_revert"; +// In-language parameterized testing. +pub const CASE_ATTRIBUTE_NAME: &str = "case"; + +// In-language fuzz testing. +pub const FUZZ_ATTRIBUTE_NAME: &str = "fuzz"; + // Allow warnings. pub const ALLOW_ATTRIBUTE_NAME: &str = "allow"; pub const ALLOW_DEAD_CODE_ARG_NAME: &str = "dead_code"; @@ -65,6 +71,7 @@ pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[ STORAGE_ATTRIBUTE_NAME, DOC_COMMENT_ATTRIBUTE_NAME, TEST_ATTRIBUTE_NAME, + CASE_ATTRIBUTE_NAME, INLINE_ATTRIBUTE_NAME, PAYABLE_ATTRIBUTE_NAME, ALLOW_ATTRIBUTE_NAME, @@ -72,6 +79,7 @@ pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[ DEPRECATED_ATTRIBUTE_NAME, FALLBACK_ATTRIBUTE_NAME, ABI_NAME_ATTRIBUTE_NAME, + FUZZ_ATTRIBUTE_NAME, ]; /// An attribute declaration. Attribute declaration diff --git a/sway-core/src/language/ty/ast_node.rs b/sway-core/src/language/ty/ast_node.rs index 4f029ee8577..075d339b613 100644 --- a/sway-core/src/language/ty/ast_node.rs +++ b/sway-core/src/language/ty/ast_node.rs @@ -221,6 +221,7 @@ impl TyAstNode { let fn_decl = decl_engine.get_function(decl_id); let TyFunctionDecl { attributes, .. } = &*fn_decl; attributes.has_any_of_kind(AttributeKind::Test) + || attributes.has_any_of_kind(AttributeKind::Fuzz) } _ => false, } diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index f732530ae2f..edf09dcd283 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -218,42 +218,42 @@ pub(crate) fn attr_decls_to_attributes( let first_doc_line = attributes .next() .expect("`chunk_by` guarantees existence of at least one element in the chunk"); - if !can_annotate(first_doc_line) { - let last_doc_line = match attributes.last() { - Some(last_attr) => last_attr, - // There is only one doc line in the complete doc comment. - None => first_doc_line, - }; + if can_annotate(first_doc_line) { + continue; + } + + let last_doc_line = attributes.last().unwrap_or(first_doc_line); + handler.emit_err( + ConvertParseTreeError::InvalidAttributeTarget { + span: Span::join( + first_doc_line.span.clone(), + &last_doc_line.span.start_span(), + ), + attribute: first_doc_line.name.clone(), + target_friendly_name, + can_only_annotate_help: first_doc_line + .can_only_annotate_help(target_friendly_name), + } + .into(), + ); + } else { + // For other attributes, the error is shown for every individual attribute. + for attribute in attributes { + if can_annotate(attribute) { + continue; + } + handler.emit_err( ConvertParseTreeError::InvalidAttributeTarget { - span: Span::join( - first_doc_line.span.clone(), - &last_doc_line.span.start_span(), - ), - attribute: first_doc_line.name.clone(), + span: attribute.name.span(), + attribute: attribute.name.clone(), target_friendly_name, - can_only_annotate_help: first_doc_line + can_only_annotate_help: attribute .can_only_annotate_help(target_friendly_name), } .into(), ); } - } else { - // For other attributes, the error is shown for every individual attribute. - for attribute in attributes { - if !can_annotate(attribute) { - handler.emit_err( - ConvertParseTreeError::InvalidAttributeTarget { - span: attribute.name.span(), - attribute: attribute.name.clone(), - target_friendly_name, - can_only_annotate_help: attribute - .can_only_annotate_help(target_friendly_name), - } - .into(), - ); - } - } } } @@ -374,6 +374,28 @@ pub(crate) fn attr_decls_to_attributes( } } + // Check fixture-based testing requirements: #[case] or #[fuzz] require #[test] + let has_test = attributes.of_kind(AttributeKind::Test).any(|_| true); + let has_parameterization = attributes.of_kind(AttributeKind::Case).any(|_| true) + || attributes.of_kind(AttributeKind::Fuzz).any(|_| true); + + if !has_parameterization || has_test { + return (handler, attributes); + } + + let first_param_attr = attributes.of_kind(AttributeKind::Case).next() + .or_else(|| attributes.of_kind(AttributeKind::Fuzz).next()); + + if let Some(attr) = first_param_attr { + handler.emit_err( + ConvertParseTreeError::ParameterizedTestRequiresTestAttribute { + span: attr.span.clone(), + attribute: attr.name.clone(), + } + .into(), + ); + } + (handler, attributes) } diff --git a/sway-core/src/transform/attribute.rs b/sway-core/src/transform/attribute.rs index ab6d8769f0e..9793713eada 100644 --- a/sway-core/src/transform/attribute.rs +++ b/sway-core/src/transform/attribute.rs @@ -179,6 +179,7 @@ impl AttributeArg { self.name.as_str() == TEST_SHOULD_REVERT_ARG_NAME } + pub fn is_error_message(&self) -> bool { self.name.as_str() == ERROR_M_ARG_NAME } @@ -347,6 +348,7 @@ pub enum AttributeKind { Storage, Inline, Test, + Case, Payable, Allow, Cfg, @@ -356,6 +358,7 @@ pub enum AttributeKind { Error, Trace, AbiName, + Fuzz, } /// Denotes if an [ItemTraitItem] belongs to an ABI or to a trait. @@ -379,6 +382,7 @@ impl AttributeKind { STORAGE_ATTRIBUTE_NAME => AttributeKind::Storage, INLINE_ATTRIBUTE_NAME => AttributeKind::Inline, TEST_ATTRIBUTE_NAME => AttributeKind::Test, + CASE_ATTRIBUTE_NAME => AttributeKind::Case, PAYABLE_ATTRIBUTE_NAME => AttributeKind::Payable, ALLOW_ATTRIBUTE_NAME => AttributeKind::Allow, CFG_ATTRIBUTE_NAME => AttributeKind::Cfg, @@ -388,6 +392,7 @@ impl AttributeKind { ERROR_ATTRIBUTE_NAME => AttributeKind::Error, TRACE_ATTRIBUTE_NAME => AttributeKind::Trace, ABI_NAME_ATTRIBUTE_NAME => AttributeKind::AbiName, + FUZZ_ATTRIBUTE_NAME => AttributeKind::Fuzz, _ => AttributeKind::Unknown, } } @@ -409,6 +414,7 @@ impl AttributeKind { Storage => false, Inline => false, Test => false, + Case => true, Payable => false, Allow => true, Cfg => true, @@ -418,6 +424,7 @@ impl AttributeKind { Error => false, Trace => false, AbiName => false, + Fuzz => false, } } } @@ -450,6 +457,8 @@ impl Attribute { Inline => Multiplicity::exactly(1), // `test`, `test(should_revert)`. Test => Multiplicity::at_most(1), + // `case(value1, value2, ...)`. + Case => Multiplicity::at_least(1), Payable => Multiplicity::zero(), Allow => Multiplicity::at_least(1), Cfg => Multiplicity::exactly(1), @@ -461,6 +470,8 @@ impl Attribute { // `trace(never)` or `trace(always)`. Trace => Multiplicity::exactly(1), AbiName => Multiplicity::exactly(1), + // `fuzz(param_iterations = 10, param_min = 1, param_max = 100)`. + Fuzz => Multiplicity::at_least(1), } } @@ -501,6 +512,7 @@ impl Attribute { Storage => MustBeIn(vec![STORAGE_READ_ARG_NAME, STORAGE_WRITE_ARG_NAME]), Inline => MustBeIn(vec![INLINE_ALWAYS_ARG_NAME, INLINE_NEVER_ARG_NAME]), Test => MustBeIn(vec![TEST_SHOULD_REVERT_ARG_NAME]), + Case => Any, // Case accepts any values as arguments Payable => None, Allow => ShouldBeIn(vec![ALLOW_DEAD_CODE_ARG_NAME, ALLOW_DEPRECATED_ARG_NAME]), Cfg => { @@ -518,6 +530,7 @@ impl Attribute { Error => MustBeIn(vec![ERROR_M_ARG_NAME]), Trace => MustBeIn(vec![TRACE_ALWAYS_ARG_NAME, TRACE_NEVER_ARG_NAME]), AbiName => MustBeIn(vec![ABI_NAME_NAME_ARG_NAME]), + Fuzz => Any, // Fuzz will accept parameter-specific arguments } } @@ -532,6 +545,8 @@ impl Attribute { Inline => No, // `test(should_revert)`, `test(should_revert = "18446744073709486084")`. Test => Maybe, + // `case(value1, value2, ...)` - case arguments are values, not key-value pairs. + Case => No, Payable => No, Allow => No, Cfg => Yes, @@ -543,6 +558,8 @@ impl Attribute { Error => Yes, Trace => No, AbiName => Yes, + // `fuzz(param_iterations = 10, param_min = 1, param_max = 100)`. + Fuzz => Yes, } } @@ -554,6 +571,7 @@ impl Attribute { Storage => false, Inline => false, Test => false, + Case => false, Payable => false, Allow => false, Cfg => false, @@ -565,6 +583,7 @@ impl Attribute { Error => false, Trace => false, AbiName => false, + Fuzz => false, } } @@ -594,32 +613,39 @@ impl Attribute { Storage => matches!(item_kind, ItemKind::Fn(_)), Inline => matches!(item_kind, ItemKind::Fn(_)), Test => matches!(item_kind, ItemKind::Fn(_)), + Case => matches!(item_kind, ItemKind::Fn(_)), Payable => false, Allow => !matches!(item_kind, ItemKind::Submodule(_)), Cfg => !matches!(item_kind, ItemKind::Submodule(_)), // TODO: Adapt once https://github.com/FuelLabs/sway/issues/6942 is implemented. - Deprecated => match item_kind { - ItemKind::Submodule(_) => false, - ItemKind::Use(_) => false, - ItemKind::Struct(_) => true, - ItemKind::Enum(_) => true, - ItemKind::Fn(_) => true, - ItemKind::Trait(_) => false, - ItemKind::Impl(_) => false, - ItemKind::Abi(_) => false, - ItemKind::Const(_) => true, - ItemKind::Storage(_) => false, - // TODO: Currently, only single configurables can be deprecated. - // Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented. - ItemKind::Configurable(_) => false, - ItemKind::TypeAlias(_) => false, - ItemKind::Error(_, _) => true, - }, + Deprecated => Self::can_deprecate_item_kind(item_kind), Fallback => matches!(item_kind, ItemKind::Fn(_)), ErrorType => matches!(item_kind, ItemKind::Enum(_)), Error => false, Trace => matches!(item_kind, ItemKind::Fn(_)), AbiName => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)), + Fuzz => matches!(item_kind, ItemKind::Fn(_)), + } + } + + fn can_deprecate_item_kind(item_kind: &ItemKind) -> bool { + matches!(item_kind, + ItemKind::Struct(_) | + ItemKind::Enum(_) | + ItemKind::Fn(_) | + ItemKind::Const(_) | + ItemKind::Error(_, _) + ) + } + + fn storage_help_text(target_friendly_name: &str) -> Vec<&'static str> { + if target_friendly_name == "function signature" { + vec![ + "\"storage\" attribute can only annotate functions that have an implementation.", + "Function signatures in ABI and trait declarations do not have implementations.", + ] + } else { + vec!["\"storage\" attribute can only annotate functions."] } } @@ -638,6 +664,7 @@ impl Attribute { Storage => false, Inline => false, Test => false, + Case => false, Payable => false, Allow => true, Cfg => true, @@ -647,6 +674,7 @@ impl Attribute { Error => struct_or_enum_field == StructOrEnumField::EnumField, Trace => false, AbiName => false, + Fuzz => false, } } @@ -664,6 +692,7 @@ impl Attribute { // because they don't have implementation. Inline => false, Test => false, + Case => false, Payable => parent == TraitItemParent::Abi && matches!(item, ItemTraitItem::Fn(..)), Allow => true, Cfg => true, @@ -676,6 +705,7 @@ impl Attribute { // because they don't have implementation. Trace => false, AbiName => false, + Fuzz => false, } } @@ -691,6 +721,7 @@ impl Attribute { Storage => matches!(item, ItemImplItem::Fn(..)), Inline => matches!(item, ItemImplItem::Fn(..)), Test => false, + Case => matches!(item, ItemImplItem::Fn(..)), Payable => parent == ImplItemParent::Contract, Allow => true, Cfg => true, @@ -700,6 +731,7 @@ impl Attribute { Error => false, Trace => matches!(item, ItemImplItem::Fn(..)), AbiName => false, + Fuzz => matches!(item, ItemImplItem::Fn(..)), } } @@ -714,6 +746,7 @@ impl Attribute { Storage => true, Inline => true, Test => false, + Case => false, Payable => abi_or_trait_item == TraitItemParent::Abi, Allow => true, Cfg => true, @@ -723,6 +756,7 @@ impl Attribute { Error => false, Trace => true, AbiName => false, + Fuzz => false, } } @@ -734,6 +768,7 @@ impl Attribute { Storage => false, Inline => false, Test => false, + Case => false, Payable => false, Allow => true, Cfg => true, @@ -744,6 +779,7 @@ impl Attribute { Error => false, Trace => false, AbiName => false, + Fuzz => false, } } @@ -755,6 +791,7 @@ impl Attribute { Storage => false, Inline => false, Test => false, + Case => false, Payable => false, Allow => true, Cfg => true, @@ -764,6 +801,7 @@ impl Attribute { Error => false, Trace => false, AbiName => false, + Fuzz => false, } } @@ -787,18 +825,7 @@ impl Attribute { vec![] }, }, - Storage => { - if target_friendly_name == "function signature" { - vec![ - "\"storage\" attribute can only annotate functions that have an implementation.", - "Function signatures in ABI and trait declarations do not have implementations.", - ] - } else { - vec![ - "\"storage\" attribute can only annotate functions.", - ] - } - }, + Storage => Self::storage_help_text(target_friendly_name), Inline => vec!["\"inline\" attribute can only annotate functions."], Test => vec!["\"test\" attribute can only annotate module functions."], Payable => vec![ @@ -819,6 +846,8 @@ impl Attribute { AbiName => vec![ "\"abi_name\" attribute can only annotate structs and enums.", ], + Case => vec!["\"case\" attribute can only annotate test functions (functions with #[test])."], + Fuzz => vec!["\"fuzz\" attribute can only annotate test functions (functions with #[test])."], }; if help.is_empty() && target_friendly_name.starts_with("module kind") { @@ -1060,6 +1089,18 @@ impl Attributes { self.of_kind(AttributeKind::Test).last() } + /// Returns all `#[case]` [Attribute]s. + pub fn cases(&self) -> impl Iterator { + self.of_kind(AttributeKind::Case) + } + + /// Returns the `#[fuzz]` [Attribute], or `None` if the + /// [Attributes] does not contain any `#[fuzz]` attributes. + pub fn fuzz(&self) -> Option<&Attribute> { + // Last-wins approach. + self.of_kind(AttributeKind::Fuzz).last() + } + /// Returns the `#[error]` [Attribute], or `None` if the /// [Attributes] does not contain any `#[error]` attributes. pub fn error(&self) -> Option<&Attribute> { diff --git a/sway-error/src/convert_parse_tree_error.rs b/sway-error/src/convert_parse_tree_error.rs index b38d825249e..45fe71f9c41 100644 --- a/sway-error/src/convert_parse_tree_error.rs +++ b/sway-error/src/convert_parse_tree_error.rs @@ -163,6 +163,11 @@ pub enum ConvertParseTreeError { arg: Ident, expected_values: Vec<&'static str>, }, + #[error("Parameterized test attribute \"{attribute}\" requires a #[test] attribute on the same function.")] + ParameterizedTestRequiresTestAttribute { + span: Span, + attribute: Ident, + }, } pub(crate) enum AttributeType { @@ -268,6 +273,7 @@ impl Spanned for ConvertParseTreeError { ConvertParseTreeError::InvalidAttributeArgExpectsValue { arg, .. } => arg.span(), ConvertParseTreeError::InvalidAttributeArgValueType { span, .. } => span.clone(), ConvertParseTreeError::InvalidAttributeArgValue { span, .. } => span.clone(), + ConvertParseTreeError::ParameterizedTestRequiresTestAttribute { span, .. } => span.clone(), } } } diff --git a/sway-parse/src/attribute.rs b/sway-parse/src/attribute.rs index 6c6ccac9d17..5ace26a2a90 100644 --- a/sway-parse/src/attribute.rs +++ b/sway-parse/src/attribute.rs @@ -543,4 +543,25 @@ mod tests { ) "#); } + + #[test] + fn parse_fuzz_attribute_no_args() { + assert_ron_snapshot!(parse::(r#" + fuzz() + "#,), @"Attribute(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n fuzz()\\n \",start:13,end:17,source_id:None,),is_raw_ident:false,),args:Some(Parens(inner:Punctuated(value_separator_pairs:[],final_value_opt:None,),span:Span(src:\"\\n fuzz()\\n \",start:17,end:19,source_id:None,),)),)"); + } + + #[test] + fn parse_case_attribute() { + assert_ron_snapshot!(parse::(r#" + case(zero, one, is_true) + "#,), @"Attribute(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:13,end:17,source_id:None,),is_raw_ident:false,),args:Some(Parens(inner:Punctuated(value_separator_pairs:[(AttributeArg(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:18,end:22,source_id:None,),is_raw_ident:false,),value:None,),CommaToken(span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:22,end:23,source_id:None,),)),(AttributeArg(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:24,end:27,source_id:None,),is_raw_ident:false,),value:None,),CommaToken(span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:27,end:28,source_id:None,),)),],final_value_opt:Some(AttributeArg(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:29,end:36,source_id:None,),is_raw_ident:false,),value:None,)),),span:Span(src:\"\\n case(zero, one, is_true)\\n \",start:17,end:37,source_id:None,),)),)"); + } + + #[test] + fn parse_fuzz_parameterized_attribute() { + assert_ron_snapshot!(parse::(r#" + fuzz(x_iterations = 10, y_min = 0, y_max = 255) + "#,), @"Attribute(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:13,end:17,source_id:None,),is_raw_ident:false,),args:Some(Parens(inner:Punctuated(value_separator_pairs:[(AttributeArg(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:18,end:30,source_id:None,),is_raw_ident:false,),value:Some(Int(LitInt(span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:33,end:35,source_id:None,),parsed:[10,],ty_opt:None,is_generated_b256:false,))),),CommaToken(span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:35,end:36,source_id:None,),)),(AttributeArg(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:37,end:42,source_id:None,),is_raw_ident:false,),value:Some(Int(LitInt(span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:45,end:46,source_id:None,),parsed:[],ty_opt:None,is_generated_b256:false,))),),CommaToken(span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:46,end:47,source_id:None,),)),],final_value_opt:Some(AttributeArg(name:BaseIdent(name_override_opt:None,span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:48,end:53,source_id:None,),is_raw_ident:false,),value:Some(Int(LitInt(span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:56,end:59,source_id:None,),parsed:[255,],ty_opt:None,is_generated_b256:false,))),)),),span:Span(src:\"\\n fuzz(x_iterations = 10, y_min = 0, y_max = 255)\\n \",start:17,end:60,source_id:None,),)),)"); + } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/Forc.lock new file mode 100644 index 00000000000..8355521d5a2 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "attributes_fuzz_invalid_args" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/Forc.toml new file mode 100644 index 00000000000..4feb90450cf --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "attributes_fuzz_invalid_args" +implicit-std = false \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/src/main.sw new file mode 100644 index 00000000000..da99d3ea7c7 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/src/main.sw @@ -0,0 +1,23 @@ +library; + +// Invalid: fuzz argument without value assignment +#[test] +#[fuzz(invalid_arg)] +fn invalid_fuzz_with_unassigned_arg() { +} + +// Invalid: case attribute requires arguments +#[test] +#[case()] +fn invalid_case_no_args() { +} + +// Invalid: case attribute used without #[test] +#[case(some_value)] +fn invalid_case_without_test() { +} + +// Invalid: fuzz attribute used without #[test] +#[fuzz(param_iterations = 10)] +fn invalid_fuzz_without_test() { +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/test.toml new file mode 100644 index 00000000000..6c8e88395e0 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_args/test.toml @@ -0,0 +1,3 @@ +category = "fail" + +# check: $() \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/Forc.lock new file mode 100644 index 00000000000..5e912ce0b6d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "attributes_fuzz_invalid_multiplicity" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/Forc.toml new file mode 100644 index 00000000000..a607148532e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "attributes_fuzz_invalid_multiplicity" +implicit-std = false \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/src/main.sw new file mode 100644 index 00000000000..aee2ed56f77 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/src/main.sw @@ -0,0 +1,22 @@ +library; + +// Invalid: multiple fuzz attributes (only one allowed per function) +#[test] +#[fuzz(param1_iterations = 10)] +#[fuzz(param2_iterations = 20)] +fn multiple_fuzz_attributes() { +} + +// Valid: This should now be allowed - test with both case and fuzz +#[test] +#[case(first_case)] +#[fuzz(param_iterations = 10)] +fn test_with_case_and_fuzz() { +} + +// Invalid: multiple test attributes (only one allowed per function) +#[test] +#[test] +#[case(some_case)] +fn multiple_test_attributes() { +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/test.toml new file mode 100644 index 00000000000..6c8e88395e0 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_multiplicity/test.toml @@ -0,0 +1,3 @@ +category = "fail" + +# check: $() \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/Forc.lock new file mode 100644 index 00000000000..fec1acbe6f1 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "attributes_fuzz_invalid_target" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/Forc.toml new file mode 100644 index 00000000000..4715847a82b --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "attributes_fuzz_invalid_target" +implicit-std = false \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/src/main.sw new file mode 100644 index 00000000000..54a564f635d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/src/main.sw @@ -0,0 +1,17 @@ +library; + +// Invalid: fuzz attribute on struct +#[fuzz(param_iterations = 10)] +struct InvalidStruct { + field: u64, +} + +// Invalid: case attribute on const +#[case(some_value)] +const INVALID_CONST: u64 = 42; + +// Invalid: case attribute on struct +#[case(field_value)] +struct AnotherInvalidStruct { + field: u64, +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/test.toml new file mode 100644 index 00000000000..fe296733d2a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/attributes_fuzz_invalid_target/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: $() +# check: $() \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/Forc.lock new file mode 100644 index 00000000000..c1812a9344f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "attributes_fuzz_valid" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/Forc.toml new file mode 100644 index 00000000000..f86b1d5af2a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "attributes_fuzz_valid" +implicit-std = false \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/src/main.sw new file mode 100644 index 00000000000..ea6a6bf73ed --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/src/main.sw @@ -0,0 +1,26 @@ +library; + +// Example 1: Simple fuzz test without specific parameters +#[test] +#[fuzz()] +fn fuzz_simple() { +} + +// Example 2: Fuzz test with specific iterations for one parameter +#[test] +#[fuzz(input1_iterations = 100)] +fn fuzz_with_single_param(input1: u64) { +} + +// Example 3: Mixed case and fuzz testing +#[test] +#[case(zero_input, small_input)] +#[fuzz(input1_iterations = 50, input2_min = 0, input2_max = 255)] +fn fuzz_with_mixed_fixtures(input1: u64, input2: u8) { +} + +// Example 4: Multiple fuzz parameter configurations +#[test] +#[fuzz(x_min = 1, x_max = 100, y_iterations = 25)] +fn fuzz_with_param_ranges(x: u32, y: u64) { +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/test.toml new file mode 100644 index 00000000000..3361f4e48c4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/attributes_fuzz_valid/test.toml @@ -0,0 +1,4 @@ +category = "compile" +expected_warnings = 9 + +# check: $() \ No newline at end of file