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..70ce59d 100644 --- a/spec/src/run.rs +++ b/spec/src/run.rs @@ -1,4 +1,3 @@ -#![cfg(test)] use std::env; use std::path::{Path, PathBuf}; @@ -6,8 +5,6 @@ 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, @@ -17,6 +14,25 @@ use wasmi::{ TableDescriptor, GlobalDescriptor, FuncInstance, RuntimeArgs, }; use wasmi::memory_units::Pages; +use wabt::spec::{Action, Visitor as ScriptVisitor, Value, run_spec}; + +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), + } +} + +fn runtime_value_to_spec(value: RuntimeValue) -> Value { + match value { + RuntimeValue::I32(v) => Value::I32(v), + RuntimeValue::I64(v) => Value::I64(v), + RuntimeValue::F32(v) => Value::F32(v), + RuntimeValue::F64(v) => Value::F64(v), + } +} #[derive(Debug)] enum Error { @@ -241,23 +257,15 @@ 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, + wasm: &[u8], spec_driver: &mut SpecDriver, ) -> Result<(), Error> { - let module = try_load_module(base_dir, module_path)?; + let module = try_load_module(wasm)?; let instance = ModuleInstance::new(&module, &ImportsBuilder::default())?; instance .run_start(spec_driver.spec_module()) @@ -266,13 +274,12 @@ fn try_load( } fn load_module( - base_dir: &Path, - path: &str, + wasm: &[u8], name: &Option, spec_driver: &mut SpecDriver, ) -> ModuleRef { let module = - try_load_module(base_dir, path).expect(&format!("Wasm file {} failed to load", path)); + 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,41 +291,12 @@ 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, @@ -328,12 +306,12 @@ fn run_action( 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, .. @@ -342,8 +320,6 @@ fn run_action( "Expected program to have loaded module {:?}", module )); - let field = jstring_to_rstring(&field); - let global = module .export_by_name(&field) .ok_or_else(|| { @@ -402,173 +378,167 @@ pub fn run_wast2wasm(name: &str) -> FixtureParams { } } +struct SpecRunner { + spec_driver: SpecDriver, +} + +impl SpecRunner { + fn assert_nans(&mut self, line: u64, action: &Action) -> Result<(), Error> { + let result = run_action(&mut self.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); + } + } + Ok(()) + } + + fn assert_incorrect_modules(&mut self, line: u64, wasm: &[u8]) -> Result<(), Error> { + let module_load = try_load(wasm, &mut self.spec_driver); + match module_load { + Ok(_) => panic!("Expected invalid module definition, got some module!"), + Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e), + } + Ok(()) + } +} + +impl ScriptVisitor for SpecRunner { + fn module(&mut self, line: u64, wasm: &[u8], name: Option) -> Result<(), Error> { + load_module(wasm, &name, &mut self.spec_driver); + Ok(()) + } + + fn assert_return(&mut self, line: u64, action: &Action, expected: &[Value]) -> Result<(), Error> { + let result = run_action(&mut self.spec_driver, action); + match result { + Ok(result) => { + 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()) + { + 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); + } + } + Ok(()) + } + + fn assert_return_canonical_nan(&mut self, line: u64, action: &Action) -> Result<(), Error> { + self.assert_nans(line, action) + } + + fn assert_return_arithmetic_nan(&mut self, line: u64, action: &Action) -> Result<(), Error> { + self.assert_nans(line, action) + } + + fn assert_exhaustion(&mut self, line: u64, action: &Action) -> Result<(), Error> { + let result = run_action(&mut self.spec_driver, action); + match result { + Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), + Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), + } + Ok(()) + } + + fn assert_trap(&mut self, line: u64, action: &Action, text: &str) -> Result<(), Error> { + Ok(()) + } + + fn assert_invalid(&mut self, line: u64, wasm: &[u8], text: &str) -> Result<(), Error> { + self.assert_incorrect_modules(line, wasm) + } + + fn assert_malformed(&mut self, line: u64, wasm: &[u8], text: &str) -> Result<(), Error> { + self.assert_incorrect_modules(line, wasm) + } + + fn assert_unlinkable(&mut self, line: u64, wasm: &[u8], text: &str) -> Result<(), Error> { + self.assert_incorrect_modules(line, wasm) + } + + fn assert_uninstantiable(&mut self, line: u64, wasm: &[u8], text: &str) -> Result<(), Error> { + match try_load(wasm, &mut self.spec_driver) { + Ok(_) => panic!("Expected error running start function at line {}", line), + Err(e) => println!("assert_uninstantiable - success ({:?})", e), + } + Ok(()) + } + + fn register(&mut self, line: u64, name: Option<&str>, as_name: &str) -> Result<(), Error> { + let module = match self.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), + }; + self.spec_driver.add_module(Some(as_name.to_owned()), module); + Ok(()) + } + + fn perform_action(&mut self, line: u64, action: &Action) -> Result<(), Error> { + match run_action(&mut self.spec_driver, action) { + Ok(_) => {} + Err(e) => panic!("Failed to invoke action at line {}: {:?}", line, e), + } + Ok(()) + } +} + pub fn spec(name: &str) { - let tmpdir = env::var("OUT_DIR").unwrap(); + // let tmpdir = env::var("OUT_DIR").unwrap(); - let fixture = run_wast2wasm(name); + // 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; - } + // 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"); - } + // 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); - 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); - } - &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 { - line, - ref name, - ref 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), - } - } - } - } -} - -// 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 + let mut spec_runner = SpecRunner { + spec_driver: SpecDriver::new(), + }; + let spec_script_path = format!("./wabt/third_party/testsuite/{}.wast", name); + run_spec(spec_script_path, &mut spec_runner).expect("success"); } 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