Skip to content

Commit

Permalink
#119: Base implementation and test. Fixed also some fixture changed a…
Browse files Browse the repository at this point in the history
…ccording to the new rust version
  • Loading branch information
la10736 committed Oct 24, 2021
1 parent e2fb0fe commit 993d74f
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 20 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Expand Up @@ -41,9 +41,9 @@ actix-rt = "2.2.0"
async-std = {version = "1.9.0", features = ["attributes"]}
lazy_static = "1.4.0"
mytest = {package = "rstest", version = "0.11.0"}
pretty_assertions = "0.7.2"
pretty_assertions = "1.0.0"
rstest_reuse = "0.1.3"
rstest_test = "0.4.0"
rstest_test = "0.5.0"
temp_testdir = "0.2.3"
unindent = "0.1.7"

Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Expand Up @@ -51,7 +51,7 @@ macro_rules! merge_errors {
$e
};
($e:expr, $($es:expr), +) => {
crate::error::_merge_errors($e, merge_errors!($($es),*));
crate::error::_merge_errors($e, merge_errors!($($es),*))
};
}

Expand Down
75 changes: 72 additions & 3 deletions src/parse/fixture.rs
Expand Up @@ -11,7 +11,7 @@ use super::{
extract_partials_return_type, parse_vector_trailing_till_double_comma, Attributes,
ExtendWithFunctionAttrs, Fixture,
};
use crate::{error::ErrorsVec, refident::RefIdent, utils::attr_is};
use crate::{error::ErrorsVec, parse::extract_once, 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 @@ -53,12 +53,14 @@ impl ExtendWithFunctionAttrs for FixtureInfo {
fixtures,
defaults,
default_return_type,
partials_return_type
partials_return_type,
is_once
) = merge_errors!(
extract_fixtures(item_fn),
extract_defaults(item_fn),
extract_default_return_type(item_fn),
extract_partials_return_type(item_fn)
extract_partials_return_type(item_fn),
extract_once(item_fn)
)?;
self.data.items.extend(
fixtures
Expand All @@ -72,6 +74,9 @@ impl ExtendWithFunctionAttrs for FixtureInfo {
for (id, return_type) in partials_return_type {
self.attributes.set_partial_return_type(id, return_type);
}
if is_once {
self.attributes.set_once();
}
Ok(())
}
}
Expand Down Expand Up @@ -284,6 +289,18 @@ impl FixtureModifiers {
))
}

pub(crate) fn set_once(&mut self) {
self.inner
.attributes
.push(Attribute::Attr(format_ident!("once")))
}

pub(crate) fn is_once(&self) -> bool {
self.iter()
.find(|&a| a == &Attribute::Attr(format_ident!("once")))
.is_some()
}

