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 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
419 .binary_filename
420 .as_ref()
421 .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
422 WasmFileType::Binary => &file.filename,
423 };
424
425 match &self.precompile_load {
426 Some(path) => {
427 let cwasm = path.join(&name[..]).with_extension("cwasm");
428 match Engine::detect_precompiled_file(&cwasm)
429 .with_context(|| format!("failed to read {cwasm:?}"))?
430 {
431 Some(Precompiled::Module) => {
432 let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
433 Ok(ModuleKind::Core(module))
434 }
435 #[cfg(feature = "component-model")]
436 Some(Precompiled::Component) => {
437 let component = unsafe {
438 component::Component::deserialize_file(self.engine(), &cwasm)?
439 };
440 Ok(ModuleKind::Component(component))
441 }
442 #[cfg(not(feature = "component-model"))]
443 Some(Precompiled::Component) => {
444 bail!("support for components disabled at compile time")
445 }
446 None => bail!("expected a cwasm file"),
447 }
448 }
449 None => {
450 let bytes = &self.modules_by_filename[&name[..]];
451
452 if wasmparser::Parser::is_core_wasm(&bytes) {
453 let module = Module::new(self.engine(), &bytes)?;
454 Ok(ModuleKind::Core(module))
455 } else {
456 #[cfg(feature = "component-model")]
457 {
458 let component = component::Component::new(self.engine(), &bytes)?;
459 Ok(ModuleKind::Component(component))
460 }
461 #[cfg(not(feature = "component-model"))]
462 bail!("component-model support not enabled");
463 }
464 }
465 }
466 }
467
468 fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
470 match name {
471 Some(name) => self.core_linker.alias_module(name, as_name),
472 None => {
473 let current = self
474 .current
475 .as_ref()
476 .ok_or(format_err!("no previous instance"))?;
477 match current {
478 InstanceKind::Core(current) => {
479 self.core_linker
480 .instance(&mut self.core_store, as_name, *current)?;
481 }
482 #[cfg(feature = "component-model")]
483 InstanceKind::Component(..) => {
484 bail!("register not implemented for components");
485 }
486 }
487 Ok(())
488 }
489 }
490 }
491
492 fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
494 let global = match self.get_export(instance_name, field)? {
495 Export::Core(e) => e
496 .into_global()
497 .ok_or_else(|| format_err!("no global named `{field}`"))?,
498 #[cfg(feature = "component-model")]
499 Export::Component(..) => bail!("no global named `{field}`"),
500 };
501 Ok(Outcome::Ok(Results::Core(vec![
502 global.get(&mut self.core_store),
503 ])))
504 }
505
506 fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
507 match result.into_result()? {
508 Results::Core(values) => {
509 if values.len() != results.len() {
510 bail!("expected {} results found {}", results.len(), values.len());
511 }
512 for (i, (v, e)) in values.iter().zip(results).enumerate() {
513 let e = match e {
514 Const::Core(core) => core,
515 _ => bail!("expected core value found other value {e:?}"),
516 };
517 core::match_val(&mut self.core_store, v, e)
518 .with_context(|| format!("result {i} didn't match"))?;
519 }
520 }
521 #[cfg(feature = "component-model")]
522 Results::Component(values) => {
523 if values.len() != results.len() {
524 bail!("expected {} results found {}", results.len(), values.len());
525 }
526 for (i, (v, e)) in values.iter().zip(results).enumerate() {
527 let e = match e {
528 Const::Component(val) => val,
529 _ => bail!("expected component value found other value {e:?}"),
530 };
531 component::match_val(e, v)
532 .with_context(|| format!("result {i} didn't match"))?;
533 }
534 }
535 }
536 Ok(())
537 }
538
539 fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
540 let trap = match result {
541 Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
542 Outcome::Trap(t) => t,
543 };
544 let actual = format!("{trap:?}");
545 if actual.contains(expected)
546 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
550 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
552 || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
554 || (expected.contains("cannot write") && actual.contains("cannot write"))
557 || (expected.contains("cannot read") && actual.contains("cannot read"))
558 {
559 return Ok(());
560 }
561 bail!("expected '{expected}', got '{actual}'")
562 }
563
564 fn assert_exception(&mut self, result: Outcome) -> Result<()> {
565 match result {
566 Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
567 Outcome::Trap(err) if err.is::<ThrownException>() => {
568 let _ = self
570 .core_store
571 .take_pending_exception()
572 .expect("there should be a pending exception on the store");
573 Ok(())
574 }
575 Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
576 }
577 }
578
579 pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
581 let wast = str::from_utf8(wast)?;
582
583 let adjust_wast = |mut err: wast::Error| {
584 err.set_path(filename.as_ref());
585 err.set_text(wast);
586 err
587 };
588
589 let mut lexer = Lexer::new(wast);
590 lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
591 let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
592 buf.track_instr_spans(self.generate_dwarf);
593 let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
594
595 let mut ast = json_from_wast::Opts::default()
596 .dwarf(self.generate_dwarf)
597 .convert(filename, wast, ast)
598 .to_wasmtime_result()?;
599
600 if !self.modules_by_filename.is_empty() {
603 self.modules_by_filename = Arc::default();
604 }
605 let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
606 for (name, bytes) in ast.wasms.drain(..) {
607 let prev = modules_by_filename.insert(name, bytes);
608 assert!(prev.is_none());
609 }
610
611 match &self.precompile_save {
612 Some(path) => {
613 let json_path = path
614 .join(Path::new(filename).file_name().unwrap())
615 .with_extension("json");
616 let json = serde_json::to_string(&ast)?;
617 std::fs::write(&json_path, json)
618 .with_context(|| format!("failed to write {json_path:?}"))?;
619 for (name, bytes) in self.modules_by_filename.iter() {
620 let cwasm_path = path.join(name).with_extension("cwasm");
621 let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
622 self.engine().precompile_module(bytes)
623 } else {
624 #[cfg(feature = "component-model")]
625 {
626 self.engine().precompile_component(bytes)
627 }
628 #[cfg(not(feature = "component-model"))]
629 bail!("component-model support not enabled");
630 };
631 if let Ok(cwasm) = cwasm {
632 std::fs::write(&cwasm_path, cwasm)
633 .with_context(|| format!("failed to write {cwasm_path:?}"))?;
634 }
635 }
636 Ok(())
637 }
638 None => self.run_directives(ast.commands, filename),
639 }
640 }
641
642 fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
643 thread::scope(|scope| {
644 let mut threads = HashMap::new();
645 for directive in directives {
646 let line = directive.line();
647 log::debug!("running directive on {filename}:{line}");
648 self.run_directive(directive, filename, &scope, &mut threads)
649 .with_context(|| format!("failed directive on {filename}:{line}"))?;
650 }
651 Ok(())
652 })
653 }
654
655 fn run_directive<'a>(
656 &mut self,
657 directive: Command<'a>,
658 filename: &'a str,
659 scope: &'a thread::Scope<'a, '_>,
661 threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
662 ) -> Result<()> {
663 use Command::*;
664
665 match directive {
666 Module {
667 name,
668 file,
669 line: _,
670 } => {
671 let module = self.module_definition(&file)?;
672 self.module(name.as_deref(), &module)?;
673 }
674 ModuleDefinition {
675 name,
676 file,
677 line: _,
678 } => {
679 let module = self.module_definition(&file)?;
680 self.modules.insert(name.map(|s| s.to_string()), module);
681 }
682 ModuleInstance {
683 instance,
684 module,
685 line: _,
686 } => {
687 let module = self
688 .modules
689 .get(&module.as_ref().map(|s| s.to_string()))
690 .cloned()
691 .ok_or_else(|| format_err!("no module named {module:?}"))?;
692 self.module(instance.as_deref(), &module)?;
693 }
694 Register { line: _, name, as_ } => {
695 self.register(name.as_deref(), &as_)?;
696 }
697 Action { action, line: _ } => {
698 self.perform_action(&action)?;
699 }
700 AssertReturn {
701 action,
702 expected,
703 line: _,
704 } => {
705 let result = self.perform_action(&action)?;
706 self.assert_return(result, &expected)?;
707 }
708 AssertTrap {
709 action,
710 text,
711 line: _,
712 } => {
713 let result = self.perform_action(&action)?;
714 self.assert_trap(result, &text)?;
715 }
716 AssertUninstantiable {
717 file,
718 text,
719 line: _,
720 } => {
721 let result = match self.module_definition(&file)? {
722 ModuleKind::Core(module) => self
723 .instantiate_module(&module)?
724 .map(|_| Results::Core(Vec::new())),
725 #[cfg(feature = "component-model")]
726 ModuleKind::Component(component) => self
727 .instantiate_component(&component)?
728 .map(|_| Results::Component(Vec::new())),
729 };
730 self.assert_trap(result, &text)?;
731 }
732 AssertExhaustion {
733 action,
734 text,
735 line: _,
736 } => {
737 let result = self.perform_action(&action)?;
738 self.assert_trap(result, &text)?;
739 }
740 AssertInvalid {
741 file,
742 text,
743 line: _,
744 } => {
745 let err = match self.module_definition(&file) {
746 Ok(_) => bail!("expected module to fail to build"),
747 Err(e) => e,
748 };
749 self.match_error_message(&text, err)?;
750 }
751 AssertMalformed {
752 file,
753 text: _,
754 line: _,
755 } => {
756 if let Ok(_) = self.module_definition(&file) {
757 bail!("expected malformed module to fail to instantiate");
758 }
759 }
760 AssertUnlinkable {
761 file,
762 text,
763 line: _,
764 } => {
765 let module = self.module_definition(&file)?;
766 let err = match self.module(None, &module) {
767 Ok(_) => bail!("expected module to fail to link"),
768 Err(e) => e,
769 };
770 self.match_error_message(&text, err)?;
771 }
772 AssertException { line: _, action } => {
773 let result = self.perform_action(&action)?;
774 self.assert_exception(result)?;
775 }
776
777 Thread {
778 name,
779 shared_module,
780 commands,
781 line: _,
782 } => {
783 let mut core_linker = Linker::new(self.engine());
784 if let Some(id) = shared_module {
785 let items = self
786 .core_linker
787 .iter(&mut self.core_store)
788 .filter(|(module, _, _)| *module == &id[..])
789 .collect::<Vec<_>>();
790 for (module, name, item) in items {
791 core_linker.define(&mut self.core_store, module, name, item)?;
792 }
793 }
794 let mut child_cx = WastContext {
795 current: None,
796 core_linker,
797 #[cfg(feature = "component-model")]
798 component_linker: component::Linker::new(self.engine()),
799 core_store: {
800 let mut store = Store::new(self.engine(), ());
801 (self.configure_store)(&mut store);
802 store
803 },
804 modules: self.modules.clone(),
805 async_runtime: self.async_runtime.as_ref().map(|_| {
806 tokio::runtime::Builder::new_current_thread()
807 .build()
808 .unwrap()
809 }),
810 generate_dwarf: self.generate_dwarf,
811 modules_by_filename: self.modules_by_filename.clone(),
812 precompile_load: self.precompile_load.clone(),
813 precompile_save: self.precompile_save.clone(),
814 configure_store: self.configure_store.clone(),
815 ignore_error_messages: self.ignore_error_messages,
816 };
817 let child = scope.spawn(move || child_cx.run_directives(commands, filename));
818 threads.insert(name.to_string(), child);
819 }
820 Wait { thread, .. } => {
821 threads
822 .remove(&thread[..])
823 .ok_or_else(|| format_err!("no thread named `{thread}`"))?
824 .join()
825 .unwrap()?;
826 }
827
828 AssertSuspension { .. } => {
829 bail!("unimplemented wast directive");
830 }
831
832 AssertMalformedCustom {
833 file: _,
834 text: _,
835 line: _,
836 }
837 | AssertInvalidCustom {
838 file: _,
839 text: _,
840 line: _,
841 } => bail!("unimplemented wast directives"),
842 }
843
844 Ok(())
845 }
846
847 pub fn run_file(&mut self, path: &Path) -> Result<()> {
849 match &self.precompile_load {
850 Some(precompile) => {
851 let file = precompile
852 .join(path.file_name().unwrap())
853 .with_extension("json");
854 let json = std::fs::read_to_string(&file)
855 .with_context(|| format!("failed to read {file:?}"))?;
856 let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
857 self.run_directives(wast.commands, &wast.source_filename)
858 }
859 None => {
860 let bytes = std::fs::read(path)
861 .with_context(|| format!("failed to read `{}`", path.display()))?;
862 self.run_wast(path.to_str().unwrap(), &bytes)
863 }
864 }
865 }
866
867 pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
870 self.generate_dwarf = enable;
871 self
872 }
873
874 fn match_error_message(&self, expected: &str, err: wasmtime::Error) -> Result<()> {
875 if self.ignore_error_messages {
876 return Ok(());
877 }
878 let actual = format!("{err:?}");
879 if actual.contains(expected) {
880 return Ok(());
881 }
882 bail!("assert_invalid: expected \"{expected}\", got \"{actual}\"",)
883 }
884}