From 36c65b6e75ef674aeea47818288e2a9d852d5944 Mon Sep 17 00:00:00 2001 From: George Cosma Date: Fri, 26 Jul 2024 13:25:45 +0300 Subject: [PATCH] feat: WASM Spec Testsuite (Attemp 2) Signed-off-by: George Cosma --- .gitmodules | 3 + .vscode/settings.json | 5 + Cargo.lock | 27 +++- Cargo.toml | 4 +- src/execution/mod.rs | 25 +++ tests/specification/dummy.wast | 95 +++++++++++ tests/specification/mod.rs | 47 ++++++ tests/specification/reports.rs | 126 +++++++++++++++ tests/specification/run.rs | 246 +++++++++++++++++++++++++++++ tests/specification/test_errors.rs | 88 +++++++++++ tests/specification/testsuite | 1 + tests/wasm_spec_testsuite.rs | 5 + 12 files changed, 668 insertions(+), 4 deletions(-) create mode 100644 .gitmodules create mode 100644 .vscode/settings.json create mode 100644 tests/specification/dummy.wast create mode 100644 tests/specification/mod.rs create mode 100644 tests/specification/reports.rs create mode 100644 tests/specification/run.rs create mode 100644 tests/specification/test_errors.rs create mode 160000 tests/specification/testsuite create mode 100644 tests/wasm_spec_testsuite.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..993e1db1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/specification/testsuite"] + path = tests/specification/testsuite + url = https://github.com/WebAssembly/testsuite.git diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..311ad1fc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.cargo.features": [ + "spec-test" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index db1a9320..94fb2729 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626,6 +626,15 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.212.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-interpreter" version = "0.1.0" @@ -636,6 +645,7 @@ dependencies = [ "log", "test-log", "wasmparser", + "wast 212.0.0", "wat", ] @@ -660,7 +670,20 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.200.0", +] + +[[package]] +name = "wast" +version = "212.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4606a05fb0aae5d11dd7d8280a640d88a63ee019360ba9be552da3d294b8d1f5" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.212.0", ] [[package]] @@ -669,7 +692,7 @@ version = "1.200.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "776cbd10e217f83869beaa3f40e312bb9e91d5eee29bbf6f560db1261b6a4c3d" dependencies = [ - "wast", + "wast 200.0.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a77bf7d3..e749811e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,13 @@ env_logger = "0.10.1" wasmparser = "0.119.0" itertools = "0.12.0" wat = "1.0.83" +wast = "212.0.0" criterion = { version = "0.5.1", features = ["html_reports"] } - [features] default = ["hooks"] hooks = [] - +spec-test = [] [[bench]] name = "hook_performance_impact" diff --git a/src/execution/mod.rs b/src/execution/mod.rs index 4ab8a333..e6fb223d 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -93,6 +93,31 @@ where } } + pub fn invoke_named_dynamic( + &mut self, + func_name: &str, + param: Vec, + ret_types: &[ValType], + ) -> Result> { + // TODO: Optimize this search for better than linear-time. Pre-processing will likely be required + let func_idx = self.exports.iter().find_map(|export| { + if export.name == func_name { + match export.desc { + ExportDesc::FuncIdx(idx) => Some(idx), + _ => None, + } + } else { + None + } + }); + + if let Some(func_idx) = func_idx { + self.invoke_dynamic(func_idx, param, ret_types) + } else { + Err(RuntimeError(FunctionNotFound)) + } + } + /// Can only invoke functions with signature `[t1] -> [t2]` as of now. pub fn invoke_func( &mut self, diff --git a/tests/specification/dummy.wast b/tests/specification/dummy.wast new file mode 100644 index 00000000..792d949e --- /dev/null +++ b/tests/specification/dummy.wast @@ -0,0 +1,95 @@ +;; i32 operations + +(module + (func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y))) + (func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y))) + (func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y))) + (func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y))) + (func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y))) + (func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y))) +) + +(assert_return (invoke "add" (i32.const 1) (i32.const 1)) (i32.const 2)) +(assert_return (invoke "add" (i32.const 1) (i32.const 0)) (i32.const 1)) +(assert_return (invoke "add" (i32.const -1) (i32.const -1)) (i32.const -2)) +(assert_return (invoke "add" (i32.const -1) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "add" (i32.const 0x7fffffff) (i32.const 1)) (i32.const 0x80000000)) +(assert_return (invoke "add" (i32.const 0x80000000) (i32.const -1)) (i32.const 0x7fffffff)) +(assert_return (invoke "add" (i32.const 0x80000000) (i32.const 0x80000000)) (i32.const 0)) +(assert_return (invoke "add" (i32.const 0x3fffffff) (i32.const 1)) (i32.const 0x40000000)) + +(assert_return (invoke "mul" (i32.const 1) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "mul" (i32.const 1) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "mul" (i32.const -1) (i32.const -1)) (i32.const 1)) +(assert_return (invoke "mul" (i32.const 0x10000000) (i32.const 4096)) (i32.const 0)) +(assert_return (invoke "mul" (i32.const 0x80000000) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "mul" (i32.const 0x80000000) (i32.const -1)) (i32.const 0x80000000)) +(assert_return (invoke "mul" (i32.const 0x7fffffff) (i32.const -1)) (i32.const 0x80000001)) +(assert_return (invoke "mul" (i32.const 0x01234567) (i32.const 0x76543210)) (i32.const 0x358e7470)) +(assert_return (invoke "mul" (i32.const 0x7fffffff) (i32.const 0x7fffffff)) (i32.const 1)) + +(assert_return (invoke "div_s" (i32.const 1) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "div_s" (i32.const 0) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "div_s" (i32.const 0) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "div_s" (i32.const -1) (i32.const -1)) (i32.const 1)) +(assert_return (invoke "div_s" (i32.const 0x80000000) (i32.const 2)) (i32.const 0xc0000000)) +(assert_return (invoke "div_s" (i32.const 0x80000001) (i32.const 1000)) (i32.const 0xffdf3b65)) +(assert_return (invoke "div_s" (i32.const 5) (i32.const 2)) (i32.const 2)) +(assert_return (invoke "div_s" (i32.const -5) (i32.const 2)) (i32.const -2)) +(assert_return (invoke "div_s" (i32.const 5) (i32.const -2)) (i32.const -2)) +(assert_return (invoke "div_s" (i32.const -5) (i32.const -2)) (i32.const 2)) +(assert_return (invoke "div_s" (i32.const 7) (i32.const 3)) (i32.const 2)) +(assert_return (invoke "div_s" (i32.const -7) (i32.const 3)) (i32.const -2)) +(assert_return (invoke "div_s" (i32.const 7) (i32.const -3)) (i32.const -2)) +(assert_return (invoke "div_s" (i32.const -7) (i32.const -3)) (i32.const 2)) +(assert_return (invoke "div_s" (i32.const 11) (i32.const 5)) (i32.const 2)) +(assert_return (invoke "div_s" (i32.const 17) (i32.const 7)) (i32.const 2)) + +(assert_return (invoke "div_u" (i32.const 1) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "div_u" (i32.const 0) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "div_u" (i32.const -1) (i32.const -1)) (i32.const 1)) +(assert_return (invoke "div_u" (i32.const 0x80000000) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "div_u" (i32.const 0x80000000) (i32.const 2)) (i32.const 0x40000000)) +(assert_return (invoke "div_u" (i32.const 0x8ff00ff0) (i32.const 0x10001)) (i32.const 0x8fef)) +(assert_return (invoke "div_u" (i32.const 0x80000001) (i32.const 1000)) (i32.const 0x20c49b)) +(assert_return (invoke "div_u" (i32.const 5) (i32.const 2)) (i32.const 2)) +(assert_return (invoke "div_u" (i32.const -5) (i32.const 2)) (i32.const 0x7ffffffd)) +(assert_return (invoke "div_u" (i32.const 5) (i32.const -2)) (i32.const 0)) +(assert_return (invoke "div_u" (i32.const -5) (i32.const -2)) (i32.const 0)) +(assert_return (invoke "div_u" (i32.const 7) (i32.const 3)) (i32.const 2)) +(assert_return (invoke "div_u" (i32.const 11) (i32.const 5)) (i32.const 2)) +(assert_return (invoke "div_u" (i32.const 17) (i32.const 7)) (i32.const 2)) + +(assert_return (invoke "rem_s" (i32.const 0x7fffffff) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const 1) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const 0) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const 0) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const -1) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const 0x80000000) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const 0x80000000) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "rem_s" (i32.const 0x80000001) (i32.const 1000)) (i32.const -647)) +(assert_return (invoke "rem_s" (i32.const 5) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "rem_s" (i32.const -5) (i32.const 2)) (i32.const -1)) +(assert_return (invoke "rem_s" (i32.const 5) (i32.const -2)) (i32.const 1)) +(assert_return (invoke "rem_s" (i32.const -5) (i32.const -2)) (i32.const -1)) +(assert_return (invoke "rem_s" (i32.const 7) (i32.const 3)) (i32.const 1)) +(assert_return (invoke "rem_s" (i32.const -7) (i32.const 3)) (i32.const -1)) +(assert_return (invoke "rem_s" (i32.const 7) (i32.const -3)) (i32.const 1)) +(assert_return (invoke "rem_s" (i32.const -7) (i32.const -3)) (i32.const -1)) +(assert_return (invoke "rem_s" (i32.const 11) (i32.const 5)) (i32.const 1)) +(assert_return (invoke "rem_s" (i32.const 17) (i32.const 7)) (i32.const 3)) + +(assert_return (invoke "rem_u" (i32.const 1) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rem_u" (i32.const 0) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "rem_u" (i32.const -1) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "rem_u" (i32.const 0x80000000) (i32.const -1)) (i32.const 0x80000000)) +(assert_return (invoke "rem_u" (i32.const 0x80000000) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "rem_u" (i32.const 0x8ff00ff0) (i32.const 0x10001)) (i32.const 0x8001)) +(assert_return (invoke "rem_u" (i32.const 0x80000001) (i32.const 1000)) (i32.const 649)) +(assert_return (invoke "rem_u" (i32.const 5) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "rem_u" (i32.const -5) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "rem_u" (i32.const 5) (i32.const -2)) (i32.const 5)) +(assert_return (invoke "rem_u" (i32.const -5) (i32.const -2)) (i32.const -5)) +(assert_return (invoke "rem_u" (i32.const 7) (i32.const 3)) (i32.const 1)) +(assert_return (invoke "rem_u" (i32.const 11) (i32.const 5)) (i32.const 1)) +(assert_return (invoke "rem_u" (i32.const 17) (i32.const 7)) (i32.const 3)) diff --git a/tests/specification/mod.rs b/tests/specification/mod.rs new file mode 100644 index 00000000..65cc75e7 --- /dev/null +++ b/tests/specification/mod.rs @@ -0,0 +1,47 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +mod reports; +mod run; +mod test_errors; + +#[test_log::test] +pub fn spec_dummy() { + let path = "./tests/specification/dummy.wast"; + let report = run::run_spec_test(path); + println!("Report for {}:\n{}", path, report); +} + +#[test_log::test] +pub fn spec_tests() { + let paths = get_wast_files(Path::new("./tests/specification/testsuite/")) + .expect("Failed to find testsuite"); + for test_path in paths { + let report = run::run_spec_test(test_path.to_str().unwrap()); + println!("Report for {}:\n{}", test_path.display(), report); + } +} + +// See: https://stackoverflow.com/a/76820878 +fn get_wast_files(base_path: &Path) -> Result, std::io::Error> { + let mut buf = vec![]; + let entries = fs::read_dir(base_path)?; + + for entry in entries { + let entry = entry?; + let meta = entry.metadata()?; + + if meta.is_dir() { + let mut subdir = get_wast_files(&entry.path())?; + buf.append(&mut subdir); + } + + if meta.is_file() && entry.path().extension().unwrap_or_default() == "wast" { + buf.push(entry.path()); + } + } + + Ok(buf) +} diff --git a/tests/specification/reports.rs b/tests/specification/reports.rs new file mode 100644 index 00000000..8ee83a7f --- /dev/null +++ b/tests/specification/reports.rs @@ -0,0 +1,126 @@ +use std::error::Error; + +pub struct WastSuccess { + filename: String, + line_number: u32, + command: String, +} + +pub struct WastError { + inner: Box, + filename: String, + line_number: u32, + command: String, +} + +impl WastError { + pub fn from_outside(error: Box, reason: &str) -> Self { + Self { + inner: error, + filename: String::from(""), + line_number: 0, + command: String::from(reason), + } + } +} + +pub struct AssertReport { + results: Vec>, +} + +impl AssertReport { + pub fn new() -> Self { + Self { + results: Vec::new(), + } + } + + pub fn push_success(&mut self, filename: String, line_number: u32, command: String) { + self.results.push(Ok(WastSuccess { + filename, + line_number, + command, + })); + } + + pub fn push_error( + &mut self, + filename: String, + line_number: u32, + command: String, + error: Box, + ) { + self.results.push(Err(WastError { + inner: error, + filename, + line_number, + command, + })); + } +} + +pub enum WastTestReport { + Asserts(AssertReport), + CompilationError(WastError), +} + +impl From for WastTestReport { + fn from(error: WastError) -> Self { + WastTestReport::CompilationError(error) + } +} + +impl From for WastTestReport { + fn from(assert_report: AssertReport) -> Self { + WastTestReport::Asserts(assert_report) + } +} + +// .------------------------. +// | Display Implementation | +// '------------------------' + +impl std::fmt::Display for WastSuccess { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[SUCCESS] {} ({}:{})", + self.command, self.filename, self.line_number + ) + } +} + +impl std::fmt::Display for WastError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "[ERROR] {} ({}:{})", + self.command, self.filename, self.line_number + )?; + write!(f, "\t{}", self.inner)?; + + Ok(()) + } +} + +impl std::fmt::Display for AssertReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for result in &self.results { + match result { + Ok(success) => writeln!(f, "{}", success)?, + Err(error) => writeln!(f, "{}", error)?, + } + } + + Ok(()) + } +} + +impl std::fmt::Display for WastTestReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WastTestReport::Asserts(assert_report) => write!(f, "{}", assert_report), + WastTestReport::CompilationError(error) => write!(f, "{}", error), + } + } +} diff --git a/tests/specification/run.rs b/tests/specification/run.rs new file mode 100644 index 00000000..db75f98a --- /dev/null +++ b/tests/specification/run.rs @@ -0,0 +1,246 @@ +use std::error::Error; +use std::panic::catch_unwind; +use std::panic::AssertUnwindSafe; + +use wasm::Value; +use wasm::{validate, RuntimeInstance}; + +use crate::specification::reports::*; +use crate::specification::test_errors::*; + +macro_rules! try_to { + ($e:expr) => { + match $e { + Ok(val) => val, + Err(err) => return err.into(), + } + }; +} + +pub fn run_spec_test(filepath: &str) -> WastTestReport { + // -=-= Initialization =-=- + let contents = try_to!(std::fs::read_to_string(filepath) + .map_err(|err| WastError::from_outside(Box::new(err), "failed to open wast file"))); + + let buf = try_to!(wast::parser::ParseBuffer::new(&contents) + .map_err(|err| WastError::from_outside(Box::new(err), "failed to create wast buffer"))); + + let wast = try_to!(wast::parser::parse::(&buf) + .map_err(|err| WastError::from_outside(Box::new(err), "failed to parse wast file"))); + + // -=-= Testing & Compilation =-=- + let mut asserts = AssertReport::new(); + + // We need to keep the wasm_bytes in-scope for the lifetime of the interpeter. + // As such, we hoist the bytes into an Option, and assign it once a module directive is found. + #[allow(unused_assignments)] + let mut wasm_bytes: Option> = None; + let mut interpeter = None; + + for directive in wast.directives { + match directive { + wast::WastDirective::Wat(mut quoted) => { + let encoded = try_to!(quoted + .encode() + .map_err(|err| WastError::from_outside(Box::new(err), "failed to encode wat"))); + + wasm_bytes = Some(encoded); + + let validation_info = + try_to!(validate(wasm_bytes.as_ref().unwrap()).map_err(|err| { + WastError::from_outside( + Box::::new(err.into()), + "WASM VALIDATION FAILED", + ) + })); + + let instance = try_to!(RuntimeInstance::new(&validation_info).map_err(|err| { + WastError::from_outside( + Box::::new(err.into()), + "failed to create runtime instance", + ) + })); + + interpeter = Some(instance); + } + wast::WastDirective::AssertReturn { + span, + exec, + results, + } => { + if interpeter.is_none() { + asserts.push_error( + filepath.to_string(), + span.linecol_in(&contents).0 as u32 + 1, + get_command(&contents, span), + Box::new(GenericError::new( + "Attempted to run assert_return before a module was compiled", + )), + ); + continue; + } + + let interpeter = interpeter.as_mut().unwrap(); + + let err_or_panic: Result<(), Box> = + match catch_unwind(AssertUnwindSafe(|| { + execute_assert_return(interpeter, exec, results) + })) { + Ok(original_result) => original_result, + Err(inner) => { + // TODO: Do we want to exit on panic? State may be left in an inconsistent state, and cascading panics may occur. + if let Ok(msg) = inner.downcast::<&str>() { + Err(Box::new(PanicError::new(msg.to_string()))) + } else { + Err(Box::new(PanicError::new("Unknown panic".into()))) + } + } + }; + + match err_or_panic { + Ok(_) => { + asserts.push_success( + filepath.to_string(), + span.linecol_in(&contents).0 as u32 + 1, + get_command(&contents, span), + ); + } + Err(inner) => { + asserts.push_error( + filepath.to_string(), + span.linecol_in(&contents).0 as u32 + 1, + get_command(&contents, span), + inner, + ); + } + } + } + wast::WastDirective::AssertMalformed { + span, + module: _, + message: _, + } + | wast::WastDirective::AssertInvalid { + span, + module: _, + message: _, + } + | wast::WastDirective::Register { + span, + name: _, + module: _, + } + | wast::WastDirective::AssertTrap { + span, + exec: _, + message: _, + } + | wast::WastDirective::AssertExhaustion { + span, + call: _, + message: _, + } + | wast::WastDirective::AssertUnlinkable { + span, + module: _, + message: _, + } + | wast::WastDirective::AssertException { span, exec: _ } => { + asserts.push_error( + filepath.to_string(), + span.linecol_in(&contents).0 as u32 + 1, + get_command(&contents, span), + Box::new(GenericError::new("Assert directive not yet implemented")), + ); + } + wast::WastDirective::Wait { span: _, thread: _ } => todo!(), + wast::WastDirective::Invoke(_) => todo!(), + wast::WastDirective::Thread(_) => todo!(), + } + } + + asserts.into() +} + +fn execute_assert_return( + interpeter: &mut RuntimeInstance, + exec: wast::WastExecute, + results: Vec, +) -> Result<(), Box> { + match exec { + wast::WastExecute::Invoke(invoke_info) => { + let args = invoke_info + .args + .into_iter() + .map(arg_to_value) + .collect::>(); + + let result_vals = results.into_iter().map(result_to_value).collect::>(); + let result_types = result_vals + .iter() + .map(|val| val.to_ty()) + .collect::>(); + + let actual = interpeter + .invoke_named_dynamic(invoke_info.name, args, &result_types) + .map_err(|err| Box::::new(err.into()))?; + + AssertEqError::assert_eq(actual, result_vals)?; + } + wast::WastExecute::Get { + span: _, + module: _, + global: _, + } => todo!(), + wast::WastExecute::Wat(_) => todo!(), + } + + Ok(()) +} + +pub fn arg_to_value(arg: wast::WastArg) -> Value { + match arg { + wast::WastArg::Core(core_arg) => match core_arg { + wast::core::WastArgCore::I32(val) => Value::I32(val as u32), + wast::core::WastArgCore::I64(val) => Value::I64(val as u64), + wast::core::WastArgCore::F32(_) => todo!(), + wast::core::WastArgCore::F64(_) => todo!(), + wast::core::WastArgCore::V128(_) => todo!(), + wast::core::WastArgCore::RefNull(_) => todo!(), + wast::core::WastArgCore::RefExtern(_) => todo!(), + wast::core::WastArgCore::RefHost(_) => todo!(), + }, + wast::WastArg::Component(_) => todo!(), + } +} + +fn result_to_value(result: wast::WastRet) -> Value { + match result { + wast::WastRet::Core(core_arg) => match core_arg { + wast::core::WastRetCore::I32(val) => Value::I32(val as u32), + wast::core::WastRetCore::I64(val) => Value::I64(val as u64), + wast::core::WastRetCore::F32(_) => todo!(), + wast::core::WastRetCore::F64(_) => todo!(), + wast::core::WastRetCore::V128(_) => todo!(), + wast::core::WastRetCore::RefNull(_) => todo!(), + wast::core::WastRetCore::RefExtern(_) => todo!(), + wast::core::WastRetCore::RefHost(_) => todo!(), + wast::core::WastRetCore::RefFunc(_) => todo!(), + wast::core::WastRetCore::RefAny => todo!(), + wast::core::WastRetCore::RefEq => todo!(), + wast::core::WastRetCore::RefArray => todo!(), + wast::core::WastRetCore::RefStruct => todo!(), + wast::core::WastRetCore::RefI31 => todo!(), + wast::core::WastRetCore::Either(_) => todo!(), + }, + wast::WastRet::Component(_) => todo!(), + } +} + +pub fn get_command(contents: &str, span: wast::token::Span) -> String { + contents[span.offset() as usize..] + .lines() + .next() + .unwrap_or("") + .to_string() +} diff --git a/tests/specification/test_errors.rs b/tests/specification/test_errors.rs new file mode 100644 index 00000000..0c94cfc7 --- /dev/null +++ b/tests/specification/test_errors.rs @@ -0,0 +1,88 @@ +use std::error::Error; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AssertEqError { + left: String, + right: String, +} + +impl AssertEqError { + pub fn assert_eq(left: T, right: T) -> Result<(), Self> { + if left != right { + return Err(AssertEqError { + left: format!("{:?}", left), + right: format!("{:?}", right), + }); + } + + Ok(()) + } +} +impl Error for AssertEqError {} +impl std::fmt::Display for AssertEqError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "assert_eq failed: left: {}, right: {}", + self.left, self.right + ) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PanicError { + message: String, +} + +impl PanicError { + pub fn new(message: String) -> Self { + PanicError { message } + } +} + +impl Error for PanicError {} +impl std::fmt::Display for PanicError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Panic: {}", self.message) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct WasmInterpreterError(wasm::Error); + +impl Error for WasmInterpreterError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.0 { + wasm::Error::MalformedUtf8String(inner) => Some(inner), + _ => None, + } + } +} + +impl From for WasmInterpreterError { + fn from(error: wasm::Error) -> Self { + WasmInterpreterError(error) + } +} + +impl std::fmt::Display for WasmInterpreterError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.0) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct GenericError(String); + +impl GenericError { + pub fn new(message: &str) -> Self { + GenericError(message.to_string()) + } +} + +impl Error for GenericError {} +impl std::fmt::Display for GenericError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/tests/specification/testsuite b/tests/specification/testsuite new file mode 160000 index 00000000..53da17c0 --- /dev/null +++ b/tests/specification/testsuite @@ -0,0 +1 @@ +Subproject commit 53da17c0936a23f68f97cde4f9346a0a374dc35f diff --git a/tests/wasm_spec_testsuite.rs b/tests/wasm_spec_testsuite.rs new file mode 100644 index 00000000..0fdfb322 --- /dev/null +++ b/tests/wasm_spec_testsuite.rs @@ -0,0 +1,5 @@ +// The reason this file exists is only to expose the `specification` module to the outside world. +// More so, the reason it wasn't added to the `lib.rs` file is because we wanted to separate the +// regular tests from the spec tests. +#[cfg(feature = "spec-test")] +mod specification;