mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 05:38:19 +00:00
Get WASI tests working
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2348,7 +2348,9 @@ name = "wasmer-wast"
|
||||
version = "1.0.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"typetag",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
"wast",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/// types for use in the WASI filesystem
|
||||
use crate::syscalls::types::*;
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use std::any::Any;
|
||||
#[cfg(unix)]
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::{
|
||||
fs,
|
||||
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`
|
||||
#[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
|
||||
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)]
|
||||
pub enum PollEvent {
|
||||
/// Data available to read
|
||||
|
||||
@@ -11,7 +11,9 @@ mod multi_value_imports;
|
||||
mod serialize;
|
||||
mod traps;
|
||||
mod utils;
|
||||
mod wasi;
|
||||
mod wast;
|
||||
|
||||
pub use crate::utils::get_compiler;
|
||||
pub use crate::wasi::run_wasi;
|
||||
pub use crate::wast::run_wast;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#![cfg(all(feature = "compiler", feature = "engine"))]
|
||||
|
||||
use crate::utils::get_compiler;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use test_utils::get_compiler_config_from_str;
|
||||
use wasmer::{Features, Store, Tunables};
|
||||
use wasmer::{Features, Store};
|
||||
#[cfg(feature = "jit")]
|
||||
use wasmer_engine_jit::JITEngine;
|
||||
use wasmer_engine_jit::JIT;
|
||||
use wasmer_wast::WasiTest;
|
||||
|
||||
// The generated tests (from build.rs) look like:
|
||||
@@ -21,15 +20,14 @@ use wasmer_wast::WasiTest;
|
||||
// }
|
||||
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!(
|
||||
"Running wasi wast `{}` with the {} compiler",
|
||||
wast_path, compiler
|
||||
);
|
||||
let features = Features::default();
|
||||
let compiler_config = get_compiler_config_from_str(compiler, false, features);
|
||||
let tunables = Tunables::for_target(compiler_config.target().triple());
|
||||
let store = Store::new(Arc::new(JITEngine::new(compiler_config, tunables)));
|
||||
let compiler_config = get_compiler(true);
|
||||
let store = Store::new(&JIT::new(&compiler_config).features(features).engine());
|
||||
|
||||
let source = {
|
||||
let mut out = String::new();
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
use crate::utils::get_compiler;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use wasmer::{Features, Store, Tunables};
|
||||
use wasmer::{Features, Store};
|
||||
#[cfg(feature = "jit")]
|
||||
use wasmer_engine_jit::JIT;
|
||||
#[cfg(feature = "native")]
|
||||
|
||||
@@ -7,10 +7,43 @@ singlepass on windows # Singlepass is not yet supported on Windows
|
||||
|
||||
## WASI
|
||||
|
||||
wasi::snapshot1::fd_read
|
||||
wasi::snapshot1::poll_oneoff
|
||||
wasi::snapshot1::fd_pread
|
||||
wasi::snapshot1::fd_close
|
||||
wasi::snapshot1::fd_allocate
|
||||
wasi::snapshot1::close_preopen_fd
|
||||
wasi::snapshot1::envvar
|
||||
# due to hard-coded direct calls into WASI for wasi unstable
|
||||
|
||||
wasitests_snapshot1::fd_read
|
||||
wasitests_snapshot1::poll_oneoff
|
||||
wasitests_snapshot1::fd_pread
|
||||
wasitests_snapshot1::fd_close
|
||||
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
|
||||
|
||||
@@ -15,7 +15,9 @@ anyhow = "1.0"
|
||||
wasmer = { path = "../../../lib/api", version = "1.0.0-alpha.1", default-features = false }
|
||||
wasmer-wasi = { path = "../../../lib/wasi", version = "1.0.0-alpha.1" }
|
||||
wast = "17.0"
|
||||
serde = "1"
|
||||
thiserror = "1.0"
|
||||
typetag = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["wat"]
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use anyhow::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io::{self, Read, Seek, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use wasmer::{ImportObject, Instance, Memory, Module, Store};
|
||||
use wasmer_wasi::types::{__wasi_filesize_t, __wasi_timestamp_t};
|
||||
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};
|
||||
|
||||
@@ -16,12 +18,15 @@ pub struct WasiTest<'a> {
|
||||
args: Vec<&'a str>,
|
||||
envs: Vec<(&'a str, &'a str)>,
|
||||
dirs: Vec<&'a str>,
|
||||
mapped_dirs: Vec<&'a str>,
|
||||
mapped_dirs: Vec<(&'a str, &'a str)>,
|
||||
assert_return: Option<AssertReturn>,
|
||||
assert_stdout: Option<AssertStdout<'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)]
|
||||
impl<'a> WasiTest<'a> {
|
||||
/// 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 instance = Instance::new(&module, &imports)?;
|
||||
let memory: &Memory = instance.exports.get("memory")?;
|
||||
// TODO:
|
||||
env.set_memory(Arc::new(memory.clone()));
|
||||
env.set_memory(memory.clone());
|
||||
|
||||
let start = instance.exports.get_function("_start")?;
|
||||
// TODO: handle errors here when the error fix gets shipped
|
||||
start
|
||||
.call(&[])
|
||||
.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)
|
||||
}
|
||||
|
||||
@@ -65,18 +93,22 @@ impl<'a> WasiTest<'a> {
|
||||
for (name, value) in &self.envs {
|
||||
builder.env(name, value);
|
||||
}
|
||||
// TODO: implement map dirs
|
||||
/*
|
||||
// TODO: check the order
|
||||
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
|
||||
.args(&self.args)
|
||||
.preopen_dirs(&self.dirs)?
|
||||
// TODO: capture stdout and stderr
|
||||
// can be done with a custom file, inserted here
|
||||
.preopen_dirs(self.dirs.iter().map(|d| {
|
||||
let mut dir = PathBuf::from(BASE_TEST_DIR);
|
||||
dir.push(d);
|
||||
dir
|
||||
}))?
|
||||
.stdout(Box::new(OutputCapturerer::new()))
|
||||
.stderr(Box::new(OutputCapturerer::new()))
|
||||
.finalize()?;
|
||||
Ok(out)
|
||||
}
|
||||
@@ -237,7 +269,7 @@ impl<'a> Parse<'a> for Preopens<'a> {
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
struct MapDirs<'a> {
|
||||
map_dirs: Vec<&'a str>,
|
||||
map_dirs: Vec<(&'a str, &'a str)>,
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for MapDirs<'a> {
|
||||
@@ -247,7 +279,10 @@ impl<'a> Parse<'a> for MapDirs<'a> {
|
||||
|
||||
while parser.peek::<&'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 })
|
||||
}
|
||||
@@ -330,3 +365,90 @@ mod test {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user