mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 13:48:26 +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"
|
version = "1.0.0-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"typetag",
|
||||||
"wasmer",
|
"wasmer",
|
||||||
"wasmer-wasi",
|
"wasmer-wasi",
|
||||||
"wast",
|
"wast",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user