tested_fixture_macros/
lib.rs1use std::mem::replace;
2
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use proc_macro_crate::{crate_name, FoundCrate};
6use quote::quote;
7use syn::{
8 ext::IdentExt,
9 parse::{Parse, ParseStream},
10 parse_macro_input, parse_quote, Attribute, Ident, ItemFn, Result, ReturnType, Token, Type,
11 TypeTuple, Visibility,
12};
13
14#[proc_macro_attribute]
23pub fn tested_fixture(attr: TokenStream, item: TokenStream) -> TokenStream {
24 tested_fixture_helper(attr, item, false)
25}
26
27#[doc(hidden)]
28#[proc_macro_attribute]
29pub fn tested_fixture_doctest(attr: TokenStream, item: TokenStream) -> TokenStream {
30 tested_fixture_helper(attr, item, true)
31}
32
33struct Attr {
34 pub attrs: Vec<Attribute>,
35 pub vis: Visibility,
36 pub ident: Ident,
37 #[allow(unused)]
38 pub colon: Option<Token![:]>,
39 pub ty: Option<Type>,
40}
41
42impl Parse for Attr {
43 fn parse(input: ParseStream) -> Result<Self> {
44 let attrs = input.call(Attribute::parse_outer)?;
45 let vis: Visibility = input.parse()?;
46 let ident = input.call(Ident::parse_any)?;
47
48 let (colon, ty) = if input.peek(Token![:]) {
49 (Some(input.parse()?), Some(input.parse()?))
50 } else {
51 (None, None)
52 };
53
54 Ok(Attr {
55 attrs,
56 vis,
57 ident,
58 colon,
59 ty,
60 })
61 }
62}
63
64fn tested_fixture_helper(attr: TokenStream, item: TokenStream, doctest: bool) -> TokenStream {
65 let found_crate =
66 crate_name("tested-fixture").expect("tested-fixture is present in `Cargo.toml`");
67 let found_crate = match found_crate {
68 FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
69 FoundCrate::Itself if doctest => Ident::new("tested_fixture", Span::call_site()),
70 FoundCrate::Itself => <Token![crate]>::default().into(),
71 };
72
73 let attr = parse_macro_input!(attr as Attr);
74 let mut func = parse_macro_input!(item as ItemFn);
75
76 let func_attrs = &func.attrs;
77 let func_vis = &func.vis;
78 let func_ident = &func.sig.ident;
79 let func_body = &func.block;
80 let func_out = match replace(&mut func.sig.output, ReturnType::Default) {
81 ReturnType::Default => Type::Tuple(TypeTuple {
82 paren_token: Default::default(),
83 elems: Default::default(),
84 }),
85 ReturnType::Type(_, ty) => *ty,
86 };
87
88 let fixture_attrs = &attr.attrs;
89 let fixture_vis = &attr.vis;
90 let fixture_ident = &attr.ident;
91 let fixture_ty = attr.ty.as_ref().unwrap_or(&func_out);
92
93 func.sig.output = ReturnType::Type(
94 Default::default(),
95 Box::new(
96 parse_quote!(std::result::Result<impl #found_crate::helpers::Unwrap::<#fixture_ty>, impl std::fmt::Debug>),
97 ),
98 );
99 let func_sig = &func.sig;
100
101 let v = quote!(
102 #(#fixture_attrs)*
103 #[cfg(test)]
104 #fixture_vis static #fixture_ident: #found_crate::helpers::Lazy<&#fixture_ty> =
105 #found_crate::helpers::Lazy::new(|| #found_crate::helpers::unwrap(#func_ident));
106
107 #(#func_attrs)*
108 #[test]
109 #func_vis #func_sig {
110 static CELL: #found_crate::helpers::OnceCell<
111 std::result::Result<
112 #func_out,
113 &str,
114 >
116 > = #found_crate::helpers::OnceCell::new();
117
118 let result = CELL.get_or_init(|| {
119 std::panic::catch_unwind(|| #func_body).map_err(|_| "panicked")
120 });
122
123 {
124 #[allow(unused_imports)]
125 use #found_crate::helpers::{Fixer, Fix};
126
127 result.as_ref().map(|x|
128 Fixer(x).fix().map(|x|
129 Fixer(x).fix().map(|x|
130 Fixer(x).fix().map(|x| Fixer(x).fix())
131 )
132 )
133 )
134 }
135 }
136
137 );
138
139 v.into()
140}