diff --git a/Cargo.lock b/Cargo.lock index 9fe18ed17..071327317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + [[package]] name = "any_ascii" version = "0.1.7" @@ -106,9 +112,24 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" dependencies = [ - "bstr", + "bstr 0.2.17", "doc-comment", - "predicates", + "predicates 2.1.5", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "assert_cmd" +version = "2.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0b2340f55d9661d76793b2bfc2eb0e62689bd79d067a95707ea762afd5e9dd" +dependencies = [ + "anstyle", + "bstr 1.3.0", + "doc-comment", + "predicates 3.0.1", "predicates-core", "predicates-tree", "wait-timeout", @@ -242,6 +263,18 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "bstr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +dependencies = [ + "memchr", + "once_cell", + "regex-automata", + "serde", +] + [[package]] name = "build-deps" version = "0.1.4" @@ -1897,11 +1930,11 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340dd3d6102fa919bd20987024a6d84954c36ec691ac1efea37742ee983c8dd5" dependencies = [ - "assert_cmd", + "assert_cmd 1.0.8", "cc", "inline-c-macro", "lazy_static", - "predicates", + "predicates 2.1.5", "regex", "rustc_version 0.3.3", "target-lexicon 0.11.2", @@ -2802,6 +2835,18 @@ dependencies = [ "regex", ] +[[package]] +name = "predicates" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba7d6ead3e3966038f68caa9fc1f860185d95a793180bbcfe0d0da47b3961ed" +dependencies = [ + "anstyle", + "difflib", + "itertools", + "predicates-core", +] + [[package]] name = "predicates-core" version = "1.0.6" @@ -5243,10 +5288,10 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c4e7a2a3363ceeb2ee60371af9460748f2bf53569b58627f1f640284ab07778" dependencies = [ - "assert_cmd", + "assert_cmd 1.0.8", "cc", "lazy_static", - "predicates", + "predicates 2.1.5", "regex", "rustc_version 0.3.3", "target-lexicon 0.11.2", @@ -5270,6 +5315,7 @@ name = "wasmer-integration-tests-cli" version = "3.2.0-alpha.1" dependencies = [ "anyhow", + "assert_cmd 2.0.10", "derivative", "dirs", "flate2", diff --git a/lib/c-api/examples/assets/staticserver.webc b/lib/c-api/examples/assets/staticserver.webc new file mode 100644 index 000000000..3cd41357e Binary files /dev/null and b/lib/c-api/examples/assets/staticserver.webc differ diff --git a/lib/cli/src/commands/run2.rs b/lib/cli/src/commands/run2.rs index 183347514..0703569db 100644 --- a/lib/cli/src/commands/run2.rs +++ b/lib/cli/src/commands/run2.rs @@ -274,6 +274,7 @@ impl wasmer_cache::Cache for WasmerHome { #[derive(Debug, Clone)] enum TargetOnDisk { WebAssemblyBinary(PathBuf), + Wat(PathBuf), Webc(PathBuf), Directory(PathBuf), Artifact(PathBuf), @@ -297,6 +298,8 @@ impl TargetOnDisk { Ok(TargetOnDisk::Webc(path)) } else if ArtifactBuild::is_deserializable(leading_bytes) { Ok(TargetOnDisk::Artifact(path)) + } else if path.extension() == Some("wat".as_ref()) { + Ok(TargetOnDisk::Wat(path)) } else { anyhow::bail!("Unable to determine how to execute \"{}\"", path.display()); } @@ -306,6 +309,7 @@ impl TargetOnDisk { match self { TargetOnDisk::WebAssemblyBinary(p) | TargetOnDisk::Webc(p) + | TargetOnDisk::Wat(p) | TargetOnDisk::Directory(p) | TargetOnDisk::Artifact(p) => p, } @@ -344,6 +348,15 @@ impl TargetOnDisk { .context("Unable to load the module from a file")?; Ok(ExecutableTarget::WebAssembly(module)) } + TargetOnDisk::Wat(wat) => { + let wat = std::fs::read(wat) + .with_context(|| format!("Unable to read \"{}\"", wat.display()))?; + let wasm = + wasmer::wat2wasm(&wat).context("Unable to convert the WAT to WebAssembly")?; + let module = + Module::new(store, wasm).context("Unable to load the module from a file")?; + Ok(ExecutableTarget::WebAssembly(module)) + } TargetOnDisk::Artifact(artifact) => { let module = unsafe { Module::deserialize_from_file(store, artifact) diff --git a/tests/integration/cli/Cargo.toml b/tests/integration/cli/Cargo.toml index cb76f0293..79e50dfaa 100644 --- a/tests/integration/cli/Cargo.toml +++ b/tests/integration/cli/Cargo.toml @@ -18,6 +18,7 @@ pretty_assertions = "1.3.0" object = "0.30.0" reqwest = { version = "0.11.14", features = ["json"] } tokio = { version = "1", features = [ "rt", "rt-multi-thread", "macros" ] } +assert_cmd = "2.0.8" [dependencies] anyhow = "1" diff --git a/tests/integration/cli/tests/run2.rs b/tests/integration/cli/tests/run2.rs new file mode 100644 index 000000000..5bfe0c812 --- /dev/null +++ b/tests/integration/cli/tests/run2.rs @@ -0,0 +1,243 @@ +use assert_cmd::{assert::Assert, prelude::OutputAssertExt, Command}; +use tempfile::TempDir; +use wasmer_integration_tests_cli::get_wasmer_path; + +fn wasmer_cli() -> Command { + Command::new(get_wasmer_path()) +} + +mod webc_on_disk { + use super::*; + + #[test] + fn wasi_runner() { + let assert = wasmer_cli() + .arg("run2") + .arg(fixtures::python()) + .arg("--") + .arg("--version") + .assert(); + + assert.success().stdout("Hello, World!"); + } + + #[test] + fn wasi_runner_with_mounted_directories() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("main.py"), "print('Hello, World!')").unwrap(); + + let assert = wasmer_cli() + .arg("run2") + .arg(fixtures::python()) + .arg("--mapdir") + .arg(format!("/app:{}", temp.path().display())) + .arg("--") + .arg("/app/main.py") + .assert(); + + assert.success().stdout("Hello, World!"); + } + + #[test] + fn wasi_runner_with_env_vars() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("main.py"), "print('Hello, World!')").unwrap(); + + let assert = wasmer_cli() + .arg("run2") + .arg(fixtures::python()) + .arg("--env") + .arg("SOME_VAR=Hello, World!") + .arg("--") + .arg("-c") + .arg("import os; print(os.environ['SOME_VAR'])") + .assert(); + + assert.success().stdout("Hello, World!"); + } + + #[test] + fn wcgi_runner() { + // Start the WCGI server in the background + let child = std::process::Command::new(get_wasmer_path()) + .arg("run2") + .arg(fixtures::static_server()) + .spawn() + .map(Child::new) + .unwrap(); + + let assert = child.join(); + + assert.stdout("Hello, World!"); + } +} + +mod wasm_on_disk { + use super::*; + + #[test] + fn wasi_executable() { + let assert = wasmer_cli() + .arg("run2") + .arg(fixtures::qjs()) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .assert(); + + assert.success().stdout("Hello, World!"); + } + + #[test] + fn no_abi() { + let assert = wasmer_cli().arg("run2").arg(fixtures::fib()).assert(); + + assert.success(); + } + + #[test] + fn error_if_no_start_function_found() { + let assert = wasmer_cli() + .arg("run2") + .arg(fixtures::wat_no_start()) + .assert(); + + assert + .failure() + .stderr("Can not find any export functions."); + } +} + +#[test] +fn wasmer_package_directory() { + let temp = TempDir::new().unwrap(); + std::fs::copy(fixtures::qjs(), temp.path().join("qjs.wasm")).unwrap(); + std::fs::copy(fixtures::qjs_wasmer_toml(), temp.path().join("wasmer.toml")).unwrap(); + + let assert = wasmer_cli() + .arg("run2") + .arg(temp.path()) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .assert(); + + assert.success().stdout("Hello, World!"); +} + +#[test] +fn pre_compiled_wasm() { + let temp = TempDir::new().unwrap(); + let dest = temp.path().join("qjs.wasmu"); + let qjs = fixtures::qjs(); + // Make sure it is compiled + wasmer_cli() + .arg("compile") + .arg("-o") + .arg(&dest) + .arg(&qjs) + .assert() + .success(); + assert!(dest.exists()); + + // Now we can try to run the compiled artifact + let assert = wasmer_cli() + .arg("run2") + .arg(&dest) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .assert(); + + assert.success().stdout("Hello, World!"); +} + +mod remote_webc { + use super::*; + + #[test] + fn quickjs_as_package_name() { + let assert = wasmer_cli() + .arg("run2") + .arg("saghul/quickjs") + .arg("--registry=https://wapm.io/") + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .assert(); + + assert.success().stdout("Hello, World!"); + } + + #[test] + fn quickjs_as_url() { + let assert = wasmer_cli() + .arg("run2") + .arg("https://wapm.io/saghul/quickjs") + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .assert(); + + assert.success().stdout("Hello, World!"); + } +} + +mod fixtures { + use std::path::{Path, PathBuf}; + + use wasmer_integration_tests_cli::{ASSET_PATH, C_ASSET_PATH}; + + /// A WEBC file containing the Python interpreter, compiled to WASI. + pub fn python() -> PathBuf { + Path::new(C_ASSET_PATH).join("python-0.1.0.wasmer") + } + + /// A WEBC file containing the WCGI static server. + pub fn static_server() -> PathBuf { + Path::new(C_ASSET_PATH).join("staticserver.webc") + } + + /// The QuickJS interpreter, compiled to a WASI module. + pub fn qjs() -> PathBuf { + Path::new(C_ASSET_PATH).join("qjs.wasm") + } + + /// The `wasmer.toml` file for QuickJS. + pub fn qjs_wasmer_toml() -> PathBuf { + Path::new(C_ASSET_PATH).join("qjs-wasmer.toml") + } + + /// An executable which calculates fib(40) and exits with no output. + pub fn fib() -> PathBuf { + Path::new(ASSET_PATH).join("fib.wat") + } + + pub fn wat_no_start() -> PathBuf { + Path::new(ASSET_PATH).join("no_start.wat") + } +} + +/// A helper that wraps [`std::process::Child`] to make sure it gets terminated +/// when it is no longer needed. +struct Child(Option); + +impl Child { + fn new(child: std::process::Child) -> Self { + Child(Some(child)) + } + + fn join(mut self) -> Assert { + let mut child = self.0.take().unwrap(); + child.kill().unwrap(); + child.wait_with_output().unwrap().assert() + } +} + +impl Drop for Child { + fn drop(&mut self) { + if let Some(mut child) = self.0.take() { + let _ = child.kill(); + } + } +}