Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wast/src/wast.rs
1692 views
1
#[cfg(feature = "component-model")]
2
use crate::component;
3
use crate::core;
4
use crate::spectest::*;
5
use anyhow::{Context as _, anyhow, bail};
6
use json_from_wast::{Action, Command, Const, WasmFile, WasmFileType};
7
use std::collections::HashMap;
8
use std::path::{Path, PathBuf};
9
use std::str;
10
use std::sync::Arc;
11
use std::thread;
12
use wasmtime::*;
13
use wast::lexer::Lexer;
14
use wast::parser::{self, ParseBuffer};
15
16
/// The wast test script language allows modules to be defined and actions
17
/// to be performed on them.
18
pub struct WastContext<T: 'static> {
19
/// Wast files have a concept of a "current" module, which is the most
20
/// recently defined.
21
current: Option<InstanceKind>,
22
core_linker: Linker<T>,
23
modules: HashMap<String, ModuleKind>,
24
#[cfg(feature = "component-model")]
25
component_linker: component::Linker<T>,
26
pub(crate) store: Store<T>,
27
pub(crate) async_runtime: Option<tokio::runtime::Runtime>,
28
generate_dwarf: bool,
29
precompile_save: Option<PathBuf>,
30
precompile_load: Option<PathBuf>,
31
32
modules_by_filename: Arc<HashMap<String, Vec<u8>>>,
33
}
34
35
enum Outcome<T = Results> {
36
Ok(T),
37
Trap(Error),
38
}
39
40
impl<T> Outcome<T> {
41
fn map<U>(self, map: impl FnOnce(T) -> U) -> Outcome<U> {
42
match self {
43
Outcome::Ok(t) => Outcome::Ok(map(t)),
44
Outcome::Trap(t) => Outcome::Trap(t),
45
}
46
}
47
48
fn into_result(self) -> Result<T> {
49
match self {
50
Outcome::Ok(t) => Ok(t),
51
Outcome::Trap(t) => Err(t),
52
}
53
}
54
}
55
56
#[derive(Debug)]
57
enum Results {
58
Core(Vec<Val>),
59
#[cfg(feature = "component-model")]
60
Component(Vec<component::Val>),
61
}
62
63
#[derive(Clone)]
64
enum ModuleKind {
65
Core(Module),
66
#[cfg(feature = "component-model")]
67
Component(component::Component),
68
}
69
70
enum InstanceKind {
71
Core(Instance),
72
#[cfg(feature = "component-model")]
73
Component(component::Instance),
74
}
75
76
enum Export {
77
Core(Extern),
78
#[cfg(feature = "component-model")]
79
Component(component::Func),
80
}
81
82
/// Whether or not to use async APIs when calling wasm during wast testing.
83
///
84
/// Passed to [`WastContext::new`].
85
#[derive(Copy, Clone, PartialEq)]
86
#[expect(missing_docs, reason = "self-describing variants")]
87
pub enum Async {
88
Yes,
89
No,
90
}
91
92
impl<T> WastContext<T>
93
where
94
T: Clone + Send + 'static,
95
{
96
/// Construct a new instance of `WastContext`.
97
///
98
/// Note that the provided `Store<T>` must have `Config::async_support`
99
/// enabled as all functions will be run with `call_async`. This is done to
100
/// support the component model async features that tests might use.
101
pub fn new(store: Store<T>, async_: Async) -> Self {
102
// Spec tests will redefine the same module/name sometimes, so we need
103
// to allow shadowing in the linker which picks the most recent
104
// definition as what to link when linking.
105
let mut core_linker = Linker::new(store.engine());
106
core_linker.allow_shadowing(true);
107
Self {
108
current: None,
109
core_linker,
110
#[cfg(feature = "component-model")]
111
component_linker: {
112
let mut linker = component::Linker::new(store.engine());
113
linker.allow_shadowing(true);
114
linker
115
},
116
store,
117
modules: Default::default(),
118
async_runtime: if async_ == Async::Yes {
119
Some(
120
tokio::runtime::Builder::new_current_thread()
121
.build()
122
.unwrap(),
123
)
124
} else {
125
None
126
},
127
generate_dwarf: true,
128
precompile_save: None,
129
precompile_load: None,
130
modules_by_filename: Arc::default(),
131
}
132
}
133
134
/// Saves precompiled modules/components into `path` instead of executing
135
/// test directives.
136
pub fn precompile_save(&mut self, path: impl AsRef<Path>) -> &mut Self {
137
self.precompile_save = Some(path.as_ref().into());
138
self
139
}
140
141
/// Loads precompiled modules/components from `path` instead of compiling
142
/// natively.
143
pub fn precompile_load(&mut self, path: impl AsRef<Path>) -> &mut Self {
144
self.precompile_load = Some(path.as_ref().into());
145
self
146
}
147
148
fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export> {
149
if let Some(module) = module {
150
return Ok(Export::Core(
151
self.core_linker
152
.get(&mut self.store, module, name)
153
.ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name))?,
154
));
155
}
156
157
let cur = self
158
.current
159
.as_ref()
160
.ok_or_else(|| anyhow!("no previous instance found"))?;
161
Ok(match cur {
162
InstanceKind::Core(i) => Export::Core(
163
i.get_export(&mut self.store, name)
164
.ok_or_else(|| anyhow!("no item named `{}` found", name))?,
165
),
166
#[cfg(feature = "component-model")]
167
InstanceKind::Component(i) => Export::Component(
168
i.get_func(&mut self.store, name)
169
.ok_or_else(|| anyhow!("no func named `{}` found", name))?,
170
),
171
})
172
}
173
174
fn instantiate_module(&mut self, module: &Module) -> Result<Outcome<Instance>> {
175
let instance = match &self.async_runtime {
176
Some(rt) => rt.block_on(self.core_linker.instantiate_async(&mut self.store, &module)),
177
None => self.core_linker.instantiate(&mut self.store, &module),
178
};
179
Ok(match instance {
180
Ok(i) => Outcome::Ok(i),
181
Err(e) => Outcome::Trap(e),
182
})
183
}
184
185
#[cfg(feature = "component-model")]
186
fn instantiate_component(
187
&mut self,
188
component: &component::Component,
189
) -> Result<Outcome<(component::Component, component::Instance)>> {
190
let instance = match &self.async_runtime {
191
Some(rt) => rt.block_on(
192
self.component_linker
193
.instantiate_async(&mut self.store, &component),
194
),
195
None => self
196
.component_linker
197
.instantiate(&mut self.store, &component),
198
};
199
Ok(match instance {
200
Ok(i) => Outcome::Ok((component.clone(), i)),
201
Err(e) => Outcome::Trap(e),
202
})
203
}
204
205
/// Register "spectest" which is used by the spec testsuite.
206
pub fn register_spectest(&mut self, config: &SpectestConfig) -> Result<()> {
207
link_spectest(&mut self.core_linker, &mut self.store, config)?;
208
#[cfg(feature = "component-model")]
209
link_component_spectest(&mut self.component_linker)?;
210
Ok(())
211
}
212
213
/// Perform the action portion of a command.
214
fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
215
match action {
216
Action::Invoke {
217
module,
218
field,
219
args,
220
} => match self.get_export(module.as_deref(), field)? {
221
Export::Core(export) => {
222
let func = export
223
.into_func()
224
.ok_or_else(|| anyhow!("no function named `{field}`"))?;
225
let values = args
226
.iter()
227
.map(|v| match v {
228
Const::Core(v) => core::val(self, v),
229
_ => bail!("expected core function, found other other argument {v:?}"),
230
})
231
.collect::<Result<Vec<_>>>()?;
232
233
let mut results =
234
vec![Val::null_func_ref(); func.ty(&self.store).results().len()];
235
let result = match &self.async_runtime {
236
Some(rt) => {
237
rt.block_on(func.call_async(&mut self.store, &values, &mut results))
238
}
239
None => func.call(&mut self.store, &values, &mut results),
240
};
241
242
Ok(match result {
243
Ok(()) => Outcome::Ok(Results::Core(results)),
244
Err(e) => Outcome::Trap(e),
245
})
246
}
247
#[cfg(feature = "component-model")]
248
Export::Component(func) => {
249
let values = args
250
.iter()
251
.map(|v| match v {
252
Const::Component(v) => component::val(v),
253
_ => bail!("expected component function, found other argument {v:?}"),
254
})
255
.collect::<Result<Vec<_>>>()?;
256
257
let mut results =
258
vec![component::Val::Bool(false); func.results(&self.store).len()];
259
let result = match &self.async_runtime {
260
Some(rt) => {
261
rt.block_on(func.call_async(&mut self.store, &values, &mut results))
262
}
263
None => func.call(&mut self.store, &values, &mut results),
264
};
265
Ok(match result {
266
Ok(()) => {
267
match &self.async_runtime {
268
Some(rt) => rt.block_on(func.post_return_async(&mut self.store))?,
269
None => func.post_return(&mut self.store)?,
270
}
271
272
Outcome::Ok(Results::Component(results))
273
}
274
Err(e) => Outcome::Trap(e),
275
})
276
}
277
},
278
Action::Get { module, field, .. } => self.get(module.as_deref(), field),
279
}
280
}
281
282
/// Instantiates the `module` provided and registers the instance under the
283
/// `name` provided if successful.
284
fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
285
match module {
286
ModuleKind::Core(module) => {
287
let instance = match self.instantiate_module(&module)? {
288
Outcome::Ok(i) => i,
289
Outcome::Trap(e) => return Err(e).context("instantiation failed"),
290
};
291
if let Some(name) = name {
292
self.core_linker.instance(&mut self.store, name, instance)?;
293
}
294
self.current = Some(InstanceKind::Core(instance));
295
}
296
#[cfg(feature = "component-model")]
297
ModuleKind::Component(module) => {
298
let (component, instance) = match self.instantiate_component(&module)? {
299
Outcome::Ok(i) => i,
300
Outcome::Trap(e) => return Err(e).context("instantiation failed"),
301
};
302
if let Some(name) = name {
303
let ty = component.component_type();
304
let mut linker = self.component_linker.instance(name)?;
305
let engine = self.store.engine().clone();
306
for (name, item) in ty.exports(&engine) {
307
match item {
308
component::types::ComponentItem::Module(_) => {
309
let module = instance.get_module(&mut self.store, name).unwrap();
310
linker.module(name, &module)?;
311
}
312
component::types::ComponentItem::Resource(_) => {
313
let resource =
314
instance.get_resource(&mut self.store, name).unwrap();
315
linker.resource(name, resource, |_, _| Ok(()))?;
316
}
317
// TODO: should ideally reflect more than just
318
// modules/resources into the linker's namespace
319
// but that's not easily supported today for host
320
// functions due to the inability to take a
321
// function from one instance and put it into the
322
// linker (must go through the host right now).
323
_ => {}
324
}
325
}
326
}
327
self.current = Some(InstanceKind::Component(instance));
328
}
329
}
330
Ok(())
331
}
332
333
/// Compiles the module `wat` into binary and returns the name found within
334
/// it, if any.
335
///
336
/// This will not register the name within `self.modules`.
337
fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
338
let name = match file.module_type {
339
WasmFileType::Text => file
340
.binary_filename
341
.as_ref()
342
.ok_or_else(|| anyhow!("cannot compile module that isn't a valid binary"))?,
343
WasmFileType::Binary => &file.filename,
344
};
345
346
match &self.precompile_load {
347
Some(path) => {
348
let cwasm = path.join(&name[..]).with_extension("cwasm");
349
match Engine::detect_precompiled_file(&cwasm)
350
.with_context(|| format!("failed to read {cwasm:?}"))?
351
{
352
Some(Precompiled::Module) => {
353
let module =
354
unsafe { Module::deserialize_file(self.store.engine(), &cwasm)? };
355
Ok(ModuleKind::Core(module))
356
}
357
#[cfg(feature = "component-model")]
358
Some(Precompiled::Component) => {
359
let component = unsafe {
360
component::Component::deserialize_file(self.store.engine(), &cwasm)?
361
};
362
Ok(ModuleKind::Component(component))
363
}
364
#[cfg(not(feature = "component-model"))]
365
Some(Precompiled::Component) => {
366
bail!("support for components disabled at compile time")
367
}
368
None => bail!("expected a cwasm file"),
369
}
370
}
371
None => {
372
let bytes = &self.modules_by_filename[&name[..]];
373
374
if wasmparser::Parser::is_core_wasm(&bytes) {
375
let module = Module::new(self.store.engine(), &bytes)?;
376
Ok(ModuleKind::Core(module))
377
} else {
378
#[cfg(feature = "component-model")]
379
{
380
let component = component::Component::new(self.store.engine(), &bytes)?;
381
Ok(ModuleKind::Component(component))
382
}
383
#[cfg(not(feature = "component-model"))]
384
bail!("component-model support not enabled");
385
}
386
}
387
}
388
}
389
390
/// Register an instance to make it available for performing actions.
391
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
392
match name {
393
Some(name) => self.core_linker.alias_module(name, as_name),
394
None => {
395
let current = self
396
.current
397
.as_ref()
398
.ok_or(anyhow!("no previous instance"))?;
399
match current {
400
InstanceKind::Core(current) => {
401
self.core_linker
402
.instance(&mut self.store, as_name, *current)?;
403
}
404
#[cfg(feature = "component-model")]
405
InstanceKind::Component(_) => {
406
bail!("register not implemented for components");
407
}
408
}
409
Ok(())
410
}
411
}
412
}
413
414
/// Get the value of an exported global from an instance.
415
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
416
let global = match self.get_export(instance_name, field)? {
417
Export::Core(e) => e
418
.into_global()
419
.ok_or_else(|| anyhow!("no global named `{field}`"))?,
420
#[cfg(feature = "component-model")]
421
Export::Component(_) => bail!("no global named `{field}`"),
422
};
423
Ok(Outcome::Ok(Results::Core(vec![
424
global.get(&mut self.store),
425
])))
426
}
427
428
fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
429
match result.into_result()? {
430
Results::Core(values) => {
431
if values.len() != results.len() {
432
bail!("expected {} results found {}", results.len(), values.len());
433
}
434
for (i, (v, e)) in values.iter().zip(results).enumerate() {
435
let e = match e {
436
Const::Core(core) => core,
437
_ => bail!("expected core value found other value {e:?}"),
438
};
439
core::match_val(&mut self.store, v, e)
440
.with_context(|| format!("result {i} didn't match"))?;
441
}
442
}
443
#[cfg(feature = "component-model")]
444
Results::Component(values) => {
445
if values.len() != results.len() {
446
bail!("expected {} results found {}", results.len(), values.len());
447
}
448
for (i, (v, e)) in values.iter().zip(results).enumerate() {
449
let e = match e {
450
Const::Component(val) => val,
451
_ => bail!("expected component value found other value {e:?}"),
452
};
453
component::match_val(e, v)
454
.with_context(|| format!("result {i} didn't match"))?;
455
}
456
}
457
}
458
Ok(())
459
}
460
461
fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
462
let trap = match result {
463
Outcome::Ok(values) => bail!("expected trap, got {:?}", values),
464
Outcome::Trap(t) => t,
465
};
466
let actual = format!("{trap:?}");
467
if actual.contains(expected)
468
// `bulk-memory-operations/bulk.wast` checks for a message that
469
// specifies which element is uninitialized, but our traps don't
470
// shepherd that information out.
471
|| (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
472
// function references call_ref
473
|| (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
474
// GC tests say "null $kind reference" but we just say "null reference".
475
|| (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
476
{
477
return Ok(());
478
}
479
bail!("expected '{}', got '{}'", expected, actual)
480
}
481
482
fn assert_exception(&mut self, result: Outcome) -> Result<()> {
483
match result {
484
Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
485
Outcome::Trap(err) if err.is::<ThrownException>() => {
486
// Discard the thrown exception.
487
let _ = self
488
.store
489
.take_pending_exception()
490
.expect("there should be a pending exception on the store");
491
Ok(())
492
}
493
Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
494
}
495
}
496
497
/// Run a wast script from a byte buffer.
498
pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
499
let wast = str::from_utf8(wast)?;
500
501
let adjust_wast = |mut err: wast::Error| {
502
err.set_path(filename.as_ref());
503
err.set_text(wast);
504
err
505
};
506
507
let mut lexer = Lexer::new(wast);
508
lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
509
let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
510
buf.track_instr_spans(self.generate_dwarf);
511
let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
512
513
let mut ast = json_from_wast::Opts::default()
514
.dwarf(self.generate_dwarf)
515
.convert(filename, wast, ast)?;
516
let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
517
for (name, bytes) in ast.wasms.drain(..) {
518
let prev = modules_by_filename.insert(name, bytes);
519
assert!(prev.is_none());
520
}
521
522
match &self.precompile_save {
523
Some(path) => {
524
let json_path = path
525
.join(Path::new(filename).file_name().unwrap())
526
.with_extension("json");
527
let json = serde_json::to_string(&ast)?;
528
std::fs::write(&json_path, json)
529
.with_context(|| format!("failed to write {json_path:?}"))?;
530
for (name, bytes) in self.modules_by_filename.iter() {
531
let cwasm_path = path.join(name).with_extension("cwasm");
532
let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
533
self.store.engine().precompile_module(bytes)
534
} else {
535
#[cfg(feature = "component-model")]
536
{
537
self.store.engine().precompile_component(bytes)
538
}
539
#[cfg(not(feature = "component-model"))]
540
bail!("component-model support not enabled");
541
};
542
if let Ok(cwasm) = cwasm {
543
std::fs::write(&cwasm_path, cwasm)
544
.with_context(|| format!("failed to write {cwasm_path:?}"))?;
545
}
546
}
547
Ok(())
548
}
549
None => self.run_directives(ast.commands, filename),
550
}
551
}
552
553
fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
554
thread::scope(|scope| {
555
let mut threads = HashMap::new();
556
for directive in directives {
557
let line = directive.line();
558
log::debug!("running directive on {filename}:{line}");
559
self.run_directive(directive, filename, &scope, &mut threads)
560
.with_context(|| format!("failed directive on {filename}:{line}"))?;
561
}
562
Ok(())
563
})
564
}
565
566
fn run_directive<'a>(
567
&mut self,
568
directive: Command<'a>,
569
filename: &'a str,
570
// wast: &'a str,
571
scope: &'a thread::Scope<'a, '_>,
572
threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
573
) -> Result<()>
574
where
575
T: 'a,
576
{
577
use Command::*;
578
579
match directive {
580
Module {
581
name,
582
file,
583
line: _,
584
} => {
585
let module = self.module_definition(&file)?;
586
self.module(name.as_deref(), &module)?;
587
}
588
ModuleDefinition {
589
name,
590
file,
591
line: _,
592
} => {
593
let module = self.module_definition(&file)?;
594
if let Some(name) = name {
595
self.modules.insert(name.to_string(), module);
596
}
597
}
598
ModuleInstance {
599
instance,
600
module,
601
line: _,
602
} => {
603
let module = module
604
.as_deref()
605
.and_then(|n| self.modules.get(n))
606
.cloned()
607
.ok_or_else(|| anyhow!("no module named {module:?}"))?;
608
self.module(instance.as_deref(), &module)?;
609
}
610
Register { line: _, name, as_ } => {
611
self.register(name.as_deref(), &as_)?;
612
}
613
Action { action, line: _ } => {
614
self.perform_action(&action)?;
615
}
616
AssertReturn {
617
action,
618
expected,
619
line: _,
620
} => {
621
let result = self.perform_action(&action)?;
622
self.assert_return(result, &expected)?;
623
}
624
AssertTrap {
625
action,
626
text,
627
line: _,
628
} => {
629
let result = self.perform_action(&action)?;
630
self.assert_trap(result, &text)?;
631
}
632
AssertUninstantiable {
633
file,
634
text,
635
line: _,
636
} => {
637
let result = match self.module_definition(&file)? {
638
ModuleKind::Core(module) => self
639
.instantiate_module(&module)?
640
.map(|_| Results::Core(Vec::new())),
641
#[cfg(feature = "component-model")]
642
ModuleKind::Component(component) => self
643
.instantiate_component(&component)?
644
.map(|_| Results::Component(Vec::new())),
645
};
646
self.assert_trap(result, &text)?;
647
}
648
AssertExhaustion {
649
action,
650
text,
651
line: _,
652
} => {
653
let result = self.perform_action(&action)?;
654
self.assert_trap(result, &text)?;
655
}
656
AssertInvalid {
657
file,
658
text,
659
line: _,
660
} => {
661
let err = match self.module_definition(&file) {
662
Ok(_) => bail!("expected module to fail to build"),
663
Err(e) => e,
664
};
665
let error_message = format!("{err:?}");
666
if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
667
bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
668
}
669
}
670
AssertMalformed {
671
file,
672
text: _,
673
line: _,
674
} => {
675
if let Ok(_) = self.module_definition(&file) {
676
bail!("expected malformed module to fail to instantiate");
677
}
678
}
679
AssertUnlinkable {
680
file,
681
text,
682
line: _,
683
} => {
684
let module = self.module_definition(&file)?;
685
let err = match self.module(None, &module) {
686
Ok(_) => bail!("expected module to fail to link"),
687
Err(e) => e,
688
};
689
let error_message = format!("{err:?}");
690
if !error_message.contains(&text[..]) {
691
bail!("assert_unlinkable: expected {text}, got {error_message}",)
692
}
693
}
694
AssertException { line: _, action } => {
695
let result = self.perform_action(&action)?;
696
self.assert_exception(result)?;
697
}
698
699
Thread {
700
name,
701
shared_module,
702
commands,
703
line: _,
704
} => {
705
let mut core_linker = Linker::new(self.store.engine());
706
if let Some(id) = shared_module {
707
let items = self
708
.core_linker
709
.iter(&mut self.store)
710
.filter(|(module, _, _)| *module == &id[..])
711
.collect::<Vec<_>>();
712
for (module, name, item) in items {
713
core_linker.define(&mut self.store, module, name, item)?;
714
}
715
}
716
let mut child_cx = WastContext {
717
current: None,
718
core_linker,
719
#[cfg(feature = "component-model")]
720
component_linker: component::Linker::new(self.store.engine()),
721
store: Store::new(self.store.engine(), self.store.data().clone()),
722
modules: self.modules.clone(),
723
async_runtime: self.async_runtime.as_ref().map(|_| {
724
tokio::runtime::Builder::new_current_thread()
725
.build()
726
.unwrap()
727
}),
728
generate_dwarf: self.generate_dwarf,
729
modules_by_filename: self.modules_by_filename.clone(),
730
precompile_load: self.precompile_load.clone(),
731
precompile_save: self.precompile_save.clone(),
732
};
733
let child = scope.spawn(move || child_cx.run_directives(commands, filename));
734
threads.insert(name.to_string(), child);
735
}
736
Wait { thread, .. } => {
737
threads
738
.remove(&thread[..])
739
.ok_or_else(|| anyhow!("no thread named `{thread}`"))?
740
.join()
741
.unwrap()?;
742
}
743
744
AssertSuspension { .. } => {
745
bail!("unimplemented wast directive");
746
}
747
}
748
749
Ok(())
750
}
751
752
/// Run a wast script from a file.
753
pub fn run_file(&mut self, path: &Path) -> Result<()> {
754
match &self.precompile_load {
755
Some(precompile) => {
756
let file = precompile
757
.join(path.file_name().unwrap())
758
.with_extension("json");
759
let json = std::fs::read_to_string(&file)
760
.with_context(|| format!("failed to read {file:?}"))?;
761
let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
762
self.run_directives(wast.commands, &wast.source_filename)
763
}
764
None => {
765
let bytes = std::fs::read(path)
766
.with_context(|| format!("failed to read `{}`", path.display()))?;
767
self.run_wast(path.to_str().unwrap(), &bytes)
768
}
769
}
770
}
771
772
/// Whether or not to generate DWARF debugging information in custom
773
/// sections in modules being tested.
774
pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
775
self.generate_dwarf = enable;
776
self
777
}
778
}
779
780
fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
781
if actual.contains(expected) {
782
return true;
783
}
784
785
// Historically wasmtime/wasm-tools tried to match the upstream error
786
// message. This generally led to a large sequence of matches here which is
787
// not easy to maintain and is particularly difficult when test suites and
788
// proposals conflict with each other (e.g. one asserts one error message
789
// and another asserts a different error message). Overall we didn't benefit
790
// a whole lot from trying to match errors so just assume the error is
791
// roughly the same and otherwise don't try to match it.
792
if test.contains("spec_testsuite") {
793
return true;
794
}
795
796
// we are in control over all non-spec tests so all the error messages
797
// there should exactly match the `assert_invalid` or such
798
false
799
}
800
801