Add WASI wast parsing logic

This commit is contained in:
Mark McCaskey
2020-06-19 15:41:51 -07:00
parent c5a879146a
commit c702d291cd
6 changed files with 246 additions and 40 deletions

11
Cargo.lock generated
View File

@ -1993,16 +1993,6 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi-test-generator"
version = "0.17.0"
dependencies = [
"glob",
"serde",
"serde_json",
"tempfile",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.63"
@ -2109,7 +2099,6 @@ dependencies = [
"structopt",
"test-generator",
"test-utils",
"wasi-test-generator",
"wasm-common",
"wasmer",
"wasmer-cache",

View File

@ -56,7 +56,6 @@ members = [
[build-dependencies]
test-generator = { path = "tests/lib/test-generator" }
wasi-test-generator = { path = "tests/lib/wasi-test-generator" }
anyhow = "1.0"
glob = "0.3"
rustc_version = "0.2"

View File

@ -12,38 +12,10 @@ use test_generator::{
build_ignores_from_textfile, test_directory, test_directory_module, wast_processor,
with_features, with_test_module, Testsuite,
};
use wasi_test_generator;
static WASITESTS_ENV_VAR: &str = "WASM_WASI_GENERATE_WASITESTS";
static WASITESTS_SET_UP_TOOLCHAIN: &str = "WASM_WASI_SET_UP_TOOLCHAIN";
static WASITESTS_GENERATE_ALL: &str = "WASI_TEST_GENERATE_ALL";
fn is_truthy_env(name: &str) -> bool {
env::var(name).map(|n| n == "1").unwrap_or_default()
}
fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=tests/ignores.txt");
println!("cargo:rerun-if-env-changed={}", WASITESTS_ENV_VAR);
println!("cargo:rerun-if-env-changed={}", WASITESTS_SET_UP_TOOLCHAIN);
println!("cargo:rerun-if-env-changed={}", WASITESTS_GENERATE_ALL);
let wasi_versions = if is_truthy_env(WASITESTS_GENERATE_ALL) {
wasi_test_generator::ALL_WASI_VERSIONS
} else {
wasi_test_generator::LATEST_WASI_VERSION
};
// Install the Rust WASI toolchains for each of the versions
if is_truthy_env(WASITESTS_SET_UP_TOOLCHAIN) {
wasi_test_generator::install_toolchains(wasi_versions);
}
// Generate the WASI Wasm files
if is_truthy_env(WASITESTS_ENV_VAR) {
wasi_test_generator::build(wasi_versions);
}
let out_dir = PathBuf::from(
env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),

View File

@ -21,6 +21,7 @@
mod error;
mod spectest;
mod wasi_wast;
mod wast;
pub use crate::error::{DirectiveError, DirectiveErrors};

View File

@ -0,0 +1,243 @@
use wast::parser::{self, Parse, Parser};
#[derive(Debug, Clone, Hash)]
pub(crate) struct WasiTest<'a> {
wasm_path: &'a str,
args: Vec<&'a str>,
envs: Vec<(&'a str, &'a str)>,
dirs: Vec<&'a str>,
mapped_dirs: Vec<&'a str>,
assert_return: Option<AssertReturn>,
assert_stdout: Option<AssertStdout<'a>>,
assert_stderr: Option<AssertStderr<'a>>,
}
mod wasi_kw {
wast::custom_keyword!(wasi_test);
wast::custom_keyword!(envs);
wast::custom_keyword!(args);
wast::custom_keyword!(preopens);
wast::custom_keyword!(map_dirs);
wast::custom_keyword!(assert_return);
wast::custom_keyword!(assert_stdout);
wast::custom_keyword!(assert_stderr);
wast::custom_keyword!(fake_i64_const = "i64.const");
}
impl<'a> Parse<'a> for WasiTest<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
parser.parens(|parser| {
parser.parse::<wasi_kw::wasi_test>()?;
// TODO: improve error message here
let wasm_path = parser.parse::<&'a str>()?;
// TODO: allow these to come in any order
let envs = if parser.peek2::<wasi_kw::envs>() {
parser.parens(|p| p.parse::<Envs>())?.envs
} else {
vec![]
};
let args = if parser.peek2::<wasi_kw::args>() {
parser.parens(|p| p.parse::<Args>())?.args
} else {
vec![]
};
let dirs = if parser.peek2::<wasi_kw::preopens>() {
parser.parens(|p| p.parse::<Preopens>())?.preopens
} else {
vec![]
};
let mapped_dirs = if parser.peek2::<wasi_kw::map_dirs>() {
parser.parens(|p| p.parse::<MapDirs>())?.map_dirs
} else {
vec![]
};
let assert_return = if parser.peek2::<wasi_kw::assert_return>() {
Some(parser.parens(|p| p.parse::<AssertReturn>())?)
} else {
None
};
let assert_stdout = if parser.peek2::<wasi_kw::assert_stdout>() {
Some(parser.parens(|p| p.parse::<AssertStdout>())?)
} else {
None
};
let assert_stderr = if parser.peek2::<wasi_kw::assert_stderr>() {
Some(parser.parens(|p| p.parse::<AssertStderr>())?)
} else {
None
};
Ok(Self {
wasm_path,
args,
envs,
dirs,
mapped_dirs,
assert_return,
assert_stdout,
assert_stderr,
})
})
}
}
#[derive(Debug, Clone, Hash)]
struct Envs<'a> {
envs: Vec<(&'a str, &'a str)>,
}
impl<'a> Parse<'a> for Envs<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
let mut envs = vec![];
parser.parse::<wasi_kw::envs>()?;
while parser.peek::<&'a str>() {
let res = parser.parse::<&'a str>()?;
let mut strs = res.split('=');
let first = strs.next().unwrap();
let second = strs.next().unwrap();
debug_assert!(strs.next().is_none());
envs.push((first, second));
}
Ok(Self { envs })
}
}
#[derive(Debug, Clone, Hash)]
struct Args<'a> {
args: Vec<&'a str>,
}
impl<'a> Parse<'a> for Args<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
let mut args = vec![];
parser.parse::<wasi_kw::args>()?;
while parser.peek::<&'a str>() {
let res = parser.parse::<&'a str>()?;
args.push(res);
}
Ok(Self { args })
}
}
#[derive(Debug, Clone, Hash)]
struct Preopens<'a> {
preopens: Vec<&'a str>,
}
impl<'a> Parse<'a> for Preopens<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
let mut preopens = vec![];
parser.parse::<wasi_kw::preopens>()?;
while parser.peek::<&'a str>() {
let res = parser.parse::<&'a str>()?;
preopens.push(res);
}
Ok(Self { preopens })
}
}
#[derive(Debug, Clone, Hash)]
struct MapDirs<'a> {
map_dirs: Vec<&'a str>,
}
impl<'a> Parse<'a> for MapDirs<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
let mut map_dirs = vec![];
parser.parse::<wasi_kw::map_dirs>()?;
while parser.peek::<&'a str>() {
let res = parser.parse::<&'a str>()?;
map_dirs.push(res);
}
Ok(Self { map_dirs })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct AssertReturn {
return_value: i64,
}
impl<'a> Parse<'a> for AssertReturn {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
parser.parse::<wasi_kw::assert_return>()?;
let return_value = parser.parens(|p| {
p.parse::<wasi_kw::fake_i64_const>()?;
p.parse::<i64>()
})?;
Ok(Self { return_value })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct AssertStdout<'a> {
expected: &'a str,
}
impl<'a> Parse<'a> for AssertStdout<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
parser.parse::<wasi_kw::assert_stdout>()?;
Ok(Self {
expected: parser.parse()?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct AssertStderr<'a> {
expected: &'a str,
}
impl<'a> Parse<'a> for AssertStderr<'a> {
fn parse(parser: Parser<'a>) -> parser::Result<Self> {
parser.parse::<wasi_kw::assert_stderr>()?;
Ok(Self {
expected: parser.parse()?,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse() {
let pb = wast::parser::ParseBuffer::new(
r#"(wasi_test "my_wasm.wasm"
(envs "HELLO=WORLD" "RUST_BACKTRACE=1")
(args "hello" "world" "--help")
(preopens "." "src/io")
(assert_return (i64.const 0))
(assert_stdout "This is a \"string\" inside a string!")
(assert_stderr "")
)"#,
)
.unwrap();
let result = wast::parser::parse::<WasiTest>(&pb).unwrap();
assert_eq!(result.args, vec!["hello", "world", "--help"]);
assert_eq!(
result.envs,
vec![("HELLO", "WORLD"), ("RUST_BACKTRACE", "1")]
);
assert_eq!(result.dirs, vec![".", "src/io"]);
assert_eq!(result.assert_return.unwrap().return_value, 0);
assert_eq!(
result.assert_stdout.unwrap().expected,
"This is a \"string\" inside a string!"
);
assert_eq!(result.assert_stderr.unwrap().expected, "");
}
}

View File

@ -504,3 +504,5 @@ impl NaNCheck for f64 {
(self.to_bits() & 0x7fff_ffff_ffff_ffff) == 0x7ff8_0000_0000_0000
}
}
wast::custom_keyword!(wasi_test);