wasmtime_test_macros/
wasmtime_test.rs1use proc_macro::TokenStream;
41use quote::{quote, ToTokens, TokenStreamExt};
42use syn::{
43 braced,
44 meta::ParseNestedMeta,
45 parse::{Parse, ParseStream},
46 parse_macro_input, token, Attribute, Ident, Result, ReturnType, Signature, Visibility,
47};
48use wasmtime_test_util::wast::Compiler;
49
50struct TestConfig {
52 strategies: Vec<Compiler>,
53 flags: wasmtime_test_util::wast::TestConfig,
54 test_attribute: Option<proc_macro2::TokenStream>,
56}
57
58impl TestConfig {
59 fn strategies_from(&mut self, meta: &ParseNestedMeta) -> Result<()> {
60 meta.parse_nested_meta(|meta| {
61 if meta.path.is_ident("not") {
62 meta.parse_nested_meta(|meta| {
63 if meta.path.is_ident("Winch") {
64 self.strategies.retain(|s| *s != Compiler::Winch);
65 Ok(())
66 } else if meta.path.is_ident("CraneliftNative") {
67 self.strategies.retain(|s| *s != Compiler::CraneliftNative);
68 Ok(())
69 } else if meta.path.is_ident("CraneliftPulley") {
70 self.strategies.retain(|s| *s != Compiler::CraneliftPulley);
71 Ok(())
72 } else {
73 Err(meta.error("Unknown strategy"))
74 }
75 })
76 } else if meta.path.is_ident("only") {
77 meta.parse_nested_meta(|meta| {
78 if meta.path.is_ident("Winch") {
79 self.strategies.retain(|s| *s == Compiler::Winch);
80 Ok(())
81 } else if meta.path.is_ident("CraneliftNative") {
82 self.strategies.retain(|s| *s == Compiler::CraneliftNative);
83 Ok(())
84 } else if meta.path.is_ident("CraneliftPulley") {
85 self.strategies.retain(|s| *s == Compiler::CraneliftPulley);
86 Ok(())
87 } else {
88 Err(meta.error("Unknown strategy"))
89 }
90 })
91 } else {
92 Err(meta.error("Unknown identifier"))
93 }
94 })?;
95
96 if self.strategies.len() == 0 {
97 Err(meta.error("Expected at least one strategy"))
98 } else {
99 Ok(())
100 }
101 }
102
103 fn wasm_features_from(&mut self, meta: &ParseNestedMeta) -> Result<()> {
104 meta.parse_nested_meta(|meta| {
105 for (feature, enabled) in self.flags.options_mut() {
106 if meta.path.is_ident(feature) {
107 *enabled = Some(true);
108 return Ok(());
109 }
110 }
111 Err(meta.error("Unsupported test feature"))
112 })?;
113
114 Ok(())
115 }
116
117 fn test_attribute_from(&mut self, meta: &ParseNestedMeta) -> Result<()> {
118 let v: syn::LitStr = meta.value()?.parse()?;
119 self.test_attribute = Some(v.value().parse()?);
120 Ok(())
121 }
122}
123
124impl Default for TestConfig {
125 fn default() -> Self {
126 Self {
127 strategies: vec![
128 Compiler::CraneliftNative,
129 Compiler::Winch,
130 Compiler::CraneliftPulley,
131 ],
132 flags: Default::default(),
133 test_attribute: None,
134 }
135 }
136}
137
138struct Block {
140 brace: token::Brace,
141 rest: proc_macro2::TokenStream,
142}
143
144impl Parse for Block {
145 fn parse(input: ParseStream) -> Result<Self> {
146 let content;
147 Ok(Self {
148 brace: braced!(content in input),
149 rest: content.parse()?,
150 })
151 }
152}
153
154impl ToTokens for Block {
155 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
156 self.brace.surround(tokens, |tokens| {
157 tokens.append_all(self.rest.clone());
158 });
159 }
160}
161
162struct Fn {
166 attrs: Vec<Attribute>,
167 visibility: Visibility,
168 sig: Signature,
169 body: Block,
170}
171
172impl Parse for Fn {
173 fn parse(input: ParseStream) -> Result<Self> {
174 let attrs = input.call(Attribute::parse_outer)?;
175 let visibility: Visibility = input.parse()?;
176 let sig: Signature = input.parse()?;
177 let body: Block = input.parse()?;
178
179 Ok(Self {
180 attrs,
181 visibility,
182 sig,
183 body,
184 })
185 }
186}
187
188impl ToTokens for Fn {
189 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
190 for attr in &self.attrs {
191 attr.to_tokens(tokens);
192 }
193 self.visibility.to_tokens(tokens);
194 self.sig.to_tokens(tokens);
195 self.body.to_tokens(tokens);
196 }
197}
198
199pub fn run(attrs: TokenStream, item: TokenStream) -> TokenStream {
200 let mut test_config = TestConfig::default();
201
202 let config_parser = syn::meta::parser(|meta| {
203 if meta.path.is_ident("strategies") {
204 test_config.strategies_from(&meta)
205 } else if meta.path.is_ident("wasm_features") {
206 test_config.wasm_features_from(&meta)
207 } else if meta.path.is_ident("with") {
208 test_config.test_attribute_from(&meta)
209 } else {
210 Err(meta.error("Unsupported attributes"))
211 }
212 });
213
214 parse_macro_input!(attrs with config_parser);
215
216 match expand(&test_config, parse_macro_input!(item as Fn)) {
217 Ok(tok) => tok,
218 Err(e) => e.into_compile_error().into(),
219 }
220}
221
222fn expand(test_config: &TestConfig, func: Fn) -> Result<TokenStream> {
223 let mut tests = if test_config.strategies == [Compiler::Winch] {
224 vec![quote! {
225 #[allow(dead_code)]
229 #func
230 }]
231 } else {
232 vec![quote! { #func }]
233 };
234 let attrs = &func.attrs;
235
236 let test_attr = test_config
237 .test_attribute
238 .clone()
239 .unwrap_or_else(|| quote! { #[test] });
240
241 for strategy in &test_config.strategies {
242 let strategy_name = format!("{strategy:?}");
243 let target = if *strategy == Compiler::Winch {
246 quote! { #[cfg(all(target_arch = "x86_64", not(miri)))] }
247 } else {
248 quote! {}
249 };
250 let (asyncness, await_) = if func.sig.asyncness.is_some() {
251 (quote! { async }, quote! { .await })
252 } else {
253 (quote! {}, quote! {})
254 };
255 let func_name = &func.sig.ident;
256 let expect = match &func.sig.output {
257 ReturnType::Default => quote! {},
258 ReturnType::Type(..) => quote! { .expect("test is expected to pass") },
259 };
260 let test_name = Ident::new(
261 &format!("{}_{}", strategy_name.to_lowercase(), func_name),
262 func_name.span(),
263 );
264
265 let should_panic = if strategy.should_fail(&test_config.flags) {
266 quote!(#[should_panic])
267 } else {
268 quote!()
269 };
270
271 let test_config = format!("wasmtime_test_util::wast::{:?}", test_config.flags)
272 .parse::<proc_macro2::TokenStream>()
273 .unwrap();
274 let strategy_ident = quote::format_ident!("{strategy_name}");
275
276 let tok = quote! {
277 #test_attr
278 #target
279 #should_panic
280 #(#attrs)*
281 #asyncness fn #test_name() {
282 let _ = env_logger::try_init();
283 let mut config = Config::new();
284 wasmtime_test_util::wasmtime_wast::apply_test_config(
285 &mut config,
286 &#test_config,
287 );
288 wasmtime_test_util::wasmtime_wast::apply_wast_config(
289 &mut config,
290 &wasmtime_test_util::wast::WastConfig {
291 compiler: wasmtime_test_util::wast::Compiler::#strategy_ident,
292 pooling: false,
293 collector: wasmtime_test_util::wast::Collector::Auto,
294 },
295 );
296 #func_name(&mut config) #await_ #expect
297 }
298 };
299
300 tests.push(tok);
301 }
302 Ok(quote! {
303 #(#tests)*
304 }
305 .into())
306}