diff --git a/.gitignore b/.gitignore index 143b1ca..6e67dd4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /target/ **/*.rs.bk Cargo.lock +spec/target diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ed58662 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spec/wabt"] + path = spec/wabt + url = https://github.com/WebAssembly/wabt.git diff --git a/spec/Cargo.toml b/spec/Cargo.toml new file mode 100644 index 0000000..954f8d4 --- /dev/null +++ b/spec/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "parity-wasm-testsuite" +version = "0.0.0" +authors = ["NikVolf "] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/nikvolf/parity-wasm" +homepage = "https://github.com/nikvolf/parity-wasm" +description = "parity-wasm testsuite" +build = "build.rs" + +[build-dependencies] +cmake = "0.1" + +[dependencies] +parity-wasm-interp = { path = ".." } +serde_json = "1.0" +serde_derive = "1.0" +serde = "1.0" + +[dev-dependencies] +parity-wasm-interp = { path = ".." } +serde_json = "1.0" +serde_derive = "1.0" +serde = "1.0" \ No newline at end of file diff --git a/spec/build.rs b/spec/build.rs new file mode 100644 index 0000000..596e8a4 --- /dev/null +++ b/spec/build.rs @@ -0,0 +1,8 @@ +extern crate cmake; +use cmake::Config; + +fn main() { + let _dst = Config::new("wabt") + .define("BUILD_TESTS", "OFF") + .build(); +} \ No newline at end of file diff --git a/spec/src/fixtures.rs b/spec/src/fixtures.rs new file mode 100644 index 0000000..814535c --- /dev/null +++ b/spec/src/fixtures.rs @@ -0,0 +1,132 @@ +macro_rules! run_test { + ($label: expr, $test_name: ident, fail) => ( + #[test] + fn $test_name() { + ::run::failing_spec($label) + } + ); + ($label: expr, $test_name: ident) => ( + #[test] + fn $test_name() { + ::run::spec($label) + } + ); +} + +run_test!("address-offset-range.fail", wasm_address_offset_range_fail, fail); +run_test!("address", wasm_address); +run_test!("binary", wasm_binary); +run_test!("block-end-label-mismatch.fail", wasm_block_end_label_mismatch_fail, fail); +run_test!("block-end-label-superfluous.fail", wasm_block_end_label_superfluous_fail, fail); +run_test!("block", wasm_block); +run_test!("br_if", wasm_br_if); +run_test!("br_table", wasm_br_table); +run_test!("br", wasm_br); +run_test!("break-drop", wasm_break_drop); +run_test!("call_indirect", wasm_call_indirect); +run_test!("call", wasm_call); +run_test!("comments", wasm_comments); +// TODO: commented out until sNaN issue is resolved: +// https://github.com/NikVolf/parity-wasm/blob/b5aaf103cf28f1e36df832f4883f55043e67894b/src/interpreter/value.rs#L510 +// run_test!("conversions", wasm_conversions); +run_test!("custom_section", wasm_custom_section); +run_test!("endianness", wasm_endianness); +run_test!("f32_exports", wasm_exports); +run_test!("f32_bitwise", wasm_f32_bitwise); +run_test!("f32_cmp", wasm_f32_cmp); +run_test!("f32.load32.fail", wasm_f32_load32_fail, fail); +run_test!("f32.load64.fail", wasm_f32_load64_fail, fail); +run_test!("f32.store32.fail", wasm_f32_store32_fail, fail); +run_test!("f32.store64.fail", wasm_f32_store64_fail, fail); +run_test!("f32", wasm_f32); +run_test!("f64_bitwise", wasm_f64_bitwise); +run_test!("f64_cmp", wasm_f64_cmp); +run_test!("f64.load32.fail", wasm_f64_load32_fail, fail); +run_test!("f64.load64.fail", wasm_f64_load64_fail, fail); +run_test!("f64.store32.fail", wasm_f64_store32_fail, fail); +run_test!("f64.store64.fail", wasm_f64_store64_fail, fail); +run_test!("f64", wasm_f64); +run_test!("fac", wasm_fac); +// TODO: commented out until sNaN issue is resolved: +// https://github.com/NikVolf/parity-wasm/blob/b5aaf103cf28f1e36df832f4883f55043e67894b/src/interpreter/value.rs#L510 +// run_test!("float_exprs", wasm_float_exprs); +// run_test!("float_literals", wasm_float_literals); +// run_test!("float_memory", wasm_float_memory); +run_test!("float_misc", wasm_float_misc); +run_test!("forward", wasm_forward); +run_test!("func_ptrs", wasm_func_ptrs); +run_test!("func-local-after-body.fail", wasm_func_local_after_body_fail, fail); +run_test!("func-local-before-param.fail", wasm_func_local_before_param_fail, fail); +run_test!("func-local-before-result.fail", wasm_func_local_before_result_fail, fail); +run_test!("func-param-after-body.fail", wasm_func_param_after_body_fail, fail); +run_test!("func-result-after-body.fail", wasm_func_result_after_body_fail, fail); +run_test!("func-result-before-param.fail", wasm_func_result_before_param_fail, fail); +run_test!("func", wasm_func); +run_test!("get_local", wasm_get_local); +run_test!("globals", wasm_globals); +run_test!("i32.load32_s.fail", wasm_i32_load32s_fail, fail); +run_test!("i32.load32_u.fail", wasm_i32_load32u_fail, fail); +run_test!("i32.load64_s.fail", wasm_i32_load64s_fail, fail); +run_test!("i32.load64_u.fail", wasm_i32_load64u_fail, fail); +run_test!("i32.store32.fail", wasm_i32_store32_fail, fail); +run_test!("i32.store64.fail", wasm_i32_store64_fail, fail); +run_test!("i32", wasm_i32); +run_test!("i64.load64_s.fail", wasm_i64_load64s_fail, fail); +run_test!("i64.load64_u.fail", wasm_i64_load64u_fail, fail); +run_test!("i64.store64.fail", wasm_i64_store64_fail, fail); +run_test!("i64", wasm_i64); +run_test!("if-else-end-label-mismatch.fail", wasm_if_else_end_label_mismatch_fail, fail); +run_test!("if-else-end-label-superfluous.fail", wasm_if_else_end_label_superfluous_fail, fail); +run_test!("if-else-label-mismatch.fail", wasm_if_else_label_mismatch_fail, fail); +run_test!("if-else-label-superfluous.fail", wasm_if_else_label_superfluous_fail, fail); +run_test!("if-end-label-mismatch.fail", wasm_if_end_label_mismatch_fail, fail); +run_test!("if-end-label-superfluous.fail", wasm_if_end_label_superfluous_fail, fail); +run_test!("if", wasm_if); +run_test!("import-after-func.fail", wasm_import_after_func_fail, fail); +run_test!("import-after-global.fail", wasm_import_after_global_fail, fail); +run_test!("import-after-memory.fail", wasm_import_after_memory_fail, fail); +run_test!("import-after-table.fail", wasm_import_after_table_fail, fail); +run_test!("imports", wasm_imports); +run_test!("int_exprs", wasm_int_exprs); +run_test!("int_literals", wasm_int_literals); +run_test!("labels", wasm_labels); +run_test!("left-to-right", wasm_left_to_right); +run_test!("linking", wasm_linking); +run_test!("load-align-0.fail", wasm_load_align_0_fail, fail); +run_test!("load-align-big.fail", wasm_load_align_big_fail, fail); +run_test!("load-align-odd.fail", wasm_load_align_odd_fail, fail); +run_test!("loop-end-label-mismatch.fail", wasm_end_label_mismatch_fail, fail); +run_test!("loop-end-label-superfluous.fail", wasm_end_label_superfluous_fail, fail); +run_test!("loop", wasm_loop); +run_test!("memory_redundancy", wasm_memory_redundancy); +run_test!("memory_trap", wasm_memory_trap); +run_test!("memory", wasm_memory); +run_test!("names", wasm_names); +run_test!("nop", wasm_nop); +run_test!("of_string-overflow-hex-u32.fail", wasm_of_string_overflow_hex_u32_fail, fail); +run_test!("of_string-overflow-hex-u64.fail", wasm_of_string_overflow_hex_u64_fail, fail); +run_test!("of_string-overflow-s32.fail", wasm_of_string_overflow_s32_fail, fail); +run_test!("of_string-overflow-s64.fail", wasm_of_string_overflow_s64_fail, fail); +run_test!("of_string-overflow-u32.fail", wasm_of_string_overflow_u32_fail, fail); +run_test!("of_string-overflow-u64.fail", wasm_of_string_overflow_u64_fail, fail); +run_test!("resizing", wasm_resizing); +run_test!("return", wasm_return); +run_test!("select", wasm_select); +run_test!("set_local", wasm_set_local); +run_test!("skip-stack-guard-page", wasm_skip_stack_guard_page); +run_test!("stack", wasm_stack); +run_test!("start", wasm_start); +run_test!("store_retval", wasm_store_retval); +run_test!("store-align-0.fail", wasm_store_align_0_fail, fail); +run_test!("store-align-big.fail", wasm_store_align_big_fail, fail); +run_test!("store-align-odd.fail", wasm_store_align_odd_fail, fail); +run_test!("switch", wasm_switch); +run_test!("tee_local", wasm_tee_local); +run_test!("traps", wasm_traps); +run_test!("typecheck", wasm_typecheck); +run_test!("unreachable", wasm_unreachable); +run_test!("unreached-invalid", wasm_unreached_invalid); +run_test!("unwind", wasm_unwind); +run_test!("utf8-custom-selection-id", wasm_utf8_custom_selection_id); +run_test!("utf8-import-field", wasm_utf8_import_field); +run_test!("utf8-import-module", wasm_utf8_import_module); diff --git a/spec/src/lib.rs b/spec/src/lib.rs new file mode 100644 index 0000000..e9038c5 --- /dev/null +++ b/spec/src/lib.rs @@ -0,0 +1,9 @@ + +#[cfg_attr(test, macro_use)] #[cfg(test)] extern crate serde_derive; +extern crate parity_wasm_interp; +extern crate serde; +extern crate serde_json; + +mod run; +mod test; +mod fixtures; diff --git a/spec/src/run.rs b/spec/src/run.rs new file mode 100644 index 0000000..cf5fb63 --- /dev/null +++ b/spec/src/run.rs @@ -0,0 +1,563 @@ +#![cfg(test)] + +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::fs::File; +use std::collections::HashMap; + +use serde_json; +use super::test; +use parity_wasm_interp::{ + Error as InterpreterError, Externals, FuncInstance, FuncRef, + GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, + MemoryInstance, MemoryRef, ModuleImportResolver, ModuleInstance, + ModuleRef, RuntimeValue, TableInstance, TableRef, ValueType, + load_from_buffer, LoadedModule, Signature, MemoryDescriptor, TableDescriptor, GlobalDescriptor, +}; + +#[derive(Debug)] +enum Error { + Load(String), + Interpreter(InterpreterError), +} + +impl From for Error { + fn from(e: InterpreterError) -> Error { + Error::Interpreter(e) + } +} + +struct SpecModule { + table: TableRef, + memory: MemoryRef, + global_i32: GlobalRef, + global_i64: GlobalRef, + global_f32: GlobalRef, + global_f64: GlobalRef, +} + +impl SpecModule { + fn new() -> Self { + SpecModule { + table: TableInstance::alloc(10, Some(20)).unwrap(), + memory: MemoryInstance::alloc(1, Some(2)).unwrap(), + global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false), + global_i64: GlobalInstance::alloc(RuntimeValue::I64(666), false), + global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false), + global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0), false), + } + } +} + +const PRINT_FUNC_INDEX: usize = 0; + +impl Externals for SpecModule { + fn invoke_index( + &mut self, + index: usize, + args: &[RuntimeValue], + ) -> Result, InterpreterError> { + match index { + PRINT_FUNC_INDEX => { + println!("print: {:?}", args); + Ok(None) + } + _ => panic!("SpecModule doesn't provide function at index {}", index), + } + } +} + +impl ModuleImportResolver for SpecModule { + fn resolve_func( + &self, + field_name: &str, + func_type: &Signature, + ) -> Result { + if field_name == "print" { + if func_type.return_type().is_some() { + return Err(InterpreterError::Instantiation( + "Function `print` have unit return type".into(), + )); + } + + let func = FuncInstance::alloc_host(func_type.clone(), PRINT_FUNC_INDEX); + return Ok(func); + } + + Err(InterpreterError::Instantiation( + format!("Unknown host func import {}", field_name), + )) + } + + fn resolve_global( + &self, + field_name: &str, + global_type: &GlobalDescriptor, + ) -> Result { + if field_name == "global" { + return match global_type.value_type() { + ValueType::I32 => Ok(self.global_i32.clone()), + ValueType::I64 => Ok(self.global_i64.clone()), + ValueType::F32 => Ok(self.global_f32.clone()), + ValueType::F64 => Ok(self.global_f64.clone()), + }; + } + + Err(InterpreterError::Instantiation( + format!("Unknown host global import {}", field_name), + )) + } + + fn resolve_memory( + &self, + field_name: &str, + _memory_type: &MemoryDescriptor, + ) -> Result { + if field_name == "memory" { + return Ok(self.memory.clone()); + } + + Err(InterpreterError::Instantiation( + format!("Unknown host memory import {}", field_name), + )) + } + + fn resolve_table( + &self, + field_name: &str, + _table_type: &TableDescriptor, + ) -> Result { + if field_name == "table" { + return Ok(self.table.clone()); + } + + Err(InterpreterError::Instantiation( + format!("Unknown host table import {}", field_name), + )) + } +} + +struct SpecDriver { + spec_module: SpecModule, + instances: HashMap, +} + +impl SpecDriver { + fn new() -> SpecDriver { + SpecDriver { + spec_module: SpecModule::new(), + instances: HashMap::new(), + } + } + + fn spec_module(&mut self) -> &mut SpecModule { + &mut self.spec_module + } + + fn add_module(&mut self, name: String, module: ModuleRef) { + self.instances.insert(name, module); + } + + fn module(&self, name: &str) -> Result { + self.instances.get(name).cloned().ok_or_else(|| { + InterpreterError::Instantiation(format!("Module not registered {}", name)) + }) + } +} + +impl ImportResolver for SpecDriver { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + func_type: &Signature, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_func(field_name, func_type) + } else { + self.module(module_name)? + .resolve_func(field_name, func_type) + } + } + + fn resolve_global( + &self, + module_name: &str, + field_name: &str, + global_type: &GlobalDescriptor, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_global(field_name, global_type) + } else { + self.module(module_name)? + .resolve_global(field_name, global_type) + } + } + + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + memory_type: &MemoryDescriptor, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_memory(field_name, memory_type) + } else { + self.module(module_name)? + .resolve_memory(field_name, memory_type) + } + } + + fn resolve_table( + &self, + module_name: &str, + field_name: &str, + table_type: &TableDescriptor, + ) -> Result { + if module_name == "spectest" { + self.spec_module.resolve_table(field_name, table_type) + } else { + self.module(module_name)? + .resolve_table(field_name, table_type) + } + } +} + +fn try_load_module(base_dir: &Path, module_path: &str) -> Result { + use std::io::prelude::*; + + let mut wasm_path = PathBuf::from(base_dir.clone()); + wasm_path.push(module_path); + let mut file = File::open(wasm_path).unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + load_from_buffer(buf).map_err(|e| Error::Load(e.to_string())) +} + +fn try_load( + base_dir: &Path, + module_path: &str, + spec_driver: &mut SpecDriver, +) -> Result<(), Error> { + let module = try_load_module(base_dir, module_path)?; + let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?; + instance.run_start(spec_driver.spec_module())?; + Ok(()) +} + +fn load_module( + base_dir: &Path, + path: &str, + name: &Option, + spec_driver: &mut SpecDriver, +) -> ModuleRef { + let module = + try_load_module(base_dir, path).expect(&format!("Wasm file {} failed to load", path)); + let instance = ModuleInstance::new(&module, spec_driver) + .expect("Instantiation failed") + .run_start(spec_driver.spec_module()) + .expect("Run start failed"); + + let module_name = name.as_ref() + .map(|s| s.as_ref()) + .unwrap_or("wasm_test") + .trim_left_matches('$'); + spec_driver.add_module(module_name.to_owned(), instance.clone()); + + instance +} + +fn runtime_value(test_val: &test::RuntimeValue) -> RuntimeValue { + match test_val.value_type.as_ref() { + "i32" => { + let unsigned: u32 = test_val.value.parse().expect("Literal parse error"); + RuntimeValue::I32(unsigned as i32) + } + "i64" => { + let unsigned: u64 = test_val.value.parse().expect("Literal parse error"); + RuntimeValue::I64(unsigned as i64) + } + "f32" => { + let unsigned: u32 = test_val.value.parse().expect("Literal parse error"); + RuntimeValue::decode_f32(unsigned) + } + "f64" => { + let unsigned: u64 = test_val.value.parse().expect("Literal parse error"); + RuntimeValue::decode_f64(unsigned) + } + _ => panic!("Unknwon runtime value type"), + } +} + +fn runtime_values(test_vals: &[test::RuntimeValue]) -> Vec { + test_vals + .iter() + .map(runtime_value) + .collect::>() +} + +fn run_action( + program: &mut SpecDriver, + action: &test::Action, +) -> Result, InterpreterError> { + match *action { + test::Action::Invoke { + ref module, + ref field, + ref args, + } => { + let module = module.clone().unwrap_or("wasm_test".into()); + let module = module.trim_left_matches('$'); + let module = program.module(&module).expect(&format!( + "Expected program to have loaded module {}", + module + )); + module.invoke_export( + &jstring_to_rstring(field), + &runtime_values(args), + program.spec_module(), + ) + } + test::Action::Get { + ref module, + ref field, + .. + } => { + let module = module.clone().unwrap_or("wasm_test".into()); + let module = module.trim_left_matches('$'); + let module = program.module(&module).expect(&format!( + "Expected program to have loaded module {}", + module + )); + let field = jstring_to_rstring(&field); + + let global = module + .export_by_name(&field) + .ok_or_else(|| { + InterpreterError::Global(format!("Expected to have export with name {}", field)) + })? + .as_global() + .ok_or_else(|| { + InterpreterError::Global(format!("Expected export {} to be a global", field)) + })?; + Ok(Some(global.get())) + } + } +} + +pub struct FixtureParams { + failing: bool, + json: String, +} + +pub fn run_wast2wasm(name: &str) -> FixtureParams { + let outdir = env::var("OUT_DIR").unwrap(); + + let mut wast2wasm_path = PathBuf::from(outdir.clone()); + wast2wasm_path.push("bin"); + wast2wasm_path.push("wast2wasm"); + + let mut json_spec_path = PathBuf::from(outdir.clone()); + json_spec_path.push(&format!("{}.json", name)); + + let wast2wasm_output = Command::new(wast2wasm_path) + .arg("--spec") + .arg("-o") + .arg(&json_spec_path) + .arg(&format!("./wabt/third_party/testsuite/{}.wast", name)) + .output() + .expect("Failed to execute process"); + + FixtureParams { + json: json_spec_path.to_str().unwrap().to_owned(), + failing: { + if !wast2wasm_output.status.success() { + println!("wasm2wast error code: {}", wast2wasm_output.status); + println!( + "wasm2wast stdout: {}", + String::from_utf8_lossy(&wast2wasm_output.stdout) + ); + println!( + "wasm2wast stderr: {}", + String::from_utf8_lossy(&wast2wasm_output.stderr) + ); + true + } else { + false + } + }, + } +} + +pub fn failing_spec(name: &str) { + let fixture = run_wast2wasm(name); + if !fixture.failing { + panic!("wasm2wast expected to fail, but terminated normally"); + } +} + +pub fn spec(name: &str) { + let tmpdir = env::var("OUT_DIR").unwrap(); + + let fixture = run_wast2wasm(name); + if fixture.failing { + panic!("wasm2wast terminated abnormally, expected to success"); + } + + let mut f = + File::open(&fixture.json).expect(&format!("Failed to load json file {}", &fixture.json)); + let spec: test::Spec = + serde_json::from_reader(&mut f).expect("Failed to deserialize JSON file"); + + let mut spec_driver = SpecDriver::new(); + let mut last_module = None; + for command in &spec.commands { + println!("command {:?}", command); + match command { + &test::Command::Module { + ref name, + ref filename, + .. + } => { + last_module = Some(load_module(tmpdir.as_ref(), &filename, &name, &mut spec_driver)); + } + &test::Command::AssertReturn { + line, + ref action, + ref expected, + } => { + let result = run_action(&mut spec_driver, action); + match result { + Ok(result) => { + let spec_expected = runtime_values(expected); + let actual_result = result.into_iter().collect::>(); + for (actual_result, spec_expected) in + actual_result.iter().zip(spec_expected.iter()) + { + assert_eq!(actual_result.value_type(), spec_expected.value_type()); + // f32::NAN != f32::NAN + match spec_expected { + &RuntimeValue::F32(val) if val.is_nan() => match actual_result { + &RuntimeValue::F32(val) => assert!(val.is_nan()), + _ => unreachable!(), // checked above that types are same + }, + &RuntimeValue::F64(val) if val.is_nan() => match actual_result { + &RuntimeValue::F64(val) => assert!(val.is_nan()), + _ => unreachable!(), // checked above that types are same + }, + spec_expected @ _ => assert_eq!(actual_result, spec_expected), + } + } + println!("assert_return at line {} - success", line); + } + Err(e) => { + panic!("Expected action to return value, got error: {:?}", e); + } + } + } + &test::Command::AssertReturnCanonicalNan { line, ref action } | + &test::Command::AssertReturnArithmeticNan { line, ref action } => { + let result = run_action(&mut spec_driver, action); + match result { + Ok(result) => { + for actual_result in result.into_iter().collect::>() { + match actual_result { + RuntimeValue::F32(val) => if !val.is_nan() { + panic!("Expected nan value, got {:?}", val) + }, + RuntimeValue::F64(val) => if !val.is_nan() { + panic!("Expected nan value, got {:?}", val) + }, + val @ _ => { + panic!("Expected action to return float value, got {:?}", val) + } + } + } + println!("assert_return_nan at line {} - success", line); + } + Err(e) => { + panic!("Expected action to return value, got error: {:?}", e); + } + } + } + &test::Command::AssertExhaustion { + line, ref action, .. + } => { + let result = run_action(&mut spec_driver, action); + match result { + Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), + Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), + } + } + &test::Command::AssertTrap { + line, ref action, .. + } => { + let result = run_action(&mut spec_driver, action); + match result { + Ok(result) => { + panic!( + "Expected action to result in a trap, got result: {:?}", + result + ); + } + Err(e) => { + println!("assert_trap at line {} - success ({:?})", line, e); + } + } + } + &test::Command::AssertInvalid { + line, ref filename, .. + } | + &test::Command::AssertMalformed { + line, ref filename, .. + } | + &test::Command::AssertUnlinkable { + line, ref filename, .. + } => { + let module_load = try_load(tmpdir.as_ref(), filename, &mut spec_driver); + match module_load { + Ok(_) => panic!("Expected invalid module definition, got some module!"), + Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e), + } + } + &test::Command::AssertUninstantiable { + line, ref filename, .. + } => match try_load(tmpdir.as_ref(), &filename, &mut spec_driver) { + Ok(_) => panic!("Expected error running start function at line {}", line), + Err(e) => println!("assert_uninstantiable - success ({:?})", e), + }, + &test::Command::Register { + ref name, + ref as_name, + .. + } => { + match name { + &Some(ref name) => assert_eq!(name.trim_left_matches('$'), as_name), // we have already registered this module without $ prefix + &None => spec_driver.add_module( + as_name.clone(), + last_module + .take() + .expect("Last module must be set for this command"), + ), + } + } + &test::Command::Action { line, ref action } => { + match run_action(&mut spec_driver, action) { + Ok(_) => {} + Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e), + } + } + } + } +} + +// Convert json string to correct rust UTF8 string. +// The reason is that, for example, rust character "\u{FEEF}" (3-byte UTF8 BOM) is represented as "\u00ef\u00bb\u00bf" in spec json. +// It is incorrect. Correct BOM representation in json is "\uFEFF" => we need to do a double utf8-parse here. +// This conversion is incorrect in general case (casting char to u8)!!! +fn jstring_to_rstring(jstring: &str) -> String { + let jstring_chars: Vec = jstring.chars().map(|c| c as u8).collect(); + let rstring = String::from_utf8(jstring_chars).unwrap(); + rstring +} diff --git a/spec/src/test.rs b/spec/src/test.rs new file mode 100644 index 0000000..8c4a2b3 --- /dev/null +++ b/spec/src/test.rs @@ -0,0 +1,104 @@ +#![cfg(test)] + +#[derive(Deserialize, Debug)] +pub struct RuntimeValue { + #[serde(rename = "type")] + pub value_type: String, + pub value: String, +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +pub enum Action { + #[serde(rename = "invoke")] + Invoke { + module: Option, + field: String, + args: Vec, + }, + #[serde(rename = "get")] + Get { + module: Option, + field: String, + } +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +pub enum Command { + #[serde(rename = "module")] + Module { + line: u64, + name: Option, + filename: String + }, + #[serde(rename = "assert_return")] + AssertReturn { + line: u64, + action: Action, + expected: Vec, + }, + #[serde(rename = "assert_return_canonical_nan")] + AssertReturnCanonicalNan { + line: u64, + action: Action, + }, + #[serde(rename = "assert_return_arithmetic_nan")] + AssertReturnArithmeticNan { + line: u64, + action: Action, + }, + #[serde(rename = "assert_trap")] + AssertTrap { + line: u64, + action: Action, + text: String, + }, + #[serde(rename = "assert_invalid")] + AssertInvalid { + line: u64, + filename: String, + text: String, + }, + #[serde(rename = "assert_malformed")] + AssertMalformed { + line: u64, + filename: String, + text: String, + }, + #[serde(rename = "assert_uninstantiable")] + AssertUninstantiable { + line: u64, + filename: String, + text: String, + }, + #[serde(rename = "assert_exhaustion")] + AssertExhaustion { + line: u64, + action: Action, + }, + #[serde(rename = "assert_unlinkable")] + AssertUnlinkable { + line: u64, + filename: String, + text: String, + }, + #[serde(rename = "register")] + Register { + line: u64, + name: Option, + #[serde(rename = "as")] + as_name: String, + }, + #[serde(rename = "action")] + Action { + line: u64, + action: Action, + }, +} + +#[derive(Deserialize, Debug)] +pub struct Spec { + pub source_filename: String, + pub commands: Vec, +} \ No newline at end of file diff --git a/spec/wabt b/spec/wabt new file mode 160000 index 0000000..1e5b546 --- /dev/null +++ b/spec/wabt @@ -0,0 +1 @@ +Subproject commit 1e5b546c65e8fef609431e77b1b2434dbf162e6c