fn extract_type(&self, attr_name: &str) -> Option<syn::ReturnType> {
self.iter()
.filter_map(|m| match m {
Expand Down Expand Up @@ -533,6 +550,39 @@ mod extend {
);
}

#[test]
fn find_once_attribute() {
let mut item_fn: ItemFn = r#"
#[simple]
#[first(comp)]
#[second::default]
#[once]
#[last::more]
fn my_fix<I, J, K>(f1: I, f2: J, f3: K) -> impl Iterator<Item=(I, J, K)> {}
"#
.ast();

let mut info = FixtureInfo::default();

info.extend_with_function_attrs(&mut item_fn).unwrap();

assert!(info.attributes.is_once(),);
}

#[test]
fn no_once_attribute() {
let mut item_fn: ItemFn = r#"
fn my_fix<I, J, K>(f1: I, f2: J, f3: K) -> impl Iterator<Item=(I, J, K)> {}
"#
.ast();

let mut info = FixtureInfo::default();

info.extend_with_function_attrs(&mut item_fn).unwrap();

assert!(!info.attributes.is_once(),);
}

mod raise_error {
use super::{assert_eq, *};
use rstest_test::assert_in;
Expand Down Expand Up @@ -596,6 +646,25 @@ mod extend {
assert_eq!(3, errors.len());
}

#[test]
fn if_once_is_defined_more_than_once() {
let mut item_fn: ItemFn = r#"
#[once]
#[once]
fn my_fix<I>() -> I {}
"#
.ast();

let mut info = FixtureInfo::default();

let error = info.extend_with_function_attrs(&mut item_fn).unwrap_err();

assert_in!(
format!("{:?}", error).to_lowercase(),
"cannot use #[once] more than once"
);
}

#[test]
fn if_default_is_defined_more_than_once() {
let mut item_fn: ItemFn = r#"
Expand Down
7 changes: 7 additions & 0 deletions src/parse/macros.rs
Expand Up @@ -16,5 +16,12 @@ macro_rules! wrap_attributes {
self.inner.attributes.iter()
}
}

#[cfg(test)]
impl $ident {
pub(crate) fn append(&mut self, attr: Attribute) {
self.inner.attributes.push(attr)
}
}
};
}
43 changes: 43 additions & 0 deletions src/parse/mod.rs
Expand Up @@ -224,6 +224,12 @@ pub(crate) fn extract_partials_return_type(
partials_type_extractor.take()
}

pub(crate) fn extract_once(item_fn: &mut ItemFn) -> Result<bool, ErrorsVec> {
let mut extractor = IsOnceAttributeFunctionExtractor::default();
extractor.visit_item_fn_mut(item_fn);
extractor.take()
}

fn extract_argument_attrs<'a, B: 'a + std::fmt::Debug>(
node: &mut FnArg,
is_valid_attr: fn(&syn::Attribute) -> bool,
Expand Down Expand Up @@ -354,6 +360,43 @@ impl VisitMut for PartialsTypeFunctionExtractor {
}
}

/// Simple struct used to visit function attributes and extract once
/// type
struct IsOnceAttributeFunctionExtractor(Result<bool, ErrorsVec>);

impl IsOnceAttributeFunctionExtractor {
fn take(self) -> Result<bool, ErrorsVec> {
self.0
}
}

impl Default for IsOnceAttributeFunctionExtractor {
fn default() -> Self {
Self(Ok(false))
}
}

impl VisitMut for IsOnceAttributeFunctionExtractor {
fn visit_item_fn_mut(&mut self, node: &mut ItemFn) {
let attrs = std::mem::take(&mut node.attrs);
let (onces, remain): (Vec<_>, Vec<_>) =
attrs.into_iter().partition(|attr| attr_is(attr, "once"));

node.attrs = remain;
if onces.len() == 1 {
self.0 = Ok(true);
} else if onces.len() > 1 {
self.0 = Err(onces
.into_iter()
.skip(1)
.map(|attr| syn::Error::new_spanned(attr, "You cannot use #[once] more than once"))
.collect::<Vec<_>>()
.into());
}
syn::visit_mut::visit_item_fn_mut(self, node);
}
}

/// Simple struct used to visit function attributes and extract case arguments and
/// eventualy parsing errors
#[derive(Default)]
Expand Down
54 changes: 49 additions & 5 deletions src/render/fixture.rs
@@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream};
use syn::{parse_quote, Ident, ItemFn};
use syn::{parse_quote, Ident, ItemFn, ReturnType};

use quote::quote;

Expand All @@ -8,6 +8,22 @@ use crate::resolver::{self, Resolver};
use crate::utils::{fn_args, fn_args_idents};
use crate::{parse::fixture::FixtureInfo, utils::generics_clean_up};

fn wrap_return_type_as_static_ref(rt: ReturnType) -> ReturnType {
match rt {
syn::ReturnType::Type(_, t) => parse_quote! {
-> &'static #t
},
o => o,
}
}

fn output_type(rt: &ReturnType) -> syn::Type {
match rt {
syn::ReturnType::Type(_, t) => *t.clone(),
_o => parse_quote! { () },
}
}

