1#[cfg(feature = "component-model")]
2use crate::component;
3use crate::core;
4use crate::spectest::*;
5use anyhow::{Context as _, anyhow, bail};
6use json_from_wast::{Action, Command, Const, WasmFile, WasmFileType};
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use std::str;
10use std::sync::Arc;
11use std::thread;
12use wasmtime::*;
13use wast::lexer::Lexer;
14use wast::parser::{self, ParseBuffer};
15
16pub struct WastContext {
19 current: Option<InstanceKind>,
22 core_linker: Linker<()>,
23 modules: HashMap<String, ModuleKind>,
24 #[cfg(feature = "component-model")]
25 component_linker: component::Linker<()>,
26
27 pub(crate) core_store: Store<()>,
32 pub(crate) async_runtime: Option<tokio::runtime::Runtime>,
33 generate_dwarf: bool,
34 precompile_save: Option<PathBuf>,
35 precompile_load: Option<PathBuf>,
36
37 modules_by_filename: Arc<HashMap<String, Vec<u8>>>,
38 configure_store: Arc<dyn Fn(&mut Store<()>) + Send + Sync>,
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(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 }
150 }
151
152 fn engine(&self) -> &Engine {
153 self.core_linker.engine()
154 }
155
156 pub fn precompile_save(&mut self, path: impl AsRef<Path>) -> &mut Self {
159 self.precompile_save = Some(path.as_ref().into());
160 self
161 }
162
163 pub fn precompile_load(&mut self, path: impl AsRef<Path>) -> &mut Self {
166 self.precompile_load = Some(path.as_ref().into());
167 self
168 }
169
170 fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export<'_>> {
171 if let Some(module) = module {
172 return Ok(Export::Core(
173 self.core_linker
174 .get(&mut self.core_store, module, name)
175 .ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name))?,
176 ));
177 }
178
179 let cur = self
180 .current
181 .as_mut()
182 .ok_or_else(|| anyhow!("no previous instance found"))?;
183 Ok(match cur {
184 InstanceKind::Core(i) => Export::Core(
185 i.get_export(&mut self.core_store, name)
186 .ok_or_else(|| anyhow!("no item named `{}` found", name))?,
187 ),
188 #[cfg(feature = "component-model")]
189 InstanceKind::Component(store, i) => {
190 let export = i
191 .get_func(&mut *store, name)
192 .ok_or_else(|| anyhow!("no func named `{}` found", name))?;
193 Export::Component(store, export)
194 }
195 })
196 }
197
198 fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> {
199 let instance = match &self.async_runtime {
200 Some(rt) => rt.block_on(
201 self.core_linker
202 .instantiate_async(&mut self.core_store, &module),
203 ),
204 None => self.core_linker.instantiate(&mut self.core_store, &module),
205 };
206 Ok(match instance {
207 Ok(i) => Outcome::Ok(i),
208 Err(e) => Outcome::Trap(e),
209 })
210 }
211
212 #[cfg(feature = "component-model")]
213 fn instantiate_component(
214 &mut self,
215 component: &component::Component,
216 ) -> Result<Outcome<(component::Component, Store<()>, component::Instance)>> {
217 let mut store = Store::new(self.engine(), ());
218 (self.configure_store)(&mut store);
219 let instance = match &self.async_runtime {
220 Some(rt) => rt.block_on(
221 self.component_linker
222 .instantiate_async(&mut store, &component),
223 ),
224 None => self.component_linker.instantiate(&mut store, &component),
225 };
226 Ok(match instance {
227 Ok(i) => Outcome::Ok((component.clone(), store, i)),
228 Err(e) => Outcome::Trap(e),
229 })
230 }
231
232 pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> {
234 link_spectest(&mut self.core_linker, &mut self.core_store, config)?;
235 #[cfg(feature = "component-model")]
236 link_component_spectest(&mut self.component_linker)?;
237 Ok(())
238 }
239
240 fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
242 struct ReplaceRuntime<'a> {
247 ctx: &'a mut WastContext,
248 rt: Option<tokio::runtime::Runtime>,
249 }
250 impl Drop for ReplaceRuntime<'_> {
251 fn drop(&mut self) {
252 self.ctx.async_runtime = self.rt.take();
253 }
254 }
255 let replace = ReplaceRuntime {
256 rt: self.async_runtime.take(),
257 ctx: self,
258 };
259 let me = &mut *replace.ctx;
260 match action {
261 Action::Invoke {
262 module,
263 field,
264 args,
265 } => match me.get_export(module.as_deref(), field)? {
266 Export::Core(export) => {
267 drop(replace);
268 let func = export
269 .into_func()
270 .ok_or_else(|| anyhow!("no function named `{field}`"))?;
271 let values = args
272 .iter()
273 .map(|v| match v {
274 Const::Core(v) => core::val(self, v),
275 _ => bail!("expected core function, found other other argument {v:?}"),
276 })
277 .collect::<Result<Vec<_>>>()?;
278
279 let mut results =
280 vec![Val::null_func_ref(); func.ty(&self.core_store).results().len()];
281 let result = match &self.async_runtime {
282 Some(rt) => rt.block_on(func.call_async(
283 &mut self.core_store,
284 &values,
285 &mut results,
286 )),
287 None => func.call(&mut self.core_store, &values, &mut results),
288 };
289
290 Ok(match result {
291 Ok(()) => Outcome::Ok(Results::Core(results)),
292 Err(e) => Outcome::Trap(e),
293 })
294 }
295 #[cfg(feature = "component-model")]
296 Export::Component(store, func) => {
297 let values = args
298 .iter()
299 .map(|v| match v {
300 Const::Component(v) => component::val(v),
301 _ => bail!("expected component function, found other argument {v:?}"),
302 })
303 .collect::<Result<Vec<_>>>()?;
304
305 let mut results = vec![component::Val::Bool(false); func.results(&store).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(|| anyhow!("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(anyhow!("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(|| anyhow!("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 '{}', got '{}'", expected, 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 let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
566 for (name, bytes) in ast.wasms.drain(..) {
567 let prev = modules_by_filename.insert(name, bytes);
568 assert!(prev.is_none());
569 }
570
571 match &self.precompile_save {
572 Some(path) => {
573 let json_path = path
574 .join(Path::new(filename).file_name().unwrap())
575 .with_extension("json");
576 let json = serde_json::to_string(&ast)?;
577 std::fs::write(&json_path, json)
578 .with_context(|| format!("failed to write {json_path:?}"))?;
579 for (name, bytes) in self.modules_by_filename.iter() {
580 let cwasm_path = path.join(name).with_extension("cwasm");
581 let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
582 self.engine().precompile_module(bytes)
583 } else {
584 #[cfg(feature = "component-model")]
585 {
586 self.engine().precompile_component(bytes)
587 }
588 #[cfg(not(feature = "component-model"))]
589 bail!("component-model support not enabled");
590 };
591 if let Ok(cwasm) = cwasm {
592 std::fs::write(&cwasm_path, cwasm)
593 .with_context(|| format!("failed to write {cwasm_path:?}"))?;
594 }
595 }
596 Ok(())
597 }
598 None => self.run_directives(ast.commands, filename),
599 }
600 }
601
602 fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
603 thread::scope(|scope| {
604 let mut threads = HashMap::new();
605 for directive in directives {
606 let line = directive.line();
607 log::debug!("running directive on {filename}:{line}");
608 self.run_directive(directive, filename, &scope, &mut threads)
609 .with_context(|| format!("failed directive on {filename}:{line}"))?;
610 }
611 Ok(())
612 })
613 }
614
615 fn run_directive<'a>(
616 &mut self,
617 directive: Command<'a>,
618 filename: &'a str,
619 scope: &'a thread::Scope<'a, '_>,
621 threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
622 ) -> Result<()> {
623 use Command::*;
624
625 match directive {
626 Module {
627 name,
628 file,
629 line: _,
630 } => {
631 let module = self.module_definition(&file)?;
632 self.module(name.as_deref(), &module)?;
633 }
634 ModuleDefinition {
635 name,
636 file,
637 line: _,
638 } => {
639 let module = self.module_definition(&file)?;
640 if let Some(name) = name {
641 self.modules.insert(name.to_string(), module);
642 }
643 }
644 ModuleInstance {
645 instance,
646 module,
647 line: _,
648 } => {
649 let module = module
650 .as_deref()
651 .and_then(|n| self.modules.get(n))
652 .cloned()
653 .ok_or_else(|| anyhow!("no module named {module:?}"))?;
654 self.module(instance.as_deref(), &module)?;
655 }
656 Register { line: _, name, as_ } => {
657 self.register(name.as_deref(), &as_)?;
658 }
659 Action { action, line: _ } => {
660 self.perform_action(&action)?;
661 }
662 AssertReturn {
663 action,
664 expected,
665 line: _,
666 } => {
667 let result = self.perform_action(&action)?;
668 self.assert_return(result, &expected)?;
669 }
670 AssertTrap {
671 action,
672 text,
673 line: _,
674 } => {
675 let result = self.perform_action(&action)?;
676 self.assert_trap(result, &text)?;
677 }
678 AssertUninstantiable {
679 file,
680 text,
681 line: _,
682 } => {
683 let result = match self.module_definition(&file)? {
684 ModuleKind::Core(module) => self
685 .instantiate_module(&module)?
686 .map(|_| Results::Core(Vec::new())),
687 #[cfg(feature = "component-model")]
688 ModuleKind::Component(component) => self
689 .instantiate_component(&component)?
690 .map(|_| Results::Component(Vec::new())),
691 };
692 self.assert_trap(result, &text)?;
693 }
694 AssertExhaustion {
695 action,
696 text,
697 line: _,
698 } => {
699 let result = self.perform_action(&action)?;
700 self.assert_trap(result, &text)?;
701 }
702 AssertInvalid {
703 file,
704 text,
705 line: _,
706 } => {
707 let err = match self.module_definition(&file) {
708 Ok(_) => bail!("expected module to fail to build"),
709 Err(e) => e,
710 };
711 let error_message = format!("{err:?}");
712 if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
713 bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
714 }
715 }
716 AssertMalformed {
717 file,
718 text: _,
719 line: _,
720 } => {
721 if let Ok(_) = self.module_definition(&file) {
722 bail!("expected malformed module to fail to instantiate");
723 }
724 }
725 AssertUnlinkable {
726 file,
727 text,
728 line: _,
729 } => {
730 let module = self.module_definition(&file)?;
731 let err = match self.module(None, &module) {
732 Ok(_) => bail!("expected module to fail to link"),
733 Err(e) => e,
734 };
735 let error_message = format!("{err:?}");
736 if !error_message.contains(&text[..]) {
737 bail!("assert_unlinkable: expected {text}, got {error_message}",)
738 }
739 }
740 AssertException { line: _, action } => {
741 let result = self.perform_action(&action)?;
742 self.assert_exception(result)?;
743 }
744
745 Thread {
746 name,
747 shared_module,
748 commands,
749 line: _,
750 } => {
751 let mut core_linker = Linker::new(self.engine());
752 if let Some(id) = shared_module {
753 let items = self
754 .core_linker
755 .iter(&mut self.core_store)
756 .filter(|(module, _, _)| *module == &id[..])
757 .collect::<Vec<_>>();
758 for (module, name, item) in items {
759 core_linker.define(&mut self.core_store, module, name, item)?;
760 }
761 }
762 let mut child_cx = WastContext {
763 current: None,
764 core_linker,
765 #[cfg(feature = "component-model")]
766 component_linker: component::Linker::new(self.engine()),
767 core_store: {
768 let mut store = Store::new(self.engine(), ());
769 (self.configure_store)(&mut store);
770 store
771 },
772 modules: self.modules.clone(),
773 async_runtime: self.async_runtime.as_ref().map(|_| {
774 tokio::runtime::Builder::new_current_thread()
775 .build()
776 .unwrap()
777 }),
778 generate_dwarf: self.generate_dwarf,
779 modules_by_filename: self.modules_by_filename.clone(),
780 precompile_load: self.precompile_load.clone(),
781 precompile_save: self.precompile_save.clone(),
782 configure_store: self.configure_store.clone(),
783 };
784 let child = scope.spawn(move || child_cx.run_directives(commands, filename));
785 threads.insert(name.to_string(), child);
786 }
787 Wait { thread, .. } => {
788 threads
789 .remove(&thread[..])
790 .ok_or_else(|| anyhow!("no thread named `{thread}`"))?
791 .join()
792 .unwrap()?;
793 }
794
795 AssertSuspension { .. } => {
796 bail!("unimplemented wast directive");
797 }
798 }
799
800 Ok(())
801 }
802
803 pub fn run_file(&mut self, path: &Path) -> Result<()> {
805 match &self.precompile_load {
806 Some(precompile) => {
807 let file = precompile
808 .join(path.file_name().unwrap())
809 .with_extension("json");
810 let json = std::fs::read_to_string(&file)
811 .with_context(|| format!("failed to read {file:?}"))?;
812 let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
813 self.run_directives(wast.commands, &wast.source_filename)
814 }
815 None => {
816 let bytes = std::fs::read(path)
817 .with_context(|| format!("failed to read `{}`", path.display()))?;
818 self.run_wast(path.to_str().unwrap(), &bytes)
819 }
820 }
821 }
822
823 pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
826 self.generate_dwarf = enable;
827 self
828 }
829}
830
831fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
832 if actual.contains(expected) {
833 return true;
834 }
835
836 if test.contains("spec_testsuite") {
844 return true;
845 }
846
847 false
850}