wasmtime_test_macros/
lib.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_wast_util::Compiler;
49
50struct TestConfig {
52 strategies: Vec<Compiler>,
53 flags: wasmtime_wast_util::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
199#[proc_macro_attribute]
200pub fn wasmtime_test(attrs: TokenStream, item: TokenStream) -> TokenStream {
201 let mut test_config = TestConfig::default();
202
203 let config_parser = syn::meta::parser(|meta| {
204 if meta.path.is_ident("strategies") {
205 test_config.strategies_from(&meta)
206 } else if meta.path.is_ident("wasm_features") {
207 test_config.wasm_features_from(&meta)
208 } else if meta.path.is_ident("with") {
209 test_config.test_attribute_from(&meta)
210 } else {
211 Err(meta.error("Unsupported attributes"))
212 }
213 });
214
215 parse_macro_input!(attrs with config_parser);
216
217 match expand(&test_config, parse_macro_input!(item as Fn)) {
218 Ok(tok) => tok,
219 Err(e) => e.into_compile_error().into(),
220 }
221}
222
223fn expand(test_config: &TestConfig, func: Fn) -> Result<TokenStream> {
224 let mut tests = if test_config.strategies == [Compiler::Winch] {
225 vec![quote! {
226 #[allow(dead_code)]
230 #func
231 }]
232 } else {
233 vec![quote! { #func }]
234 };
235 let attrs = &func.attrs;
236
237 let test_attr = test_config
238 .test_attribute
239 .clone()
240 .unwrap_or_else(|| quote! { #[test] });
241
242 for strategy in &test_config.strategies {
243 let strategy_name = format!("{strategy:?}");
244 let target = if *strategy == Compiler::Winch {
247 quote! { #[cfg(all(target_arch = "x86_64", not(miri)))] }
248 } else {
249 quote! {}
250 };
251 let (asyncness, await_) = if func.sig.asyncness.is_some() {
252 (quote! { async }, quote! { .await })
253 } else {
254 (quote! {}, quote! {})
255 };
256 let func_name = &func.sig.ident;
257 let expect = match &func.sig.output {
258 ReturnType::Default => quote! {},
259 ReturnType::Type(..) => quote! { .expect("test is expected to pass") },
260 };
261 let test_name = Ident::new(
262 &format!("{}_{}", strategy_name.to_lowercase(), func_name),
263 func_name.span(),
264 );
265
266 let should_panic = if strategy.should_fail(&test_config.flags) {
267 quote!(#[should_panic])
268 } else {
269 quote!()
270 };
271
272 let test_config = format!("wasmtime_wast_util::{:?}", test_config.flags)
273 .parse::<proc_macro2::TokenStream>()
274 .unwrap();
275 let strategy_ident = quote::format_ident!("{strategy_name}");
276
277 let tok = quote! {
278 #test_attr
279 #target
280 #should_panic
281 #(#attrs)*
282 #asyncness fn #test_name() {
283 let mut config = Config::new();
284 component_test_util::apply_test_config(
285 &mut config,
286 &#test_config,
287 );
288 component_test_util::apply_wast_config(
289 &mut config,
290 &wasmtime_wast_util::WastConfig {
291 compiler: wasmtime_wast_util::Compiler::#strategy_ident,
292 pooling: false,
293 collector: wasmtime_wast_util::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}