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<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}
39
40enum Outcome<T = Results> {
41 Ok(T),
42 Trap(Error),
43}
44
45impl<T> Outcome<T> {
46 fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
47 match self {
48 Outcome::Ok(t) => Outcome::Ok(map(t)),
49 Outcome::Trap(t) => Outcome::Trap(t),
50 }
51 }
52
53 fn into_result(self) -> Result<T> {
54 match self {
55 Outcome::Ok(t) => Ok(t),
56 Outcome::Trap(t) => Err(t),
57 }
58 }
59}
60
61#[derive(Debug)]
62enum Results {
63 Core(Vec<Val>),
64 #[cfg(feature = "component-model")]
65 Component(Vec<component::Val>),
66}
67
68#[derive(Clone)]
69enum ModuleKind {
70 Core(Module),
71 #[cfg(feature = "component-model")]
72 Component(component::Component),
73}
74
75enum InstanceKind {
76 Core(Instance),
77 #[cfg(feature = "component-model")]
78 Component(Store<()>, component::Instance),
79}
80
81enum Export<'a> {
82 Core(Extern),
83 #[cfg(feature = "component-model")]
84 Component(&'a mut Store<()>, component::Func),
85
86 _Unused(std::convert::Infallible, &'a ()),
89}
90
91#[derive(Debug, Copy, Clone, PartialEq)]
95#[expect(missing_docs, reason = "self-describing variants")]
96pub enum Async {
97 Yes,
98 No,
99}
100
101impl WastContext {
102 pub fn new(
110 engine: &Engine,
111 async_: Async,
112 configure: impl Fn(&mut Store<()>) + Send + Sync + 'static,
113 ) -> Self {
114 let mut core_linker = Linker::new(engine);
118 core_linker.allow_shadowing(true);
119 Self {
120 current: None,
121 core_linker,
122 #[cfg(feature = "component-model")]
123 component_linker: {
124 let mut linker = component::Linker::new(engine);
125 linker.allow_shadowing(true);
126 linker
127 },
128 core_store: {
129 let mut store = Store::new(engine, ());
130 configure(&mut store);
131 store
132 },
133 modules: Default::default(),
134 async_runtime: if async_ == Async::Yes {
135 Some(
136 tokio::runtime::Builder::new_current_thread()
137 .build()
138 .unwrap(),
139 )
140 } else {
141 None
142 },
143 generate_dwarf: true,
144 precompile_save: None,
145 precompile_load: None,
146 modules_by_filename: Arc::default(),
147 configure_store: Arc::new(configure),
148 }
149 }
150
151 fn engine(&self) -> &Engine {
152 self.core_linker.engine()
153 }
154
155 pub fn precompile_save(&mut self, path: impl AsRef<Path>) -> &mut Self {
158 self.precompile_save = Some(path.as_ref().into());
159 self
160 }
161
162 pub fn precompile_load(&mut self, path: impl AsRef<Path>) -> &mut Self {
165 self.precompile_load = Some(path.as_ref().into());
166 self
167 }
168
169 fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export<'_>> {
170 if let Some(module) = module {
171 return Ok(Export::Core(
172 self.core_linker
173 .get(&mut self.core_store, module, name)
174 .ok_or_else(|| format_err!("no item named `{module}::{name}` found"))?,
175 ));
176 }
177
178 let cur = self
179 .current
180 .as_mut()
181 .ok_or_else(|| format_err!("no previous instance found"))?;
182 Ok(match cur {
183 InstanceKind::Core(i) => Export::Core(
184 i.get_export(&mut self.core_store, name)
185 .ok_or_else(|| format_err!("no item named `{name}` found"))?,
186 ),
187 #[cfg(feature = "component-model")]
188 InstanceKind::Component(store, i) => {
189 let export = i
190 .get_func(&mut *store, name)
191 .ok_or_else(|| format_err!("no func named `{name}` found"))?;
192 Export::Component(store, export)
193 }
194 })
195 }
196
197 fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> {
198 let instance = match &self.async_runtime {
199 Some(rt) => rt.block_on(
200 self.core_linker
201 .instantiate_async(&mut self.core_store, &module),
202 ),
203 None => self.core_linker.instantiate(&mut self.core_store, &module),
204 };
205 Ok(match instance {
206 Ok(i) => Outcome::Ok(i),
207 Err(e) => Outcome::Trap(e),
208 })
209 }
210
211 #[cfg(feature = "component-model")]
212 fn instantiate_component(
213 &mut self,
214 component: &component::Component,
215 ) -> Result<Outcome<(component::Component, Store<()>, component::Instance)>> {
216 let mut store = Store::new(self.engine(), ());
217 (self.configure_store)(&mut store);
218 let instance = match &self.async_runtime {
219 Some(rt) => rt.block_on(
220 self.component_linker
221 .instantiate_async(&mut store, &component),
222 ),
223 None => self.component_linker.instantiate(&mut store, &component),
224 };
225 Ok(match instance {
226 Ok(i) => Outcome::Ok((component.clone(), store, i)),
227 Err(e) => Outcome::Trap(e),
228 })
229 }
230
231 pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> {
233 link_spectest(&mut self.core_linker, &mut self.core_store, config)?;
234 #[cfg(feature = "component-model")]
235 link_component_spectest(&mut self.component_linker)?;
236 Ok(())
237 }
238
239 fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
241 struct ReplaceRuntime<'a> {
246 ctx: &'a mut WastContext,
247 rt: Option<tokio::runtime::Runtime>,
248 }
249 impl Drop for ReplaceRuntime<'_> {
250 fn drop(&mut self) {
251 self.ctx.async_runtime = self.rt.take();
252 }
253 }
254 let replace = ReplaceRuntime {
255 rt: self.async_runtime.take(),
256 ctx: self,
257 };
258 let me = &mut *replace.ctx;
259 match action {
260 Action::Invoke {
261 module,
262 field,
263 args,
264 } => match me.get_export(module.as_deref(), field)? {
265 Export::Core(export) => {
266 drop(replace);
267 let func = export
268 .into_func()
269 .ok_or_else(|| format_err!("no function named `{field}`"))?;
270 let values = args
271 .iter()
272 .map(|v| match v {
273 Const::Core(v) => core::val(self, v),
274 _ => bail!("expected core function, found other other argument {v:?}"),
275 })
276 .collect::<Result<Vec<_>>>()?;
277
278 let mut results =
279 vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()];
280 let result = match &self.async_runtime {
281 Some(rt) => rt.block_on(func.call_async(
282 &mut self.core_store,
283 &values,
284 &mut results,
285 )),
286 None => func.call(&mut self.core_store, &values, &mut results),
287 };
288
289 Ok(match result {
290 Ok(()) => Outcome::Ok(Results::Core(results)),
291 Err(e) => Outcome::Trap(e),
292 })
293 }
294 #[cfg(feature = "component-model")]
295 Export::Component(store, func) => {
296 let values = args
297 .iter()
298 .map(|v| match v {
299 Const::Component(v) => component::val(v),
300 _ => bail!("expected component function, found other argument {v:?}"),
301 })
302 .collect::<Result<Vec<_>>>()?;
303
304 let mut results =
305 vec![component::Val::Bool(false); func.ty(&store).results().len()];
306 let result = match &replace.rt {
307 Some(rt) => {
308 rt.block_on(func.call_async(&mut *store, &values, &mut results))
309 }
310 None => func.call(&mut *store, &values, &mut results),
311 };
312 Ok(match result {
313 Ok(()) => {
314 let result = match &replace.rt {
315 Some(rt) => rt.block_on(func.post_return_async(&mut *store)),
316 None => func.post_return(&mut *store),
317 };
318
319 match result {
320 Ok(()) => Outcome::Ok(Results::Component(results)),
321 Err(e) => Outcome::Trap(e),
322 }
323 }
324 Err(e) => Outcome::Trap(e),
325 })
326 }
327 },
328 Action::Get { module, field, .. } => me.get(module.as_deref(), field),
329 }
330 }
331
332 fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
335 match module {
336 ModuleKind::Core(module) => {
337 let instance = match self.instantiate_module(&module)? {
338 Outcome::Ok(i) => i,
339 Outcome::Trap(e) => return Err(e).context("instantiation failed"),
340 };
341 if let Some(name) = name {
342 self.core_linker
343 .instance(&mut self.core_store, name, instance)?;
344 }
345 self.current = Some(InstanceKind::Core(instance));
346 }
347 #[cfg(feature = "component-model")]
348 ModuleKind::Component(module) => {
349 let (component, mut store, instance) = match self.instantiate_component(&module)? {
350 Outcome::Ok(i) => i,
351 Outcome::Trap(e) => return Err(e).context("instantiation failed"),
352 };
353 if let Some(name) = name {
354 let ty = component.component_type();
355 let engine = self.engine().clone();
356 let mut linker = self.component_linker.instance(name)?;
357 for (name, item) in ty.exports(&engine) {
358 match item {
359 component::types::ComponentItem::Module(_) => {
360 let module = instance.get_module(&mut store, name).unwrap();
361 linker.module(name, &module)?;
362 }
363 component::types::ComponentItem::Resource(_) => {
364 let resource = instance.get_resource(&mut store, name).unwrap();
365 linker.resource(name, resource, |_, _| Ok(()))?;
366 }
367 _ => {}
374 }
375 }
376 }
377 self.current = Some(InstanceKind::Component(store, instance));
378 }
379 }
380 Ok(())
381 }
382
383 fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
388 let name = match file.module_type {
389 WasmFileType::Text => file
390 .binary_filename
391 .as_ref()
392 .ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
393 WasmFileType::Binary => &file.filename,
394 };
395
396 match &self.precompile_load {
397 Some(path) => {
398 let cwasm = path.join(&name[..]).with_extension("cwasm");
399 match Engine::detect_precompiled_file(&cwasm)
400 .with_context(|| format!("failed to read {cwasm:?}"))?
401 {
402 Some(Precompiled::Module) => {
403 let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
404 Ok(ModuleKind::Core(module))
405 }
406 #[cfg(feature = "component-model")]
407 Some(Precompiled::Component) => {
408 let component = unsafe {
409 component::Component::deserialize_file(self.engine(), &cwasm)?
410 };
411 Ok(ModuleKind::Component(component))
412 }
413 #[cfg(not(feature = "component-model"))]
414 Some(Precompiled::Component) => {
415 bail!("support for components disabled at compile time")
416 }
417 None => bail!("expected a cwasm file"),
418 }
419 }
420 None => {
421 let bytes = &self.modules_by_filename[&name[..]];
422
423 if wasmparser::Parser::is_core_wasm(&bytes) {
424 let module = Module::new(self.engine(), &bytes)?;
425 Ok(ModuleKind::Core(module))
426 } else {
427 #[cfg(feature = "component-model")]
428 {
429 let component = component::Component::new(self.engine(), &bytes)?;
430 Ok(ModuleKind::Component(component))
431 }
432 #[cfg(not(feature = "component-model"))]
433 bail!("component-model support not enabled");
434 }
435 }
436 }
437 }
438
439 fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
441 match name {
442 Some(name) => self.core_linker.alias_module(name, as_name),
443 None => {
444 let current = self
445 .current
446 .as_ref()
447 .ok_or(format_err!("no previous instance"))?;
448 match current {
449 InstanceKind::Core(current) => {
450 self.core_linker
451 .instance(&mut self.core_store, as_name, *current)?;
452 }
453 #[cfg(feature = "component-model")]
454 InstanceKind::Component(..) => {
455 bail!("register not implemented for components");
456 }
457 }
458 Ok(())
459 }
460 }
461 }
462
463 fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
465 let global = match self.get_export(instance_name, field)? {
466 Export::Core(e) => e
467 .into_global()
468 .ok_or_else(|| format_err!("no global named `{field}`"))?,
469 #[cfg(feature = "component-model")]
470 Export::Component(..) => bail!("no global named `{field}`"),
471 };
472 Ok(Outcome::Ok(Results::Core(vec![
473 global.get(&mut self.core_store),
474 ])))
475 }
476
477 fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
478 match result.into_result()? {
479 Results::Core(values) => {
480 if values.len() != results.len() {
481 bail!("expected {} results found {}", results.len(), values.len());
482 }
483 for (i, (v, e)) in values.iter().zip(results).enumerate() {
484 let e = match e {
485 Const::Core(core) => core,
486 _ => bail!("expected core value found other value {e:?}"),
487 };
488 core::match_val(&mut self.core_store, v, e)
489 .with_context(|| format!("result {i} didn't match"))?;
490 }
491 }
492 #[cfg(feature = "component-model")]
493 Results::Component(values) => {
494 if values.len() != results.len() {
495 bail!("expected {} results found {}", results.len(), values.len());
496 }
497 for (i, (v, e)) in values.iter().zip(results).enumerate() {
498 let e = match e {
499 Const::Component(val) => val,
500 _ => bail!("expected component value found other value {e:?}"),
501 };
502 component::match_val(e, v)
503 .with_context(|| format!("result {i} didn't match"))?;
504 }
505 }
506 }
507 Ok(())
508 }
509
510 fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
511 let trap = match result {
512 Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
513 Outcome::Trap(t) => t,
514 };
515 let actual = format!("{trap:?}");
516 if actual.contains(expected)
517 || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
521 || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
523 || (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
525 {
526 return Ok(());
527 }
528 bail!("expected '{expected}', got '{actual}'")
529 }
530
531 fn assert_exception(&mut self, result: Outcome) -> Result<()> {
532 match result {
533 Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
534 Outcome::Trap(err) if err.is::<ThrownException>() => {
535 let _ = self
537 .core_store
538 .take_pending_exception()
539 .expect("there should be a pending exception on the store");
540 Ok(())
541 }
542 Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
543 }
544 }
545
546 pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
548 let wast = str::from_utf8(wast)?;
549
550 let adjust_wast = |mut err: wast::Error| {
551 err.set_path(filename.as_ref());
552 err.set_text(wast);
553 err
554 };
555
556 let mut lexer = Lexer::new(wast);
557 lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
558 let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
559 buf.track_instr_spans(self.generate_dwarf);
560 let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
561
562 let mut ast = json_from_wast::Opts::default()
563 .dwarf(self.generate_dwarf)
564 .convert(filename, wast, ast)
565 .to_wasmtime_result()?;
566
567 if !self.modules_by_filename.is_empty() {
570 self.modules_by_filename = Arc::default();
571 }
572 let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
573 for (name, bytes) in ast.wasms.drain(..) {
574 let prev = modules_by_filename.insert(name, bytes);
575 assert!(prev.is_none());
576 }
577
578 match &self.precompile_save {
579 Some(path) => {
580 let json_path = path
581 .join(Path::new(filename).file_name().unwrap())
582 .with_extension("json");
583 let json = serde_json::to_string(&ast)?;
584 std::fs::write(&json_path, json)
585 .with_context(|| format!("failed to write {json_path:?}"))?;
586 for (name, bytes) in self.modules_by_filename.iter() {
587 let cwasm_path = path.join(name).with_extension("cwasm");
588 let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
589 self.engine().precompile_module(bytes)
590 } else {
591 #[cfg(feature = "component-model")]
592 {
593 self.engine().precompile_component(bytes)
594 }
595 #[cfg(not(feature = "component-model"))]
596 bail!("component-model support not enabled");
597 };
598 if let Ok(cwasm) = cwasm {
599 std::fs::write(&cwasm_path, cwasm)
600 .with_context(|| format!("failed to write {cwasm_path:?}"))?;
601 }
602 }
603 Ok(())
604 }
605 None => self.run_directives(ast.commands, filename),
606 }
607 }
608
609 fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
610 thread::scope(|scope| {
611 let mut threads = HashMap::new();
612 for directive in directives {
613 let line = directive.line();
614 log::debug!("running directive on {filename}:{line}");
615 self.run_directive(directive, filename, &scope, &mut threads)
616 .with_context(|| format!("failed directive on {filename}:{line}"))?;
617 }
618 Ok(())
619 })
620 }
621
622 fn run_directive<'a>(
623 &mut self,
624 directive: Command<'a>,
625 filename: &'a str,
626 scope: &'a thread::Scope<'a, '_>,
628 threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
629 ) -> Result<()> {
630 use Command::*;
631
632 match directive {
633 Module {
634 name,
635 file,
636 line: _,
637 } => {
638 let module = self.module_definition(&file)?;
639 self.module(name.as_deref(), &module)?;
640 }
641 ModuleDefinition {
642 name,
643 file,
644 line: _,
645 } => {
646 let module = self.module_definition(&file)?;
647 if let Some(name) = name {
648 self.modules.insert(name.to_string(), module);
649 }
650 }
651 ModuleInstance {
652 instance,
653 module,
654 line: _,
655 } => {
656 let module = module
657 .as_deref()
658 .and_then(|n| self.modules.get(n))
659 .cloned()
660 .ok_or_else(|| format_err!("no module named {module:?}"))?;
661 self.module(instance.as_deref(), &module)?;
662 }
663 Register { line: _, name, as_ } => {
664 self.register(name.as_deref(), &as_)?;
665 }
666 Action { action, line: _ } => {
667 self.perform_action(&action)?;
668 }
669 AssertReturn {
670 action,
671 expected,
672 line: _,
673 } => {
674 let result = self.perform_action(&action)?;
675 self.assert_return(result, &expected)?;
676 }
677 AssertTrap {
678 action,
679 text,
680 line: _,
681 } => {
682 let result = self.perform_action(&action)?;
683 self.assert_trap(result, &text)?;
684 }
685 AssertUninstantiable {
686 file,
687 text,
688 line: _,
689 } => {
690 let result = match self.module_definition(&file)? {
691 ModuleKind::Core(module) => self
692 .instantiate_module(&module)?
693 .map(|_| Results::Core(Vec::new())),
694 #[cfg(feature = "component-model")]
695 ModuleKind::Component(component) => self
696 .instantiate_component(&component)?
697 .map(|_| Results::Component(Vec::new())),
698 };
699 self.assert_trap(result, &text)?;
700 }
701 AssertExhaustion {
702 action,
703 text,
704 line: _,
705 } => {
706 let result = self.perform_action(&action)?;
707 self.assert_trap(result, &text)?;
708 }
709 AssertInvalid {
710 file,
711 text,
712 line: _,
713 } => {
714 let err = match self.module_definition(&file) {
715 Ok(_) => bail!("expected module to fail to build"),
716 Err(e) => e,
717 };
718 let error_message = format!("{err:?}");
719 if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
720 bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
721 }
722 }
723 AssertMalformed {
724 file,
725 text: _,
726 line: _,
727 } => {
728 if let Ok(_) = self.module_definition(&file) {
729 bail!("expected malformed module to fail to instantiate");
730 }
731 }
732 AssertUnlinkable {
733 file,
734 text,
735 line: _,
736 } => {
737 let module = self.module_definition(&file)?;
738 let err = match self.module(None, &module) {
739 Ok(_) => bail!("expected module to fail to link"),
740 Err(e) => e,
741 };
742 let error_message = format!("{err:?}");
743 if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
744 bail!("assert_unlinkable: expected {text}, got {error_message}",)
745 }
746 }
747 AssertException { line: _, action } => {
748 let result = self.perform_action(&action)?;
749 self.assert_exception(result)?;
750 }
751
752 Thread {
753 name,
754 shared_module,
755 commands,
756 line: _,
757 } => {
758 let mut core_linker = Linker::new(self.engine());
759 if let Some(id) = shared_module {
760 let items = self
761 .core_linker
762 .iter(&mut self.core_store)
763 .filter(|(module, _, _)| *module == &id[..])
764 .collect::<Vec<_>>();
765 for (module, name, item) in items {
766 core_linker.define(&mut self.core_store, module, name, item)?;
767 }
768 }
769 let mut child_cx = WastContext {
770 current: None,
771 core_linker,
772 #[cfg(feature = "component-model")]
773 component_linker: component::Linker::new(self.engine()),
774 core_store: {
775 let mut store = Store::new(self.engine(), ());
776 (self.configure_store)(&mut store);
777 store
778 },
779 modules: self.modules.clone(),
780 async_runtime: self.async_runtime.as_ref().map(|_| {
781 tokio::runtime::Builder::new_current_thread()
782 .build()
783 .unwrap()
784 }),
785 generate_dwarf: self.generate_dwarf,
786 modules_by_filename: self.modules_by_filename.clone(),
787 precompile_load: self.precompile_load.clone(),
788 precompile_save: self.precompile_save.clone(),
789 configure_store: self.configure_store.clone(),
790 };
791 let child = scope.spawn(move || child_cx.run_directives(commands, filename));
792 threads.insert(name.to_string(), child);
793 }
794 Wait { thread, .. } => {
795 threads
796 .remove(&thread[..])
797 .ok_or_else(|| format_err!("no thread named `{thread}`"))?
798 .join()
799 .unwrap()?;
800 }
801
802 AssertSuspension { .. } => {
803 bail!("unimplemented wast directive");
804 }
805 }
806
807 Ok(())
808 }
809
810 pub fn run_file(&mut self, path: &Path) -> Result<()> {
812 match &self.precompile_load {
813 Some(precompile) => {
814 let file = precompile
815 .join(path.file_name().unwrap())
816 .with_extension("json");
817 let json = std::fs::read_to_string(&file)
818 .with_context(|| format!("failed to read {file:?}"))?;
819 let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
820 self.run_directives(wast.commands, &wast.source_filename)
821 }
822 None => {
823 let bytes = std::fs::read(path)
824 .with_context(|| format!("failed to read `{}`", path.display()))?;
825 self.run_wast(path.to_str().unwrap(), &bytes)
826 }
827 }
828 }
829
830 pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
833 self.generate_dwarf = enable;
834 self
835 }
836}
837
838fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
839 if actual.contains(expected) {
840 return true;
841 }
842
843 if test.contains("spec_testsuite") {
851 return true;
852 }
853
854 false
857}