Skip to main content

wasmtime_wast/
wast.rs

1#[cfg(feature = "component-model")]
2use crate::component;
3use crate::core;
4use crate::spectest::*;
5use json_from_wast::{Action, Command, Const, WasmFile, WasmFileType};
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8use std::str;
9use std::sync::Arc;
10use std::thread;
11use wasmtime::{error::Context as _, *};
12use wast::lexer::Lexer;
13use wast::parser::{self, ParseBuffer};
14
15/// The wast test script language allows modules to be defined and actions
16/// to be performed on them.
17pub struct WastContext {
18    /// Wast files have a concept of a "current" module, which is the most
19    /// recently defined.
20    current: Option<InstanceKind>,
21    core_linker: Linker<()>,
22    modules: HashMap<Option<String>, ModuleKind>,
23    #[cfg(feature = "component-model")]
24    component_linker: component::Linker<()>,
25
26    /// The store used for core wasm tests/primitives.
27    ///
28    /// Note that components each get their own store so this is not used for
29    /// component-model testing.
30    pub(crate) core_store: Store<()>,
31    pub(crate) async_runtime: Option<tokio::runtime::Runtime>,
32    generate_dwarf: bool,
33    precompile_save: Option<PathBuf>,
34    precompile_load: Option<PathBuf>,
35
36    modules_by_filename: Arc<HashMap<String, Vec<u8>>>,
37    configure_store: Arc<dyn Fn(&mut Store<()>) + Send + Sync>,
38}
39
40enum Outcome<T = Results> {
41    Ok(T),
42    Trap(Error),
43}
44
45impl<T> Outcome<T> {
46    fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
47        match self {
48            Outcome::Ok(t) => Outcome::Ok(map(t)),
49            Outcome::Trap(t) => Outcome::Trap(t),
50        }
51    }
52
53    fn into_result(self) -> Result<T> {
54        match self {
55            Outcome::Ok(t) => Ok(t),
56            Outcome::Trap(t) => Err(t),
57        }
58    }
59}
60
61#[derive(Debug)]
62enum Results {
63    Core(Vec<Val>),
64    #[cfg(feature = "component-model")]
65    Component(Vec<component::Val>),
66}
67
68#[derive(Clone)]
69enum ModuleKind {
70    Core(Module),
71    #[cfg(feature = "component-model")]
72    Component(component::Component),
73}
74
75enum InstanceKind {
76    Core(Instance),
77    #[cfg(feature = "component-model")]
78    Component(Store<()>, component::Instance),
79}
80
81enum Export<'a> {
82    Core(Extern),
83    #[cfg(feature = "component-model")]
84    Component(&'a mut Store<()>, component::Func),
85
86    /// Impossible-to-construct variant to consider `'a` used when the
87    /// `component-model` feature is disabled.
88    _Unused(std::convert::Infallible, &'a ()),
89}
90
91/// Whether or not to use async APIs when calling wasm during wast testing.
92///
93/// Passed to [`WastContext::new`].
94#[derive(Debug, Copy, Clone, PartialEq)]
95#[expect(missing_docs, reason = "self-describing variants")]
96pub enum Async {
97    Yes,
98    No,
99}
100
101impl WastContext {
102    /// Construct a new instance of `WastContext`.
103    ///
104    /// The `engine` provided is used for all store/module/component creation
105    /// and should be appropriately configured by the caller. The `async_`
106    /// configuration indicates whether functions are invoked either async or
107    /// sync, and then the `configure` callback is used whenever a store is
108    /// created to further configure its settings.
109    pub fn new(
110        engine: &Engine,
111        async_: Async,
112        configure: impl Fn(&mut Store<()>) + Send + Sync + 'static,
113    ) -> Self {
114        // Spec tests will redefine the same module/name sometimes, so we need
115        // to allow shadowing in the linker which picks the most recent
116        // definition as what to link when linking.
117        let mut core_linker = Linker::new(engine);
118        core_linker.allow_shadowing(true);
119        Self {
120            current: None,
121            core_linker,
122            #[cfg(feature = "component-model")]
123            component_linker: {
124                let mut linker = component::Linker::new(engine);
125                linker.allow_shadowing(true);
126                linker
127            },
128            core_store: {
129                let mut store = Store::new(engine, ());
130                configure(&mut store);
131                store
132            },
133            modules: Default::default(),
134            async_runtime: if async_ == Async::Yes {
135                Some(
136                    tokio::runtime::Builder::new_current_thread()
137                        .build()
138                        .unwrap(),
139                )
140            } else {
141                None
142            },
143            generate_dwarf: true,
144            precompile_save: None,
145            precompile_load: None,
146            modules_by_filename: Arc::default(),
147            configure_store: Arc::new(configure),
148        }
149    }
150
151    fn engine(&self) -> &Engine {
152        self.core_linker.engine()
153    }
154
155    /// Saves precompiled modules/components into `path` instead of executing
156    /// test directives.
157    pub fn precompile_save(&mut self, path: impl AsRef<Path>) -> &mut Self {
158        self.precompile_save = Some(path.as_ref().into());
159        self
160    }
161
162    /// Loads precompiled modules/components from `path` instead of compiling
163    /// natively.
164    pub fn precompile_load(&mut self, path: impl AsRef<Path>) -> &mut Self {
165        self.precompile_load = Some(path.as_ref().into());
166        self
167    }
168
169    fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export<'_>> {
170        if let Some(module) = module {
171            return Ok(Export::Core(
172                self.core_linker
173                    .get(&mut self.core_store, module, name)
174                    .with_context(|| format_err!("no item named `{module}::{name}` found"))?,
175            ));
176        }
177
178        let cur = self
179            .current
180            .as_mut()
181            .ok_or_else(|| format_err!("no previous instance found"))?;
182        Ok(match cur {
183            InstanceKind::Core(i) => Export::Core(
184                i.get_export(&mut self.core_store, name)
185                    .ok_or_else(|| format_err!("no item named `{name}` found"))?,
186            ),
187            #[cfg(feature = "component-model")]
188            InstanceKind::Component(store, i) => {
189                let export = i
190                    .get_func(&mut *store, name)
191                    .ok_or_else(|| format_err!("no func named `{name}` found"))?;
192                Export::Component(store, export)
193            }
194        })
195    }
196
197    fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> {
198        let instance = match &self.async_runtime {
199            Some(rt) => rt.block_on(
200                self.core_linker
201                    .instantiate_async(&mut self.core_store, &module),
202            ),
203            None => self.core_linker.instantiate(&mut self.core_store, &module),
204        };
205        Ok(match instance {
206            Ok(i) => Outcome::Ok(i),
207            Err(e) => Outcome::Trap(e),
208        })
209    }
210
211    #[cfg(feature = "component-model")]
212    fn instantiate_component(
213        &mut self,
214        component: &component::Component,
215    ) -> Result<Outcome<(component::Component, Store<()>, component::Instance)>> {
216        let mut store = Store::new(self.engine(), ());
217        (self.configure_store)(&mut store);
218        let instance = match &self.async_runtime {
219            Some(rt) => rt.block_on(
220                self.component_linker
221                    .instantiate_async(&mut store, &component),
222            ),
223            None => self.component_linker.instantiate(&mut store, &component),
224        };
225        Ok(match instance {
226            Ok(i) => Outcome::Ok((component.clone(), store, i)),
227            Err(e) => Outcome::Trap(e),
228        })
229    }
230
231    /// Register "spectest" which is used by the spec testsuite.
232    pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> {
233        link_spectest(&mut self.core_linker, &mut self.core_store, config)?;
234        #[cfg(feature = "component-model")]
235        link_component_spectest(&mut self.component_linker)?;
236        Ok(())
237    }
238
239    /// Register the "wasmtime" module, which provides utilities that our misc
240    /// tests use.
241    pub fn register_wasmtime(&mut self) -> Result<()> {
242        self.core_linker
243            .func_wrap("wasmtime", "gc", |mut caller: Caller<_>| {
244                caller.gc(None)?;
245                Ok(())
246            })?;
247        #[cfg(feature = "component-model")]
248        {
249            let mut i = self.component_linker.instance("wasmtime")?;
250            i.func_wrap(
251                "set-max-table-capacity",
252                |mut store, (capacity,): (u32,)| {
253                    store
254                        .as_context_mut()
255                        .concurrent_resource_table()
256                        .expect("table must be present")
257                        .set_max_capacity(capacity.try_into().unwrap());
258                    Ok(())
259                },
260            )?;
261        }
262        Ok(())
263    }
264
265    /// Perform the action portion of a command.
266    fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
267        // Need to simultaneously borrow `self.async_runtime` and a `&mut
268        // Store` from components so work around the borrow checker issues by
269        // taking out the async runtime here and putting it back through a
270        // destructor.
271        struct ReplaceRuntime<'a> {
272            ctx: &'a mut WastContext,
273            rt: Option<tokio::runtime::Runtime>,
274        }
275        impl Drop for ReplaceRuntime<'_> {
276            fn drop(&mut self) {
277                self.ctx.async_runtime = self.rt.take();
278            }
279        }
280        let replace = ReplaceRuntime {
281            rt: self.async_runtime.take(),
282            ctx: self,
283        };
284        let me = &mut *replace.ctx;
285        match action {
286            Action::Invoke {
287                module,
288                field,
289                args,
290            } => match me.get_export(module.as_deref(), field)? {
291                Export::Core(export) => {
292                    drop(replace);
293                    let func = export
294                        .into_func()
295                        .ok_or_else(|| format_err!("no function named `{field}`"))?;
296                    let values = args
297                        .iter()
298                        .map(|v| match v {
299                            Const::Core(v) => core::val(self, v),
300                            _ => bail!("expected core function, found other other argument {v:?}"),
301                        })
302                        .collect::<Result<Vec<_>>>()?;
303
304                    let mut results =
305                        vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()];
306                    let result = match &self.async_runtime {
307                        Some(rt) => rt.block_on(func.call_async(
308                            &mut self.core_store,
309                            &values,
310                            &mut results,
311                        )),
312                        None => func.call(&mut self.core_store, &values, &mut results),
313                    };
314
315                    Ok(match result {
316                        Ok(()) => Outcome::Ok(Results::Core(results)),
317                        Err(e) => Outcome::Trap(e),
318                    })
319                }
320                #[cfg(feature = "component-model")]
321                Export::Component(store, func) => {
322                    let values = args
323                        .iter()
324                        .map(|v| match v {
325                            Const::Component(v) => component::val(v),
326                            _ => bail!("expected component function, found other argument {v:?}"),
327                        })
328                        .collect::<Result<Vec<_>>>()?;
329
330                    let mut results =
331                        vec![component::Val::Bool(false); func.ty(&store).results().len()];
332                    let result = match &replace.rt {
333                        Some(rt) => {
334                            rt.block_on(func.call_async(&mut *store, &values, &mut results))
335                        }
336                        None => func.call(&mut *store, &values, &mut results),
337                    };
338                    Ok(match result {
339                        Ok(()) => Outcome::Ok(Results::Component(results)),
340                        Err(e) => Outcome::Trap(e),
341                    })
342                }
343            },
344            Action::Get { module, field, .. } => me.get(module.as_deref(), field),
345        }
346    }
347
348    /// Instantiates the `module` provided and registers the instance under the
349    /// `name` provided if successful.
350    fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
351        match module {
352            ModuleKind::Core(module) => {
353                let instance = match self.instantiate_module(&module)? {
354                    Outcome::Ok(i) => i,
355                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
356                };
357                if let Some(name) = name {
358                    self.core_linker
359                        .instance(&mut self.core_store, name, instance)?;
360                }
361                self.current = Some(InstanceKind::Core(instance));
362            }
363            #[cfg(feature = "component-model")]
364            ModuleKind::Component(module) => {
365                let (component, mut store, instance) = match self.instantiate_component(&module)? {
366                    Outcome::Ok(i) => i,
367                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
368                };
369                if let Some(name) = name {
370                    let ty = component.component_type();
371                    let engine = self.engine().clone();
372                    let mut linker = self.component_linker.instance(name)?;
373                    for (name, item) in ty.exports(&engine) {
374                        match item {
375                            component::types::ComponentItem::Module(_) => {
376                                let module = instance.get_module(&mut store, name).unwrap();
377                                linker.module(name, &module)?;
378                            }
379                            component::types::ComponentItem::Resource(_) => {
380                                let resource = instance.get_resource(&mut store, name).unwrap();
381                                linker.resource(name, resource, |_, _| Ok(()))?;
382                            }
383                            // TODO: should ideally reflect more than just
384                            // modules/resources into the linker's namespace
385                            // but that's not easily supported today for host
386                            // functions due to the inability to take a
387                            // function from one instance and put it into the
388                            // linker (must go through the host right now).
389                            _ => {}
390                        }
391                    }
392                }
393                self.current = Some(InstanceKind::Component(store, instance));
394            }
395        }
396        Ok(())
397    }
398
399    /// Compiles the module `wat` into binary and returns the name found within
400    /// it, if any.
401    ///
402    /// This will not register the name within `self.modules`.
403    fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
404        let name = match file.module_type {
405            WasmFileType::Text => file
406                .binary_filename
407                .as_ref()
408                .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
409            WasmFileType::Binary => &file.filename,
410        };
411
412        match &self.precompile_load {
413            Some(path) => {
414                let cwasm = path.join(&name[..]).with_extension("cwasm");
415                match Engine::detect_precompiled_file(&cwasm)
416                    .with_context(|| format!("failed to read {cwasm:?}"))?
417                {
418                    Some(Precompiled::Module) => {
419                        let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
420                        Ok(ModuleKind::Core(module))
421                    }
422                    #[cfg(feature = "component-model")]
423                    Some(Precompiled::Component) => {
424                        let component = unsafe {
425                            component::Component::deserialize_file(self.engine(), &cwasm)?
426                        };
427                        Ok(ModuleKind::Component(component))
428                    }
429                    #[cfg(not(feature = "component-model"))]
430                    Some(Precompiled::Component) => {
431                        bail!("support for components disabled at compile time")
432                    }
433                    None => bail!("expected a cwasm file"),
434                }
435            }
436            None => {
437                let bytes = &self.modules_by_filename[&name[..]];
438
439                if wasmparser::Parser::is_core_wasm(&bytes) {
440                    let module = Module::new(self.engine(), &bytes)?;
441                    Ok(ModuleKind::Core(module))
442                } else {
443                    #[cfg(feature = "component-model")]
444                    {
445                        let component = component::Component::new(self.engine(), &bytes)?;
446                        Ok(ModuleKind::Component(component))
447                    }
448                    #[cfg(not(feature = "component-model"))]
449                    bail!("component-model support not enabled");
450                }
451            }
452        }
453    }
454
455    /// Register an instance to make it available for performing actions.
456    fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
457        match name {
458            Some(name) => self.core_linker.alias_module(name, as_name),
459            None => {
460                let current = self
461                    .current
462                    .as_ref()
463                    .ok_or(format_err!("no previous instance"))?;
464                match current {
465                    InstanceKind::Core(current) => {
466                        self.core_linker
467                            .instance(&mut self.core_store, as_name, *current)?;
468                    }
469                    #[cfg(feature = "component-model")]
470                    InstanceKind::Component(..) => {
471                        bail!("register not implemented for components");
472                    }
473                }
474                Ok(())
475            }
476        }
477    }
478
479    /// Get the value of an exported global from an instance.
480    fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
481        let global = match self.get_export(instance_name, field)? {
482            Export::Core(e) => e
483                .into_global()
484                .ok_or_else(|| format_err!("no global named `{field}`"))?,
485            #[cfg(feature = "component-model")]
486            Export::Component(..) => bail!("no global named `{field}`"),
487        };
488        Ok(Outcome::Ok(Results::Core(vec![
489            global.get(&mut self.core_store),
490        ])))
491    }
492
493    fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
494        match result.into_result()? {
495            Results::Core(values) => {
496                if values.len() != results.len() {
497                    bail!("expected {} results found {}", results.len(), values.len());
498                }
499                for (i, (v, e)) in values.iter().zip(results).enumerate() {
500                    let e = match e {
501                        Const::Core(core) => core,
502                        _ => bail!("expected core value found other value {e:?}"),
503                    };
504                    core::match_val(&mut self.core_store, v, e)
505                        .with_context(|| format!("result {i} didn't match"))?;
506                }
507            }
508            #[cfg(feature = "component-model")]
509            Results::Component(values) => {
510                if values.len() != results.len() {
511                    bail!("expected {} results found {}", results.len(), values.len());
512                }
513                for (i, (v, e)) in values.iter().zip(results).enumerate() {
514                    let e = match e {
515                        Const::Component(val) => val,
516                        _ => bail!("expected component value found other value {e:?}"),
517                    };
518                    component::match_val(e, v)
519                        .with_context(|| format!("result {i} didn't match"))?;
520                }
521            }
522        }
523        Ok(())
524    }
525
526    fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
527        let trap = match result {
528            Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
529            Outcome::Trap(t) => t,
530        };
531        let actual = format!("{trap:?}");
532        if actual.contains(expected)
533            // `bulk-memory-operations/bulk.wast` checks for a message that
534            // specifies which element is uninitialized, but our traps don't
535            // shepherd that information out.
536            || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
537            // function references call_ref
538            || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
539            // GC tests say "null $kind reference" but we just say "null reference".
540            || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
541        {
542            return Ok(());
543        }
544        bail!("expected '{expected}', got '{actual}'")
545    }
546
547    fn assert_exception(&mut self, result: Outcome) -> Result<()> {
548        match result {
549            Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
550            Outcome::Trap(err) if err.is::<ThrownException>() => {
551                // Discard the thrown exception.
552                let _ = self
553                    .core_store
554                    .take_pending_exception()
555                    .expect("there should be a pending exception on the store");
556                Ok(())
557            }
558            Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
559        }
560    }
561
562    /// Run a wast script from a byte buffer.
563    pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
564        let wast = str::from_utf8(wast)?;
565
566        let adjust_wast = |mut err: wast::Error| {
567            err.set_path(filename.as_ref());
568            err.set_text(wast);
569            err
570        };
571
572        let mut lexer = Lexer::new(wast);
573        lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
574        let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
575        buf.track_instr_spans(self.generate_dwarf);
576        let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
577
578        let mut ast = json_from_wast::Opts::default()
579            .dwarf(self.generate_dwarf)
580            .convert(filename, wast, ast)
581            .to_wasmtime_result()?;
582
583        // Clear out any modules, if any, from a previous `*.wast` file being
584        // run, if any.
585        if !self.modules_by_filename.is_empty() {
586            self.modules_by_filename = Arc::default();
587        }
588        let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
589        for (name, bytes) in ast.wasms.drain(..) {
590            let prev = modules_by_filename.insert(name, bytes);
591            assert!(prev.is_none());
592        }
593
594        match &self.precompile_save {
595            Some(path) => {
596                let json_path = path
597                    .join(Path::new(filename).file_name().unwrap())
598                    .with_extension("json");
599                let json = serde_json::to_string(&ast)?;
600                std::fs::write(&json_path, json)
601                    .with_context(|| format!("failed to write {json_path:?}"))?;
602                for (name, bytes) in self.modules_by_filename.iter() {
603                    let cwasm_path = path.join(name).with_extension("cwasm");
604                    let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
605                        self.engine().precompile_module(bytes)
606                    } else {
607                        #[cfg(feature = "component-model")]
608                        {
609                            self.engine().precompile_component(bytes)
610                        }
611                        #[cfg(not(feature = "component-model"))]
612                        bail!("component-model support not enabled");
613                    };
614                    if let Ok(cwasm) = cwasm {
615                        std::fs::write(&cwasm_path, cwasm)
616                            .with_context(|| format!("failed to write {cwasm_path:?}"))?;
617                    }
618                }
619                Ok(())
620            }
621            None => self.run_directives(ast.commands, filename),
622        }
623    }
624
625    fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
626        thread::scope(|scope| {
627            let mut threads = HashMap::new();
628            for directive in directives {
629                let line = directive.line();
630                log::debug!("running directive on {filename}:{line}");
631                self.run_directive(directive, filename, &scope, &mut threads)
632                    .with_context(|| format!("failed directive on {filename}:{line}"))?;
633            }
634            Ok(())
635        })
636    }
637
638    fn run_directive<'a>(
639        &mut self,
640        directive: Command<'a>,
641        filename: &'a str,
642        // wast: &'a str,
643        scope: &'a thread::Scope<'a, '_>,
644        threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
645    ) -> Result<()> {
646        use Command::*;
647
648        match directive {
649            Module {
650                name,
651                file,
652                line: _,
653            } => {
654                let module = self.module_definition(&file)?;
655                self.module(name.as_deref(), &module)?;
656            }
657            ModuleDefinition {
658                name,
659                file,
660                line: _,
661            } => {
662                let module = self.module_definition(&file)?;
663                self.modules.insert(name.map(|s| s.to_string()), module);
664            }
665            ModuleInstance {
666                instance,
667                module,
668                line: _,
669            } => {
670                let module = self
671                    .modules
672                    .get(&module.as_ref().map(|s| s.to_string()))
673                    .cloned()
674                    .ok_or_else(|| format_err!("no module named {module:?}"))?;
675                self.module(instance.as_deref(), &module)?;
676            }
677            Register { line: _, name, as_ } => {
678                self.register(name.as_deref(), &as_)?;
679            }
680            Action { action, line: _ } => {
681                self.perform_action(&action)?;
682            }
683            AssertReturn {
684                action,
685                expected,
686                line: _,
687            } => {
688                let result = self.perform_action(&action)?;
689                self.assert_return(result, &expected)?;
690            }
691            AssertTrap {
692                action,
693                text,
694                line: _,
695            } => {
696                let result = self.perform_action(&action)?;
697                self.assert_trap(result, &text)?;
698            }
699            AssertUninstantiable {
700                file,
701                text,
702                line: _,
703            } => {
704                let result = match self.module_definition(&file)? {
705                    ModuleKind::Core(module) => self
706                        .instantiate_module(&module)?
707                        .map(|_| Results::Core(Vec::new())),
708                    #[cfg(feature = "component-model")]
709                    ModuleKind::Component(component) => self
710                        .instantiate_component(&component)?
711                        .map(|_| Results::Component(Vec::new())),
712                };
713                self.assert_trap(result, &text)?;
714            }
715            AssertExhaustion {
716                action,
717                text,
718                line: _,
719            } => {
720                let result = self.perform_action(&action)?;
721                self.assert_trap(result, &text)?;
722            }
723            AssertInvalid {
724                file,
725                text,
726                line: _,
727            } => {
728                let err = match self.module_definition(&file) {
729                    Ok(_) => bail!("expected module to fail to build"),
730                    Err(e) => e,
731                };
732                let error_message = format!("{err:?}");
733                if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
734                    bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
735                }
736            }
737            AssertMalformed {
738                file,
739                text: _,
740                line: _,
741            } => {
742                if let Ok(_) = self.module_definition(&file) {
743                    bail!("expected malformed module to fail to instantiate");
744                }
745            }
746            AssertUnlinkable {
747                file,
748                text,
749                line: _,
750            } => {
751                let module = self.module_definition(&file)?;
752                let err = match self.module(None, &module) {
753                    Ok(_) => bail!("expected module to fail to link"),
754                    Err(e) => e,
755                };
756                let error_message = format!("{err:?}");
757                if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
758                    bail!("assert_unlinkable: expected {text}, got {error_message}",)
759                }
760            }
761            AssertException { line: _, action } => {
762                let result = self.perform_action(&action)?;
763                self.assert_exception(result)?;
764            }
765
766            Thread {
767                name,
768                shared_module,
769                commands,
770                line: _,
771            } => {
772                let mut core_linker = Linker::new(self.engine());
773                if let Some(id) = shared_module {
774                    let items = self
775                        .core_linker
776                        .iter(&mut self.core_store)
777                        .filter(|(module, _, _)| *module == &id[..])
778                        .collect::<Vec<_>>();
779                    for (module, name, item) in items {
780                        core_linker.define(&mut self.core_store, module, name, item)?;
781                    }
782                }
783                let mut child_cx = WastContext {
784                    current: None,
785                    core_linker,
786                    #[cfg(feature = "component-model")]
787                    component_linker: component::Linker::new(self.engine()),
788                    core_store: {
789                        let mut store = Store::new(self.engine(), ());
790                        (self.configure_store)(&mut store);
791                        store
792                    },
793                    modules: self.modules.clone(),
794                    async_runtime: self.async_runtime.as_ref().map(|_| {
795                        tokio::runtime::Builder::new_current_thread()
796                            .build()
797                            .unwrap()
798                    }),
799                    generate_dwarf: self.generate_dwarf,
800                    modules_by_filename: self.modules_by_filename.clone(),
801                    precompile_load: self.precompile_load.clone(),
802                    precompile_save: self.precompile_save.clone(),
803                    configure_store: self.configure_store.clone(),
804                };
805                let child = scope.spawn(move || child_cx.run_directives(commands, filename));
806                threads.insert(name.to_string(), child);
807            }
808            Wait { thread, .. } => {
809                threads
810                    .remove(&thread[..])
811                    .ok_or_else(|| format_err!("no thread named `{thread}`"))?
812                    .join()
813                    .unwrap()?;
814            }
815
816            AssertSuspension { .. } => {
817                bail!("unimplemented wast directive");
818            }
819
820            AssertMalformedCustom {
821                file: _,
822                text: _,
823                line: _,
824            }
825            | AssertInvalidCustom {
826                file: _,
827                text: _,
828                line: _,
829            } => bail!("unimplemented wast directives"),
830        }
831
832        Ok(())
833    }
834
835    /// Run a wast script from a file.
836    pub fn run_file(&mut self, path: &Path) -> Result<()> {
837        match &self.precompile_load {
838            Some(precompile) => {
839                let file = precompile
840                    .join(path.file_name().unwrap())
841                    .with_extension("json");
842                let json = std::fs::read_to_string(&file)
843                    .with_context(|| format!("failed to read {file:?}"))?;
844                let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
845                self.run_directives(wast.commands, &wast.source_filename)
846            }
847            None => {
848                let bytes = std::fs::read(path)
849                    .with_context(|| format!("failed to read `{}`", path.display()))?;
850                self.run_wast(path.to_str().unwrap(), &bytes)
851            }
852        }
853    }
854
855    /// Whether or not to generate DWARF debugging information in custom
856    /// sections in modules being tested.
857    pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
858        self.generate_dwarf = enable;
859        self
860    }
861}
862
863fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
864    if actual.contains(expected) {
865        return true;
866    }
867
868    // Historically wasmtime/wasm-tools tried to match the upstream error
869    // message. This generally led to a large sequence of matches here which is
870    // not easy to maintain and is particularly difficult when test suites and
871    // proposals conflict with each other (e.g. one asserts one error message
872    // and another asserts a different error message). Overall we didn't benefit
873    // a whole lot from trying to match errors so just assume the error is
874    // roughly the same and otherwise don't try to match it.
875    if test.contains("spec_testsuite") {
876        return true;
877    }
878
879    // we are in control over all non-spec tests so all the error messages
880    // there should exactly match the `assert_invalid` or such
881    false
882}