* Fixes CraneStation/wasmtime#440 This commit introduces a couple of changes/fixes: * it annotates `log::debug!` messages with "host" to differentiate between file descriptors stored on the WASI side (aka the wrappers) and those managed by the host (aka the wrapped) * it fixes CraneStation/wasmtime#440, i.e., incorrect passing of file descriptor to `poll_oneoff` where currently errenously we pass in the wrapper instead of the wrapped value * it adds a couple more `log::debug!` macros calls for easier future debugging * Add partial refactorting to poll_oneoff This commit lays the groundwork for more clean up to come in subsequent commits. * Finalise refactoring of `poll_oneoff` * Fix compilation error on Windows * Address majority of suggestions and refactor Co-authored-by: Marcin Mielniczuk <marmistrz.dev@zoho.eu> * Add poll_oneoff test case * Leave timeout in nanoseconds in ClockEventData Instead of converting the timeout value from nanoseconds to milliseconds in the host-independent impl, move the conversion to *nix-specific impl as the conversion is currently only warranted by the POSIX `poll` syscall. * Don't fail immediately on bad descriptor If the user specifies an invalid descriptor inside a subscription, don't fail immediately but rather generate an event with the thrown WASI error code, and continue with the remaining, potentially correct subscriptions.
226 lines
7.6 KiB
Rust
226 lines
7.6 KiB
Rust
//! Build program to generate a program which runs all the testsuites.
|
|
//!
|
|
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
|
//! to automatically run the files in parallel.
|
|
//!
|
|
//! Idea adapted from: https://github.com/CraneStation/wasmtime/blob/master/build.rs
|
|
//! Thanks @sunfishcode
|
|
|
|
fn main() {
|
|
#[cfg(feature = "wasm_tests")]
|
|
wasm_tests::build_and_generate_tests();
|
|
}
|
|
|
|
#[cfg(feature = "wasm_tests")]
|
|
mod wasm_tests {
|
|
use std::env;
|
|
use std::fs::{read_dir, DirEntry, File};
|
|
use std::io::{self, Write};
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Stdio};
|
|
|
|
pub(crate) fn build_and_generate_tests() {
|
|
// Validate if any of test sources are present and if they changed
|
|
let bin_tests = std::fs::read_dir("misc_testsuite/src/bin")
|
|
.expect("wasm_tests feature requires initialized misc_testsuite: `git submodule update --init`?");
|
|
for test in bin_tests {
|
|
if let Ok(test_file) = test {
|
|
let test_file_path = test_file
|
|
.path()
|
|
.into_os_string()
|
|
.into_string()
|
|
.expect("test file path");
|
|
println!("cargo:rerun-if-changed={}", test_file_path);
|
|
}
|
|
}
|
|
|
|
// Build tests to OUT_DIR (target/*/build/wasi-common-*/out/wasm32-wasi/release/*.wasm)
|
|
let out_dir = PathBuf::from(
|
|
env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
|
|
);
|
|
let mut out = File::create(out_dir.join("misc_testsuite_tests.rs"))
|
|
.expect("error generating test source file");
|
|
build_tests("misc_testsuite", &out_dir).expect("building tests");
|
|
test_directory(&mut out, "misc_testsuite", &out_dir).expect("generating tests");
|
|
}
|
|
|
|
fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> {
|
|
// if the submodule has not been checked out, the build will stall
|
|
if !Path::new(&format!("{}/Cargo.toml", testsuite)).exists() {
|
|
panic!("Testsuite {} not checked out", testsuite);
|
|
}
|
|
|
|
let mut cmd = Command::new("cargo");
|
|
cmd.args(&[
|
|
"build",
|
|
"--release",
|
|
"--target=wasm32-wasi",
|
|
"--target-dir",
|
|
out_dir.to_str().unwrap(),
|
|
])
|
|
.stdout(Stdio::inherit())
|
|
.stderr(Stdio::inherit())
|
|
.current_dir(testsuite);
|
|
let output = cmd.output()?;
|
|
|
|
let status = output.status;
|
|
if !status.success() {
|
|
panic!(
|
|
"Building tests failed: exit code: {}",
|
|
status.code().unwrap()
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn test_directory(out: &mut File, testsuite: &str, out_dir: &Path) -> io::Result<()> {
|
|
let mut dir_entries: Vec<_> = read_dir(out_dir.join("wasm32-wasi/release"))
|
|
.expect("reading testsuite directory")
|
|
.map(|r| r.expect("reading testsuite directory entry"))
|
|
.filter(|dir_entry| {
|
|
let p = dir_entry.path();
|
|
if let Some(ext) = p.extension() {
|
|
// Only look at wast files.
|
|
if ext == "wasm" {
|
|
// Ignore files starting with `.`, which could be editor temporary files
|
|
if let Some(stem) = p.file_stem() {
|
|
if let Some(stemstr) = stem.to_str() {
|
|
if !stemstr.starts_with('.') {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
false
|
|
})
|
|
.collect();
|
|
|
|
dir_entries.sort_by_key(|dir| dir.path());
|
|
|
|
writeln!(
|
|
out,
|
|
"mod {} {{",
|
|
Path::new(testsuite)
|
|
.file_stem()
|
|
.expect("testsuite filename should have a stem")
|
|
.to_str()
|
|
.expect("testsuite filename should be representable as a string")
|
|
.replace("-", "_")
|
|
)?;
|
|
writeln!(out, " use super::{{runtime, utils, setup_log}};")?;
|
|
for dir_entry in dir_entries {
|
|
write_testsuite_tests(out, dir_entry, testsuite)?;
|
|
}
|
|
writeln!(out, "}}")?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_testsuite_tests(
|
|
out: &mut File,
|
|
dir_entry: DirEntry,
|
|
testsuite: &str,
|
|
) -> io::Result<()> {
|
|
let path = dir_entry.path();
|
|
let stemstr = path
|
|
.file_stem()
|
|
.expect("file_stem")
|
|
.to_str()
|
|
.expect("to_str");
|
|
|
|
writeln!(out, " #[test]")?;
|
|
if ignore(testsuite, stemstr) {
|
|
writeln!(out, " #[ignore]")?;
|
|
}
|
|
writeln!(
|
|
out,
|
|
" fn {}() -> Result<(), String> {{",
|
|
avoid_keywords(&stemstr.replace("-", "_"))
|
|
)?;
|
|
writeln!(out, " setup_log();")?;
|
|
write!(out, " let path = std::path::Path::new(\"")?;
|
|
// Write out the string with escape_debug to prevent special characters such
|
|
// as backslash from being reinterpreted.
|
|
for c in path.display().to_string().chars() {
|
|
write!(out, "{}", c.escape_debug())?;
|
|
}
|
|
writeln!(out, "\");")?;
|
|
writeln!(out, " let data = utils::read_wasm(path)?;")?;
|
|
writeln!(
|
|
out,
|
|
" let bin_name = utils::extract_exec_name_from_path(path)?;"
|
|
)?;
|
|
let workspace = if no_preopens(testsuite, stemstr) {
|
|
"None"
|
|
} else {
|
|
writeln!(
|
|
out,
|
|
" let workspace = utils::prepare_workspace(&bin_name)?;"
|
|
)?;
|
|
"Some(workspace.path())"
|
|
};
|
|
writeln!(
|
|
out,
|
|
" runtime::instantiate(&data, &bin_name, {})",
|
|
workspace
|
|
)?;
|
|
writeln!(out, " }}")?;
|
|
writeln!(out)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Rename tests which have the same name as Rust keywords.
|
|
fn avoid_keywords(name: &str) -> &str {
|
|
match name {
|
|
"if" => "if_",
|
|
"loop" => "loop_",
|
|
"type" => "type_",
|
|
"const" => "const_",
|
|
"return" => "return_",
|
|
other => other,
|
|
}
|
|
}
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(not(windows))] {
|
|
/// Ignore tests that aren't supported yet.
|
|
fn ignore(_testsuite: &str, _name: &str) -> bool {
|
|
false
|
|
}
|
|
} else {
|
|
/// Ignore tests that aren't supported yet.
|
|
fn ignore(testsuite: &str, name: &str) -> bool {
|
|
if testsuite == "misc_testsuite" {
|
|
match name {
|
|
"readlink_no_buffer" => true,
|
|
"dangling_symlink" => true,
|
|
"symlink_loop" => true,
|
|
"truncation_rights" => true,
|
|
"fd_readdir" => true,
|
|
"path_rename_trailing_slashes" => true,
|
|
"poll_oneoff" => true,
|
|
_ => false,
|
|
}
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Mark tests which do not require preopens
|
|
fn no_preopens(testsuite: &str, name: &str) -> bool {
|
|
if testsuite == "misc_testsuite" {
|
|
match name {
|
|
"big_random_buf" => true,
|
|
"clock_time_get" => true,
|
|
"sched_yield" => true,
|
|
_ => false,
|
|
}
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|