Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/wasi.rs
2448 views
1
//! Run the tests in `wasi_testsuite` using Wasmtime's CLI binary and checking
2
//! the results with a [wasi-testsuite] spec.
3
//!
4
//! [wasi-testsuite]: https://github.com/WebAssembly/wasi-testsuite
5
6
use anyhow::{Result, anyhow};
7
use libtest_mimic::{Arguments, Trial};
8
use serde_derive::Deserialize;
9
use std::collections::HashMap;
10
use std::fmt::Write;
11
use std::fs;
12
use std::path::Path;
13
use std::process::Output;
14
use tempfile::TempDir;
15
use wit_component::ComponentEncoder;
16
17
const KNOWN_FAILURES: &[&str] = &[
18
"filesystem-hard-links",
19
"filesystem-read-directory",
20
// FIXME(#11524)
21
"remove_directory_trailing_slashes",
22
#[cfg(target_vendor = "apple")]
23
"filesystem-advise",
24
// FIXME(WebAssembly/wasi-testsuite#128)
25
#[cfg(windows)]
26
"fd_fdstat_set_rights",
27
#[cfg(windows)]
28
"filesystem-flags-and-type",
29
#[cfg(windows)]
30
"path_link",
31
#[cfg(windows)]
32
"dangling_fd",
33
#[cfg(windows)]
34
"dangling_symlink",
35
#[cfg(windows)]
36
"file_allocate",
37
#[cfg(windows)]
38
"file_pread_pwrite",
39
#[cfg(windows)]
40
"file_seek_tell",
41
#[cfg(windows)]
42
"file_truncation",
43
#[cfg(windows)]
44
"file_unbuffered_write",
45
#[cfg(windows)]
46
"interesting_paths",
47
#[cfg(windows)]
48
"isatty",
49
#[cfg(windows)]
50
"fd_readdir",
51
#[cfg(windows)]
52
"nofollow_errors",
53
#[cfg(windows)]
54
"overwrite_preopen",
55
#[cfg(windows)]
56
"path_exists",
57
#[cfg(windows)]
58
"path_filestat",
59
#[cfg(windows)]
60
"path_open_create_existing",
61
#[cfg(windows)]
62
"path_open_dirfd_not_dir",
63
#[cfg(windows)]
64
"path_open_missing",
65
#[cfg(windows)]
66
"path_open_read_write",
67
#[cfg(windows)]
68
"path_rename",
69
#[cfg(windows)]
70
"path_rename_dir_trailing_slashes",
71
#[cfg(windows)]
72
"path_symlink_trailing_slashes",
73
#[cfg(windows)]
74
"readlink",
75
#[cfg(windows)]
76
"remove_nonempty_directory",
77
#[cfg(windows)]
78
"renumber",
79
#[cfg(windows)]
80
"symlink_create",
81
#[cfg(windows)]
82
"stdio",
83
#[cfg(windows)]
84
"symlink_filestat",
85
#[cfg(windows)]
86
"truncation_rights",
87
#[cfg(windows)]
88
"symlink_loop",
89
#[cfg(windows)]
90
"unlink_file_trailing_slashes",
91
// Once cm-async changes have percolated this can be removed.
92
"filesystem-flags-and-type",
93
"multi-clock-wait",
94
"monotonic-clock",
95
"filesystem-advise",
96
];
97
98
fn main() -> Result<()> {
99
env_logger::init();
100
101
let mut trials = Vec::new();
102
if !cfg!(miri) {
103
find_tests("tests/wasi_testsuite/wasi-common".as_ref(), &mut trials).unwrap();
104
find_tests("tests/wasi_testsuite/wasi-threads".as_ref(), &mut trials).unwrap();
105
}
106
107
libtest_mimic::run(&Arguments::from_args(), trials).exit()
108
}
109
110
fn find_tests(path: &Path, trials: &mut Vec<Trial>) -> Result<()> {
111
for entry in path.read_dir()? {
112
let entry = entry?;
113
let path = entry.path();
114
if entry.file_type()?.is_dir() {
115
find_tests(&path, trials)?;
116
continue;
117
}
118
if path.extension().and_then(|s| s.to_str()) != Some("wasm") {
119
continue;
120
}
121
122
// Test the core wasm itself.
123
trials.push(Trial::test(
124
format!("wasmtime-wasi - {}", path.display()),
125
{
126
let path = path.clone();
127
move || run_test(&path, false).map_err(|e| format!("{e:?}").into())
128
},
129
));
130
131
// Also test the component version using the wasip1 adapter. Note that
132
// this is skipped for `wasi-threads` since that's not supported in
133
// components and it's also skipped for assemblyscript because that
134
// doesn't support the wasip1 adapter.
135
if !path.iter().any(|p| p == "wasm32-wasip3")
136
&& !path.iter().any(|p| p == "wasi-threads")
137
&& !path.iter().any(|p| p == "assemblyscript")
138
{
139
trials.push(Trial::test(
140
format!("wasip1 adapter - {}", path.display()),
141
move || run_test(&path, true).map_err(|e| format!("{e:?}").into()),
142
));
143
}
144
}
145
Ok(())
146
}
147
148
fn run_test(path: &Path, componentize: bool) -> Result<()> {
149
let wasmtime = Path::new(env!("CARGO_BIN_EXE_wasmtime"));
150
let test_name = path.file_stem().unwrap().to_str().unwrap();
151
let target_dir = wasmtime.parent().unwrap().parent().unwrap();
152
let parent_dir = path.parent().ok_or(anyhow!("module has no parent?"))?;
153
let spec = if let Ok(contents) = fs::read_to_string(&path.with_extension("json")) {
154
serde_json::from_str(&contents)?
155
} else {
156
Spec::default()
157
};
158
159
let mut td = TempDir::new_in(&target_dir)?;
160
td.disable_cleanup(true);
161
let path = if componentize {
162
let module = fs::read(path).expect("read wasm module");
163
let component = ComponentEncoder::default()
164
.module(module.as_slice())?
165
.validate(true)
166
.adapter(
167
"wasi_snapshot_preview1",
168
&fs::read(test_programs_artifacts::ADAPTER_COMMAND)?,
169
)?
170
.encode()?;
171
let stem = path.file_stem().unwrap().to_str().unwrap();
172
let component_path = td.path().join(format!("{stem}.component.wasm"));
173
fs::write(&component_path, component)?;
174
component_path
175
} else {
176
path.to_path_buf()
177
};
178
179
let Spec {
180
args,
181
dirs,
182
env,
183
exit_code: _,
184
stderr: _,
185
stdout: _,
186
} = &spec;
187
let mut cmd = wasmtime_test_util::command(wasmtime);
188
cmd.arg("run");
189
for dir in dirs {
190
cmd.arg("--dir");
191
let src = parent_dir.join(dir);
192
let dst = td.path().join(dir);
193
cp_r(&src, &dst)?;
194
cmd.arg(format!("{}::{dir}", dst.display()));
195
}
196
for (k, v) in env {
197
cmd.arg("--env");
198
cmd.arg(format!("{k}={v}"));
199
}
200
let mut should_fail = KNOWN_FAILURES.contains(&test_name);
201
if path.iter().any(|p| p == "wasm32-wasip3") {
202
cmd.arg("-Sp3,http").arg("-Wcomponent-model-async");
203
if !cfg!(feature = "component-model-async") {
204
should_fail = true;
205
}
206
}
207
cmd.arg(path);
208
cmd.args(args);
209
210
let result = cmd.output()?;
211
td.disable_cleanup(true);
212
let ok = spec == result;
213
match (ok, should_fail) {
214
// If this test passed and is not a known failure, or if it failed and
215
// it's a known failure, then flag this test as "ok".
216
(true, false) | (false, true) => Ok(()),
217
218
// If this test failed and it's not known to fail, explain why.
219
(false, false) => {
220
td.disable_cleanup(false);
221
let mut msg = String::new();
222
writeln!(msg, " command: {cmd:?}")?;
223
writeln!(msg, " spec: {spec:#?}")?;
224
writeln!(msg, " result.status: {}", result.status)?;
225
if !result.stdout.is_empty() {
226
write!(
227
msg,
228
" result.stdout:\n {}",
229
String::from_utf8_lossy(&result.stdout).replace("\n", "\n ")
230
)?;
231
}
232
if !result.stderr.is_empty() {
233
writeln!(
234
msg,
235
" result.stderr:\n {}",
236
String::from_utf8_lossy(&result.stderr).replace("\n", "\n ")
237
)?;
238
}
239
anyhow::bail!("{msg}\nFAILED! The result does not match the specification");
240
}
241
242
// If this test passed but it's flagged as should be failed, then fail
243
// this test for someone to update `KNOWN_FAILURES`.
244
(true, true) => {
245
anyhow::bail!("test passed but it's listed in `KNOWN_FAILURES`")
246
}
247
}
248
}
249
250
fn cp_r(path: &Path, dst: &Path) -> Result<()> {
251
fs::create_dir(dst)?;
252
for entry in path.read_dir()? {
253
let entry = entry?;
254
let path = entry.path();
255
let dst = dst.join(entry.file_name());
256
if entry.file_type()?.is_dir() {
257
cp_r(&path, &dst)?;
258
} else {
259
fs::copy(&path, &dst)?;
260
}
261
}
262
Ok(())
263
}
264
265
#[derive(Debug, Default, Deserialize)]
266
struct Spec {
267
#[serde(default)]
268
args: Vec<String>,
269
#[serde(default)]
270
dirs: Vec<String>,
271
#[serde(default)]
272
env: HashMap<String, String>,
273
exit_code: Option<i32>,
274
stderr: Option<String>,
275
stdout: Option<String>,
276
}
277
278
impl PartialEq<Output> for Spec {
279
fn eq(&self, other: &Output) -> bool {
280
self.exit_code.unwrap_or(0) == other.status.code().unwrap()
281
&& matches_or_missing(&self.stdout, &other.stdout)
282
&& matches_or_missing(&self.stderr, &other.stderr)
283
}
284
}
285
286
fn matches_or_missing(a: &Option<String>, b: &[u8]) -> bool {
287
a.as_ref()
288
.map(|s| s == &String::from_utf8_lossy(b))
289
.unwrap_or(true)
290
}
291
292