Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2041,36 +2041,54 @@ 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();

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::<u64>())
.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
)),
match (test_attr, fuzz_attr) {
(Some(_), Some(_)) => {
bail!(
"Function \"{}\" cannot have both #[test] and #[fuzz] attributes",
test_function_decl.name
);
}
(None, None) => {
unreachable!("`test_function_decl` is guaranteed to be a test or fuzz function and it must have a `#[test]` or `#[fuzz]` attribute");
}
_ => {} // Valid: exactly one attribute present
}

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::<u64>())
.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 =
Expand Down
10 changes: 10 additions & 0 deletions sway-ast/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ 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 fuzz testing.
pub const FUZZ_ATTRIBUTE_NAME: &str = "fuzz";
pub const FUZZ_PARAM_ATTRIBUTE_NAME: &str = "fuzz_param";
pub const FUZZ_PARAM_NAME_ARG_NAME: &str = "name";
pub const FUZZ_PARAM_ITERATION_ARG_NAME: &str = "iteration";
pub const FUZZ_PARAM_MIN_VAL_ARG_NAME: &str = "min_val";
pub const FUZZ_PARAM_MAX_VAL_ARG_NAME: &str = "max_val";

// Allow warnings.
pub const ALLOW_ATTRIBUTE_NAME: &str = "allow";
pub const ALLOW_DEAD_CODE_ARG_NAME: &str = "dead_code";
Expand Down Expand Up @@ -72,6 +80,8 @@ pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[
DEPRECATED_ATTRIBUTE_NAME,
FALLBACK_ATTRIBUTE_NAME,
ABI_NAME_ATTRIBUTE_NAME,
FUZZ_ATTRIBUTE_NAME,
FUZZ_PARAM_ATTRIBUTE_NAME,
];

/// An attribute declaration. Attribute declaration
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/language/ty/ast_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
66 changes: 66 additions & 0 deletions sway-core/src/transform/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,22 @@ impl AttributeArg {
self.name.as_str() == TEST_SHOULD_REVERT_ARG_NAME
}

pub fn is_fuzz_param_name(&self) -> bool {
self.name.as_str() == FUZZ_PARAM_NAME_ARG_NAME
}

pub fn is_fuzz_param_iteration(&self) -> bool {
self.name.as_str() == FUZZ_PARAM_ITERATION_ARG_NAME
}

pub fn is_fuzz_param_min_val(&self) -> bool {
self.name.as_str() == FUZZ_PARAM_MIN_VAL_ARG_NAME
}

pub fn is_fuzz_param_max_val(&self) -> bool {
self.name.as_str() == FUZZ_PARAM_MAX_VAL_ARG_NAME
}

pub fn is_error_message(&self) -> bool {
self.name.as_str() == ERROR_M_ARG_NAME
}
Expand Down Expand Up @@ -356,6 +372,8 @@ pub enum AttributeKind {
Error,
Trace,
AbiName,
Fuzz,
FuzzParam,
}

/// Denotes if an [ItemTraitItem] belongs to an ABI or to a trait.
Expand Down Expand Up @@ -388,6 +406,8 @@ impl AttributeKind {
ERROR_ATTRIBUTE_NAME => AttributeKind::Error,
TRACE_ATTRIBUTE_NAME => AttributeKind::Trace,
ABI_NAME_ATTRIBUTE_NAME => AttributeKind::AbiName,
FUZZ_ATTRIBUTE_NAME => AttributeKind::Fuzz,
FUZZ_PARAM_ATTRIBUTE_NAME => AttributeKind::FuzzParam,
_ => AttributeKind::Unknown,
}
}
Expand Down Expand Up @@ -418,6 +438,8 @@ impl AttributeKind {
Error => false,
Trace => false,
AbiName => false,
Fuzz => false,
FuzzParam => true,
}
}
}
Expand Down Expand Up @@ -461,6 +483,10 @@ impl Attribute {
// `trace(never)` or `trace(always)`.
Trace => Multiplicity::exactly(1),
AbiName => Multiplicity::exactly(1),
// `fuzz` takes no arguments.
Fuzz => Multiplicity::zero(),
// `fuzz_param(name = "foo", iteration = 100)`.
FuzzParam => Multiplicity::at_least(1),
}
}

