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
15pub struct WastContext {
18 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 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 _Unused(std::convert::Infallible, &'a ()),
90}
91
92#[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 pub fn new(
111 engine: &Engine,
112 async_: Async,
113 configure: impl Fn(&mut Store<()>) + Send + Sync + 'static,
114 ) -> Self {
115 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 pub fn ignore_error_messages(&mut self, ignore: bool) -> &mut Self {
160 self.ignore_error_messages = ignore;
161 self
162 }
163
164 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 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 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 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 fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
280 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 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 _ => {}
403 }
404 }
405 }
406 self.current = Some(InstanceKind::Component(store, instance));
407 }
408 }
409 Ok(())
410 }
411
412 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 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 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 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
547 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
549 || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
551 || (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 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 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 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 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 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 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}