Skip to content

Commit 4f386b7

Browse files
authored
Refactor the #[wasmtime_test] macro (#9627)
* Refactor the `#[wasmtime_test]` macro * Start tests with a blank slate of features instead of with the default set of features enables (ensures each test explicitly specifies required features) * Reuse test features from `wasmtime_wast_util` to avoid duplicating listings of features. Also shares logic for "should this compiler fail this test because of unsupported features". * Move logic in `tests/wast.rs` to apply test configuration to a `Config` to a new location that can be shared across suites. * Add a new feature for `simd` and flag tests that need it with the feature. This is done in preparation for adding a new compiler strategy of Pulley to be able to flag tests as passing for pulley or not. * Review feedback
1 parent 27ce0ba commit 4f386b7

31 files changed

+299
-168
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fuzzing/src/generators/config.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,33 +122,50 @@ impl Config {
122122
/// This will additionally update limits in the pooling allocator to be able
123123
/// to execute all tests.
124124
pub fn make_wast_test_compliant(&mut self, test: &WastTest) -> WastConfig {
125+
let wasmtime_wast_util::TestConfig {
126+
memory64,
127+
custom_page_sizes,
128+
multi_memory,
129+
threads,
130+
gc,
131+
function_references,
132+
relaxed_simd,
133+
reference_types,
134+
tail_call,
135+
extended_const,
136+
wide_arithmetic,
137+
component_model_more_flags,
138+
simd,
139+
140+
hogs_memory: _,
141+
nan_canonicalization: _,
142+
gc_types: _,
143+
} = test.config;
144+
125145
// Enable/disable some proposals that aren't configurable in wasm-smith
126146
// but are configurable in Wasmtime.
127-
self.module_config.function_references_enabled = test
128-
.config
129-
.function_references
130-
.or(test.config.gc)
131-
.unwrap_or(false);
132-
self.module_config.component_model_more_flags =
133-
test.config.component_model_more_flags.unwrap_or(false);
147+
self.module_config.function_references_enabled =
148+
function_references.or(gc).unwrap_or(false);
149+
self.module_config.component_model_more_flags = component_model_more_flags.unwrap_or(false);
134150

135151
// Enable/disable proposals that wasm-smith has knobs for which will be
136152
// read when creating `wasmtime::Config`.
137153
let config = &mut self.module_config.config;
138154
config.bulk_memory_enabled = true;
139155
config.multi_value_enabled = true;
140-
config.simd_enabled = true;
141-
config.wide_arithmetic_enabled = test.config.wide_arithmetic.unwrap_or(false);
142-
config.memory64_enabled = test.config.memory64.unwrap_or(false);
143-
config.tail_call_enabled = test.config.tail_call.unwrap_or(false);
144-
config.custom_page_sizes_enabled = test.config.custom_page_sizes.unwrap_or(false);
145-
config.threads_enabled = test.config.threads.unwrap_or(false);
146-
config.gc_enabled = test.config.gc.unwrap_or(false);
156+
config.wide_arithmetic_enabled = wide_arithmetic.unwrap_or(false);
157+
config.memory64_enabled = memory64.unwrap_or(false);
158+
config.relaxed_simd_enabled = relaxed_simd.unwrap_or(false);
159+
config.simd_enabled = config.relaxed_simd_enabled || simd.unwrap_or(false);
160+
config.tail_call_enabled = tail_call.unwrap_or(false);
161+
config.custom_page_sizes_enabled = custom_page_sizes.unwrap_or(false);
162+
config.threads_enabled = threads.unwrap_or(false);
163+
config.gc_enabled = gc.unwrap_or(false);
147164
config.reference_types_enabled = config.gc_enabled
148165
|| self.module_config.function_references_enabled
149-
|| test.config.reference_types.unwrap_or(false);
150-
config.extended_const_enabled = test.config.extended_const.unwrap_or(false);
151-
if test.config.multi_memory.unwrap_or(false) {
166+
|| reference_types.unwrap_or(false);
167+
config.extended_const_enabled = extended_const.unwrap_or(false);
168+
if multi_memory.unwrap_or(false) {
152169
config.max_memories = limits::MEMORIES_PER_MODULE as usize;
153170
} else {
154171
config.max_memories = 1;

crates/misc/component-test-util/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ env_logger = { workspace = true }
1212
anyhow = { workspace = true }
1313
arbitrary = { workspace = true, features = ["derive"] }
1414
wasmtime = { workspace = true, features = ["component-model", "async"] }
15+
wasmtime-wast-util = { path = '../../wast-util' }

crates/misc/component-test-util/src/lib.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,80 @@ forward_impls! {
129129
Float32 => f32,
130130
Float64 => f64,
131131
}
132+
133+
/// Helper method to apply `wast_config` to `config`.
134+
pub fn apply_wast_config(config: &mut Config, wast_config: &wasmtime_wast_util::WastConfig) {
135+
config.strategy(match wast_config.compiler {
136+
wasmtime_wast_util::Compiler::Cranelift => wasmtime::Strategy::Cranelift,
137+
wasmtime_wast_util::Compiler::Winch => wasmtime::Strategy::Winch,
138+
});
139+
config.collector(match wast_config.collector {
140+
wasmtime_wast_util::Collector::Auto => wasmtime::Collector::Auto,
141+
wasmtime_wast_util::Collector::Null => wasmtime::Collector::Null,
142+
wasmtime_wast_util::Collector::DeferredReferenceCounting => {
143+
wasmtime::Collector::DeferredReferenceCounting
144+
}
145+
});
146+
}
147+
148+
/// Helper method to apply `test_config` to `config`.
149+
pub fn apply_test_config(config: &mut Config, test_config: &wasmtime_wast_util::TestConfig) {
150+
let wasmtime_wast_util::TestConfig {
151+
memory64,
152+
custom_page_sizes,
153+
multi_memory,
154+
threads,
155+
gc,
156+
function_references,
157+
relaxed_simd,
158+
reference_types,
159+
tail_call,
160+
extended_const,
161+
wide_arithmetic,
162+
component_model_more_flags,
163+
nan_canonicalization,
164+
simd,
165+
166+
hogs_memory: _,
167+
gc_types: _,
168+
} = *test_config;
169+
// Note that all of these proposals/features are currently default-off to
170+
// ensure that we annotate all tests accurately with what features they
171+
// need, even in the future when features are stabilized.
172+
let memory64 = memory64.unwrap_or(false);
173+
let custom_page_sizes = custom_page_sizes.unwrap_or(false);
174+
let multi_memory = multi_memory.unwrap_or(false);
175+
let threads = threads.unwrap_or(false);
176+
let gc = gc.unwrap_or(false);
177+
let tail_call = tail_call.unwrap_or(false);
178+
let extended_const = extended_const.unwrap_or(false);
179+
let wide_arithmetic = wide_arithmetic.unwrap_or(false);
180+
let component_model_more_flags = component_model_more_flags.unwrap_or(false);
181+
let nan_canonicalization = nan_canonicalization.unwrap_or(false);
182+
let relaxed_simd = relaxed_simd.unwrap_or(false);
183+
184+
// Some proposals in wasm depend on previous proposals. For example the gc
185+
// proposal depends on function-references which depends on reference-types.
186+
// To avoid needing to enable all of them at once implicitly enable
187+
// downstream proposals once the end proposal is enabled (e.g. when enabling
188+
// gc that also enables function-references and reference-types).
189+
let function_references = gc || function_references.unwrap_or(false);
190+
let reference_types = function_references || reference_types.unwrap_or(false);
191+
let simd = relaxed_simd || simd.unwrap_or(false);
192+
193+
config
194+
.wasm_multi_memory(multi_memory)
195+
.wasm_threads(threads)
196+
.wasm_memory64(memory64)
197+
.wasm_function_references(function_references)
198+
.wasm_gc(gc)
199+
.wasm_reference_types(reference_types)
200+
.wasm_relaxed_simd(relaxed_simd)
201+
.wasm_simd(simd)
202+
.wasm_tail_call(tail_call)
203+
.wasm_custom_page_sizes(custom_page_sizes)
204+
.wasm_extended_const(extended_const)
205+
.wasm_wide_arithmetic(wide_arithmetic)
206+
.wasm_component_model_more_flags(component_model_more_flags)
207+
.cranelift_nan_canonicalization(nan_canonicalization);
208+
}

crates/test-macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ quote = "1.0"
2121
proc-macro2 = "1.0"
2222
syn = { workspace = true, features = ["full"] }
2323
anyhow = { workspace = true }
24+
wasmtime-wast-util = { path = '../wast-util' }

crates/test-macros/src/lib.rs

Lines changed: 47 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,19 @@
2929
//! If the wasm feature is not supported by any of the compiler strategies, no
3030
//! tests will be generated for such strategy.
3131
use proc_macro::TokenStream;
32-
use proc_macro2::Span;
3332
use quote::{quote, ToTokens, TokenStreamExt};
3433
use syn::{
3534
braced,
3635
meta::ParseNestedMeta,
3736
parse::{Parse, ParseStream},
3837
parse_macro_input, token, Attribute, Ident, Result, ReturnType, Signature, Visibility,
3938
};
39+
use wasmtime_wast_util::Compiler;
4040

4141
/// Test configuration.
4242
struct TestConfig {
43-
/// Supported compiler strategies.
44-
strategies: Vec<Ident>,
45-
/// Known WebAssembly features that will be turned on by default in the
46-
/// resulting Config.
47-
/// The identifiers in this list are features that are off by default in
48-
/// Wasmtime's Config, which will be explicitly turned on for a given test.
49-
wasm_features: Vec<Ident>,
50-
/// Flag to track if there are Wasm features not supported by Winch.
51-
wasm_features_unsupported_by_winch: bool,
43+
strategies: Vec<Compiler>,
44+
flags: wasmtime_wast_util::TestConfig,
5245
/// The test attribute to use. Defaults to `#[test]`.
5346
test_attribute: Option<proc_macro2::TokenStream>,
5447
}
@@ -58,9 +51,11 @@ impl TestConfig {
5851
meta.parse_nested_meta(|meta| {
5952
if meta.path.is_ident("not") {
6053
meta.parse_nested_meta(|meta| {
61-
if meta.path.is_ident("Winch") || meta.path.is_ident("Cranelift") {
62-
let id = meta.path.require_ident()?.clone();
63-
self.strategies.retain(|s| *s != id);
54+
if meta.path.is_ident("Winch") {
55+
self.strategies.retain(|s| *s != Compiler::Winch);
56+
Ok(())
57+
} else if meta.path.is_ident("Cranelift") {
58+
self.strategies.retain(|s| *s != Compiler::Cranelift);
6459
Ok(())
6560
} else {
6661
Err(meta.error("Unknown strategy"))
@@ -80,32 +75,15 @@ impl TestConfig {
8075

8176
fn wasm_features_from(&mut self, meta: &ParseNestedMeta) -> Result<()> {
8277
meta.parse_nested_meta(|meta| {
83-
if meta.path.is_ident("gc") || meta.path.is_ident("function_references") {
84-
let feature = meta.path.require_ident()?.clone();
85-
self.wasm_features.push(feature.clone());
86-
self.wasm_features_unsupported_by_winch = true;
87-
Ok(())
88-
} else if meta.path.is_ident("simd")
89-
|| meta.path.is_ident("relaxed_simd")
90-
|| meta.path.is_ident("reference_types")
91-
|| meta.path.is_ident("tail_call")
92-
|| meta.path.is_ident("threads")
93-
{
94-
self.wasm_features_unsupported_by_winch = true;
95-
Ok(())
96-
} else {
97-
Err(meta.error("Unsupported wasm feature"))
78+
for (feature, enabled) in self.flags.options_mut() {
79+
if meta.path.is_ident(feature) {
80+
*enabled = Some(true);
81+
return Ok(());
82+
}
9883
}
84+
Err(meta.error("Unsupported test feature"))
9985
})?;
10086

101-
if self.wasm_features.len() > 2 {
102-
return Err(meta.error("Expected at most 2 off-by-default wasm features"));
103-
}
104-
105-
if self.wasm_features_unsupported_by_winch {
106-
self.strategies.retain(|s| s.to_string() != "Winch");
107-
}
108-
10987
Ok(())
11088
}
11189

@@ -119,12 +97,8 @@ impl TestConfig {
11997
impl Default for TestConfig {
12098
fn default() -> Self {
12199
Self {
122-
strategies: vec![
123-
Ident::new("Cranelift", Span::call_site()),
124-
Ident::new("Winch", Span::call_site()),
125-
],
126-
wasm_features: vec![],
127-
wasm_features_unsupported_by_winch: false,
100+
strategies: vec![Compiler::Cranelift, Compiler::Winch],
101+
flags: Default::default(),
128102
test_attribute: None,
129103
}
130104
}
@@ -216,9 +190,7 @@ pub fn wasmtime_test(attrs: TokenStream, item: TokenStream) -> TokenStream {
216190
}
217191

218192
fn expand(test_config: &TestConfig, func: Fn) -> Result<TokenStream> {
219-
let mut tests = if test_config.strategies.len() == 1
220-
&& test_config.strategies.get(0).map(|s| s.to_string()) == Some("Winch".to_string())
221-
{
193+
let mut tests = if test_config.strategies == [Compiler::Winch] {
222194
vec![quote! {
223195
// This prevents dead code warning when the macro is invoked as:
224196
// #[wasmtime_test(strategies(not(Cranelift))]
@@ -236,10 +208,10 @@ fn expand(test_config: &TestConfig, func: Fn) -> Result<TokenStream> {
236208
.clone()
237209
.unwrap_or_else(|| quote! { #[test] });
238210

239-
for ident in &test_config.strategies {
240-
let strategy_name = ident.to_string();
211+
for strategy in &test_config.strategies {
212+
let strategy_name = format!("{strategy:?}");
241213
// Winch currently only offers support for x64.
242-
let target = if strategy_name == "Winch" {
214+
let target = if *strategy == Compiler::Winch {
243215
quote! { #[cfg(target_arch = "x86_64")] }
244216
} else {
245217
quote! {}
@@ -250,31 +222,46 @@ fn expand(test_config: &TestConfig, func: Fn) -> Result<TokenStream> {
250222
(quote! {}, quote! {})
251223
};
252224
let func_name = &func.sig.ident;
253-
let ret = match &func.sig.output {
225+
let expect = match &func.sig.output {
254226
ReturnType::Default => quote! {},
255-
ReturnType::Type(_, ty) => quote! { -> #ty },
227+
ReturnType::Type(..) => quote! { .expect("test is expected to pass") },
256228
};
257229
let test_name = Ident::new(
258230
&format!("{}_{}", strategy_name.to_lowercase(), func_name),
259231
func_name.span(),
260232
);
261233

262-
let config_setup = test_config.wasm_features.iter().map(|f| {
263-
let method_name = Ident::new(&format!("wasm_{f}"), f.span());
264-
quote! {
265-
config.#method_name(true);
266-
}
267-
});
234+
let should_panic = if strategy.should_fail(&test_config.flags) {
235+
quote!(#[should_panic])
236+
} else {
237+
quote!()
238+
};
239+
240+
let test_config = format!("wasmtime_wast_util::{:?}", test_config.flags)
241+
.parse::<proc_macro2::TokenStream>()
242+
.unwrap();
243+
let strategy_ident = quote::format_ident!("{strategy_name}");
268244

269245
let tok = quote! {
270246
#test_attr
271247
#target
248+
#should_panic
272249
#(#attrs)*
273-
#asyncness fn #test_name() #ret {
250+
#asyncness fn #test_name() {
274251
let mut config = Config::new();
275-
config.strategy(Strategy::#ident);
276-
#(#config_setup)*
277-
#func_name(&mut config) #await_
252+
component_test_util::apply_test_config(
253+
&mut config,
254+
&#test_config,
255+
);
256+
component_test_util::apply_wast_config(
257+
&mut config,
258+
&wasmtime_wast_util::WastConfig {
259+
compiler: wasmtime_wast_util::Compiler::#strategy_ident,
260+
pooling: false,
261+
collector: wasmtime_wast_util::Collector::Auto,
262+
},
263+
);
264+
#func_name(&mut config) #await_ #expect
278265
}
279266
};
280267

0 commit comments

Comments
 (0)