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