diff --git a/.gitmodules b/.gitmodules index ed58662..186818e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "spec/wabt"] - path = spec/wabt - url = https://github.com/WebAssembly/wabt.git +[submodule "spec/testsuite"] + path = spec/testsuite + url = https://github.com/webassembly/testsuite diff --git a/spec/Cargo.toml b/spec/Cargo.toml index d63b900..125774b 100644 --- a/spec/Cargo.toml +++ b/spec/Cargo.toml @@ -7,19 +7,7 @@ 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] wasmi = { path = ".." } -serde_json = "1.0" -serde_derive = "1.0" -serde = "1.0" - -[dev-dependencies] -wasmi = { path = ".." } -serde_json = "1.0" -serde_derive = "1.0" -serde = "1.0" \ No newline at end of file +wabt = "0.2.0" diff --git a/spec/build.rs b/spec/build.rs deleted file mode 100644 index 596e8a4..0000000 --- a/spec/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -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/lib.rs b/spec/src/lib.rs index f40f2d6..21bac1a 100644 --- a/spec/src/lib.rs +++ b/spec/src/lib.rs @@ -1,9 +1,6 @@ -#[cfg_attr(test, macro_use)] #[cfg(test)] extern crate serde_derive; extern crate wasmi; -extern crate serde; -extern crate serde_json; +extern crate wabt; mod run; -mod test; mod fixtures; diff --git a/spec/src/run.rs b/spec/src/run.rs index cd8c0c7..f348df0 100644 --- a/spec/src/run.rs +++ b/spec/src/run.rs @@ -1,27 +1,30 @@ #![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 wasmi::{ - Error as InterpreterError, Externals, FuncRef, - GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, - MemoryInstance, MemoryRef, ModuleImportResolver, ModuleInstance, - ModuleRef, RuntimeValue, TableInstance, TableRef, ValueType, - Module, Signature, MemoryDescriptor, Trap, - TableDescriptor, GlobalDescriptor, FuncInstance, RuntimeArgs, -}; + Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor, + GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor, + MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap, + ValueType}; use wasmi::memory_units::Pages; +use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value}; + +fn spec_to_runtime_value(value: Value) -> RuntimeValue { + match value { + Value::I32(v) => RuntimeValue::I32(v), + Value::I64(v) => RuntimeValue::I64(v), + Value::F32(v) => RuntimeValue::F32(v), + Value::F64(v) => RuntimeValue::F64(v), + } +} #[derive(Debug)] enum Error { Load(String), Start(Trap), + Script(script::Error), Interpreter(InterpreterError), } @@ -31,6 +34,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: script::Error) -> Error { + Error::Script(e) + } +} + struct SpecModule { table: TableRef, memory: MemoryRef, @@ -88,9 +97,10 @@ impl ModuleImportResolver for SpecModule { return Ok(func); } - Err(InterpreterError::Instantiation( - format!("Unknown host func import {}", field_name), - )) + Err(InterpreterError::Instantiation(format!( + "Unknown host func import {}", + field_name + ))) } fn resolve_global( @@ -107,9 +117,10 @@ impl ModuleImportResolver for SpecModule { }; } - Err(InterpreterError::Instantiation( - format!("Unknown host global import {}", field_name), - )) + Err(InterpreterError::Instantiation(format!( + "Unknown host global import {}", + field_name + ))) } fn resolve_memory( @@ -121,9 +132,10 @@ impl ModuleImportResolver for SpecModule { return Ok(self.memory.clone()); } - Err(InterpreterError::Instantiation( - format!("Unknown host memory import {}", field_name), - )) + Err(InterpreterError::Instantiation(format!( + "Unknown host memory import {}", + field_name + ))) } fn resolve_table( @@ -135,9 +147,10 @@ impl ModuleImportResolver for SpecModule { return Ok(self.table.clone()); } - Err(InterpreterError::Instantiation( - format!("Unknown host table import {}", field_name), - )) + Err(InterpreterError::Instantiation(format!( + "Unknown host table import {}", + field_name + ))) } } @@ -176,9 +189,9 @@ impl SpecDriver { fn module_or_last(&self, name: Option<&str>) -> Result { match name { Some(name) => self.module(name), - None => self.last_module.clone().ok_or_else(|| { - InterpreterError::Instantiation("No modules registered".into()) - }), + None => self.last_module + .clone() + .ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())), } } } @@ -241,23 +254,12 @@ impl ImportResolver for SpecDriver { } } -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(); - Module::from_buffer(buf).map_err(|e| Error::Load(e.to_string())) +fn try_load_module(wasm: &[u8]) -> Result { + Module::from_buffer(wasm).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)?; +fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> { + let module = try_load_module(wasm)?; let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?; instance .run_start(spec_driver.spec_module()) @@ -265,14 +267,8 @@ fn try_load( 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)); +fn load_module(wasm: &[u8], name: &Option, spec_driver: &mut SpecDriver) -> ModuleRef { + let module = try_load_module(wasm).expect(&format!("Wasm failed to load")); let instance = ModuleInstance::new(&module, spec_driver) .expect("Instantiation failed") .run_start(spec_driver.spec_module()) @@ -284,66 +280,42 @@ fn load_module( 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, + action: &Action, ) -> Result, InterpreterError> { match *action { - test::Action::Invoke { + Action::Invoke { ref module, ref field, ref args, } => { - let module = program.module_or_last(module.as_ref().map(|x| x.as_ref())).expect(&format!( - "Expected program to have loaded module {:?}", - module - )); + let module = program + .module_or_last(module.as_ref().map(|x| x.as_ref())) + .expect(&format!( + "Expected program to have loaded module {:?}", + module + )); module.invoke_export( - &jstring_to_rstring(field), - &runtime_values(args), + field, + &args.iter() + .cloned() + .map(spec_to_runtime_value) + .collect::>(), program.spec_module(), ) } - test::Action::Get { + Action::Get { ref module, ref field, .. } => { - let module = program.module_or_last(module.as_ref().map(|x| x.as_ref())).expect(&format!( - "Expected program to have loaded module {:?}", - module - )); - let field = jstring_to_rstring(&field); - + let module = program + .module_or_last(module.as_ref().map(|x| x.as_ref())) + .expect(&format!( + "Expected program to have loaded module {:?}", + module + )); let global = module .export_by_name(&field) .ok_or_else(|| { @@ -359,92 +331,29 @@ fn run_action( } } -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("wast2json"); - - let mut json_spec_path = PathBuf::from(outdir.clone()); - json_spec_path.push(&format!("{}.json", name)); - - let wast2wasm_output = Command::new(wast2wasm_path) - .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!("wast2json error code: {}", wast2wasm_output.status); - println!( - "wast2json stdout: {}", - String::from_utf8_lossy(&wast2wasm_output.stdout) - ); - println!( - "wast2json stderr: {}", - String::from_utf8_lossy(&wast2wasm_output.stderr) - ); - true - } else { - false - } - }, - } -} - pub fn spec(name: &str) { - let tmpdir = env::var("OUT_DIR").unwrap(); - - let fixture = run_wast2wasm(name); - - let wast2wasm_fail_expected = name.ends_with(".fail"); - if wast2wasm_fail_expected { - if !fixture.failing { - panic!("wast2json expected to fail, but terminated normally"); - } - // Failing fixture, bail out. - return; - } - - if fixture.failing { - panic!("wast2json 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"); + println!("running test: {}", name); + try_spec(name).expect("Failed to run spec"); +} +fn try_spec(name: &str) -> Result<(), Error> { let mut spec_driver = SpecDriver::new(); - for command in &spec.commands { - println!("command {:?}", command); - match command { - &test::Command::Module { - ref name, - ref filename, - .. - } => { - load_module(tmpdir.as_ref(), &filename, &name, &mut spec_driver); + let spec_script_path = format!("testsuite/{}.wast", name); + let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script"); + while let Some(Command { kind, line }) = parser.next()? { + match kind { + CommandKind::Module { name, module, .. } => { + load_module(&module.into_vec()?, &name, &mut spec_driver); } - &test::Command::AssertReturn { - line, - ref action, - ref expected, - } => { - let result = run_action(&mut spec_driver, action); + CommandKind::AssertReturn { action, expected } => { + let result = run_action(&mut spec_driver, &action); match result { Ok(result) => { - let spec_expected = runtime_values(expected); + let spec_expected = expected + .iter() + .cloned() + .map(spec_to_runtime_value) + .collect::>(); let actual_result = result.into_iter().collect::>(); for (actual_result, spec_expected) in actual_result.iter().zip(spec_expected.iter()) @@ -470,9 +379,9 @@ pub fn spec(name: &str) { } } } - &test::Command::AssertReturnCanonicalNan { line, ref action } | - &test::Command::AssertReturnArithmeticNan { line, ref action } => { - let result = run_action(&mut spec_driver, action); + CommandKind::AssertReturnCanonicalNan { action } + | CommandKind::AssertReturnArithmeticNan { action } => { + let result = run_action(&mut spec_driver, &action); match result { Ok(result) => { for actual_result in result.into_iter().collect::>() { @@ -495,19 +404,15 @@ pub fn spec(name: &str) { } } } - &test::Command::AssertExhaustion { - line, ref action, .. - } => { - let result = run_action(&mut spec_driver, action); + CommandKind::AssertExhaustion { 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); + CommandKind::AssertTrap { action, .. } => { + let result = run_action(&mut spec_driver, &action); match result { Ok(result) => { panic!( @@ -520,55 +425,34 @@ pub fn spec(name: &str) { } } } - &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); + CommandKind::AssertInvalid { module, .. } + | CommandKind::AssertMalformed { module, .. } + | CommandKind::AssertUnlinkable { module, .. } => { + let module_load = try_load(&module.into_vec()?, &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 { - line, - ref name, - ref as_name, - .. - } => { + CommandKind::AssertUninstantiable { module, .. } => { + match try_load(&module.into_vec()?, &mut spec_driver) { + Ok(_) => panic!("Expected error running start function at line {}", line), + Err(e) => println!("assert_uninstantiable - success ({:?})", e), + } + } + CommandKind::Register { name, as_name, .. } => { let module = match spec_driver.module_or_last(name.as_ref().map(|x| x.as_ref())) { Ok(module) => module, Err(e) => panic!("No such module, at line {} - ({:?})", e, line), }; spec_driver.add_module(Some(as_name.clone()), module); } - &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), - } - } + CommandKind::PerformAction(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 + Ok(()) } diff --git a/spec/src/test.rs b/spec/src/test.rs deleted file mode 100644 index 8c4a2b3..0000000 --- a/spec/src/test.rs +++ /dev/null @@ -1,104 +0,0 @@ -#![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/testsuite b/spec/testsuite new file mode 160000 index 0000000..b38bf51 --- /dev/null +++ b/spec/testsuite @@ -0,0 +1 @@ +Subproject commit b38bf51ea9c956951b9466aa8243d9727d009bb2 diff --git a/spec/wabt b/spec/wabt deleted file mode 160000 index a74072b..0000000 --- a/spec/wabt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a74072b2163ca20645e4a313636507cb3984f5fb