diff --git a/.github/workflows/iroha2-dev-pr-wasm.yaml b/.github/workflows/iroha2-dev-pr-wasm.yaml index d78d1d03e2c..1b310b41a0f 100644 --- a/.github/workflows/iroha2-dev-pr-wasm.yaml +++ b/.github/workflows/iroha2-dev-pr-wasm.yaml @@ -47,5 +47,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 + - name: Install iroha_wasm_test_runner + run: cd .. && cargo install --path tools/wasm_test_runner - name: Run tests run: mold --run cargo test --tests --no-fail-fast --quiet diff --git a/Cargo.lock b/Cargo.lock index c03b35cc834..dd799b31c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,9 +192,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arbitrary" @@ -3542,6 +3542,14 @@ dependencies = [ "wasmtime", ] +[[package]] +name = "iroha_wasm_test_runner" +version = "2.0.0-pre-rc.19" +dependencies = [ + "anyhow", + "wasmtime", +] + [[package]] name = "is-terminal" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index cfad00e687a..4ad54b655e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -319,6 +319,7 @@ members = [ "tools/parity_scale_decoder", "tools/swarm", "tools/wasm_builder_cli", + "tools/wasm_test_runner", "version", "version/derive", "wasm_codec", diff --git a/Dockerfile.build b/Dockerfile.build index 7ad68def7fd..41f5632342f 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -23,7 +23,6 @@ RUN rustup component add llvm-tools-preview clippy RUN rustup component add rust-src RUN rustup component add rustfmt RUN rustup target add wasm32-unknown-unknown -RUN cargo install webassembly-test-runner RUN cargo install cargo-llvm-cov # TODO: Figure out a way to pull in libgit2, which doesn't crash if this useless variable is gone. diff --git a/Dockerfile.build.glibc b/Dockerfile.build.glibc index 8ae5ff306c0..41471d32972 100644 --- a/Dockerfile.build.glibc +++ b/Dockerfile.build.glibc @@ -12,7 +12,6 @@ RUN rustup component add llvm-tools-preview clippy RUN rustup component add rust-src RUN rustup component add rustfmt RUN rustup target add wasm32-unknown-unknown -RUN cargo install webassembly-test-runner RUN cargo install cargo-llvm-cov # TODO: Figure out a way to pull in libgit2, which doesn't crash if this useless variable is gone. diff --git a/ffi/.cargo/config.toml b/ffi/.cargo/config.toml index 4ca8f7bc93b..10c68cf82ce 100644 --- a/ffi/.cargo/config.toml +++ b/ffi/.cargo/config.toml @@ -1,2 +1,2 @@ [target.wasm32-unknown-unknown] -runner = "webassembly-test-runner" +runner = "iroha_wasm_test_runner" diff --git a/tools/wasm_test_runner/Cargo.toml b/tools/wasm_test_runner/Cargo.toml new file mode 100644 index 00000000000..682ad1ccce1 --- /dev/null +++ b/tools/wasm_test_runner/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "iroha_wasm_test_runner" + +edition.workspace = true +version.workspace = true +authors.workspace = true +license.workspace = true + +[lints] +workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +wasmtime = { workspace = true } +anyhow = "1.0.75" diff --git a/tools/wasm_test_runner/src/main.rs b/tools/wasm_test_runner/src/main.rs new file mode 100644 index 00000000000..fe6bd7478d6 --- /dev/null +++ b/tools/wasm_test_runner/src/main.rs @@ -0,0 +1,82 @@ +//! A tool to run `WebAssembly` tests +//! +//! This copies functionality of `webassembly-test-runner`, but with an ability to indicate failure with an exit code. + +use std::process::ExitCode; + +use anyhow::{bail, Result}; +use wasmtime::{Engine, Instance, Module, Store}; + +struct TestMeta<'a> { + name: &'a str, + ignore: bool, +} + +fn main() -> Result { + let argv0 = std::env::args().next().unwrap(); + + let file = match std::env::args().nth(1) { + Some(it) => it, + None => { + bail!("usage: {} tests.wasm", argv0); + } + }; + // Modules can be compiled through either the text or binary format + let engine = Engine::default(); + let module = Module::from_file(&engine, &file)?; + let mut tests = Vec::new(); + for export in module.exports() { + if let Some(name) = export.name().strip_prefix("$webassembly-test$") { + let mut ignore = true; + let name = name.strip_prefix("ignore$").unwrap_or_else(|| { + ignore = false; + name + }); + tests.push((export, TestMeta { name, ignore })); + } + } + let total = tests.len(); + + eprintln!("\nrunning {} tests", total); + let mut store = Store::new(&engine, ()); + let mut instance = Instance::new(&mut store, &module, &[])?; + let mut passed = 0; + let mut failed = 0; + let mut ignored = 0; + for (export, meta) in tests { + eprint!("test {} ...", meta.name); + if meta.ignore { + ignored += 1; + eprintln!(" ignored") + } else { + let f = instance.get_typed_func::<(), ()>(&mut store, export.name())?; + + let pass = f.call(&mut store, ()).is_ok(); + if pass { + passed += 1; + eprintln!(" ok") + } else { + // Reset instance on test failure. WASM uses `panic=abort`, so + // `Drop`s are not called after test failures, and a failed test + // might leave an instance in an inconsistent state. + store = Store::new(&engine, ()); + instance = Instance::new(&mut store, &module, &[])?; + + failed += 1; + eprintln!(" FAILED") + } + } + } + eprintln!( + "\ntest result: {}. {} passed; {} failed; {} ignored;", + if failed > 0 { "FAILED" } else { "ok" }, + passed, + failed, + ignored, + ); + Ok(if failed > 0 { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + }) +} diff --git a/wasm/.cargo/config.toml b/wasm/.cargo/config.toml index 00ec8ee28dc..0134e4439a2 100644 --- a/wasm/.cargo/config.toml +++ b/wasm/.cargo/config.toml @@ -2,4 +2,4 @@ target = "wasm32-unknown-unknown" [target.wasm32-unknown-unknown] -runner = "webassembly-test-runner" +runner = "iroha_wasm_test_runner" diff --git a/wasm/README.md b/wasm/README.md index 8629f9966fd..b221dcc903a 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -8,10 +8,10 @@ Check the [WASM section of our tutorial](https://hyperledger.github.io/iroha-2-d ## Running tests -To be able to run tests compiled for `wasm32-unknown-unknown` target install `webassembly-test-runner`: +To be able to run tests compiled for `wasm32-unknown-unknown` target install `iroha_wasm_test_runner` from the root of the iroha repository: ```bash -cargo install webassembly-test-runner +cargo install --path tools/wasm_test_runner ``` Then run tests: diff --git a/wasm/src/debug.rs b/wasm/src/debug.rs index 0f81f10336d..ed88fa048f1 100644 --- a/wasm/src/debug.rs +++ b/wasm/src/debug.rs @@ -169,15 +169,17 @@ mod tests { use webassembly_test::webassembly_test; - use crate::_decode_from_raw; - fn get_dbg_message() -> &'static str { "dbg_message" } #[no_mangle] pub unsafe extern "C" fn _dbg_mock(ptr: *const u8, len: usize) { - assert_eq!(_decode_from_raw::(ptr, len), get_dbg_message()); + use parity_scale_codec::DecodeAll; + + // can't use _decode_from_raw here, because we must NOT take the ownership + let bytes = core::slice::from_raw_parts(ptr, len); + assert_eq!(String::decode_all(&mut &*bytes).unwrap(), get_dbg_message()); } #[webassembly_test] diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 29b467f7681..fb97b6bac78 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -448,7 +448,7 @@ mod tests { } fn get_test_expression() -> EvaluatesTo { - Add::new(1_u32, 2_u32).into() + Add::new(2_u32, 3_u32).into() } #[no_mangle] diff --git a/wasm/src/log.rs b/wasm/src/log.rs index 30cf22d3c58..2adcfede7f2 100644 --- a/wasm/src/log.rs +++ b/wasm/src/log.rs @@ -88,7 +88,6 @@ mod tests { use webassembly_test::webassembly_test; use super::*; - use crate::_decode_from_raw; fn get_log_message() -> &'static str { "log_message" @@ -96,7 +95,9 @@ mod tests { #[no_mangle] pub unsafe extern "C" fn _log_mock(ptr: *const u8, len: usize) { - let (log_level, msg) = _decode_from_raw::<(u8, String)>(ptr, len); + // can't use _decode_from_raw here, because we must NOT take the ownership + let bytes = core::slice::from_raw_parts(ptr, len); + let (log_level, msg) = <(u8, String)>::decode_all(&mut &*bytes).unwrap(); assert_eq!(log_level, 3); assert_eq!(msg, get_log_message()); }