Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/test-macros/src/wasmtime_test.rs
1692 views
1
//! Wasmtime test macro.
2
//!
3
//! This macro is a helper to define tests that exercise multiple configuration
4
//! combinations for Wasmtime. Currently compiler strategies and wasm features
5
//! are supported.
6
//!
7
//! Usage
8
//!
9
//! To exclude a compiler strategy:
10
//!
11
//! ```rust
12
//! #[wasmtime_test(strategies(not(Winch)))]
13
//! fn my_test(config: &mut Config) -> Result<()> {
14
//! Ok(())
15
//! }
16
//! ```
17
//!
18
//! To use just one specific compiler strategy:
19
//!
20
//! ```rust
21
//! #[wasmtime_test(strategies(only(Winch)))]
22
//! fn my_test(config: &mut Config) -> Result<()> {
23
//! Ok(())
24
//! }
25
//! ```
26
//!
27
//! To explicitly indicate that a wasm features is needed
28
//! ```
29
//! #[wasmtime_test(wasm_features(gc))]
30
//! fn my_wasm_gc_test(config: &mut Config) -> Result<()> {
31
//! Ok(())
32
//! }
33
//! ```
34
//!
35
//! If the specified wasm feature is disabled by default, the macro will enable
36
//! the feature in the configuration passed to the test.
37
//!
38
//! If the wasm feature is not supported by any of the compiler strategies, no
39
//! tests will be generated for such strategy.
40
use proc_macro::TokenStream;
41
use quote::{ToTokens, TokenStreamExt, quote};
42
use syn::{
43
Attribute, Ident, Result, ReturnType, Signature, Visibility, braced,
44
meta::ParseNestedMeta,
45
parse::{Parse, ParseStream},
46
parse_macro_input, token,
47
};
48
use wasmtime_test_util::wast::Compiler;
49
50
/// Test configuration.
51
struct TestConfig {
52
strategies: Vec<Compiler>,
53
flags: wasmtime_test_util::wast::TestConfig,
54
/// The test attribute to use. Defaults to `#[test]`.
55
test_attribute: Option<proc_macro2::TokenStream>,
56
}
57
58
impl 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
124
impl 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
138
/// A generic function body represented as a braced [`TokenStream`].
139
struct Block {
140
brace: token::Brace,
141
rest: proc_macro2::TokenStream,
142
}
143
144
impl 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
154
impl 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
162
/// Custom function parser.
163
/// Parses the function's attributes, visibility and signature, leaving the
164
/// block as an opaque [`TokenStream`].
165
struct Fn {
166
attrs: Vec<Attribute>,
167
visibility: Visibility,
168
sig: Signature,
169
body: Block,
170
}
171
172
impl 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
188
impl 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
pub 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
222
fn expand(test_config: &TestConfig, func: Fn) -> Result<TokenStream> {
223
let mut tests = vec![quote! { #func }];
224
let attrs = &func.attrs;
225
226
let test_attr = test_config
227
.test_attribute
228
.clone()
229
.unwrap_or_else(|| quote! { #[test] });
230
231
for strategy in &test_config.strategies {
232
let strategy_name = format!("{strategy:?}");
233
let (asyncness, await_) = if func.sig.asyncness.is_some() {
234
(quote! { async }, quote! { .await })
235
} else {
236
(quote! {}, quote! {})
237
};
238
let func_name = &func.sig.ident;
239
match &func.sig.output {
240
ReturnType::Default => {
241
return Err(syn::Error::new(func_name.span(), "Expected `Restult<()>`"));
242
}
243
ReturnType::Type(..) => {}
244
};
245
let test_name = Ident::new(
246
&format!("{}_{}", strategy_name.to_lowercase(), func_name),
247
func_name.span(),
248
);
249
250
// Ignore non-pulley tests in Miri as that's the only compiler which
251
// works in Miri.
252
let ignore_miri = match strategy {
253
Compiler::CraneliftPulley => quote!(),
254
_ => quote!(#[cfg_attr(miri, ignore)]),
255
};
256
257
let test_config = format!("wasmtime_test_util::wast::{:?}", test_config.flags)
258
.parse::<proc_macro2::TokenStream>()
259
.unwrap();
260
let strategy_ident = quote::format_ident!("{strategy_name}");
261
262
let tok = quote! {
263
#test_attr
264
#(#attrs)*
265
#ignore_miri
266
#asyncness fn #test_name() {
267
// Skip this test completely if the compiler doesn't support
268
// this host.
269
let compiler = wasmtime_test_util::wast::Compiler::#strategy_ident;
270
if !compiler.supports_host() {
271
return;
272
}
273
let _ = env_logger::try_init();
274
let mut config = Config::new();
275
wasmtime_test_util::wasmtime_wast::apply_test_config(
276
&mut config,
277
&#test_config,
278
);
279
wasmtime_test_util::wasmtime_wast::apply_wast_config(
280
&mut config,
281
&wasmtime_test_util::wast::WastConfig {
282
compiler,
283
pooling: false,
284
collector: wasmtime_test_util::wast::Collector::Auto,
285
},
286
);
287
let result = #func_name(&mut config) #await_;
288
if compiler.should_fail(&#test_config) {
289
assert!(result.is_err());
290
} else {
291
result.unwrap();
292
}
293
}
294
};
295
296
tests.push(tok);
297
}
298
Ok(quote! {
299
#(#tests)*
300
}
301
.into())
302
}
303
304