From 50cfa0f5ef04b46cfc3cede965b2171c588fc9e6 Mon Sep 17 00:00:00 2001 From: Rerumu Date: Fri, 20 May 2022 20:04:50 -0400 Subject: [PATCH] Add basic test code --- Cargo.toml | 1 + dev-test/Cargo.toml | 25 ++++++ dev-test/src/lib.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 dev-test/Cargo.toml create mode 100644 dev-test/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 20703ec..eeec3b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "codegen-luajit", "codegen-luau", "dev-fuzz", + "dev-test", "wasm-ast", "wasm-synth" ] diff --git a/dev-test/Cargo.toml b/dev-test/Cargo.toml new file mode 100644 index 0000000..4303148 --- /dev/null +++ b/dev-test/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "dev-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.test-generator] +version = "0.3.0" + +[dependencies.wast] +version = "41.0.0" + +[dependencies.parity-wasm] +git = "https://github.com/paritytech/parity-wasm.git" +features = ["multi_value", "sign_ext"] + +[dependencies.wasm-ast] +path = "../wasm-ast" + +[dependencies.codegen-luajit] +path = "../codegen-luajit" + +[dependencies.codegen-luau] +path = "../codegen-luau" diff --git a/dev-test/src/lib.rs b/dev-test/src/lib.rs new file mode 100644 index 0000000..110bede --- /dev/null +++ b/dev-test/src/lib.rs @@ -0,0 +1,201 @@ +#![cfg(test)] + +use std::{ + io::{Result as IResult, Write}, + marker::PhantomData, + path::{Path, PathBuf}, + process::Command, +}; + +use parity_wasm::elements::Module as BinModule; +use wast::{ + core::Module as AstModule, parser::ParseBuffer, token::Id, QuoteWat, Wast, WastDirective, Wat, +}; + +use wasm_ast::builder::TypeInfo; + +struct TypedModule<'a> { + name: &'a str, + module: &'a BinModule, + type_info: TypeInfo<'a>, +} + +impl<'a> TypedModule<'a> { + fn resolve_id(id: Option) -> &str { + id.map_or("temp", |v| v.name()) + } + + fn from_id(id: Option>, module: &'a BinModule) -> Self { + Self { + module, + name: Self::resolve_id(id), + type_info: TypeInfo::from_module(module), + } + } +} + +trait Target: Sized { + fn executable() -> String; + + fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> IResult<()>; + + fn write_runtime(w: &mut dyn Write) -> IResult<()>; + + fn write_module(data: TypedModule, w: &mut dyn Write) -> IResult<()>; +} + +struct LuaJIT; + +impl Target for LuaJIT { + fn executable() -> String { + std::env::var("LUAJIT_PATH").unwrap_or_else(|_| "luajit".to_string()) + } + + fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> IResult<()> { + writeln!(w, "local {} = {}", post, pre) + } + + fn write_runtime(w: &mut dyn Write) -> IResult<()> { + let runtime = codegen_luajit::RUNTIME; + + writeln!(w, "local rt = (function() {runtime} end)()") + } + + fn write_module(data: TypedModule, w: &mut dyn Write) -> IResult<()> { + write!(w, "local temp_{} = (function() ", data.name)?; + codegen_luajit::translate(data.module, &data.type_info, w)?; + writeln!(w, "end)(nil)") + } +} + +struct Luau; + +impl Target for Luau { + fn executable() -> String { + std::env::var("LUAU_PATH").unwrap_or_else(|_| "luajit".to_string()) + } + + fn write_register(post: &str, pre: &str, w: &mut dyn Write) -> IResult<()> { + writeln!(w, "local {} = {}", post, pre) + } + + fn write_runtime(w: &mut dyn Write) -> IResult<()> { + let runtime = codegen_luau::RUNTIME; + + writeln!(w, "local rt = (function() {runtime} end)()") + } + + fn write_module(data: TypedModule, w: &mut dyn Write) -> IResult<()> { + write!(w, "local temp_{} = (function() ", data.name)?; + codegen_luau::translate(data.module, &data.type_info, w)?; + writeln!(w, "end)(nil)") + } +} + +fn try_into_ast_module(data: QuoteWat) -> Option { + if let QuoteWat::Wat(Wat::Module(data)) = data { + Some(data) + } else { + None + } +} + +// Only proceed with tests that observe any state. +fn parse_and_validate<'a>(buffer: &'a ParseBuffer) -> Option> { + let loaded: Wast = wast::parser::parse(buffer).unwrap(); + let observer = loaded.directives.iter().any(|v| { + matches!( + v, + WastDirective::AssertTrap { .. } + | WastDirective::AssertReturn { .. } + | WastDirective::AssertExhaustion { .. } + ) + }); + + observer.then(|| loaded) +} + +struct Tester { + _marker: PhantomData, +} + +impl Tester { + fn test(name: &str, source: &str) -> IResult<()> { + if let Some(data) = Self::run_generation(source)? { + let temp = std::env::temp_dir().join("wasm-test-".to_string() + name); + + std::fs::write(&temp, &data)?; + Self::run_command(&temp)?; + } + + Ok(()) + } + + fn write_variant(variant: WastDirective, w: &mut dyn Write) -> IResult<()> { + match variant { + WastDirective::Wat(data) => { + let mut ast = try_into_ast_module(data).expect("Must be a module"); + let bytes = ast.encode().unwrap(); + let temp = parity_wasm::deserialize_buffer(&bytes).unwrap(); + let data = TypedModule::from_id(ast.id, &temp); + + T::write_module(data, w) + } + WastDirective::Register { name, module, .. } => { + T::write_register(name, TypedModule::resolve_id(module), w) + } + // WastDirective::Invoke(_) => todo!(), + // WastDirective::AssertTrap { exec, message, .. } => todo!(), + // WastDirective::AssertReturn { exec, results, .. } => todo!(), + // WastDirective::AssertExhaustion { call, message, .. } => todo!(), + _ => Ok(()), + } + } + + fn run_command(file: &Path) -> IResult<()> { + let result = Command::new(T::executable()).arg(file).output()?; + + if result.status.success() { + Ok(()) + } else { + let data = String::from_utf8_lossy(&result.stderr); + + panic!("{}", data); + } + } + + fn run_generation(source: &str) -> IResult>> { + let lexed = ParseBuffer::new(source).expect("Failed to tokenize"); + let parsed = match parse_and_validate(&lexed) { + Some(v) => v, + None => return Ok(None), + }; + + let mut data = Vec::new(); + + T::write_runtime(&mut data)?; + + for variant in parsed.directives { + Self::write_variant(variant, &mut data)?; + } + + Ok(Some(data)) + } +} + +static DO_NOT_RUN: [&str; 3] = ["binary-leb128.wast", "conversions.wast", "names.wast"]; + +#[test_generator::test_resources("dev-test/spec/*.wast")] +fn translate_file(path: PathBuf) { + let path = path.strip_prefix("dev-test/").unwrap(); + let name = path.file_name().unwrap().to_str().unwrap(); + + if DO_NOT_RUN.contains(&name) { + return; + } + + let source = std::fs::read_to_string(path).unwrap(); + + Tester::::test(name, &source).unwrap(); + // Tester::::test(name, &source).unwrap(); +}