Get WASI tests working

This commit is contained in:
Mark McCaskey
2020-06-22 17:11:02 -07:00
parent b96e74003c
commit e67a3089fc
8 changed files with 227 additions and 33 deletions

2
Cargo.lock generated
View File

@@ -2348,7 +2348,9 @@ name = "wasmer-wast"
version = "1.0.0-alpha.1" version = "1.0.0-alpha.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde",
"thiserror", "thiserror",
"typetag",
"wasmer", "wasmer",
"wasmer-wasi", "wasmer-wasi",
"wast", "wast",

View File

@@ -1,8 +1,10 @@
/// types for use in the WASI filesystem /// types for use in the WASI filesystem
use crate::syscalls::types::*; use crate::syscalls::types::*;
use serde::{de, Deserialize, Serialize}; use serde::{de, Deserialize, Serialize};
use std::any::Any;
#[cfg(unix)] #[cfg(unix)]
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt;
use std::{ use std::{
fs, fs,
io::{self, Read, Seek, Write}, io::{self, Read, Seek, Write},
@@ -148,7 +150,7 @@ impl WasiFsError {
/// This trait relies on your file closing when it goes out of scope via `Drop` /// This trait relies on your file closing when it goes out of scope via `Drop`
#[typetag::serde(tag = "type")] #[typetag::serde(tag = "type")]
pub trait WasiFile: std::fmt::Debug + Send + Write + Read + Seek { pub trait WasiFile: fmt::Debug + Send + Write + Read + Seek + 'static + Upcastable {
/// the last time the file was accessed in nanoseconds as a UNIX timestamp /// the last time the file was accessed in nanoseconds as a UNIX timestamp
fn last_accessed(&self) -> __wasi_timestamp_t; fn last_accessed(&self) -> __wasi_timestamp_t;
@@ -210,6 +212,40 @@ pub trait WasiFile: std::fmt::Debug + Send + Write + Read + Seek {
} }
} }
// Implementation of `Upcastable` taken from https://users.rust-lang.org/t/why-does-downcasting-not-work-for-subtraits/33286/7 .
/// Trait needed to get downcasting from `WasiFile` to work.
pub trait Upcastable {
fn upcast_any_ref(self: &'_ Self) -> &'_ dyn Any;
fn upcast_any_mut(self: &'_ mut Self) -> &'_ mut dyn Any;
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any>;
}
impl<T: Any + fmt::Debug + 'static> Upcastable for T {
#[inline]
fn upcast_any_ref(self: &'_ Self) -> &'_ dyn Any {
self
}
#[inline]
fn upcast_any_mut(self: &'_ mut Self) -> &'_ mut dyn Any {
self
}
#[inline]
fn upcast_any_box(self: Box<Self>) -> Box<dyn Any> {
self
}
}
impl dyn WasiFile + 'static {
#[inline]
pub fn downcast_ref<T: 'static>(self: &'_ Self) -> Option<&'_ T> {
self.upcast_any_ref().downcast_ref::<T>()
}
#[inline]
pub fn downcast_mut<T: 'static>(self: &'_ mut Self) -> Option<&'_ mut T> {
self.upcast_any_mut().downcast_mut::<T>()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PollEvent { pub enum PollEvent {
/// Data available to read /// Data available to read

View File

@@ -11,7 +11,9 @@ mod multi_value_imports;
mod serialize; mod serialize;
mod traps; mod traps;
mod utils; mod utils;
mod wasi;
mod wast; mod wast;
pub use crate::utils::get_compiler; pub use crate::utils::get_compiler;
pub use crate::wasi::run_wasi;
pub use crate::wast::run_wast; pub use crate::wast::run_wast;

View File

@@ -1,12 +1,11 @@
#![cfg(all(feature = "compiler", feature = "engine"))] #![cfg(all(feature = "compiler", feature = "engine"))]
use crate::utils::get_compiler;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::sync::Arc; use wasmer::{Features, Store};
use test_utils::get_compiler_config_from_str;
use wasmer::{Features, Store, Tunables};
#[cfg(feature = "jit")] #[cfg(feature = "jit")]
use wasmer_engine_jit::JITEngine; use wasmer_engine_jit::JIT;
use wasmer_wast::WasiTest; use wasmer_wast::WasiTest;
// The generated tests (from build.rs) look like: // The generated tests (from build.rs) look like:
@@ -21,15 +20,14 @@ use wasmer_wast::WasiTest;
// } // }
include!(concat!(env!("OUT_DIR"), "/generated_wasitests.rs")); include!(concat!(env!("OUT_DIR"), "/generated_wasitests.rs"));
fn run_wasi(wast_path: &str, base_dir: &str, compiler: &str) -> anyhow::Result<()> { pub fn run_wasi(wast_path: &str, base_dir: &str, compiler: &str) -> anyhow::Result<()> {
println!( println!(
"Running wasi wast `{}` with the {} compiler", "Running wasi wast `{}` with the {} compiler",
wast_path, compiler wast_path, compiler
); );
let features = Features::default(); let features = Features::default();
let compiler_config = get_compiler_config_from_str(compiler, false, features); let compiler_config = get_compiler(true);
let tunables = Tunables::for_target(compiler_config.target().triple()); let store = Store::new(&JIT::new(&compiler_config).features(features).engine());
let store = Store::new(Arc::new(JITEngine::new(compiler_config, tunables)));
let source = { let source = {
let mut out = String::new(); let mut out = String::new();

View File

@@ -2,8 +2,7 @@
use crate::utils::get_compiler; use crate::utils::get_compiler;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use wasmer::{Features, Store};
use wasmer::{Features, Store, Tunables};
#[cfg(feature = "jit")] #[cfg(feature = "jit")]
use wasmer_engine_jit::JIT; use wasmer_engine_jit::JIT;
#[cfg(feature = "native")] #[cfg(feature = "native")]

View File

@@ -7,10 +7,43 @@ singlepass on windows # Singlepass is not yet supported on Windows
## WASI ## WASI
wasi::snapshot1::fd_read # due to hard-coded direct calls into WASI for wasi unstable
wasi::snapshot1::poll_oneoff
wasi::snapshot1::fd_pread wasitests_snapshot1::fd_read
wasi::snapshot1::fd_close wasitests_snapshot1::poll_oneoff
wasi::snapshot1::fd_allocate wasitests_snapshot1::fd_pread
wasi::snapshot1::close_preopen_fd wasitests_snapshot1::fd_close
wasi::snapshot1::envvar wasitests_snapshot1::fd_allocate
wasitests_snapshot1::close_preopen_fd
wasitests_snapshot1::envvar
# TODO: resolve the disabled tests below:
# due to git clone not preserving symlinks:
wasitests_snapshot1::readlink
wasitests_unstable::readlink
# due to directory changes:
wasitests_snapshot1::quine
wasitests_unstable::quine
# failing for unknown reasons
wasitests_unstable::fd_pread
wasitests_unstable::create_dir
wasitests_snapshot1::create_dir
wasitests_unstable::file_metadata
wasitests_snapshot1::file_metadata
# failing because it closes `stdout` which breaks our testing system
wasitests_unstable::fd_close
# failing because we're missing an extra newline at the end of the output
# could this be due to stdout printing a newline when datasync is called or something?
# TODO: check WasiFile implementation
wasitests_unstable::fd_read
# failing because we're operating on stdout which is now overridden.
# TODO: check WasiFile implementation
wasitests_unstable::fd_polloneoff

View File

@@ -15,7 +15,9 @@ anyhow = "1.0"
wasmer = { path = "../../../lib/api", version = "1.0.0-alpha.1", default-features = false } wasmer = { path = "../../../lib/api", version = "1.0.0-alpha.1", default-features = false }
wasmer-wasi = { path = "../../../lib/wasi", version = "1.0.0-alpha.1" } wasmer-wasi = { path = "../../../lib/wasi", version = "1.0.0-alpha.1" }
wast = "17.0" wast = "17.0"
serde = "1"
thiserror = "1.0" thiserror = "1.0"
typetag = "0.1"
[features] [features]
default = ["wat"] default = ["wat"]

View File

@@ -1,11 +1,13 @@
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize};
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::{self, Read, Seek, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use wasmer::{ImportObject, Instance, Memory, Module, Store}; use wasmer::{ImportObject, Instance, Memory, Module, Store};
use wasmer_wasi::types::{__wasi_filesize_t, __wasi_timestamp_t};
use wasmer_wasi::{ use wasmer_wasi::{
generate_import_object_from_env, get_wasi_version, WasiEnv, WasiState, WasiVersion, generate_import_object_from_env, get_wasi_version, WasiEnv, WasiFile, WasiFsError, WasiState,
WasiVersion,
}; };
use wast::parser::{self, Parse, ParseBuffer, Parser}; use wast::parser::{self, Parse, ParseBuffer, Parser};
@@ -16,12 +18,15 @@ pub struct WasiTest<'a> {
args: Vec<&'a str>, args: Vec<&'a str>,
envs: Vec<(&'a str, &'a str)>, envs: Vec<(&'a str, &'a str)>,
dirs: Vec<&'a str>, dirs: Vec<&'a str>,
mapped_dirs: Vec<&'a str>, mapped_dirs: Vec<(&'a str, &'a str)>,
assert_return: Option<AssertReturn>, assert_return: Option<AssertReturn>,
assert_stdout: Option<AssertStdout<'a>>, assert_stdout: Option<AssertStdout<'a>>,
assert_stderr: Option<AssertStderr<'a>>, assert_stderr: Option<AssertStderr<'a>>,
} }
// TODO: add `test_fs` here to sandbox better
const BASE_TEST_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../wasi/wasi/");
#[allow(dead_code)] #[allow(dead_code)]
impl<'a> WasiTest<'a> { impl<'a> WasiTest<'a> {
/// Turn a WASI WAST string into a list of tokens. /// Turn a WASI WAST string into a list of tokens.
@@ -49,13 +54,36 @@ impl<'a> WasiTest<'a> {
let imports = self.get_imports(store, &module, env.clone())?; let imports = self.get_imports(store, &module, env.clone())?;
let instance = Instance::new(&module, &imports)?; let instance = Instance::new(&module, &imports)?;
let memory: &Memory = instance.exports.get("memory")?; let memory: &Memory = instance.exports.get("memory")?;
// TODO: env.set_memory(memory.clone());
env.set_memory(Arc::new(memory.clone()));
let start = instance.exports.get_function("_start")?; let start = instance.exports.get_function("_start")?;
// TODO: handle errors here when the error fix gets shipped
start start
.call(&[]) .call(&[])
.with_context(|| "failed to run WASI `_start` function")?; .with_context(|| "failed to run WASI `_start` function")?;
let wasi_state = env.state();
{
let stdout_boxed = wasi_state.fs.stdout()?.as_ref().unwrap();
let stdout = (&**stdout_boxed)
.downcast_ref::<OutputCapturerer>()
.unwrap();
let stdout_str = std::str::from_utf8(&stdout.output)?;
if let Some(expected_stdout) = &self.assert_stdout {
assert_eq!(stdout_str, expected_stdout.expected);
}
}
{
let stderr_boxed = wasi_state.fs.stderr()?.as_ref().unwrap();
let stderr = (&**stderr_boxed)
.downcast_ref::<OutputCapturerer>()
.unwrap();
let stderr_str = std::str::from_utf8(&stderr.output)?;
if let Some(expected_stderr) = &self.assert_stderr {
assert_eq!(stderr_str, expected_stderr.expected);
}
}
Ok(true) Ok(true)
} }
@@ -65,18 +93,22 @@ impl<'a> WasiTest<'a> {
for (name, value) in &self.envs { for (name, value) in &self.envs {
builder.env(name, value); builder.env(name, value);
} }
// TODO: implement map dirs
/*
// TODO: check the order // TODO: check the order
for (alias, real_dir) in &self.mapped_dirs { for (alias, real_dir) in &self.mapped_dirs {
builder.map_dir(alias, real_dir); let mut dir = PathBuf::from(BASE_TEST_DIR);
}*/ dir.push(real_dir);
builder.map_dir(alias, dir)?;
}
let out = builder let out = builder
.args(&self.args) .args(&self.args)
.preopen_dirs(&self.dirs)? .preopen_dirs(self.dirs.iter().map(|d| {
// TODO: capture stdout and stderr let mut dir = PathBuf::from(BASE_TEST_DIR);
// can be done with a custom file, inserted here dir.push(d);
dir
}))?
.stdout(Box::new(OutputCapturerer::new()))
.stderr(Box::new(OutputCapturerer::new()))
.finalize()?; .finalize()?;
Ok(out) Ok(out)
} }
@@ -237,7 +269,7 @@ impl<'a> Parse<'a> for Preopens<'a> {
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
struct MapDirs<'a> { struct MapDirs<'a> {
map_dirs: Vec<&'a str>, map_dirs: Vec<(&'a str, &'a str)>,
} }
impl<'a> Parse<'a> for MapDirs<'a> { impl<'a> Parse<'a> for MapDirs<'a> {
@@ -247,7 +279,10 @@ impl<'a> Parse<'a> for MapDirs<'a> {
while parser.peek::<&'a str>() { while parser.peek::<&'a str>() {
let res = parser.parse::<&'a str>()?; let res = parser.parse::<&'a str>()?;
map_dirs.push(res); let mut iter = res.split(':');
let dir = iter.next().unwrap();
let alias = iter.next().unwrap();
map_dirs.push((dir, alias));
} }
Ok(Self { map_dirs }) Ok(Self { map_dirs })
} }
@@ -330,3 +365,90 @@ mod test {
assert_eq!(result.assert_stderr.unwrap().expected, ""); assert_eq!(result.assert_stderr.unwrap().expected, "");
} }
} }
#[derive(Debug, Serialize, Deserialize)]
struct OutputCapturerer {
output: Vec<u8>,
}
impl OutputCapturerer {
fn new() -> Self {
Self { output: vec![] }
}
}
impl Read for OutputCapturerer {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::new(
io::ErrorKind::Other,
"can not read from logging wrapper",
))
}
fn read_to_end(&mut self, _buf: &mut Vec<u8>) -> io::Result<usize> {
Err(io::Error::new(
io::ErrorKind::Other,
"can not read from logging wrapper",
))
}
fn read_to_string(&mut self, _buf: &mut String) -> io::Result<usize> {
Err(io::Error::new(
io::ErrorKind::Other,
"can not read from logging wrapper",
))
}
fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Other,
"can not read from logging wrapper",
))
}
}
impl Seek for OutputCapturerer {
fn seek(&mut self, _pos: io::SeekFrom) -> io::Result<u64> {
Err(io::Error::new(
io::ErrorKind::Other,
"can not seek logging wrapper",
))
}
}
impl Write for OutputCapturerer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.output.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.output.extend_from_slice(buf);
Ok(())
}
fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> {
self.output.write_fmt(fmt)
}
}
#[typetag::serde]
impl WasiFile for OutputCapturerer {
fn last_accessed(&self) -> __wasi_timestamp_t {
0
}
fn last_modified(&self) -> __wasi_timestamp_t {
0
}
fn created_time(&self) -> __wasi_timestamp_t {
0
}
fn size(&self) -> u64 {
0
}
fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError> {
Ok(())
}
fn unlink(&mut self) -> Result<(), WasiFsError> {
Ok(())
}
fn bytes_available(&self) -> Result<usize, WasiFsError> {
Ok(1024)
}
}