Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wast/src/wast.rs
3071 views
1
#[cfg(feature = "component-model")]
2
use crate::component;
3
use crate::core;
4
use crate::spectest::*;
5
use json_from_wast::{Action, Command, Const, WasmFile, WasmFileType};
6
use std::collections::HashMap;
7
use std::path::{Path, PathBuf};
8
use std::str;
9
use std::sync::Arc;
10
use std::thread;
11
use wasmtime::{error::Context as _, *};
12
use wast::lexer::Lexer;
13
use wast::parser::{self, ParseBuffer};
14
15
/// The wast test script language allows modules to be defined and actions
16
/// to be performed on them.
17
pub struct WastContext {
18
/// Wast files have a concept of a "current" module, which is the most
19
/// recently defined.
20
current: Option<InstanceKind>,
21
core_linker: Linker<()>,
22
modules: HashMap<String, ModuleKind>,
23
#[cfg(feature = "component-model")]
24
component_linker: component::Linker<()>,
25
26
/// The store used for core wasm tests/primitives.
27
///
28
/// Note that components each get their own store so this is not used for
29
/// component-model testing.
30
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
40
enum Outcome<T = Results> {
41
Ok(T),
42
Trap(Error),
43
}
44
45
impl<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)]
62
enum Results {
63
Core(Vec<Val>),
64
#[cfg(feature = "component-model")]
65
Component(Vec<component::Val>),
66
}
67
68
#[derive(Clone)]
69
enum ModuleKind {
70
Core(Module),
71
#[cfg(feature = "component-model")]
72
Component(component::Component),
73
}
74
75
enum InstanceKind {
76
Core(Instance),
77
#[cfg(feature = "component-model")]
78
Component(Store<()>, component::Instance),
79
}
80
81
enum Export<'a> {
82
Core(Extern),
83
#[cfg(feature = "component-model")]
84
Component(&'a mut Store<()>, component::Func),
85
86
/// Impossible-to-construct variant to consider `'a` used when the
87
/// `component-model` feature is disabled.
88
_Unused(std::convert::Infallible, &'a ()),
89
}
90
91
/// Whether or not to use async APIs when calling wasm during wast testing.
92
///
93
/// Passed to [`WastContext::new`].
94
#[derive(Debug, Copy, Clone, PartialEq)]
95
#[expect(missing_docs, reason = "self-describing variants")]
96
pub enum Async {
97
Yes,
98
No,
99
}
100
101
impl WastContext {
102
/// Construct a new instance of `WastContext`.
103
///
104
/// The `engine` provided is used for all store/module/component creation
105
/// and should be appropriately configured by the caller. The `async_`
106
/// configuration indicates whether functions are invoked either async or
107
/// sync, and then the `configure` callback is used whenever a store is
108
/// created to further configure its settings.
109
pub fn new(
110
engine: &Engine,
111
async_: Async,
112
configure: impl Fn(&mut Store<()>) + Send + Sync + 'static,
113
) -> Self {
114
// Spec tests will redefine the same module/name sometimes, so we need
115
// to allow shadowing in the linker which picks the most recent
116
// definition as what to link when linking.
117
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
/// Saves precompiled modules/components into `path` instead of executing
156
/// test directives.
157
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
/// Loads precompiled modules/components from `path` instead of compiling
163
/// natively.
164
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
/// Register "spectest" which is used by the spec testsuite.
232
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
/// Perform the action portion of a command.
240
fn perform_action(&mut self, action: &Action<'_>) -> Result<Outcome> {
241
// Need to simultaneously borrow `self.async_runtime` and a `&mut
242
// Store` from components so work around the borrow checker issues by
243
// taking out the async runtime here and putting it back through a
244
// destructor.
245
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(()) => Outcome::Ok(Results::Component(results)),
314
Err(e) => Outcome::Trap(e),
315
})
316
}
317
},
318
Action::Get { module, field, .. } => me.get(module.as_deref(), field),
319
}
320
}
321
322
/// Instantiates the `module` provided and registers the instance under the
323
/// `name` provided if successful.
324
fn module(&mut self, name: Option<&str>, module: &ModuleKind) -> Result<()> {
325
match module {
326
ModuleKind::Core(module) => {
327
let instance = match self.instantiate_module(&module)? {
328
Outcome::Ok(i) => i,
329
Outcome::Trap(e) => return Err(e).context("instantiation failed"),
330
};
331
if let Some(name) = name {
332
self.core_linker
333
.instance(&mut self.core_store, name, instance)?;
334
}
335
self.current = Some(InstanceKind::Core(instance));
336
}
337
#[cfg(feature = "component-model")]
338
ModuleKind::Component(module) => {
339
let (component, mut store, instance) = match self.instantiate_component(&module)? {
340
Outcome::Ok(i) => i,
341
Outcome::Trap(e) => return Err(e).context("instantiation failed"),
342
};
343
if let Some(name) = name {
344
let ty = component.component_type();
345
let engine = self.engine().clone();
346
let mut linker = self.component_linker.instance(name)?;
347
for (name, item) in ty.exports(&engine) {
348
match item {
349
component::types::ComponentItem::Module(_) => {
350
let module = instance.get_module(&mut store, name).unwrap();
351
linker.module(name, &module)?;
352
}
353
component::types::ComponentItem::Resource(_) => {
354
let resource = instance.get_resource(&mut store, name).unwrap();
355
linker.resource(name, resource, |_, _| Ok(()))?;
356
}
357
// TODO: should ideally reflect more than just
358
// modules/resources into the linker's namespace
359
// but that's not easily supported today for host
360
// functions due to the inability to take a
361
// function from one instance and put it into the
362
// linker (must go through the host right now).
363
_ => {}
364
}
365
}
366
}
367
self.current = Some(InstanceKind::Component(store, instance));
368
}
369
}
370
Ok(())
371
}
372
373
/// Compiles the module `wat` into binary and returns the name found within
374
/// it, if any.
375
///
376
/// This will not register the name within `self.modules`.
377
fn module_definition(&mut self, file: &WasmFile) -> Result<ModuleKind> {
378
let name = match file.module_type {
379
WasmFileType::Text => file
380
.binary_filename
381
.as_ref()
382
.ok_or_else(|| format_err!("cannot compile module that isn't a valid binary"))?,
383
WasmFileType::Binary => &file.filename,
384
};
385
386
match &self.precompile_load {
387
Some(path) => {
388
let cwasm = path.join(&name[..]).with_extension("cwasm");
389
match Engine::detect_precompiled_file(&cwasm)
390
.with_context(|| format!("failed to read {cwasm:?}"))?
391
{
392
Some(Precompiled::Module) => {
393
let module = unsafe { Module::deserialize_file(self.engine(), &cwasm)? };
394
Ok(ModuleKind::Core(module))
395
}
396
#[cfg(feature = "component-model")]
397
Some(Precompiled::Component) => {
398
let component = unsafe {
399
component::Component::deserialize_file(self.engine(), &cwasm)?
400
};
401
Ok(ModuleKind::Component(component))
402
}
403
#[cfg(not(feature = "component-model"))]
404
Some(Precompiled::Component) => {
405
bail!("support for components disabled at compile time")
406
}
407
None => bail!("expected a cwasm file"),
408
}
409
}
410
None => {
411
let bytes = &self.modules_by_filename[&name[..]];
412
413
if wasmparser::Parser::is_core_wasm(&bytes) {
414
let module = Module::new(self.engine(), &bytes)?;
415
Ok(ModuleKind::Core(module))
416
} else {
417
#[cfg(feature = "component-model")]
418
{
419
let component = component::Component::new(self.engine(), &bytes)?;
420
Ok(ModuleKind::Component(component))
421
}
422
#[cfg(not(feature = "component-model"))]
423
bail!("component-model support not enabled");
424
}
425
}
426
}
427
}
428
429
/// Register an instance to make it available for performing actions.
430
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
431
match name {
432
Some(name) => self.core_linker.alias_module(name, as_name),
433
None => {
434
let current = self
435
.current
436
.as_ref()
437
.ok_or(format_err!("no previous instance"))?;
438
match current {
439
InstanceKind::Core(current) => {
440
self.core_linker
441
.instance(&mut self.core_store, as_name, *current)?;
442
}
443
#[cfg(feature = "component-model")]
444
InstanceKind::Component(..) => {
445
bail!("register not implemented for components");
446
}
447
}
448
Ok(())
449
}
450
}
451
}
452
453
/// Get the value of an exported global from an instance.
454
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
455
let global = match self.get_export(instance_name, field)? {
456
Export::Core(e) => e
457
.into_global()
458
.ok_or_else(|| format_err!("no global named `{field}`"))?,
459
#[cfg(feature = "component-model")]
460
Export::Component(..) => bail!("no global named `{field}`"),
461
};
462
Ok(Outcome::Ok(Results::Core(vec![
463
global.get(&mut self.core_store),
464
])))
465
}
466
467
fn assert_return(&mut self, result: Outcome, results: &[Const]) -> Result<()> {
468
match result.into_result()? {
469
Results::Core(values) => {
470
if values.len() != results.len() {
471
bail!("expected {} results found {}", results.len(), values.len());
472
}
473
for (i, (v, e)) in values.iter().zip(results).enumerate() {
474
let e = match e {
475
Const::Core(core) => core,
476
_ => bail!("expected core value found other value {e:?}"),
477
};
478
core::match_val(&mut self.core_store, v, e)
479
.with_context(|| format!("result {i} didn't match"))?;
480
}
481
}
482
#[cfg(feature = "component-model")]
483
Results::Component(values) => {
484
if values.len() != results.len() {
485
bail!("expected {} results found {}", results.len(), values.len());
486
}
487
for (i, (v, e)) in values.iter().zip(results).enumerate() {
488
let e = match e {
489
Const::Component(val) => val,
490
_ => bail!("expected component value found other value {e:?}"),
491
};
492
component::match_val(e, v)
493
.with_context(|| format!("result {i} didn't match"))?;
494
}
495
}
496
}
497
Ok(())
498
}
499
500
fn assert_trap(&self, result: Outcome, expected: &str) -> Result<()> {
501
let trap = match result {
502
Outcome::Ok(values) => bail!("expected trap, got {values:?}"),
503
Outcome::Trap(t) => t,
504
};
505
let actual = format!("{trap:?}");
506
if actual.contains(expected)
507
// `bulk-memory-operations/bulk.wast` checks for a message that
508
// specifies which element is uninitialized, but our traps don't
509
// shepherd that information out.
510
|| (expected.contains("uninitialized element 2") && actual.contains("uninitialized element"))
511
// function references call_ref
512
|| (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference")))
513
// GC tests say "null $kind reference" but we just say "null reference".
514
|| (expected.contains("null") && expected.contains("reference") && actual.contains("null reference"))
515
{
516
return Ok(());
517
}
518
bail!("expected '{expected}', got '{actual}'")
519
}
520
521
fn assert_exception(&mut self, result: Outcome) -> Result<()> {
522
match result {
523
Outcome::Ok(values) => bail!("expected exception, got {values:?}"),
524
Outcome::Trap(err) if err.is::<ThrownException>() => {
525
// Discard the thrown exception.
526
let _ = self
527
.core_store
528
.take_pending_exception()
529
.expect("there should be a pending exception on the store");
530
Ok(())
531
}
532
Outcome::Trap(err) => bail!("expected exception, got {err:?}"),
533
}
534
}
535
536
/// Run a wast script from a byte buffer.
537
pub fn run_wast(&mut self, filename: &str, wast: &[u8]) -> Result<()> {
538
let wast = str::from_utf8(wast)?;
539
540
let adjust_wast = |mut err: wast::Error| {
541
err.set_path(filename.as_ref());
542
err.set_text(wast);
543
err
544
};
545
546
let mut lexer = Lexer::new(wast);
547
lexer.allow_confusing_unicode(filename.ends_with("names.wast"));
548
let mut buf = ParseBuffer::new_with_lexer(lexer).map_err(adjust_wast)?;
549
buf.track_instr_spans(self.generate_dwarf);
550
let ast = parser::parse::<wast::Wast>(&buf).map_err(adjust_wast)?;
551
552
let mut ast = json_from_wast::Opts::default()
553
.dwarf(self.generate_dwarf)
554
.convert(filename, wast, ast)
555
.to_wasmtime_result()?;
556
557
// Clear out any modules, if any, from a previous `*.wast` file being
558
// run, if any.
559
if !self.modules_by_filename.is_empty() {
560
self.modules_by_filename = Arc::default();
561
}
562
let modules_by_filename = Arc::get_mut(&mut self.modules_by_filename).unwrap();
563
for (name, bytes) in ast.wasms.drain(..) {
564
let prev = modules_by_filename.insert(name, bytes);
565
assert!(prev.is_none());
566
}
567
568
match &self.precompile_save {
569
Some(path) => {
570
let json_path = path
571
.join(Path::new(filename).file_name().unwrap())
572
.with_extension("json");
573
let json = serde_json::to_string(&ast)?;
574
std::fs::write(&json_path, json)
575
.with_context(|| format!("failed to write {json_path:?}"))?;
576
for (name, bytes) in self.modules_by_filename.iter() {
577
let cwasm_path = path.join(name).with_extension("cwasm");
578
let cwasm = if wasmparser::Parser::is_core_wasm(&bytes) {
579
self.engine().precompile_module(bytes)
580
} else {
581
#[cfg(feature = "component-model")]
582
{
583
self.engine().precompile_component(bytes)
584
}
585
#[cfg(not(feature = "component-model"))]
586
bail!("component-model support not enabled");
587
};
588
if let Ok(cwasm) = cwasm {
589
std::fs::write(&cwasm_path, cwasm)
590
.with_context(|| format!("failed to write {cwasm_path:?}"))?;
591
}
592
}
593
Ok(())
594
}
595
None => self.run_directives(ast.commands, filename),
596
}
597
}
598
599
fn run_directives(&mut self, directives: Vec<Command<'_>>, filename: &str) -> Result<()> {
600
thread::scope(|scope| {
601
let mut threads = HashMap::new();
602
for directive in directives {
603
let line = directive.line();
604
log::debug!("running directive on {filename}:{line}");
605
self.run_directive(directive, filename, &scope, &mut threads)
606
.with_context(|| format!("failed directive on {filename}:{line}"))?;
607
}
608
Ok(())
609
})
610
}
611
612
fn run_directive<'a>(
613
&mut self,
614
directive: Command<'a>,
615
filename: &'a str,
616
// wast: &'a str,
617
scope: &'a thread::Scope<'a, '_>,
618
threads: &mut HashMap<String, thread::ScopedJoinHandle<'a, Result<()>>>,
619
) -> Result<()> {
620
use Command::*;
621
622
match directive {
623
Module {
624
name,
625
file,
626
line: _,
627
} => {
628
let module = self.module_definition(&file)?;
629
self.module(name.as_deref(), &module)?;
630
}
631
ModuleDefinition {
632
name,
633
file,
634
line: _,
635
} => {
636
let module = self.module_definition(&file)?;
637
if let Some(name) = name {
638
self.modules.insert(name.to_string(), module);
639
}
640
}
641
ModuleInstance {
642
instance,
643
module,
644
line: _,
645
} => {
646
let module = module
647
.as_deref()
648
.and_then(|n| self.modules.get(n))
649
.cloned()
650
.ok_or_else(|| format_err!("no module named {module:?}"))?;
651
self.module(instance.as_deref(), &module)?;
652
}
653
Register { line: _, name, as_ } => {
654
self.register(name.as_deref(), &as_)?;
655
}
656
Action { action, line: _ } => {
657
self.perform_action(&action)?;
658
}
659
AssertReturn {
660
action,
661
expected,
662
line: _,
663
} => {
664
let result = self.perform_action(&action)?;
665
self.assert_return(result, &expected)?;
666
}
667
AssertTrap {
668
action,
669
text,
670
line: _,
671
} => {
672
let result = self.perform_action(&action)?;
673
self.assert_trap(result, &text)?;
674
}
675
AssertUninstantiable {
676
file,
677
text,
678
line: _,
679
} => {
680
let result = match self.module_definition(&file)? {
681
ModuleKind::Core(module) => self
682
.instantiate_module(&module)?
683
.map(|_| Results::Core(Vec::new())),
684
#[cfg(feature = "component-model")]
685
ModuleKind::Component(component) => self
686
.instantiate_component(&component)?
687
.map(|_| Results::Component(Vec::new())),
688
};
689
self.assert_trap(result, &text)?;
690
}
691
AssertExhaustion {
692
action,
693
text,
694
line: _,
695
} => {
696
let result = self.perform_action(&action)?;
697
self.assert_trap(result, &text)?;
698
}
699
AssertInvalid {
700
file,
701
text,
702
line: _,
703
} => {
704
let err = match self.module_definition(&file) {
705
Ok(_) => bail!("expected module to fail to build"),
706
Err(e) => e,
707
};
708
let error_message = format!("{err:?}");
709
if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
710
bail!("assert_invalid: expected \"{text}\", got \"{error_message}\"",)
711
}
712
}
713
AssertMalformed {
714
file,
715
text: _,
716
line: _,
717
} => {
718
if let Ok(_) = self.module_definition(&file) {
719
bail!("expected malformed module to fail to instantiate");
720
}
721
}
722
AssertUnlinkable {
723
file,
724
text,
725
line: _,
726
} => {
727
let module = self.module_definition(&file)?;
728
let err = match self.module(None, &module) {
729
Ok(_) => bail!("expected module to fail to link"),
730
Err(e) => e,
731
};
732
let error_message = format!("{err:?}");
733
if !is_matching_assert_invalid_error_message(filename, &text, &error_message) {
734
bail!("assert_unlinkable: expected {text}, got {error_message}",)
735
}
736
}
737
AssertException { line: _, action } => {
738
let result = self.perform_action(&action)?;
739
self.assert_exception(result)?;
740
}
741
742
Thread {
743
name,
744
shared_module,
745
commands,
746
line: _,
747
} => {
748
let mut core_linker = Linker::new(self.engine());
749
if let Some(id) = shared_module {
750
let items = self
751
.core_linker
752
.iter(&mut self.core_store)
753
.filter(|(module, _, _)| *module == &id[..])
754
.collect::<Vec<_>>();
755
for (module, name, item) in items {
756
core_linker.define(&mut self.core_store, module, name, item)?;
757
}
758
}
759
let mut child_cx = WastContext {
760
current: None,
761
core_linker,
762
#[cfg(feature = "component-model")]
763
component_linker: component::Linker::new(self.engine()),
764
core_store: {
765
let mut store = Store::new(self.engine(), ());
766
(self.configure_store)(&mut store);
767
store
768
},
769
modules: self.modules.clone(),
770
async_runtime: self.async_runtime.as_ref().map(|_| {
771
tokio::runtime::Builder::new_current_thread()
772
.build()
773
.unwrap()
774
}),
775
generate_dwarf: self.generate_dwarf,
776
modules_by_filename: self.modules_by_filename.clone(),
777
precompile_load: self.precompile_load.clone(),
778
precompile_save: self.precompile_save.clone(),
779
configure_store: self.configure_store.clone(),
780
};
781
let child = scope.spawn(move || child_cx.run_directives(commands, filename));
782
threads.insert(name.to_string(), child);
783
}
784
Wait { thread, .. } => {
785
threads
786
.remove(&thread[..])
787
.ok_or_else(|| format_err!("no thread named `{thread}`"))?
788
.join()
789
.unwrap()?;
790
}
791
792
AssertSuspension { .. } => {
793
bail!("unimplemented wast directive");
794
}
795
}
796
797
Ok(())
798
}
799
800
/// Run a wast script from a file.
801
pub fn run_file(&mut self, path: &Path) -> Result<()> {
802
match &self.precompile_load {
803
Some(precompile) => {
804
let file = precompile
805
.join(path.file_name().unwrap())
806
.with_extension("json");
807
let json = std::fs::read_to_string(&file)
808
.with_context(|| format!("failed to read {file:?}"))?;
809
let wast = serde_json::from_str::<json_from_wast::Wast<'_>>(&json)?;
810
self.run_directives(wast.commands, &wast.source_filename)
811
}
812
None => {
813
let bytes = std::fs::read(path)
814
.with_context(|| format!("failed to read `{}`", path.display()))?;
815
self.run_wast(path.to_str().unwrap(), &bytes)
816
}
817
}
818
}
819
820
/// Whether or not to generate DWARF debugging information in custom
821
/// sections in modules being tested.
822
pub fn generate_dwarf(&mut self, enable: bool) -> &mut Self {
823
self.generate_dwarf = enable;
824
self
825
}
826
}
827
828
fn is_matching_assert_invalid_error_message(test: &str, expected: &str, actual: &str) -> bool {
829
if actual.contains(expected) {
830
return true;
831
}
832
833
// Historically wasmtime/wasm-tools tried to match the upstream error
834
// message. This generally led to a large sequence of matches here which is
835
// not easy to maintain and is particularly difficult when test suites and
836
// proposals conflict with each other (e.g. one asserts one error message
837
// and another asserts a different error message). Overall we didn't benefit
838
// a whole lot from trying to match errors so just assume the error is
839
// roughly the same and otherwise don't try to match it.
840
if test.contains("spec_testsuite") {
841
return true;
842
}
843
844
// we are in control over all non-spec tests so all the error messages
845
// there should exactly match the `assert_invalid` or such
846
false
847
}
848
849