Expand Down Expand Up @@ -518,6 +544,13 @@ 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 => None,
FuzzParam => MustBeIn(vec![
FUZZ_PARAM_NAME_ARG_NAME,
FUZZ_PARAM_ITERATION_ARG_NAME,
FUZZ_PARAM_MIN_VAL_ARG_NAME,
FUZZ_PARAM_MAX_VAL_ARG_NAME,
]),
}
}

Expand All @@ -543,6 +576,9 @@ impl Attribute {
Error => Yes,
Trace => No,
AbiName => Yes,
Fuzz => No,
// `fuzz_param(name = "foo", iteration = 100)`.
FuzzParam => Yes,
}
}

Expand All @@ -565,6 +601,8 @@ impl Attribute {
Error => false,
Trace => false,
AbiName => false,
Fuzz => false,
FuzzParam => false,
}
}

Expand Down Expand Up @@ -620,6 +658,8 @@ impl Attribute {
Error => false,
Trace => matches!(item_kind, ItemKind::Fn(_)),
AbiName => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
Fuzz => matches!(item_kind, ItemKind::Fn(_)),
FuzzParam => matches!(item_kind, ItemKind::Fn(_)),
}
}

Expand Down Expand Up @@ -647,6 +687,8 @@ impl Attribute {
Error => struct_or_enum_field == StructOrEnumField::EnumField,
Trace => false,
AbiName => false,
Fuzz => false,
FuzzParam => false,
}
}

Expand Down Expand Up @@ -676,6 +718,8 @@ impl Attribute {
// because they don't have implementation.
Trace => false,
AbiName => false,
Fuzz => false,
FuzzParam => false,
}
}

Expand All @@ -700,6 +744,8 @@ impl Attribute {
Error => false,
Trace => matches!(item, ItemImplItem::Fn(..)),
AbiName => false,
Fuzz => matches!(item, ItemImplItem::Fn(..)),
FuzzParam => matches!(item, ItemImplItem::Fn(..)),
}
}

Expand All @@ -723,6 +769,8 @@ impl Attribute {
Error => false,
Trace => true,
AbiName => false,
Fuzz => false,
FuzzParam => false,
}
}

Expand All @@ -744,6 +792,8 @@ impl Attribute {
Error => false,
Trace => false,
AbiName => false,
Fuzz => false,
FuzzParam => false,
}
}

Expand All @@ -764,6 +814,8 @@ impl Attribute {
Error => false,
Trace => false,
AbiName => false,
Fuzz => false,
FuzzParam => false,
}
}

Expand Down Expand Up @@ -819,6 +871,8 @@ impl Attribute {
AbiName => vec![
"\"abi_name\" attribute can only annotate structs and enums.",
],
Fuzz => vec!["\"fuzz\" attribute can only annotate module functions."],
FuzzParam => vec!["\"fuzz_param\" attribute can only annotate module functions."],
};

if help.is_empty() && target_friendly_name.starts_with("module kind") {
Expand Down Expand Up @@ -1060,6 +1114,18 @@ impl Attributes {
self.of_kind(AttributeKind::Test).last()
}

/// 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 all `#[fuzz_param]` [Attribute]s.
pub fn fuzz_params(&self) -> impl Iterator<Item = &Attribute> {
self.of_kind(AttributeKind::FuzzParam)
}

/// Returns the `#[error]` [Attribute], or `None` if the
/// [Attributes] does not contain any `#[error]` attributes.
pub fn error(&self) -> Option<&Attribute> {
Expand Down
Loading
Loading