Skip to content

Commit

Permalink
Fix #24
Browse files Browse the repository at this point in the history
  • Loading branch information
smoelius committed Aug 13, 2021
1 parent 738eb2d commit 2ef5e50
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 3 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ The primary effects of the `test_fuzz` macro are:

#### Options

- **`bounds = "where_predicates"`** - Impose `where_predicates` (e.g., trait bounds) on the struct used to serialize/deserialize arguments. This may be necessary, e.g., if a target's argument type is an associated type. For an example, see [associated_type.rs](examples/tests/associated_type.rs#L27) in this repository.

- **`concretize = "parameters"`** - Use `parameters` as the target's type parameters when fuzzing. Example:

```rust
Expand Down
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test-fuzz = { path = "../test-fuzz", version = "=0.1.0-alpha.24", default-featur
[dev-dependencies]
lazy_static = "1.4.0"
parse_duration = "2.1.1"
serde_json = "1.0.66"

[features]
bar_fuzz = []
53 changes: 53 additions & 0 deletions examples/tests/associated_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// smoelius: Associated types are considered a legitimate reason to put a bound on a struct
// parameter. See:
// * https://github.com/rust-lang/rust-clippy/issues/1689
// * https://github.com/rust-lang/api-guidelines/issues/6
//
// This example is based in part on:
// https://docs.serde.rs/serde_json/#creating-json-by-serializing-data-structures

mod associated_type {
use serde::{de, Deserialize, Serialize};

trait Serializable {
type Out: Clone + de::DeserializeOwned + Serialize + PartialEq + Eq;
fn serialize(&self) -> Self::Out;
}

impl<T> Serializable for T
where
T: Serialize,
{
type Out = String;
fn serialize(&self) -> Self::Out {
serde_json::to_string(self).unwrap()
}
}

#[test_fuzz::test_fuzz(concretize = "Address", bounds = "T: Serializable")]
fn serializes_to<T>(x: &T, y: &T::Out) -> bool
where
T: Clone + de::DeserializeOwned + Serialize + Serializable,
{
&<T as Serializable>::serialize(x) == y
}

#[derive(Clone, Serialize, Deserialize)]
struct Address {
street: String,
city: String,
}

#[test]
fn test() {
let address = Address {
street: "10 Downing Street".to_owned(),
city: "London".to_owned(),
};

assert!(serializes_to(
&address,
&String::from("{\"street\":\"10 Downing Street\",\"city\":\"London\"}")
));
}
}
21 changes: 18 additions & 3 deletions macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use syn::{
AttributeArgs, Block, Expr, FnArg, GenericArgument, GenericMethodArgument, GenericParam,
Generics, Ident, ImplItem, ImplItemMethod, ItemFn, ItemImpl, ItemMod, Pat, Path, PathArguments,
PathSegment, ReturnType, Signature, Stmt, Type, TypePath, TypeReference, Visibility,
WhereClause, WherePredicate,
};
use toolchain_find::find_installed_component;
use unzip_n::unzip_n;
Expand Down Expand Up @@ -135,6 +136,8 @@ fn map_method(

#[derive(Clone, Debug, Default, FromMeta)]
struct TestFuzzOpts {
#[darling(default)]
bounds: Option<String>,
#[darling(default)]
concretize: Option<String>,
#[darling(default)]
Expand Down Expand Up @@ -225,6 +228,18 @@ fn map_method_or_fn(
let (impl_generics, ty_generics, where_clause) = combined_generics.split_for_impl();
let (impl_generics_deserializable, _, _) = combined_generics_deserializable.split_for_impl();

let args_where_clause: Option<WhereClause> = opts.bounds.as_ref().map(|bounds| {
let tokens = TokenStream::from_str(&bounds).expect("Could not tokenize string");
let where_predicates = Parser::parse(
Punctuated::<WherePredicate, token::Comma>::parse_terminated,
tokens,
)
.expect("Could not parse type bounds");
parse_quote! {
where #where_predicates
}
});

// smoelius: "Constraints don’t count as 'using' a type parameter," as explained by Daniel Keep
// here: https://users.rust-lang.org/t/error-parameter-t-is-never-used-e0392-but-i-use-it/5673
// So, for each type parameter `T`, add a `PhantomData<T>` member to `Args` to ensure that `T`
Expand Down Expand Up @@ -445,7 +460,7 @@ fn map_method_or_fn(
#[derive(serde::Serialize)]
struct Args #ty_generics (
#(#pub_arg_tys),*
);
) #args_where_clause;
let args = Args(
#(#args_is),*
);
Expand All @@ -459,7 +474,7 @@ fn map_method_or_fn(
#[derive(serde::Deserialize)]
struct Args #ty_generics (
#(#pub_arg_tys),*
);
) #args_where_clause;
let args = test_fuzz::runtime::read_args::<Args #ty_generics_as_turbofish, _>(#serde_format, reader);
args.map(|args| #mod_ident :: Args(
#(#args_is),*
Expand Down Expand Up @@ -547,7 +562,7 @@ fn map_method_or_fn(

pub(super) struct Args #ty_generics (
#(#pub_arg_tys),*
);
) #args_where_clause;

#mod_items

Expand Down

0 comments on commit 2ef5e50

Please sign in to comment.