From e1261b3632326eea6220d30aa18feb4021526d9e Mon Sep 17 00:00:00 2001 From: michele Date: Mon, 5 Apr 2021 15:23:15 +0200 Subject: [PATCH] #111: implemented for test cases... need lot of refactoring --- src/refident.rs | 20 ++- src/render/mod.rs | 146 +++++++++++++++++- .../rstest/convert_string_literal.rs | 13 ++ tests/rstest/mod.rs | 14 ++ 4 files changed, 182 insertions(+), 11 deletions(-) create mode 100644 tests/resources/rstest/convert_string_literal.rs diff --git a/src/refident.rs b/src/refident.rs index f5a1b40..8df5add 100644 --- a/src/refident.rs +++ b/src/refident.rs @@ -1,7 +1,7 @@ /// Provide `RefIdent` and `MaybeIdent` traits that give a shortcut to extract identity reference /// (`syn::Ident` struct). use proc_macro2::Ident; -use syn::{FnArg, Pat, PatType}; +use syn::{FnArg, Pat, PatType, Type}; pub trait RefIdent { /// Return the reference to ident if any @@ -43,10 +43,24 @@ impl MaybeIdent for FnArg { } } -impl MaybeIdent for syn::Type { +impl MaybeIdent for Type { fn maybe_ident(&self) -> Option<&Ident> { match self { - syn::Type::Path(tp) if tp.qself.is_none() => tp.path.get_ident(), + Type::Path(tp) if tp.qself.is_none() => tp.path.get_ident(), + _ => None, + } + } +} + +pub trait MaybeType { + /// Return the reference to type if any + fn maybe_type(&self) -> Option<&Type>; +} + +impl MaybeType for FnArg { + fn maybe_type(&self) -> Option<&Type> { + match self { + FnArg::Typed(PatType { ty, .. }) => Some(ty.as_ref()), _ => None, } } diff --git a/src/render/mod.rs b/src/render/mod.rs index 656ba0a..3a92f33 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -13,9 +13,7 @@ use syn::{ use quote::{format_ident, quote}; -use crate::refident::MaybeIdent; -use crate::resolver::{self, Resolver}; -use crate::utils::{attr_ends_with, fn_args_idents}; +use crate::utils::attr_ends_with; use crate::{ parse::{ rstest::{RsTestAttributes, RsTestData, RsTestInfo}, @@ -24,15 +22,26 @@ use crate::{ }, utils::attr_is, }; +use crate::{ + refident::{MaybeIdent, MaybeType}, + resolver::{self, Resolver}, +}; use wrapper::WrapByModule; pub(crate) use fixture::render as fixture; pub(crate) fn single(mut test: ItemFn, info: RsTestInfo) -> TokenStream { let resolver = resolver::fixtures::get(info.data.fixtures()); - let args = fn_args_idents(&test).cloned().collect::>(); + let args = test.sig.inputs.iter().cloned().collect::>(); let attrs = std::mem::replace(&mut test.attrs, Default::default()); let asyncness = test.sig.asyncness.clone(); + let generic_types = test + .sig + .generics + .type_params() + .map(|tp| &tp.ident) + .cloned() + .collect::>(); single_test_case( &test.sig.ident, @@ -44,6 +53,7 @@ pub(crate) fn single(mut test: ItemFn, info: RsTestInfo) -> TokenStream { Some(&test), resolver, &info.attributes, + &generic_types, ) } @@ -184,13 +194,14 @@ fn render_exec_call(fn_path: Path, args: &[Ident], is_async: bool) -> TokenStrea fn single_test_case<'a>( name: &Ident, testfn_name: &Ident, - args: &[Ident], + args: &[FnArg], attrs: &[Attribute], output: &ReturnType, asyncness: Option, test_impl: Option<&ItemFn>, resolver: impl Resolver, attributes: &'a RsTestAttributes, + generic_types: &[Ident], ) -> TokenStream { let (attrs, trace_me): (Vec<_>, Vec<_>) = attrs.iter().cloned().partition(|a| !attr_is(a, "trace")); @@ -198,7 +209,12 @@ fn single_test_case<'a>( if trace_me.len() > 0 { attributes.add_trace(format_ident!("trace")); } - let inject = resolve_args(args.iter(), &resolver); + let inject = resolve_new_args(args.iter(), &resolver, generic_types); + let args = args + .iter() + .filter_map(MaybeIdent::maybe_ident) + .cloned() + .collect::>(); let trace_args = trace_arguments(args.iter(), &attributes); let is_async = asyncness.is_some(); @@ -211,7 +227,7 @@ fn single_test_case<'a>( } else { Some(resolve_default_test_attr(is_async)) }; - let execute = render_exec_call(testfn_name.clone().into(), args, is_async); + let execute = render_exec_call(testfn_name.clone().into(), &args, is_async); quote! { #test_attr @@ -252,6 +268,101 @@ fn trace_arguments<'a>( fn default_fixture_resolve(ident: &Ident) -> Cow { Cow::Owned(parse_quote! { #ident::default() }) } +fn fnarg_2_fixture(arg: &FnArg, resolver: &impl Resolver, generic_types: &[Ident]) -> Option { + let ident = arg.maybe_ident()?; + let arg_type = arg.maybe_type()?; + let id_str = ident.to_string(); + let fixture_name = if id_str.starts_with("_") && !id_str.starts_with("__") { + Cow::Owned(Ident::new(&id_str[1..], ident.span())) + } else { + Cow::Borrowed(ident) + }; + + let mut fixture = resolver + .resolve(&fixture_name) + .map(|e| e.clone()) + .unwrap_or_else(|| default_fixture_resolve(&fixture_name)); + + let is_explicit_type = match arg_type { + Type::ImplTrait(_) + | Type::TraitObject(_) + | Type::Infer(_) + | Type::Group(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Verbatim(_) => false, + _ => true, + }; + + let is_literal_str = match fixture.as_ref() { + &Expr::Lit(syn::ExprLit { ref lit, .. }) => match lit { + syn::Lit::Str(_) => true, + _ => false, + }, + _ => false, + }; + if is_literal_str + && is_explicit_type + && arg_type + .maybe_ident() + .map(|id| generic_types.iter().find(|&tp| tp == id).is_none()) + .unwrap_or_default() + { + let new_fixture: Expr = parse_quote! { + { + struct __Wrap(std::marker::PhantomData); + + trait __ViaParseDebug<'a, T> { + fn magic_conversion(&self, input: &'a str) -> T; + } + + impl<'a, T> __ViaParseDebug<'a, T> for &&__Wrap + where + T: std::str::FromStr, + T::Err: std::fmt::Debug, + { + fn magic_conversion(&self, input: &'a str) -> T { + T::from_str(input).unwrap() + } + } + + trait __ViaParse<'a, T> { + fn magic_conversion(&self, input: &'a str) -> T; + } + + impl<'a, T> __ViaParse<'a, T> for &__Wrap + where + T: std::str::FromStr, + { + fn magic_conversion(&self, input: &'a str) -> T { + match T::from_str(input) { + Ok(v) => v, + Err(_) => { + panic!("Cannot parse '{}' to get {}", input, std::stringify!(#arg_type)); + } + } + } + } + + trait __ViaIdent<'a, T> { + fn magic_conversion(&self, input: &'a str) -> T; + } + + impl<'a> __ViaIdent<'a, &'a str> for &&__Wrap<&'a str> { + fn magic_conversion(&self, input: &'a str) -> &'a str { + input + } + } + (&&&__Wrap::<#arg_type>(std::marker::PhantomData)).magic_conversion(#fixture) + } + }; + fixture = Cow::Owned(new_fixture); + } + Some(parse_quote! { + let #ident = #fixture; + }) +} fn arg_2_fixture(ident: &Ident, resolver: &impl Resolver) -> Stmt { let id_str = ident.to_string(); @@ -270,6 +381,17 @@ fn arg_2_fixture(ident: &Ident, resolver: &impl Resolver) -> Stmt { } } +fn resolve_new_args<'a>( + args: impl Iterator, + resolver: &impl Resolver, + generic_types: &[Ident], +) -> TokenStream { + let define_vars = args.map(|arg| fnarg_2_fixture(arg, resolver, generic_types)); + quote! { + #(#define_vars)* + } +} + fn resolve_args<'a>( args: impl Iterator, resolver: &impl Resolver, @@ -348,10 +470,17 @@ impl<'a> TestCaseRender<'a> { } fn render(self, testfn: &ItemFn, attributes: &RsTestAttributes) -> TokenStream { - let args = fn_args_idents(&testfn).cloned().collect::>(); + let args = testfn.sig.inputs.iter().cloned().collect::>(); let mut attrs = testfn.attrs.clone(); attrs.extend(self.attrs.iter().cloned()); let asyncness = testfn.sig.asyncness.clone(); + let generic_types = testfn + .sig + .generics + .type_params() + .map(|tp| &tp.ident) + .cloned() + .collect::>(); single_test_case( &self.name, @@ -363,6 +492,7 @@ impl<'a> TestCaseRender<'a> { None, self.resolver, &attributes, + &generic_types ) } } diff --git a/tests/resources/rstest/convert_string_literal.rs b/tests/resources/rstest/convert_string_literal.rs new file mode 100644 index 0000000..8dcf1bd --- /dev/null +++ b/tests/resources/rstest/convert_string_literal.rs @@ -0,0 +1,13 @@ +use rstest::*; +use std::net::SocketAddr; + +#[rstest] +#[case(true, "1.2.3.4:42")] +#[case(true, r#"4.3.2.1:24"#)] +#[case(false, "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443")] +#[case(false, r#"[2aa1:db8:85a3:8af:1319:8a2e:375:4873]:344"#)] +#[case(false, "this.is.not.a.socket.address")] +#[case(false, r#"this.is.not.a.socket.address"#)] +fn cases(#[case] expected: bool, #[case] addr: SocketAddr) { + assert_eq!(expected, addr.is_ipv4()); +} diff --git a/tests/rstest/mod.rs b/tests/rstest/mod.rs index a694064..f10a5c6 100644 --- a/tests/rstest/mod.rs +++ b/tests/rstest/mod.rs @@ -828,6 +828,20 @@ mod matrix { } } +#[test] +fn convert_string_literal() { + let (output, _) = run_test("convert_string_literal.rs"); + + TestResults::new() + .ok("cases::case_1") + .ok("cases::case_2") + .ok("cases::case_3") + .ok("cases::case_4") + .fail("cases::case_5") + .fail("cases::case_6") + .assert(output); +} + #[test] fn happy_path() { let (output, _) = run_test("happy_path.rs");