Merge pull request #7142 from paritytech/pwasm-run
WASM test runner utility
This commit is contained in:
commit
e8787c1acf
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -2477,6 +2477,22 @@ dependencies = [
|
|||||||
"getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
"getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pwasm-run-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ethcore-bigint 0.2.1",
|
||||||
|
"ethcore-logger 1.9.0",
|
||||||
|
"ethjson 0.1.0",
|
||||||
|
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"vm 0.1.0",
|
||||||
|
"wasm 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quasi"
|
name = "quasi"
|
||||||
version = "0.32.0"
|
version = "0.32.0"
|
||||||
|
@ -116,4 +116,4 @@ lto = false
|
|||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["ethstore/cli", "ethkey/cli", "evmbin", "whisper", "chainspec", "dapps/js-glue"]
|
members = ["ethstore/cli", "ethkey/cli", "evmbin", "whisper", "chainspec", "dapps/js-glue", "ethcore/wasm/run"]
|
||||||
|
19
ethcore/wasm/run/Cargo.toml
Normal file
19
ethcore/wasm/run/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "pwasm-run-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
ethcore-bigint = { path = "../../../util/bigint" }
|
||||||
|
ethjson = { path = "../../../json" }
|
||||||
|
vm = { path = "../../vm" }
|
||||||
|
wasm = { path = "../" }
|
||||||
|
clap = "2.24"
|
||||||
|
ethcore-logger = { path = "../../../logger" }
|
||||||
|
rustc-hex = "1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["ethcore-bigint/std"]
|
31
ethcore/wasm/run/res/sample-fixture.json
Normal file
31
ethcore/wasm/run/res/sample-fixture.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"caption": "Sample test",
|
||||||
|
"wasm": "./res/sample1.wasm",
|
||||||
|
"address": "0x1000000000000000000000000000000000000001",
|
||||||
|
"sender": "0x1000000000000000000000000000000000000002",
|
||||||
|
"value": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"gasLimit": 100000,
|
||||||
|
"payload": "0x",
|
||||||
|
"asserts": [
|
||||||
|
{ "Return": "0x01" },
|
||||||
|
{ "UsedGas": 17 },
|
||||||
|
{ "HasCall": { "codeAddress": "0x1000000000000000000000000000000000000002" }},
|
||||||
|
{ "HasStorage":
|
||||||
|
{
|
||||||
|
"key": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
"value": "0x0000000000000000000000000000000000000000000000000000000000000002"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Keccak test",
|
||||||
|
"wasm": "./res/sample2.wasm",
|
||||||
|
"payload": "0x736f6d657468696e67",
|
||||||
|
"gasLimit": 100000,
|
||||||
|
"asserts": [
|
||||||
|
{ "Return": "0x68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
BIN
ethcore/wasm/run/res/sample1.wasm
Normal file
BIN
ethcore/wasm/run/res/sample1.wasm
Normal file
Binary file not shown.
BIN
ethcore/wasm/run/res/sample2.wasm
Normal file
BIN
ethcore/wasm/run/res/sample2.wasm
Normal file
Binary file not shown.
49
ethcore/wasm/run/src/fixture.rs
Normal file
49
ethcore/wasm/run/src/fixture.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use ethjson::uint::Uint;
|
||||||
|
use ethjson::hash::{Address, H256};
|
||||||
|
use ethjson::bytes::Bytes;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Fixture {
|
||||||
|
pub caption: Cow<'static, String>,
|
||||||
|
#[serde(rename="wasm")]
|
||||||
|
pub wasm_file: Cow<'static, String>,
|
||||||
|
pub address: Option<Address>,
|
||||||
|
pub sender: Option<Address>,
|
||||||
|
pub value: Option<Uint>,
|
||||||
|
#[serde(rename="gasLimit")]
|
||||||
|
pub gas_limit: Option<u64>,
|
||||||
|
pub payload: Option<Bytes>,
|
||||||
|
pub storage: Option<Vec<StorageEntry>>,
|
||||||
|
pub asserts: Vec<Assert>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct StorageEntry {
|
||||||
|
pub key: Uint,
|
||||||
|
pub value: Uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct CallLocator {
|
||||||
|
pub sender: Option<Address>,
|
||||||
|
pub receiver: Option<Address>,
|
||||||
|
pub value: Option<Uint>,
|
||||||
|
pub data: Option<Bytes>,
|
||||||
|
#[serde(rename="codeAddress")]
|
||||||
|
pub code_address: Option<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct StorageAssert {
|
||||||
|
pub key: H256,
|
||||||
|
pub value: H256,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub enum Assert {
|
||||||
|
HasCall(CallLocator),
|
||||||
|
HasStorage(StorageAssert),
|
||||||
|
UsedGas(u64),
|
||||||
|
Return(Bytes),
|
||||||
|
}
|
46
ethcore/wasm/run/src/main.rs
Normal file
46
ethcore/wasm/run/src/main.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
extern crate ethcore_bigint;
|
||||||
|
extern crate ethjson;
|
||||||
|
extern crate wasm;
|
||||||
|
extern crate vm;
|
||||||
|
extern crate clap;
|
||||||
|
extern crate ethcore_logger;
|
||||||
|
extern crate rustc_hex;
|
||||||
|
|
||||||
|
mod fixture;
|
||||||
|
mod runner;
|
||||||
|
|
||||||
|
use fixture::Fixture;
|
||||||
|
use clap::{App, Arg};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::ethcore_logger::init_log();
|
||||||
|
|
||||||
|
let matches = App::new("pwasm-run-test")
|
||||||
|
.arg(Arg::with_name("target")
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.multiple(true)
|
||||||
|
.help("JSON fixture"))
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let mut exit_code = 0;
|
||||||
|
|
||||||
|
for target in matches.values_of("target").expect("No target parameter") {
|
||||||
|
let mut f = fs::File::open(target).expect("Failed to open file");
|
||||||
|
let fixtures: Vec<Fixture> = serde_json::from_reader(&mut f).expect("Failed to deserialize json");
|
||||||
|
|
||||||
|
for fixture in fixtures.into_iter() {
|
||||||
|
let fails = runner::run_fixture(&fixture);
|
||||||
|
for fail in fails.iter() {
|
||||||
|
exit_code = 1;
|
||||||
|
println!("Failed assert in test \"{}\" ('{}'): {}", fixture.caption.as_ref(), target, fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(exit_code);
|
||||||
|
}
|
204
ethcore/wasm/run/src/runner.rs
Normal file
204
ethcore/wasm/run/src/runner.rs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
use fixture::{Fixture, Assert, CallLocator};
|
||||||
|
use wasm::WasmInterpreter;
|
||||||
|
use vm::{self, Vm, GasLeft, ActionParams, ActionValue};
|
||||||
|
use vm::tests::FakeExt;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
use std::{fs, path, fmt};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ethcore_bigint::prelude::{U256, H256, H160};
|
||||||
|
use rustc_hex::ToHex;
|
||||||
|
|
||||||
|
fn load_code<P: AsRef<path::Path>>(p: P) -> io::Result<Vec<u8>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut f = fs::File::open(p)?;
|
||||||
|
f.read_to_end(&mut result)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wasm_interpreter() -> WasmInterpreter {
|
||||||
|
WasmInterpreter::new().expect("wasm interpreter to create without errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Fail {
|
||||||
|
Return { expected: Vec<u8>, actual: Vec<u8> },
|
||||||
|
UsedGas { expected: u64, actual: u64 },
|
||||||
|
Runtime(String),
|
||||||
|
Load(io::Error),
|
||||||
|
NoCall(CallLocator),
|
||||||
|
StorageMismatch { key: H256, expected: H256, actual: Option<H256> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fail {
|
||||||
|
fn runtime(err: vm::Error) -> Vec<Fail> {
|
||||||
|
vec![Fail::Runtime(format!("{}", err))]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(err: io::Error) -> Vec<Fail> {
|
||||||
|
vec![Fail::Load(err)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Fail {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::Fail::*;
|
||||||
|
match *self {
|
||||||
|
Return { ref expected, ref actual } =>
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Expected to return result: 0x{} ({} bytes), but got 0x{} ({} bytes)",
|
||||||
|
expected.to_hex(),
|
||||||
|
expected.len(),
|
||||||
|
actual.to_hex(),
|
||||||
|
actual.len()
|
||||||
|
),
|
||||||
|
UsedGas { expected, actual } =>
|
||||||
|
write!(f, "Expected to use gas: {}, but got actual gas used: {}", expected, actual),
|
||||||
|
|
||||||
|
Runtime(ref s) =>
|
||||||
|
write!(f, "WASM Runtime error: {}", s),
|
||||||
|
|
||||||
|
Load(ref e) =>
|
||||||
|
write!(f, "Load i/o error: {}", e),
|
||||||
|
|
||||||
|
NoCall(ref call) =>
|
||||||
|
write!(f, "Call not found: {:?}", call),
|
||||||
|
|
||||||
|
StorageMismatch { ref key, ref expected, actual: Some(ref actual)} =>
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Storage key {} value mismatch, expected {}, got: {}",
|
||||||
|
key.as_ref().to_vec().to_hex(),
|
||||||
|
expected.as_ref().to_vec().to_hex(),
|
||||||
|
actual.as_ref().to_vec().to_hex(),
|
||||||
|
),
|
||||||
|
|
||||||
|
StorageMismatch { ref key, ref expected, actual: None} =>
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"No expected storage value for key {} found, expected {}",
|
||||||
|
key.as_ref().to_vec().to_hex(),
|
||||||
|
expected.as_ref().to_vec().to_hex(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_fixture(fixture: &Fixture) -> Vec<Fail> {
|
||||||
|
let mut params = ActionParams::default();
|
||||||
|
|
||||||
|
params.code = Some(Arc::new(
|
||||||
|
match load_code(fixture.wasm_file.as_ref()) {
|
||||||
|
Ok(code) => code,
|
||||||
|
Err(e) => { return Fail::load(e); },
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(ref address) = fixture.address {
|
||||||
|
params.address = address.clone().into();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(gas_limit) = fixture.gas_limit {
|
||||||
|
params.gas = U256::from(gas_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref data) = fixture.payload {
|
||||||
|
params.data = Some(data.clone().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = fixture.value {
|
||||||
|
params.value = ActionValue::Transfer(value.clone().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ext = FakeExt::new();
|
||||||
|
if let Some(ref storage) = fixture.storage {
|
||||||
|
for storage_entry in storage.iter() {
|
||||||
|
let key: U256 = storage_entry.key.into();
|
||||||
|
let val: U256 = storage_entry.value.into();
|
||||||
|
ext.store.insert(key.into(), val.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut interpreter = wasm_interpreter();
|
||||||
|
|
||||||
|
let interpreter_return = match interpreter.exec(params, &mut ext) {
|
||||||
|
Ok(ret) => ret,
|
||||||
|
Err(e) => { return Fail::runtime(e); }
|
||||||
|
};
|
||||||
|
let (gas_left, result) = match interpreter_return {
|
||||||
|
GasLeft::Known(gas) => { (gas, Vec::new()) },
|
||||||
|
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fails = Vec::new();
|
||||||
|
|
||||||
|
for assert in fixture.asserts.iter() {
|
||||||
|
match *assert {
|
||||||
|
Assert::Return(ref data) => {
|
||||||
|
if &data[..] != &result[..] {
|
||||||
|
fails.push(Fail::Return { expected: (&data[..]).to_vec(), actual: (&result[..]).to_vec() })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Assert::UsedGas(gas) => {
|
||||||
|
let used_gas = fixture.gas_limit.unwrap_or(0) - gas_left.low_u64();
|
||||||
|
if gas != used_gas {
|
||||||
|
fails.push(Fail::UsedGas { expected: gas, actual: used_gas });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Assert::HasCall(ref locator) => {
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
for fake_call in ext.calls.iter() {
|
||||||
|
let mut match_ = true;
|
||||||
|
if let Some(ref data) = locator.data {
|
||||||
|
if data.as_ref() != &fake_call.data[..] { match_ = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref code_addr) = locator.code_address {
|
||||||
|
if fake_call.code_address.unwrap_or(H160::zero()) != code_addr.clone().into() { match_ = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref sender) = locator.sender {
|
||||||
|
if fake_call.sender_address.unwrap_or(H160::zero()) != sender.clone().into() { match_ = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref receiver) = locator.receiver {
|
||||||
|
if fake_call.receive_address.unwrap_or(H160::zero()) != receiver.clone().into() { match_ = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if match_ {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
fails.push(Fail::NoCall(locator.clone()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Assert::HasStorage(ref storage_entry) => {
|
||||||
|
let expected_storage_key: H256 = storage_entry.key.clone().into();
|
||||||
|
let expected_storage_value: H256 = storage_entry.value.clone().into();
|
||||||
|
let val = ext.store.get(&expected_storage_key);
|
||||||
|
|
||||||
|
if let Some(val) = val {
|
||||||
|
if val != &expected_storage_value {
|
||||||
|
fails.push(Fail::StorageMismatch {
|
||||||
|
key: expected_storage_key,
|
||||||
|
expected: expected_storage_value,
|
||||||
|
actual: Some(val.clone())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fails.push(Fail::StorageMismatch {
|
||||||
|
key: expected_storage_key,
|
||||||
|
expected: expected_storage_value,
|
||||||
|
actual: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fails
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user