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)]
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 }
271 Ok(())
272 }
273
274 fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
276 struct ReplaceRuntime<'a> {
281 ctx: &'a mut WastContext,
282 rt: Option<tokio::runtime::Runtime>,
283 }
284 impl Drop for ReplaceRuntime<'_> {
285 fn drop(&mut self) {
286 self.ctx.async_runtime = self.rt.take();
287 }
288 }
289 let replace = ReplaceRuntime {
290 rt: self.async_runtime.take(),
291 ctx: self,
292 };
293 let me = &mut *replace.ctx;
294 match action {
295 Action::Invoke {
296 module,
297 field,
298 args,
299 } => match me.get_export(module.as_deref(), field)? {
300 Export::Core(export) => {
301 drop(replace);
302 let func = export
303 .into_func()
304 .ok_or_else(|| format_err!("no function named `{field}`"))?;
305 let values = args
306 .iter()
307 .map(|v| match v {
308 Const::Core(v) => core::val(self, v),
309 _ => bail!("expected core function, found other other argument {v:?}"),
310 })
311 .collect::<Result<Vec<_>>>()?;
312
313 let mut results =
314 vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()];
315 let result = match &self.async_runtime {
316 Some(rt) => rt.block_on(func.call_async(
317 &mut self.core_store,
318 &values,
319 &mut results,
320 )),
321 None => func.call(&mut self.core_store, &values, &mut results),
322 };
323
324 Ok(match result {
325 Ok(()) => Outcome::Ok(Results::Core(results)),
326 Err(e) => Outcome::Trap(e),
327 })
328 }
329 #[cfg(feature = "component-model")]
330 Export::Component(store, func) => {
331 let values = args
332 .iter()
333 .map(|v| match v {
334 Const::Component(v) => component::val(v),
335 _ => bail!("expected component function, found other argument {v:?}"),
336 })
337 .collect::<Result<Vec<_>>>()?;
338
339 let mut results =
340 vec![component::Val::Bool(false); func.ty(&store).results().len()];
341 let result = match &replace.rt {
342 Some(rt) => {
343 rt.block_on(func.call_async(&mut *store, &values, &mut results))
344 }
345 None => func.call(&mut *store, &values, &mut results),
346 };
347 Ok(match result {
348 Ok(()) => Outcome::Ok(Results::Component(results)),
349 Err(e) => Outcome::Trap(e),
350 })
351 }
352 },
353 Action::Get { module, field, .. } => me.get(module.as_deref(), field),
354 }
355 }
356
357 fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
360 match module {
361 ModuleKind::Core(module) => {
362 let instance = match self.instantiate_module(&module)? {
363 Outcome::Ok(i) => i,
364 Outcome::Trap(e) => return Err(e).context("instantiation failed"),
365 };
366 if let Some(name) = name {
367 self.core_linker
368 .instance(&mut self.core_store, name, instance)?;
369 }
370 self.current = Some(InstanceKind::Core(instance));
371 }
372 #[cfg(feature = "component-model")]
373 ModuleKind::Component(module) => {
374 let (component, mut store, instance) = match self.instantiate_component(&module)? {
375 Outcome::Ok(i) => i,
376 Outcome::Trap(e) => return Err(e).context("instantiation failed"),
377 };
378 if let Some(name) = name {
379 let ty = component.component_type();
380 let engine = self.engine().clone();
381 let mut linker = self.component_linker.instance(name)?;
382 for (name, item) in ty.exports(&engine) {
383 match item {
384 component::types::ComponentItem::Module(_) => {
385 let module = instance.get_module(&mut store, name).unwrap();
386 linker.module(name, &module)?;
387 }
388 component::types::ComponentItem::Resource(_) => {
389 let resource = instance.get_resource(&mut store, name).unwrap();
390 linker.resource(name, resource, |_, _| Ok(()))?;
391 }
392 _ => {}
399 }
400 }
401 }
402 self.current = Some(InstanceKind::Component(store, instance));
403 }
404 }
405 Ok(())
406 }
407
408 fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
413 let name = match file.module_type {
414 WasmFileType::Text => file
415 .binary_filename
416 .as_ref()
417 .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
418 WasmFileType::Binary => &file.filename,
419 };
420
421 match &self.precompile_load {
422 Some(path) => {
423 let cwasm = path.join(&name[..]).with_extension("cwasm");
424 match Engine::detect_precompiled_file(&cwasm)
425 .with_context(|| format!("failed to read {cwasm:?}"))?
426 {
427 Some(Precompiled::Module) => {
428 let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
429 Ok(ModuleKind::Core(module))
430 }
431 #[cfg(feature = "component-model")]
432 Some(Precompiled::Component) => {
433 let component = unsafe {
434 component::Component::deserialize_file(self.engine(), &cwasm)?
435 };
436 Ok(ModuleKind::Component(component))
437 }
438 #[cfg(not(feature = "component-model"))]
439 Some(Precompiled::Component) => {
440 bail!("support for components disabled at compile time")
441 }
442 None => bail!("expected a cwasm file"),
443 }
444 }
445 None => {
446 let bytes = &self.modules_by_filename[&name[..]];
447
448 if wasmparser::Parser::is_core_wasm(&bytes) {
449 let module = Module::new(self.engine(), &bytes)?;
450 Ok(ModuleKind::Core(module))
451 } else {
452 #[cfg(feature = "component-model")]
453 {
454 let component = component::Component::new(self.engine(), &bytes)?;
455 Ok(ModuleKind::Component(component))
456 }
457 #[cfg(not(feature = "component-model"))]
458 bail!("component-model support not enabled");
459 }
460 }
461 }
462 }
463
464 fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
466 match name {
467 Some(name) => self.core_linker.alias_module(name, as_name),
468 None => {
469 let current = self
470 .current
471 .as_ref()
472 .ok_or(format_err!("no previous instance"))?;
473 match current {
474 InstanceKind::Core(current) => {
475 self.core_linker
476 .instance(&mut self.core_store, as_name, *current)?;
477 }
478 #[cfg(feature = "component-model")]
479 InstanceKind::Component(..) => {
480 bail!("register not implemented for components");
481 }
482 }
483 Ok(())
484 }
485 }
486 }
487
488 fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
490 let global = match self.get_export(instance_name, field)? {
491 Export::Core(e) => e
492 .into_global()
493 .ok_or_else(|| format_err!("no global named `{field}`"))?,
494 #[cfg(feature = "component-model")]
495 Export::Component(..) => bail!("no global named `{field}`"),
496 };
497 Ok(Outcome::Ok(Results::Core(vec![
498 global.get(&mut self.core_store),
499 ])))
500 }
501
502 fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
503 match result.into_result()? {
504 Results::Core(values) => {
505 if values.len() != results.len() {
506 bail!("expected {} results found {}", results.len(), values.len());
507 }
508 for (i, (v, e)) in values.iter().zip(results).enumerate() {
509 let e = match e {
510 Const::Core(core) => core,
511 _ => bail!("expected core value found other value {e:?}"),
512 };
513 core::match_val(&mut self.core_store, v, e)
514 .with_context(|| format!("result {i} didn't match"))?;
515 }
516 }
517 #[cfg(feature = "component-model")]
518 Results::Component(values) => {
519 if values.len() != results.len() {
520 bail!("expected {} results found {}", results.len(), values.len());
521 }
522 for (i, (v, e)) in values.iter().zip(results).enumerate() {
523 let e = match e {
524 Const::Component(val) => val,
525 _ => bail!("expected component value found other value {e:?}"),
526 };
527 component::match_val(e, v)
528 .with_context(|| format!("result {i} didn't match"))?;
529 }
530 }
531 }
532 Ok(())
533 }
534
535 fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
536 let trap = match result {
537 Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
538 Outcome::Trap(t) => t,
539 };
540 let actual = format!("{trap:?}");
541 if actual.contains(expected)
542 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
546 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
548 || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
550 || (expected.contains("cannot write") && actual.contains("cannot write"))
553 || (expected.contains("cannot read") && actual.contains("cannot read"))
554 {
555 return Ok(());
556 }
557 bail!("expected '{expected}', got '{actual}'")
558 }
559
560 fn assert_exception(&mut self, result: Outcome) -> Result<()> {
561 match result {
562 Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
563 Outcome::Trap(err) if err.is::<ThrownException>() => {
564 let _ = self
566 .core_store
567 .take_pending_exception()
568 .expect("there should be a pending exception on the store");
569 Ok(())
570 }
571 Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
572 }
573 }
574
575 pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
577 let wast = str::from_utf8(wast)?;
578
579 let adjust_wast = |mut err: wast::Error| {
580 err.set_path(filename.as_ref());
581 err.set_text(wast);
582 err
583 };
584
585 let mut lexer = Lexer::new(wast);
586 lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
587 let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
588 buf.track_instr_spans(self.generate_dwarf);
589 let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
590
591 let mut ast = json_from_wast::Opts::default()
592 .dwarf(self.generate_dwarf)
593 .convert(filename, wast, ast)
594 .to_wasmtime_result()?;
595
596 if !self.modules_by_filename.is_empty() {
599 self.modules_by_filename = Arc::default();
600 }
601 let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
602 for (name, bytes) in ast.wasms.drain(..) {
603 let prev = modules_by_filename.insert(name, bytes);
604 assert!(prev.is_none());
605 }
606
607 match &self.precompile_save {
608 Some(path) => {
609 let json_path = path
610 .join(Path::new(filename).file_name().unwrap())
611 .with_extension("json");
612 let json = serde_json::to_string(&ast)?;
613 std::fs::write(&json_path, json)
614 .with_context(|| format!("failed to write {json_path:?}"))?;
615 for (name, bytes) in self.modules_by_filename.iter() {
616 let cwasm_path = path.join(name).with_extension("cwasm");
617 let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
618 self.engine().precompile_module(bytes)
619 } else {
620 #[cfg(feature = "component-model")]
621 {
622 self.engine().precompile_component(bytes)
623 }
624 #[cfg(not(feature = "component-model"))]
625 bail!("component-model support not enabled");
626 };
627 if let Ok(cwasm) = cwasm {
628 std::fs::write(&cwasm_path, cwasm)
629 .with_context(|| format!("failed to write {cwasm_path:?}"))?;
630 }
631 }
632 Ok(())
633 }
634 None => self.run_directives(ast.commands, filename),
635 }
636 }
637
638 fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
639 thread::scope(|scope| {
640 let mut threads = HashMap::new();
641 for directive in directives {
642 let line = directive.line();
643 log::debug!("running directive on {filename}:{line}");
644 self.run_directive(directive, filename, &scope, &mut threads)
645 .with_context(|| format!("failed directive on {filename}:{line}"))?;
646 }
647 Ok(())
648 })
649 }
650
651 fn run_directive<'a>(
652 &mut self,
653 directive: Command<'a>,
654 filename: &'a str,
655 scope: &'a thread::Scope<'a, '_>,
657 threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
658 ) -> Result<()> {
659 use Command::*;
660
661 match directive {
662 Module {
663 name,
664 file,
665 line: _,
666 } => {
667 let module = self.module_definition(&file)?;
668 self.module(name.as_deref(), &module)?;
669 }
670 ModuleDefinition {
671 name,
672 file,
673 line: _,
674 } => {
675 let module = self.module_definition(&file)?;
676 self.modules.insert(name.map(|s| s.to_string()), module);
677 }
678 ModuleInstance {
679 instance,
680 module,
681 line: _,
682 } => {
683 let module = self
684 .modules
685 .get(&module.as_ref().map(|s| s.to_string()))
686 .cloned()
687 .ok_or_else(|| format_err!("no module named {module:?}"))?;
688 self.module(instance.as_deref(), &module)?;
689 }
690 Register { line: _, name, as_ } => {
691 self.register(name.as_deref(), &as_)?;
692 }
693 Action { action, line: _ } => {
694 self.perform_action(&action)?;
695 }
696 AssertReturn {
697 action,
698 expected,
699 line: _,
700 } => {
701 let result = self.perform_action(&action)?;
702 self.assert_return(result, &expected)?;
703 }
704 AssertTrap {
705 action,
706 text,
707 line: _,
708 } => {
709 let result = self.perform_action(&action)?;
710 self.assert_trap(result, &text)?;
711 }
712 AssertUninstantiable {
713 file,
714 text,
715 line: _,
716 } => {
717 let result = match self.module_definition(&file)? {
718 ModuleKind::Core(module) => self
719 .instantiate_module(&module)?
720 .map(|_| Results::Core(Vec::new())),
721 #[cfg(feature = "component-model")]
722 ModuleKind::Component(component) => self
723 .instantiate_component(&component)?
724 .map(|_| Results::Component(Vec::new())),
725 };
726 self.assert_trap(result, &text)?;
727 }
728 AssertExhaustion {
729 action,
730 text,
731 line: _,
732 } => {
733 let result = self.perform_action(&action)?;
734 self.assert_trap(result, &text)?;
735 }
736 AssertInvalid {
737 file,
738 text,
739 line: _,
740 } => {
741 let err = match self.module_definition(&file) {
742 Ok(_) => bail!("expected module to fail to build"),
743 Err(e) => e,
744 };
745 self.match_error_message(&text, err)?;
746 }
747 AssertMalformed {
748 file,
749 text: _,
750 line: _,
751 } => {
752 if let Ok(_) = self.module_definition(&file) {
753 bail!("expected malformed module to fail to instantiate");
754 }
755 }
756 AssertUnlinkable {
757 file,
758 text,
759 line: _,
760 } => {
761 let module = self.module_definition(&file)?;
762 let err = match self.module(None, &module) {
763 Ok(_) => bail!("expected module to fail to link"),
764 Err(e) => e,
765 };
766 self.match_error_message(&text, err)?;
767 }
768 AssertException { line: _, action } => {
769 let result = self.perform_action(&action)?;
770 self.assert_exception(result)?;
771 }
772
773 Thread {
774 name,
775 shared_module,
776 commands,
777 line: _,
778 } => {
779 let mut core_linker = Linker::new(self.engine());
780 if let Some(id) = shared_module {
781 let items = self
782 .core_linker
783 .iter(&mut self.core_store)
784 .filter(|(module, _, _)| *module == &id[..])
785 .collect::<Vec<_>>();
786 for (module, name, item) in items {
787 core_linker.define(&mut self.core_store, module, name, item)?;
788 }
789 }
790 let mut child_cx = WastContext {
791 current: None,
792 core_linker,
793 #[cfg(feature = "component-model")]
794 component_linker: component::Linker::new(self.engine()),
795 core_store: {
796 let mut store = Store::new(self.engine(), ());
797 (self.configure_store)(&mut store);
798 store
799 },
800 modules: self.modules.clone(),
801 async_runtime: self.async_runtime.as_ref().map(|_| {
802 tokio::runtime::Builder::new_current_thread()
803 .build()
804 .unwrap()
805 }),
806 generate_dwarf: self.generate_dwarf,
807 modules_by_filename: self.modules_by_filename.clone(),
808 precompile_load: self.precompile_load.clone(),
809 precompile_save: self.precompile_save.clone(),
810 configure_store: self.configure_store.clone(),
811 ignore_error_messages: self.ignore_error_messages,
812 };
813 let child = scope.spawn(move || child_cx.run_directives(commands, filename));
814 threads.insert(name.to_string(), child);
815 }
816 Wait { thread, .. } => {
817 threads
818 .remove(&thread[..])
819 .ok_or_else(|| format_err!("no thread named `{thread}`"))?
820 .join()
821 .unwrap()?;
822 }
823
824 AssertSuspension { .. } => {
825 bail!("unimplemented wast directive");
826 }
827
828 AssertMalformedCustom {
829 file: _,
830 text: _,
831 line: _,
832 }
833 | AssertInvalidCustom {
834 file: _,
835 text: _,
836 line: _,
837 } => bail!("unimplemented wast directives"),
838 }
839
840 Ok(())
841 }
842
843 pub fn run_file(&mut self, path: &Path) -> Result<()> {
845 match &self.precompile_load {
846 Some(precompile) => {
847 let file = precompile
848 .join(path.file_name().unwrap())
849 .with_extension("json");
850 let json = std::fs::read_to_string(&file)
851 .with_context(|| format!("failed to read {file:?}"))?;
852 let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
853 self.run_directives(wast.commands, &wast.source_filename)
854 }
855 None => {
856 let bytes = std::fs::read(path)
857 .with_context(|| format!("failed to read `{}`", path.display()))?;
858 self.run_wast(path.to_str().unwrap(), &bytes)
859 }
860 }
861 }
862
863 pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
866 self.generate_dwarf = enable;
867 self
868 }
869
870 fn match_error_message(&self, expected: &str, err: wasmtime::Error) -> Result<()> {
871 if self.ignore_error_messages {
872 return Ok(());
873 }
874 let actual = format!("{err:?}");
875 if actual.contains(expected) {
876 return Ok(());
877 }
878 bail!("assert_invalid: expected \"{expected}\", got \"{actual}\"",)
879 }
880}