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<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                    .ok_or_else(|| 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    /// Perform the action portion of a command.
240    fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
241        // Need to simultaneously borrow `self.async_runtime` and a `&mut
242        // Store` from components so work around the borrow checker issues by
243        // taking out the async runtime here and putting it back through a
244        // destructor.
245        struct ReplaceRuntime<'a> {
246            ctx: &'a mut WastContext,
247            rt: Option<tokio::runtime::Runtime>,
248        }
249        impl Drop for ReplaceRuntime<'_> {
250            fn drop(&mut self) {
251                self.ctx.async_runtime = self.rt.take();
252            }
253        }
254        let replace = ReplaceRuntime {
255            rt: self.async_runtime.take(),
256            ctx: self,
257        };
258        let me = &mut *replace.ctx;
259        match action {
260            Action::Invoke {
261                module,
262                field,
263                args,
264            } => match me.get_export(module.as_deref(), field)? {
265                Export::Core(export) => {
266                    drop(replace);
267                    let func = export
268                        .into_func()
269                        .ok_or_else(|| format_err!("no function named `{field}`"))?;
270                    let values = args
271                        .iter()
272                        .map(|v| match v {
273                            Const::Core(v) => core::val(self, v),
274                            _ => bail!("expected core function, found other other argument {v:?}"),
275                        })
276                        .collect::<Result<Vec<_>>>()?;
277
278                    let mut results =
279                        vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()];
280                    let result = match &self.async_runtime {
281                        Some(rt) => rt.block_on(func.call_async(
282                            &mut self.core_store,
283                            &values,
284                            &mut results,
285                        )),
286                        None => func.call(&mut self.core_store, &values, &mut results),
287                    };
288
289                    Ok(match result {
290                        Ok(()) => Outcome::Ok(Results::Core(results)),
291                        Err(e) => Outcome::Trap(e),
292                    })
293                }
294                #[cfg(feature = "component-model")]
295                Export::Component(store, func) => {
296                    let values = args
297                        .iter()
298                        .map(|v| match v {
299                            Const::Component(v) => component::val(v),
300                            _ => bail!("expected component function, found other argument {v:?}"),
301                        })
302                        .collect::<Result<Vec<_>>>()?;
303
304                    let mut results =
305                        vec![component::Val::Bool(false); func.ty(&store).results().len()];
306                    let result = match &replace.rt {
307                        Some(rt) => {
308                            rt.block_on(func.call_async(&mut *store, &values, &mut results))
309                        }
310                        None => func.call(&mut *store, &values, &mut results),
311                    };
312                    Ok(match result {
313                        Ok(()) => Outcome::Ok(Results::Component(results)),
314                        Err(e) => Outcome::Trap(e),
315                    })
316                }
317            },
318            Action::Get { module, field, .. } => me.get(module.as_deref(), field),
319        }
320    }
321
322    /// Instantiates the `module` provided and registers the instance under the
323    /// `name` provided if successful.
324    fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
325        match module {
326            ModuleKind::Core(module) => {
327                let instance = match self.instantiate_module(&module)? {
328                    Outcome::Ok(i) => i,
329                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
330                };
331                if let Some(name) = name {
332                    self.core_linker
333                        .instance(&mut self.core_store, name, instance)?;
334                }
335                self.current = Some(InstanceKind::Core(instance));
336            }
337            #[cfg(feature = "component-model")]
338            ModuleKind::Component(module) => {
339                let (component, mut store, instance) = match self.instantiate_component(&module)? {
340                    Outcome::Ok(i) => i,
341                    Outcome::Trap(e) => return Err(e).context("instantiation failed"),
342                };
343                if let Some(name) = name {
344                    let ty = component.component_type();
345                    let engine = self.engine().clone();
346                    let mut linker = self.component_linker.instance(name)?;
347                    for (name, item) in ty.exports(&engine) {
348                        match item {
349                            component::types::ComponentItem::Module(_) => {
350                                let module = instance.get_module(&mut store, name).unwrap();
351                                linker.module(name, &module)?;
352                            }
353                            component::types::ComponentItem::Resource(_) => {
354                                let resource = instance.get_resource(&mut store, name).unwrap();
355                                linker.resource(name, resource, |_, _| Ok(()))?;
356                            }
357                            // TODO: should ideally reflect more than just
358                            // modules/resources into the linker's namespace
359                            // but that's not easily supported today for host
360                            // functions due to the inability to take a
361                            // function from one instance and put it into the
362                            // linker (must go through the host right now).
363                            _ => {}
364                        }
365                    }
366                }
367                self.current = Some(InstanceKind::Component(store, instance));
368            }
369        }
370        Ok(())
371    }
372
373    /// Compiles the module `wat` into binary and returns the name found within
374    /// it, if any.
375    ///
376    /// This will not register the name within `self.modules`.
377    fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
378        let name = match file.module_type {
379            WasmFileType::Text => file
380                .binary_filename
381                .as_ref()
382                .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
383            WasmFileType::Binary => &file.filename,
384        };
385
386        match &self.precompile_load {
387            Some(path) => {
388                let cwasm = path.join(&name[..]).with_extension("cwasm");
389                match Engine::detect_precompiled_file(&cwasm)
390                    .with_context(|| format!("failed to read {cwasm:?}"))?
391                {
392                    Some(Precompiled::Module) => {
393                        let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
394                        Ok(ModuleKind::Core(module))
395                    }
396                    #[cfg(feature = "component-model")]
397                    Some(Precompiled::Component) => {
398                        let component = unsafe {
399                            component::Component::deserialize_file(self.engine(), &cwasm)?
400                        };
401                        Ok(ModuleKind::Component(component))
402                    }
403                    #[cfg(not(feature = "component-model"))]
404                    Some(Precompiled::Component) => {
405                        bail!("support for components disabled at compile time")
406                    }
407                    None => bail!("expected a cwasm file"),
408                }
409            }
410            None => {
411                let bytes = &self.modules_by_filename[&name[..]];
412
413                if wasmparser::Parser::is_core_wasm(&bytes) {
414                    let module = Module::new(self.engine(), &bytes)?;
415                    Ok(ModuleKind::Core(module))
416                } else {
417                    #[cfg(feature = "component-model")]
418                    {
419                        let component = component::Component::new(self.engine(), &bytes)?;
420                        Ok(ModuleKind::Component(component))
421                    }
422                    #[cfg(not(feature = "component-model"))]
423                    bail!("component-model support not enabled");
424                }
425            }
426        }
427    }
428
429    /// Register an instance to make it available for performing actions.
430    fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
431        match name {
432            Some(name) => self.core_linker.alias_module(name, as_name),
433            None => {
434                let current = self
435                    .current
436                    .as_ref()
437                    .ok_or(format_err!("no previous instance"))?;
438                match current {
439                    InstanceKind::Core(current) => {
440                        self.core_linker
441                            .instance(&mut self.core_store, as_name, *current)?;
442                    }
443                    #[cfg(feature = "component-model")]
444                    InstanceKind::Component(..) => {
445                        bail!("register not implemented for components");
446                    }
447                }
448                Ok(())
449            }
450        }
451    }
452
453    /// Get the value of an exported global from an instance.
454    fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
455        let global = match self.get_export(instance_name, field)? {
456            Export::Core(e) => e
457                .into_global()
458                .ok_or_else(|| format_err!("no global named `{field}`"))?,
459            #[cfg(feature = "component-model")]
460            Export::Component(..) => bail!("no global named `{field}`"),
461        };
462        Ok(Outcome::Ok(Results::Core(vec![
463            global.get(&mut self.core_store),
464        ])))
465    }
466
467    fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
468        match result.into_result()? {
469            Results::Core(values) => {
470                if values.len() != results.len() {
471                    bail!("expected {} results found {}", results.len(), values.len());
472                }
473                for (i, (v, e)) in values.iter().zip(results).enumerate() {
474                    let e = match e {
475                        Const::Core(core) => core,
476                        _ => bail!("expected core value found other value {e:?}"),
477                    };
478                    core::match_val(&mut self.core_store, v, e)
479                        .with_context(|| format!("result {i} didn't match"))?;
480                }
481            }
482            #[cfg(feature = "component-model")]
483            Results::Component(values) => {
484                if values.len() != results.len() {
485                    bail!("expected {} results found {}", results.len(), values.len());
486                }
487                for (i, (v, e)) in values.iter().zip(results).enumerate() {
488                    let e = match e {
489                        Const::Component(val) => val,
490                        _ => bail!("expected component value found other value {e:?}"),
491                    };
492                    component::match_val(e, v)
493                        .with_context(|| format!("result {i} didn't match"))?;
494                }
495            }
496        }
497        Ok(())
498    }
499
500    fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
501        let trap = match result {
502            Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
503            Outcome::Trap(t) => t,
504        };
505        let actual = format!("{trap:?}");
506        if actual.contains(expected)
507            // `bulk-memory-operations/bulk.wast` checks for a message that
508            // specifies which element is uninitialized, but our traps don't
509            // shepherd that information out.
510            || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
511            // function references call_ref
512            || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
513            // GC tests say "null $kind reference" but we just say "null reference".
514            || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
515        {
516            return Ok(());
517        }
518        bail!("expected '{expected}', got '{actual}'")
519    }
520
521    fn assert_exception(&mut self, result: Outcome) -> Result<()> {
522        match result {
523            Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
524            Outcome::Trap(err) if err.is::<ThrownException>() => {
525                // Discard the thrown exception.
526                let _ = self
527                    .core_store
528                    .take_pending_exception()
529                    .expect("there should be a pending exception on the store");
530                Ok(())
531            }
532            Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
533        }
534    }
535
536    /// Run a wast script from a byte buffer.
537    pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
538        let wast = str::from_utf8(wast)?;
539
540        let adjust_wast = |mut err: wast::Error| {
541            err.set_path(filename.as_ref());
542            err.set_text(wast);
543            err
544        };
545
546        let mut lexer = Lexer::new(wast);
547        lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
548        let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
549        buf.track_instr_spans(self.generate_dwarf);
550        let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
551
552        let mut ast = json_from_wast::Opts::default()
553            .dwarf(self.generate_dwarf)
554            .convert(filename, wast, ast)
555            .to_wasmtime_result()?;
556
557        // Clear out any modules, if any, from a previous `*.wast` file being
558        // run, if any.
559        if !self.modules_by_filename.is_empty() {
560            self.modules_by_filename = Arc::default();
561        }
562        let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
563        for (name, bytes) in ast.wasms.drain(..) {
564            let prev = modules_by_filename.insert(name, bytes);
565            assert!(prev.is_none());
566        }
567
568        match &self.precompile_save {
569            Some(path) => {
570                let json_path = path
571                    .join(Path::new(filename).file_name().unwrap())
572                    .with_extension("json");
573                let json = serde_json::to_string(&ast)?;
574                std::fs::write(&json_path, json)
575                    .with_context(|| format!("failed to write {json_path:?}"))?;
576                for (name, bytes) in self.modules_by_filename.iter() {
577                    let cwasm_path = path.join(name).with_extension("cwasm");
578                    let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
579                        self.engine().precompile_module(bytes)
580                    } else {
581                        #[cfg(feature = "component-model")]
582                        {
583                            self.engine().precompile_component(bytes)
584                        }
585                        #[cfg(not(feature = "component-model"))]
586                        bail!("component-model support not enabled");
587                    };
588                    if let Ok(cwasm) = cwasm {
589                        std::fs::write(&cwasm_path, cwasm)
590                            .with_context(|| format!("failed to write {cwasm_path:?}"))?;
591                    }
592                }
593                Ok(())
594            }
595            None => self.run_directives(ast.commands, filename),
596        }
597    }
598
599    fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
600        thread::scope(|scope| {
601            let mut threads = HashMap::new();
602            for directive in directives {
603                let line = directive.line();
604                log::debug!("running directive on {filename}:{line}");
605                self.run_directive(directive, filename, &scope, &mut threads)
606                    .with_context(|| format!("failed directive on {filename}:{line}"))?;
607            }
608            Ok(())
609        })
610    }
611
612    fn run_directive<'a>(
613        &mut self,
614        directive: Command<'a>,
615        filename: &'a str,
616        // wast: &'a str,
617        scope: &'a thread::Scope<'a, '_>,
618        threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
619    ) -> Result<()> {
620        use Command::*;
621
622        match directive {
623            Module {
624                name,
625                file,
626                line: _,
627            } => {
628                let module = self.module_definition(&file)?;
629                self.module(name.as_deref(), &module)?;
630            }
631            ModuleDefinition {
632                name,
633                file,
634                line: _,
635            } => {
636                let module = self.module_definition(&file)?;
637                if let Some(name) = name {
638                    self.modules.insert(name.to_string(), module);
639                }
640            }
641            ModuleInstance {
642                instance,
643                module,
644                line: _,
645            } => {
646                let module = module
647                    .as_deref()
648                    .and_then(|n| self.modules.get(n))
649                    .cloned()
650                    .ok_or_else(|| format_err!("no module named {module:?}"))?;
651                self.module(instance.as_deref(), &module)?;
652            }
653            Register { line: _, name, as_ } => {
654                self.register(name.as_deref(), &as_)?;
655            }
656            Action { action, line: _ } => {
657                self.perform_action(&action)?;
658            }
659            AssertReturn {
660                action,
661                expected,
662                line: _,
663            } => {
664                let result = self.perform_action(&action)?;
665                self.assert_return(result, &expected)?;
666            }
667            AssertTrap {
668                action,
669                text,
670                line: _,
671            } => {
672                let result = self.perform_action(&action)?;
673                self.assert_trap(result, &text)?;
674            }
675            AssertUninstantiable {
676                file,
677                text,
678                line: _,
679            } => {
680                let result = match self.module_definition(&file)? {
681                    ModuleKind::Core(module) => self
682                        .instantiate_module(&module)?
683                        .map(|_| Results::Core(Vec::new())),
684                    #[cfg(feature = "component-model")]
685                    ModuleKind::Component(component) => self
686                        .instantiate_component(&component)?
687                        .map(|_| Results::Component(Vec::new())),
688                };
689                self.assert_trap(result, &text)?;
690            }
691            AssertExhaustion {
692                action,
693                text,
694                line: _,
695            } => {
696                let result = self.perform_action(&action)?;
697                self.assert_trap(result, &text)?;
698            }
699            AssertInvalid {
700                file,
701                text,
702                line: _,
703            } => {
704                let err = match self.module_definition(&file) {
705                    Ok(_) => bail!("expected module to fail to build"),
706                    Err(e) => e,
707                };
708                let error_message = format!("{err:?}");
709                if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
710                    bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
711                }
712            }
713            AssertMalformed {
714                file,
715                text: _,
716                line: _,
717            } => {
718                if let Ok(_) = self.module_definition(&file) {
719                    bail!("expected malformed module to fail to instantiate");
720                }
721            }
722            AssertUnlinkable {
723                file,
724                text,
725                line: _,
726            } => {
727                let module = self.module_definition(&file)?;
728                let err = match self.module(None, &module) {
729                    Ok(_) => bail!("expected module to fail to link"),
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_unlinkable: expected {text}, got {error_message}",)
735                }
736            }
737            AssertException { line: _, action } => {
738                let result = self.perform_action(&action)?;
739                self.assert_exception(result)?;
740            }
741
742            Thread {
743                name,
744                shared_module,
745                commands,
746                line: _,
747            } => {
748                let mut core_linker = Linker::new(self.engine());
749                if let Some(id) = shared_module {
750                    let items = self
751                        .core_linker
752                        .iter(&mut self.core_store)
753                        .filter(|(module, _, _)| *module == &id[..])
754                        .collect::<Vec<_>>();
755                    for (module, name, item) in items {
756                        core_linker.define(&mut self.core_store, module, name, item)?;
757                    }
758                }
759                let mut child_cx = WastContext {
760                    current: None,
761                    core_linker,
762                    #[cfg(feature = "component-model")]
763                    component_linker: component::Linker::new(self.engine()),
764                    core_store: {
765                        let mut store = Store::new(self.engine(), ());
766                        (self.configure_store)(&mut store);
767                        store
768                    },
769                    modules: self.modules.clone(),
770                    async_runtime: self.async_runtime.as_ref().map(|_| {
771                        tokio::runtime::Builder::new_current_thread()
772                            .build()
773                            .unwrap()
774                    }),
775                    generate_dwarf: self.generate_dwarf,
776                    modules_by_filename: self.modules_by_filename.clone(),
777                    precompile_load: self.precompile_load.clone(),
778                    precompile_save: self.precompile_save.clone(),
779                    configure_store: self.configure_store.clone(),
780                };
781                let child = scope.spawn(move || child_cx.run_directives(commands, filename));
782                threads.insert(name.to_string(), child);
783            }
784            Wait { thread, .. } => {
785                threads
786                    .remove(&thread[..])
787                    .ok_or_else(|| format_err!("no thread named `{thread}`"))?
788                    .join()
789                    .unwrap()?;
790            }
791
792            AssertSuspension { .. } => {
793                bail!("unimplemented wast directive");
794            }
795        }
796
797        Ok(())
798    }
799
800    /// Run a wast script from a file.
801    pub fn run_file(&mut self, path: &Path) -> Result<()> {
802        match &self.precompile_load {
803            Some(precompile) => {
804                let file = precompile
805                    .join(path.file_name().unwrap())
806                    .with_extension("json");
807                let json = std::fs::read_to_string(&file)
808                    .with_context(|| format!("failed to read {file:?}"))?;
809                let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
810                self.run_directives(wast.commands, &wast.source_filename)
811            }
812            None => {
813                let bytes = std::fs::read(path)
814                    .with_context(|| format!("failed to read `{}`", path.display()))?;
815                self.run_wast(path.to_str().unwrap(), &bytes)
816            }
817        }
818    }
819
820    /// Whether or not to generate DWARF debugging information in custom
821    /// sections in modules being tested.
822    pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
823        self.generate_dwarf = enable;
824        self
825    }
826}
827
828fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
829    if actual.contains(expected) {
830        return true;
831    }
832
833    // Historically wasmtime/wasm-tools tried to match the upstream error
834    // message. This generally led to a large sequence of matches here which is
835    // not easy to maintain and is particularly difficult when test suites and
836    // proposals conflict with each other (e.g. one asserts one error message
837    // and another asserts a different error message). Overall we didn't benefit
838    // a whole lot from trying to match errors so just assume the error is
839    // roughly the same and otherwise don't try to match it.
840    if test.contains("spec_testsuite") {
841        return true;
842    }
843
844    // we are in control over all non-spec tests so all the error messages
845    // there should exactly match the `assert_invalid` or such
846    false
847}