pub(crate) fn render(fixture: ItemFn, info: FixtureInfo) -> TokenStream {
let name = &fixture.sig.ident;
let asyncness = &fixture.sig.asyncness.clone();
Expand All @@ -16,15 +32,15 @@ pub(crate) fn render(fixture: ItemFn, info: FixtureInfo) -> TokenStream {
let orig_args = &fixture.sig.inputs;
let orig_attrs = &fixture.attrs;
let generics = &fixture.sig.generics;
let default_output = info
let mut default_output = info
.attributes
.extract_default_type()
.unwrap_or_else(|| fixture.sig.output.clone());
let default_generics =
generics_clean_up(&fixture.sig.generics, std::iter::empty(), &default_output);
let default_where_clause = &default_generics.where_clause;
let where_clause = &fixture.sig.generics.where_clause;
let output = &fixture.sig.output;
let mut output = fixture.sig.output.clone();
let visibility = &fixture.vis;
let resolver = (
resolver::fixtures::get(info.data.fixtures()),
Expand All @@ -40,7 +56,19 @@ pub(crate) fn render(fixture: ItemFn, info: FixtureInfo) -> TokenStream {
(1..=orig_args.len()).map(|n| render_partial_impl(&fixture, n, &resolver, &info));

let call_get = render_exec_call(parse_quote! { Self::get }, args, asyncness.is_some());
let call_impl = render_exec_call(parse_quote! { #name }, args, asyncness.is_some());
let mut call_impl = render_exec_call(parse_quote! { #name }, args, asyncness.is_some());

if info.attributes.is_once() {
let return_type = output_type(&output);
call_impl = parse_quote! {
static mut S: Option<#return_type> = None;
static CELL: std::sync::Once = std::sync::Once::new();
CELL.call_once(|| unsafe { S = Some(#call_impl) });
unsafe { S.as_ref().unwrap() }
};
output = wrap_return_type_as_static_ref(output);
default_output = wrap_return_type_as_static_ref(default_output);
}

quote! {
#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -118,7 +146,7 @@ mod should {
use crate::parse::{Attribute, Attributes};

use super::*;
use crate::test::assert_eq;
use crate::test::{assert_eq, *};
use mytest::*;
use rstest_reuse::*;

Expand Down Expand Up @@ -191,6 +219,22 @@ mod should {
assert_eq!(item_fn.sig, signature);
}

#[test]
fn return_a_static_reference_if_once_attribute() {
let item_fn = parse_str::<ItemFn>(r#"
pub fn test<R: AsRef<str>, B>(mut s: String, v: &u32, a: &mut [i32], r: R) -> (u32, B, String, &str)
where B: Borrow<u32>
{ }
"#).unwrap();
let info = FixtureInfo::default().with_once();

let out: FixtureOutput = parse2(render(item_fn.clone(), info)).unwrap();

let signature = select_method(out.core_impl, "get").unwrap().sig;

assert_eq!(signature.output, "-> &'static (u32, B, String, &str)".ast())
}

#[template]
#[rstest(
method => ["default", "get", "partial_1", "partial_2", "partial_3"])
Expand Down
14 changes: 14 additions & 0 deletions src/test.rs
Expand Up @@ -304,3 +304,17 @@ impl<T: ToTokens> DisplayCode for T {
self.to_token_stream().to_string()
}
}

impl crate::parse::fixture::FixtureInfo {
pub(crate) fn with_once(mut self) -> Self {
self.attributes = self.attributes.with_once();
self
}
}

impl crate::parse::fixture::FixtureModifiers {
pub(crate) fn with_once(mut self) -> Self {
self.append(Attribute::attr("once"));
self
}
}
24 changes: 18 additions & 6 deletions tests/fixture/mod.rs
Expand Up @@ -19,6 +19,8 @@ fn run_test(res: &str) -> (std::process::Output, String) {
}

mod should {
use rstest_test::CountMessageOccurrence;

use super::*;

#[test]
Expand Down Expand Up @@ -58,9 +60,7 @@ mod should {
fn rename() {
let (output, _) = run_test("rename.rs");

TestResults::new()
.ok("test")
.assert(output);
TestResults::new().ok("test").assert(output);
}

mod accept_and_return {
Expand Down Expand Up @@ -198,6 +198,20 @@ mod should {
.assert(output);
}

#[test]
fn accept_once_attribute_and_call_fixture_just_once() {
let project = prj("once.rs").with_nocapture();

let output = project.run_tests().unwrap();

// Just to see the errors if fixture doesn't compile
assert_in!(output.stderr.str(), "Exec fixture() just once");

let occurences = output.stderr.str().count("Exec fixture() just once");

assert_eq!(1, occurences);
}

#[test]
fn show_correct_errors() {
let prj = prj("errors.rs");
Expand Down Expand Up @@ -239,9 +253,7 @@ mod should {
--> {}/src/lib.rs:16:29
|
16 | fn error_fixture_wrong_type(fixture: String) {{
| ^^^^^^^
| |
",
| ^^^^^^^",
name
)
.unindent()
Expand Down

0 comments on commit 993d74f

Please sign in to comment.