Skip to content

Commit

Permalink
Close #107 and #108: implemented and documented
Browse files Browse the repository at this point in the history
  • Loading branch information
la10736 committed May 16, 2021
1 parent d909aba commit 63481c1
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 74 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,8 @@

### Add

- Rename fixture (See #107 and #108)

### Changed

### Fixed
Expand Down
63 changes: 61 additions & 2 deletions src/lib.rs
Expand Up @@ -348,6 +348,24 @@ use quote::ToTokens;
/// If you need, you can use `#[future]` attribute also with an implicit lifetime reference
/// because the macro will replace the implicit lifetime with an explicit one.
///
/// # Rename
///
/// Sometimes you want to have long and descriptive name for your fixture but you prefer to use a much
/// shorter name for argument that represent it in your fixture or test. You can rename the fixture
/// using `#[from(short_name)]` attribute like following example:
///
/// ```
/// use rstest::*;
///
/// #[fixture]
/// fn long_and_boring_descriptive_name() -> i32 { 42 }
///
/// #[rstest]
/// fn the_test(#[from(long_and_boring_descriptive_name)] short: i32) {
/// assert_eq!(42, short)
/// }
/// ```
///
/// # Partial Injection
///
/// You can also partialy inject fixture dependency using `#[with(v1, v2, ..)]` attribute:
Expand Down Expand Up @@ -376,7 +394,6 @@ use quote::ToTokens;
/// attribute will inject `v1, ..., vn` expression as fixture arguments: all remaining arguments
/// will be resolved as fixtures.
///
///
/// Sometimes the return type cannot be infered so you must define it: For the few times you may
/// need to do it, you can use the `#[default(type)]`, `#[partial_n(type)]` function attribute
/// to define it:
Expand Down Expand Up @@ -428,6 +445,19 @@ use quote::ToTokens;
/// #[fixture(twenty_one=21, two=2)]
/// fn injected(twenty_one: i32, two: i32) -> i32 { twenty_one * two }
/// ```
///
/// ## Rename
/// ```
/// # use rstest::*;
/// #[fixture]
/// fn long_and_boring_descriptive_name() -> i32 { 42 }
///
/// #[rstest(long_and_boring_descriptive_name as short)]
/// fn the_test(short: i32) {
/// assert_eq!(42, short)
/// }
/// ```
///
/// ## Partial Injection
/// ```
/// # use rstest::*;
Expand Down Expand Up @@ -547,6 +577,22 @@ pub fn fixture(
/// }
/// ```
///
/// If you want to use long and descriptive names for your fixture but prefer to use
/// shorter names inside your tests you use rename feature described in
/// [fixture rename](attr.fixture.html#rename):
///
/// ```
/// use rstest::*;
///
/// #[fixture]
/// fn long_and_boring_descriptive_name() -> i32 { 42 }
///
/// #[rstest]
/// fn the_test(#[from(long_and_boring_descriptive_name)] short: i32) {
/// assert_eq!(42, short)
/// }
/// ```
///
/// Sometimes is useful to have some parametes in your fixtures but your test would
/// override the fixture's default values in some cases. Like in
/// [fixture partial injection](attr.fixture.html#partial-injection) you use `#[with]`
Expand Down Expand Up @@ -1020,7 +1066,8 @@ pub fn fixture(
/// - `arg_i` could be one of the follow
/// - `ident` that match to one of function arguments for parametrized cases
/// - `case[::description](v1, ..., vl)` a test case
/// - `fixture(v1, ..., vl)` where fixture is one of function arguments
/// - `fixture(v1, ..., vl) [as argument_name]` where fixture is the injected
/// fixture and argument_name (default use fixture) is one of function arguments
/// that and `v1, ..., vl` is a partial list of fixture's arguments
/// - `ident => [v1, ..., vl]` where `ident` is one of function arguments and
/// `v1, ..., vl` is a list of values for ident
Expand All @@ -1045,6 +1092,18 @@ pub fn fixture(
/// }
/// ```
///
/// ## Fixture Rename
/// ```
/// # use rstest::*;
/// #[fixture]
/// fn long_and_boring_descriptive_name() -> i32 { 42 }
///
/// #[rstest(long_and_boring_descriptive_name as short)]
/// fn the_test(short: i32) {
/// assert_eq!(42, short)
/// }
/// ```
///
/// ## Parametrized
///
/// ```
Expand Down
130 changes: 112 additions & 18 deletions src/parse/fixture.rs
Expand Up @@ -9,10 +9,10 @@ use syn::{
use super::{
extract_argument_attrs, extract_default_return_type, extract_defaults, extract_fixtures,
extract_partials_return_type, parse_vector_trailing_till_double_comma, Attributes,
ExtendWithFunctionAttrs, Fixture, Positional,
ExtendWithFunctionAttrs, Fixture,
};
use crate::parse::Attribute;
use crate::{error::ErrorsVec, refident::RefIdent, utils::attr_is};
use crate::{parse::Attribute, utils::attr_in};
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens};

Expand Down Expand Up @@ -76,24 +76,58 @@ impl ExtendWithFunctionAttrs for FixtureInfo {
}
}

fn parse_attribute_args_just_once<'a, T: Parse>(
attributes: impl Iterator<Item = &'a syn::Attribute>,
name: &str,
) -> (Option<T>, Vec<syn::Error>) {
let mut errors = Vec::new();
let val = attributes
.filter(|&a| attr_is(a, name))
.map(|a| (a, a.parse_args::<T>()))
.fold(None, |first, (a, res)| match (first, res) {
(None, Ok(parsed)) => Some(parsed),
(first, Err(err)) => {
errors.push(err);
first
}
(first, _) => {
errors.push(syn::Error::new_spanned(
a,
format!(
"You cannot use '{}' attribute more than once for the same argument",
name
),
));
first
}
});
(val, errors)
}

/// Simple struct used to visit function attributes and extract Fixtures and
/// eventualy parsing errors
#[derive(Default)]
pub(crate) struct FixturesFunctionExtractor(pub(crate) Vec<Fixture>, pub(crate) Vec<syn::Error>);

impl VisitMut for FixturesFunctionExtractor {
fn visit_fn_arg_mut(&mut self, node: &mut FnArg) {
for r in extract_argument_attrs(
node,
|a| attr_is(a, "with"),
|a, name| {
a.parse_args::<Positional>()
.map(|p| Fixture::new(name.clone(), p))
},
) {
match r {
Ok(fixture) => self.0.push(fixture),
Err(err) => self.1.push(err),
if let FnArg::Typed(ref mut arg) = node {
let name = match arg.pat.as_ref() {
syn::Pat::Ident(ident) => ident.ident.clone(),
_ => return,
};
let (extracted, remain): (Vec<_>, Vec<_>) = std::mem::take(&mut arg.attrs)
.into_iter()
.partition(|attr| attr_in(attr, &["with", "from"]));
arg.attrs = remain;

let (pos, errors) = parse_attribute_args_just_once(extracted.iter(), "with");
self.1.extend(errors.into_iter());
let (resolve, errors) = parse_attribute_args_just_once(extracted.iter(), "from");
self.1.extend(errors.into_iter());
if pos.is_some() || resolve.is_some() {
self.0
.push(Fixture::new(name, resolve, pos.unwrap_or_default()))
}
}
}
Expand Down Expand Up @@ -282,8 +316,8 @@ mod should {

let expected = FixtureInfo {
data: vec![
fixture("my_fixture", vec!["42", r#""other""#]).into(),
fixture("other", vec!["vec![42]"]).into(),
fixture("my_fixture", &["42", r#""other""#]).into(),
fixture("other", &["vec![42]"]).into(),
arg_value("value", "42").into(),
arg_value("other_value", "vec![1.0]").into(),
]
Expand Down Expand Up @@ -332,7 +366,7 @@ mod should {
let data = parse_fixture(r#"my_fixture(42, "other")"#);

let expected = FixtureInfo {
data: vec![fixture("my_fixture", vec!["42", r#""other""#]).into()].into(),
data: vec![fixture("my_fixture", &["42", r#""other""#]).into()].into(),
..Default::default()
};

Expand Down Expand Up @@ -377,8 +411,8 @@ mod extend {

let expected = FixtureInfo {
data: vec![
fixture("f1", vec!["2"]).into(),
fixture("f2", vec!["vec![1,2]", r#""s""#]).into(),
fixture("f1", &["2"]).into(),
fixture("f2", &["vec![1,2]", r#""s""#]).into(),
]
.into(),
..Default::default()
Expand All @@ -388,6 +422,36 @@ mod extend {
assert_eq!(expected, info);
}

#[test]
fn rename_with_attributes() {
let mut item_fn = r#"
fn test_fn(
#[from(long_fixture_name)]
#[with(42, "other")] short: u32,
#[from(simple)]
s: &str,
no_change: i32) {
}
"#
.ast();

let expected = FixtureInfo {
data: vec![
fixture("short", &["42", r#""other""#])
.with_resolve("long_fixture_name")
.into(),
fixture("s", &[]).with_resolve("simple").into(),
]
.into(),
..Default::default()
};

let mut data = FixtureInfo::default();
data.extend_with_function_attrs(&mut item_fn).unwrap();

assert_eq!(expected, data);
}

#[test]
fn use_default_values_attributes() {
let to_parse = r#"
Expand Down Expand Up @@ -502,6 +566,36 @@ mod extend {
assert_eq!(1, errors.len());
}

#[test]
fn with_used_more_than_once() {
let mut item_fn: ItemFn = r#"
fn my_fix(#[with(1)] #[with(2)] fixture1: &str, #[with(1)] #[with(2)] #[with(3)] fixture2: &str) {}
"#
.ast();

let errors = FixtureInfo::default()
.extend_with_function_attrs(&mut item_fn)
.err()
.unwrap_or_default();

assert_eq!(3, errors.len());
}

#[test]
fn from_used_more_than_once() {
let mut item_fn: ItemFn = r#"
fn my_fix(#[from(a)] #[from(b)] fixture1: &str, #[from(c)] #[from(d)] #[from(e)] fixture2: &str) {}
"#
.ast();

let errors = FixtureInfo::default()
.extend_with_function_attrs(&mut item_fn)
.err()
.unwrap_or_default();

assert_eq!(3, errors.len());
}

#[test]
fn if_default_is_defined_more_than_once() {
let mut item_fn: ItemFn = r#"
Expand Down
42 changes: 32 additions & 10 deletions src/parse/mod.rs
Expand Up @@ -3,7 +3,7 @@ use syn::{
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
token,
token::{self, Paren},
visit_mut::VisitMut,
FnArg, Ident, ItemFn, Token,
};
Expand All @@ -27,10 +27,10 @@ pub(crate) mod macros;

pub(crate) mod expressions;
pub(crate) mod fixture;
pub(crate) mod future;
pub(crate) mod rstest;
pub(crate) mod testcase;
pub(crate) mod vlist;
pub(crate) mod future;

pub(crate) trait ExtendWithFunctionAttrs {
fn extend_with_function_attrs(
Expand Down Expand Up @@ -117,7 +117,7 @@ pub(crate) fn drain_stream(input: ParseStream) {
});
}

#[derive(PartialEq, Debug, Clone)]
#[derive(PartialEq, Debug, Clone, Default)]
pub(crate) struct Positional(pub(crate) Vec<syn::Expr>);

impl Parse for Positional {
Expand All @@ -133,22 +133,44 @@ impl Parse for Positional {
#[derive(PartialEq, Debug, Clone)]
pub(crate) struct Fixture {
pub(crate) name: Ident,
pub(crate) resolve: Option<Ident>,
pub(crate) positional: Positional,
}

impl Fixture {
pub(crate) fn new(name: Ident, positional: Positional) -> Self {
Self { name, positional }
pub(crate) fn new(name: Ident, resolve: Option<Ident>, positional: Positional) -> Self {
Self {
name,
resolve,
positional,
}
}
}

impl Parse for Fixture {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let content;
let _ = syn::parenthesized!(content in input);
let positional = content.parse()?;
Ok(Self::new(name, positional))
let resolve = input.parse()?;
if input.peek(Paren) || input.peek(Token![as]) {
let positional = if input.peek(Paren) {
let content;
let _ = syn::parenthesized!(content in input);
content.parse()?
} else {
Default::default()
};

if input.peek(Token![as]) {
let _: Token![as] = input.parse()?;
Ok(Self::new(input.parse()?, Some(resolve), positional))
} else {
Ok(Self::new(resolve, None, positional))
}
} else {
Err(syn::Error::new(
input.span(),
"fixture need arguments or 'as new_name' format",
))
}
}
}

Expand Down

0 comments on commit 63481c1

Please sign in to comment.