1use proc_macro::TokenStream;
10use quote::quote;
11use syn::{
12 parse::{Parse, ParseStream},
13 parse_macro_input, Expr, ExprArray, ExprLit, ExprTuple, Ident, ItemFn, Lit, Result, Token,
14};
15
16struct AssayAttribute {
17 include: Option<Vec<String>>,
18 should_panic: bool,
19 env: Option<Vec<(String, String)>>,
20 setup: Option<Expr>,
21 teardown: Option<Expr>,
22}
23
24impl Parse for AssayAttribute {
25 fn parse(input: ParseStream) -> Result<Self> {
26 let mut include = None;
27 let mut should_panic = false;
28 let mut env = None;
29 let mut setup = None;
30 let mut teardown = None;
31
32 while input.peek(Ident) || {
33 if input.peek(Token![,]) {
34 let _: Token![,] = input.parse()?;
35 }
36 input.peek(Ident)
37 } {
38 let ident: Ident = input.parse()?;
39 match ident.to_string().as_str() {
40 "include" => {
41 let _: Token![=] = input.parse()?;
42 let array: ExprArray = input.parse()?;
43 include = Some(
44 array
45 .elems
46 .into_iter()
47 .filter_map(|e| match e {
48 Expr::Lit(ExprLit {
49 lit: Lit::Str(lit_str),
50 ..
51 }) => Some(lit_str.value()),
52 _ => None,
53 })
54 .collect(),
55 );
56 }
57 "should_panic" => should_panic = true,
58 "env" => {
59 let _: Token![=] = input.parse()?;
60 let array: ExprArray = input.parse()?;
61 env = Some(
62 array
63 .elems
64 .into_iter()
65 .filter_map(|e| match e {
66 Expr::Tuple(ExprTuple { elems, .. }) => match (&elems[0], &elems[1]) {
67 (
68 Expr::Lit(ExprLit {
69 lit: Lit::Str(lit_1),
70 ..
71 }),
72 Expr::Lit(ExprLit {
73 lit: Lit::Str(lit_2),
74 ..
75 }),
76 ) => Some((lit_1.value(), lit_2.value())),
77 _ => None,
78 },
79 _ => None,
80 })
81 .collect(),
82 );
83 }
84 val @ "setup" | val @ "teardown" => {
85 let _: Token![=] = input.parse()?;
86 let x = input.parse()?;
87 if val == "setup" {
88 setup = Some(x);
89 } else {
90 teardown = Some(x);
91 }
92 }
93 _ => {}
94 }
95 }
96
97 Ok(AssayAttribute {
98 include,
99 should_panic,
100 env,
101 setup,
102 teardown,
103 })
104 }
105}
106
107#[proc_macro_attribute]
108pub fn assay(attr: TokenStream, item: TokenStream) -> TokenStream {
109 let attr = parse_macro_input!(attr as AssayAttribute);
110
111 let include = if let Some(include) = attr.include {
112 let mut out = quote! {
113 let fs = assay::PrivateFS::new()?;
114 };
115 for file in include {
116 out = quote! {
117 #out
118 fs.include(#file)?;
119 };
120 }
121 out
122 } else {
123 quote! {
124 let fs = assay::PrivateFS::new()?;
125 }
126 };
127
128 let should_panic = if attr.should_panic {
129 quote! { #[should_panic] }
130 } else {
131 quote! {}
132 };
133
134 let env = if let Some(env) = attr.env {
135 let mut out = quote! {};
136 for (k, v) in env {
137 out = quote! {
138 #out
139 std::env::set_var(#k,#v);
140 };
141 }
142 out
143 } else {
144 quote! {}
145 };
146
147 let setup = match attr.setup {
148 Some(expr) => quote! { #expr; },
149 None => quote! {},
150 };
151 let teardown = match attr.teardown {
152 Some(expr) => quote! { #expr; },
153 None => quote! {},
154 };
155
156 let func = parse_macro_input!(item as ItemFn);
158 let vis = func.vis;
159 let mut sig = func.sig;
160 let name = sig.ident.clone();
161 let asyncness = sig.asyncness.take();
162 let block = func.block;
163 let body = if asyncness.is_some() {
164 quote! {
165 async fn inner_async() -> Result<(), Box<dyn std::error::Error>> {
166 #block
167 Ok(())
168 }
169 assay::Runtime::new()?.block_on(inner_async())?;
170 }
171 } else {
172 quote! { #block }
173 };
174
175 let expanded = quote! {
176 #[test]
177 #should_panic
178 #vis #sig {
179 fn modify(_: &mut std::process::Command) {}
180
181 fn parent(child: &mut assay::ChildWrapper, _: &mut std::fs::File) {
182 let child = child.wait().unwrap();
183 if !child.success() {
184 panic!("Assay test failed")
185 }
186 }
187
188 fn child() {
189 #[allow(unreachable_code)]
190 if let Err(e) = || -> Result<(), Box<dyn std::error::Error>> {
191 use assay::{assert_eq, assert_ne};
192 #include
193 #setup
194 #env
195 #body
196 #teardown
197 Ok(())
198 }() {
199 panic!("Error: {}", e);
200 }
201 }
202
203 if std::env::var("NEXTEST")
204 .ok()
205 .as_ref()
206 .map(|s| s.as_str() == "1")
207 .unwrap_or(false)
208 {
209 child();
210 } else {
211 let name = {
212 let mut module = module_path!()
213 .split("::")
214 .into_iter()
215 .skip(1)
216 .collect::<Vec<_>>();
217 module.push(stringify!(#name));
218 module.join("::")
219 };
220
221 assay::fork(
222 &name,
223 assay::rusty_fork_id!(),
224 modify,
225 parent,
226 child
227 ).expect("We forked the test using assay");
228 }
229 }
230 };
231
232 TokenStream::from(expanded)
234}