diff --git a/Cargo.lock b/Cargo.lock index 88bfeb4eb54..b4cda4db232 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5864,6 +5864,7 @@ dependencies = [ "target-lexicon 0.12.10", "tempfile", "tokio", + "wasmer-registry 5.2.0", ] [[package]] diff --git a/Makefile b/Makefile index 101453e7ef3..6e6046f281f 100644 --- a/Makefile +++ b/Makefile @@ -619,12 +619,12 @@ test-wasi: test-integration-cli: build-wasmer build-capi package-capi-headless package distribution cp ./dist/wasmer.tar.gz ./link.tar.gz rustup target add wasm32-wasi - WASMER_DIR=`pwd`/package $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner --no-fail-fast -p wasmer-integration-tests-cli -- --nocapture --test-threads=1 + WASMER_DIR=`pwd`/package $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner --no-fail-fast -p wasmer-integration-tests-cli # Before running this in the CI, we need to set up link.tar.gz and /cache/wasmer-[target].tar.gz test-integration-cli-ci: rustup target add wasm32-wasi - $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner -p wasmer-integration-tests-cli -- --test-threads=1 --nocapture + $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner -p wasmer-integration-tests-cli test-integration-ios: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --features webc_runner -p wasmer-integration-tests-ios diff --git a/lib/cli/src/commands/whoami.rs b/lib/cli/src/commands/whoami.rs index e6ec1dc2a1c..81bc962ac20 100644 --- a/lib/cli/src/commands/whoami.rs +++ b/lib/cli/src/commands/whoami.rs @@ -12,8 +12,9 @@ impl Whoami { /// Execute `wasmer whoami` pub fn execute(&self) -> Result<(), anyhow::Error> { let registry = self.env.registry_endpoint()?; + let token = self.env.token(); let (registry, username) = - wasmer_registry::whoami(self.env.dir(), Some(registry.as_str()), None)?; + wasmer_registry::whoami(self.env.dir(), Some(registry.as_str()), token.as_deref())?; println!("logged into registry {registry:?} as user {username:?}"); Ok(()) } diff --git a/lib/wasi-web/src/common.rs b/lib/wasi-web/src/common.rs index c4f787295f3..798076aa5f8 100644 --- a/lib/wasi-web/src/common.rs +++ b/lib/wasi-web/src/common.rs @@ -1,5 +1,6 @@ use std::cell::Cell; +use anyhow::Context; use js_sys::Function; #[allow(unused_imports, dead_code)] use tracing::{debug, error, info, trace, warn}; @@ -157,22 +158,24 @@ pub async fn fetch( } let request = { - let request = Request::new_with_str_and_init(&url, &opts) - .map_err(|_| anyhow::anyhow!("Could not construct request object"))?; + let request = Request::new_with_str_and_init(url, &opts) + .map_err(js_error) + .context("Could not construct request object")?; let set_headers = request.headers(); for (name, val) in headers.iter() { let val = String::from_utf8_lossy(val.as_bytes()); - set_headers.set(name.as_str(), &val).map_err(|_| { - anyhow::anyhow!("could not apply request header: '{name}': '{val}'") - })?; + set_headers + .set(name.as_str(), &val) + .map_err(js_error) + .with_context(|| format!("could not apply request header: '{name}': '{val}'"))?; } request }; - let resp_value = match fetch_internal(&request).await.ok() { - Some(a) => a, - None => { + let resp_value = match fetch_internal(&request).await { + Ok(a) => a, + Err(e) => { // If the request failed it may be because of CORS so if a cors proxy // is configured then try again with the cors proxy let url_store; @@ -180,25 +183,28 @@ pub async fn fetch( url_store = format!("https://{}/{}", cors_proxy, url); url_store.as_str() } else { - // TODO: more descriptive error. - return Err(anyhow::anyhow!("Could not fetch '{url}'")); + return Err(js_error(e).context(format!("Could not fetch '{url}'"))); }; let request = Request::new_with_str_and_init(url, &opts) - .map_err(|_| anyhow::anyhow!("Could not construct request for url '{url}'"))?; + .map_err(js_error) + .with_context(|| format!("Could not construct request for url '{url}'"))?; let set_headers = request.headers(); for (name, val) in headers.iter() { let value = String::from_utf8_lossy(val.as_bytes()); - set_headers.set(name.as_str(), &value).map_err(|_| { - anyhow::anyhow!("Could not apply request header: '{name}': '{value}'") - })?; + set_headers + .set(name.as_str(), &value) + .map_err(js_error) + .with_context(|| { + anyhow::anyhow!("Could not apply request header: '{name}': '{value}'") + })?; } - fetch_internal(&request).await.map_err(|_| { - // TODO: more descriptive error. - anyhow::anyhow!("Could not fetch '{url}'") - })? + fetch_internal(&request) + .await + .map_err(js_error) + .with_context(|| anyhow::anyhow!("Could not fetch '{url}'"))? } }; assert!(resp_value.is_instance_of::()); @@ -215,6 +221,20 @@ pub async fn fetch( Ok(resp) } +/// Try to extract the most appropriate error message from a [`JsValue`], +/// falling back to a generic error message. +fn js_error(value: JsValue) -> anyhow::Error { + if let Some(e) = value.dyn_ref::() { + anyhow::Error::msg(String::from(e.message())) + } else if let Some(obj) = value.dyn_ref::() { + return anyhow::Error::msg(String::from(obj.to_string())); + } else if let Some(s) = value.dyn_ref::() { + return anyhow::Error::msg(String::from(s)); + } else { + anyhow::Error::msg("An unknown error occurred") + } +} + /* pub async fn fetch_data( url: &str, @@ -231,10 +251,10 @@ pub async fn fetch_data( pub async fn get_response_data(resp: Response) -> Result, anyhow::Error> { let resp = { JsFuture::from(resp.array_buffer().unwrap()) }; - let arrbuff_value = resp.await.map_err(|_| { - // TODO: forward error message - anyhow::anyhow!("Could not retrieve response body") - })?; + let arrbuff_value = resp + .await + .map_err(js_error) + .with_context(|| format!("Could not retrieve response body"))?; assert!(arrbuff_value.is_instance_of::()); //let arrbuff: js_sys::ArrayBuffer = arrbuff_value.dyn_into().unwrap(); diff --git a/lib/wasix/src/runtime/module_cache/filesystem.rs b/lib/wasix/src/runtime/module_cache/filesystem.rs index 47f95f837a1..7ac89de9584 100644 --- a/lib/wasix/src/runtime/module_cache/filesystem.rs +++ b/lib/wasix/src/runtime/module_cache/filesystem.rs @@ -111,6 +111,7 @@ impl ModuleCache for FileSystemCache { } temp.persist(&path).map_err(CacheError::other)?; + tracing::debug!(path=%path.display(), "Saved to disk"); Ok(()) } diff --git a/lib/wasix/src/runtime/resolver/wapm_source.rs b/lib/wasix/src/runtime/resolver/wapm_source.rs index 5eda690aa43..1a4437f39ef 100644 --- a/lib/wasix/src/runtime/resolver/wapm_source.rs +++ b/lib/wasix/src/runtime/resolver/wapm_source.rs @@ -51,6 +51,7 @@ impl WapmSource { if let Some(cache) = &self.cache { match cache.lookup_cached_query(package_name) { Ok(Some(cached)) => { + tracing::debug!("Cache hit!"); return Ok(cached); } Ok(None) => {} diff --git a/lib/wasix/src/runtime/resolver/web_source.rs b/lib/wasix/src/runtime/resolver/web_source.rs index 275edf6eaf9..c5234368114 100644 --- a/lib/wasix/src/runtime/resolver/web_source.rs +++ b/lib/wasix/src/runtime/resolver/web_source.rs @@ -76,7 +76,7 @@ impl WebSource { // Next we check if we definitely got a cache hit let state = match classify_cache_using_mtime(cache_info, self.retry_period) { Ok(path) => { - tracing::debug!(path=%path.display(), "Cache hit"); + tracing::debug!(path=%path.display(), "Cache hit!"); return Ok(path); } Err(s) => s, diff --git a/tests/integration/cli/Cargo.toml b/tests/integration/cli/Cargo.toml index 363562af66c..4fbe40f9b6b 100644 --- a/tests/integration/cli/Cargo.toml +++ b/tests/integration/cli/Cargo.toml @@ -33,6 +33,7 @@ tar = "0.4.38" flate2 = "1.0.24" dirs = "4.0.0" derivative = { version = "^2" } +wasmer-registry = { path = "../../../lib/registry", default-features = false } [features] default = ["webc_runner"] diff --git a/tests/integration/cli/src/assets.rs b/tests/integration/cli/src/assets.rs index 946f851772a..f6692636d82 100644 --- a/tests/integration/cli/src/assets.rs +++ b/tests/integration/cli/src/assets.rs @@ -1,51 +1,70 @@ -use std::env; use std::path::PathBuf; +use std::{env, path::Path}; -pub const C_ASSET_PATH: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../lib/c-api/examples/assets/" -); -pub const ASSET_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../tests/examples/"); +pub fn c_asset_path() -> &'static Path { + Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../lib/c-api/examples/assets/" + )) +} -pub const WASMER_INCLUDE_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../lib/c-api/"); +pub fn asset_path() -> &'static Path { + Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../tests/examples/" + )) +} -#[cfg(feature = "debug")] -pub const WASMER_TARGET_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../target/debug/"); -#[cfg(feature = "debug")] -pub const WASMER_TARGET_PATH_2: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/", - env!("CARGO_BUILD_TARGET"), - "/debug/" -); +pub fn wasmer_include_path() -> &'static Path { + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../../lib/c-api/")) +} -/* env var TARGET is set by tests/integration/cli/build.rs on compile-time */ +pub fn wasmer_target_path() -> &'static Path { + let path = if cfg!(feature = "debug") { + concat!(env!("CARGO_MANIFEST_DIR"), "/../../../target/debug/") + } else { + concat!(env!("CARGO_MANIFEST_DIR"), "/../../../target/release/") + }; + Path::new(path) +} -#[cfg(not(feature = "debug"))] -pub const WASMER_TARGET_PATH: &str = - concat!(env!("CARGO_MANIFEST_DIR"), "/../../../target/release/"); -#[cfg(not(feature = "debug"))] -pub const WASMER_TARGET_PATH_2: &str = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../../target/", - env!("CARGO_BUILD_TARGET"), - "/release/" -); +pub fn wasmer_target_path_2() -> &'static Path { + let path = if cfg!(feature = "debug") { + concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/", + env!("CARGO_BUILD_TARGET"), + "/debug/" + ) + } else { + concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../target/", + env!("CARGO_BUILD_TARGET"), + "/release/" + ) + }; + Path::new(path) +} -#[cfg(not(windows))] -pub const LIBWASMER_FILENAME: &str = "libwasmer.a"; +/* env var TARGET is set by tests/integration/cli/build.rs on compile-time */ -#[cfg(windows)] -pub const LIBWASMER_FILENAME: &str = "wasmer.lib"; +pub const LIBWASMER_FILENAME: &str = { + if cfg!(windows) { + "wasmer.lib" + } else { + "libwasmer.a" + } +}; /// Get the path to the `libwasmer.a` static library. pub fn get_libwasmer_path() -> PathBuf { - let mut ret = PathBuf::from( - env::var("WASMER_TEST_LIBWASMER_PATH") - .unwrap_or_else(|_| format!("{}{}", WASMER_TARGET_PATH, LIBWASMER_FILENAME)), - ); + let mut ret = env::var("WASMER_TEST_LIBWASMER_PATH") + .map(PathBuf::from) + .unwrap_or_else(|_| wasmer_target_path().join(LIBWASMER_FILENAME)); + if !ret.exists() { - ret = PathBuf::from(format!("{}{}", WASMER_TARGET_PATH_2, LIBWASMER_FILENAME)); + ret = wasmer_target_path_2().join(LIBWASMER_FILENAME); } if !ret.exists() { panic!("Could not find libwasmer path! {:?}", ret); @@ -55,23 +74,22 @@ pub fn get_libwasmer_path() -> PathBuf { /// Get the path to the `wasmer` executable to be used in this test. pub fn get_wasmer_path() -> PathBuf { - let mut ret = PathBuf::from( - env::var("WASMER_TEST_WASMER_PATH") - .unwrap_or_else(|_| format!("{}wasmer", WASMER_TARGET_PATH)), - ); + let mut ret = env::var("WASMER_TEST_WASMER_PATH") + .map(PathBuf::from) + .unwrap_or_else(|_| wasmer_target_path().join("wasmer")); + if !ret.exists() { - ret = PathBuf::from(format!("{}wasmer", WASMER_TARGET_PATH_2)); + ret = wasmer_target_path_2().join("wasmer"); } if !ret.exists() { ret = match get_repo_root_path() { Some(s) => { - #[cfg(target_os = "windows")] - { - s.join("target").join("release").join("wasmer.exe") - } - #[cfg(not(target_os = "windows"))] - { - s.join("target").join("release").join("wasmer") + let release_dir = s.join("target").join("release"); + + if cfg!(windows) { + release_dir.join("wasmer.exe") + } else { + release_dir.join("wasmer") } } None => { @@ -83,20 +101,15 @@ pub fn get_wasmer_path() -> PathBuf { if !ret.exists() { ret = match get_repo_root_path() { Some(s) => { - #[cfg(target_os = "windows")] - { - s.join("target") - .join(target_lexicon::HOST.to_string()) - .join("release") - .join("wasmer.exe") - } - #[cfg(not(target_os = "windows"))] - { - s.join("target") - .join(target_lexicon::HOST.to_string()) - .join("release") - .join("wasmer") - } + let executable = if cfg!(windows) { + "wasmer.exe" + } else { + "wasmer" + }; + s.join("target") + .join(target_lexicon::HOST.to_string()) + .join("release") + .join(executable) } None => { panic!("Could not find wasmer executable path! {:?}", ret); @@ -137,3 +150,26 @@ pub fn get_repo_root_path() -> Option { } result } + +pub fn get_wasmer_dir() -> Result { + if let Ok(s) = std::env::var("WASMER_DIR") { + Ok(Path::new(&s).to_path_buf()) + } else if let Some(root_dir) = get_repo_root_path().and_then(|root| { + if root.join("package").exists() { + Some(root.join("package")) + } else { + None + } + }) { + Ok(root_dir) + } else { + let home_dir = dirs::home_dir() + .ok_or(anyhow::anyhow!("no home dir"))? + .join(".wasmer"); + if home_dir.exists() { + Ok(home_dir) + } else { + Err(anyhow::anyhow!("no .wasmer home dir")) + } + } +} diff --git a/tests/integration/cli/src/fixtures.rs b/tests/integration/cli/src/fixtures.rs new file mode 100644 index 00000000000..7f047852b61 --- /dev/null +++ b/tests/integration/cli/src/fixtures.rs @@ -0,0 +1,64 @@ +//! Paths for commonly used test files. + +use std::path::{Path, PathBuf}; + +use crate::{asset_path, c_asset_path}; + +/// A WEBC file containing the Python interpreter, compiled to WASI. +pub fn python() -> PathBuf { + c_asset_path().join("python-0.1.0.wasmer") +} + +/// A WEBC file containing the coreutils. +pub fn coreutils() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("webc") + .join("coreutils-1.0.16-e27dbb4f-2ef2-4b44-b46a-ddd86497c6d7.webc") +} + +/// A WEBC file containing bash. +pub fn bash() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("webc") + .join("bash-1.0.16-f097441a-a80b-4e0d-87d7-684918ef4bb6.webc") +} + +/// A WEBC file containing `wat2wasm`, `wasm-validate`, and other helpful +/// WebAssembly-related commands. +pub fn wabt() -> PathBuf { + c_asset_path().join("wabt-1.0.37.wasmer") +} + +/// A WEBC file containing the WCGI static server. +pub fn static_server() -> PathBuf { + c_asset_path().join("staticserver.webc") +} + +/// The QuickJS interpreter, compiled to a WASI module. +pub fn qjs() -> PathBuf { + c_asset_path().join("qjs.wasm") +} + +pub fn hello() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("webc") + .join("hello-0.1.0-665d2ddc-80e6-4845-85d3-4587b1693bb7.webc") +} + +/// The `wasmer.toml` file for QuickJS. +pub fn qjs_wasmer_toml() -> PathBuf { + c_asset_path().join("qjs-wasmer.toml") +} + +/// A `*.wat` file which calculates fib(40) and exits with no output. +pub fn fib() -> PathBuf { + asset_path().join("fib.wat") +} + +/// A `*.wat` file with no `_start()` function. +pub fn wat_no_start() -> PathBuf { + asset_path().join("no_start.wat") +} diff --git a/tests/integration/cli/src/lib.rs b/tests/integration/cli/src/lib.rs index 43d6ada6a69..2411acdbaa9 100644 --- a/tests/integration/cli/src/lib.rs +++ b/tests/integration/cli/src/lib.rs @@ -1,10 +1,9 @@ #![forbid(unsafe_code)] -//! CLI integration tests - -pub mod assets; +mod assets; +pub mod fixtures; pub mod link_code; -pub mod util; +mod util; pub use assets::*; pub use util::*; diff --git a/tests/integration/cli/tests/config.rs b/tests/integration/cli/tests/config.rs index 994382fef23..491ae4b2589 100644 --- a/tests/integration/cli/tests/config.rs +++ b/tests/integration/cli/tests/config.rs @@ -1,58 +1,55 @@ +use std::path::Path; + use assert_cmd::Command; -use std::path::{Path, PathBuf}; -use wasmer_integration_tests_cli::{get_repo_root_path, get_wasmer_path}; - -fn get_wasmer_dir() -> Result { - if let Ok(s) = std::env::var("WASMER_DIR") { - Ok(Path::new(&s).to_path_buf()) - } else if let Some(root_dir) = get_repo_root_path().and_then(|root| { - if root.join("package").exists() { - Some(root.join("package")) - } else { - None - } - }) { - Ok(root_dir) - } else { - let home_dir = dirs::home_dir() - .ok_or(anyhow::anyhow!("no home dir"))? - .join(".wasmer"); - if home_dir.exists() { - Ok(home_dir) - } else { - Err(anyhow::anyhow!("no .wasmer home dir")) - } - } +use predicates::str::contains; +use tempfile::TempDir; +use wasmer_integration_tests_cli::get_wasmer_path; +use wasmer_registry::WasmerConfig; + +fn setup_wasmer_dir() -> TempDir { + let temp = TempDir::new().unwrap(); + + let config_path = WasmerConfig::get_file_location(temp.path()); + WasmerConfig::default().save(&config_path).unwrap(); + + temp +} + +fn contains_path(path: impl AsRef) -> predicates::str::ContainsPredicate { + let expected = path.as_ref().display().to_string(); + contains(expected) +} + +fn wasmer_cmd(temp: &TempDir) -> Command { + let mut cmd = Command::new(get_wasmer_path()); + cmd.env("WASMER_DIR", temp.path()); + cmd } #[test] fn wasmer_config_multiget() { - let wasmer_dir = get_wasmer_dir().unwrap(); + let temp = setup_wasmer_dir(); + let wasmer_dir = temp.path(); + let bin_path = wasmer_dir.join("bin"); let include_path = wasmer_dir.join("include"); - let bin = format!("{}", bin_path.display()); + let bin = bin_path.display().to_string(); let include = format!("-I{}", include_path.display()); - let assert = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--bindir") .arg("--cflags") - .assert(); - - assert + .env("WASMER_DIR", wasmer_dir) + .assert() .success() - .stdout(predicates::str::contains(&bin)) - .stdout(predicates::str::contains(&include)); + .stdout(contains(bin)) + .stdout(contains(include)); } #[test] -fn wasmer_config_error() { - let assert = Command::new(get_wasmer_path()) - .arg("config") - .arg("--bindir") - .arg("--cflags") - .arg("--pkg-config") - .assert(); +fn wasmer_config_conflicting_flags() { + let temp = setup_wasmer_dir(); let expected_1 = if cfg!(windows) { "Usage: wasmer.exe config --bindir --cflags" @@ -60,129 +57,112 @@ fn wasmer_config_error() { "Usage: wasmer config --bindir --cflags" }; - assert - .stderr(predicates::str::contains( + wasmer_cmd(&temp) + .arg("config") + .arg("--bindir") + .arg("--cflags") + .arg("--pkg-config") + .assert() + .stderr(contains( "error: the argument '--bindir' cannot be used with '--pkg-config'", )) - .stderr(predicates::str::contains(expected_1)) - .stderr(predicates::str::contains( - "For more information, try '--help'.", - )); + .stderr(contains(expected_1)) + .stderr(contains("For more information, try '--help'.")); } #[test] -fn config_works() -> anyhow::Result<()> { - let bindir = Command::new(get_wasmer_path()) +fn c_flags() { + let temp = setup_wasmer_dir(); + let wasmer_dir = temp.path(); + + wasmer_cmd(&temp) .arg("config") .arg("--bindir") - .output()?; - - let bin_path = get_wasmer_dir()?.join("bin"); - assert_eq!( - String::from_utf8(bindir.stdout).unwrap(), - format!("{}\n", bin_path.display()) - ); + .assert() + .success() + .stdout(contains_path(temp.path().join("bin"))); - let bindir = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--cflags") - .output()?; - - let include_path = get_wasmer_dir()?.join("include"); - assert_eq!( - String::from_utf8(bindir.stdout).unwrap(), - format!("-I{}\n", include_path.display()) - ); + .assert() + .success() + .stdout(contains(format!( + "-I{}\n", + wasmer_dir.join("include").display() + ))); - let bindir = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--includedir") - .output()?; - - let include_path = get_wasmer_dir()?.join("include"); - assert_eq!( - String::from_utf8(bindir.stdout).unwrap(), - format!("{}\n", include_path.display()) - ); + .assert() + .success() + .stdout(contains_path(wasmer_dir.join("include"))); - let bindir = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--libdir") - .output()?; - - let lib_path = get_wasmer_dir()?.join("lib"); - assert_eq!( - String::from_utf8(bindir.stdout).unwrap(), - format!("{}\n", lib_path.display()) - ); + .assert() + .success() + .stdout(contains_path(wasmer_dir.join("lib"))); - let bindir = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--libs") - .output()?; + .assert() + .stdout(contains(format!( + "-L{} -lwasmer\n", + wasmer_dir.join("lib").display() + ))); - let lib_path = get_wasmer_dir()?.join("lib"); - assert_eq!( - String::from_utf8(bindir.stdout).unwrap(), - format!("-L{} -lwasmer\n", lib_path.display()) - ); - - let bindir = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--prefix") - .output()?; - - let wasmer_dir = get_wasmer_dir()?; - assert_eq!( - String::from_utf8(bindir.stdout).unwrap(), - format!("{}\n", wasmer_dir.display()) - ); + .assert() + .success() + .stdout(contains_path(wasmer_dir)); - let bindir = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("--pkg-config") - .output()?; - - let bin_path = format!("{}", bin_path.display()); - let include_path = format!("{}", include_path.display()); - let lib_path = format!("{}", lib_path.display()); - let wasmer_dir = format!("{}", wasmer_dir.display()); - - let args = vec![ - format!("prefix={wasmer_dir}"), - format!("exec_prefix={bin_path}"), - format!("includedir={include_path}"), - format!("libdir={lib_path}"), + .output() + .unwrap(); + + let pkg_config = vec![ + format!("prefix={}", wasmer_dir.display()), + format!("exec_prefix={}", wasmer_dir.join("bin").display()), + format!("includedir={}", wasmer_dir.join("include").display()), + format!("libdir={}", wasmer_dir.join("lib").display()), format!(""), format!("Name: wasmer"), format!("Description: The Wasmer library for running WebAssembly"), format!("Version: {}", env!("CARGO_PKG_VERSION")), - format!("Cflags: -I{include_path}"), - format!("Libs: -L{lib_path} -lwasmer"), - ]; + format!("Cflags: -I{}", wasmer_dir.join("include").display()), + format!("Libs: -L{} -lwasmer", wasmer_dir.join("lib").display()), + ] + .join("\n"); - let lines = String::from_utf8(bindir.stdout) + assert!(output.status.success()); + let stderr = std::str::from_utf8(&output.stdout) .unwrap() - .lines() - .map(|s| s.trim().to_string()) - .collect::>(); - - assert_eq!(lines, args); + .replace("\r\n", "\n"); + assert_eq!(stderr.trim(), pkg_config.trim()); - let output = Command::new(get_wasmer_path()) + wasmer_cmd(&temp) .arg("config") .arg("--config-path") - .output()?; + .assert() + .success() + .stdout(contains_path(temp.path().join("wasmer.toml"))); +} - let config_path = get_wasmer_dir()?.join("wasmer.toml"); - assert_eq!( - String::from_utf8_lossy(&output.stdout), - format!("{}\n", config_path.display()) - ); +#[test] +fn get_and_set_config_fields() -> anyhow::Result<()> { + let temp = setup_wasmer_dir(); // ---- config get - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("registry.token") @@ -190,7 +170,7 @@ fn config_works() -> anyhow::Result<()> { let original_token = String::from_utf8_lossy(&output.stdout); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("registry.token") @@ -199,7 +179,7 @@ fn config_works() -> anyhow::Result<()> { assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("registry.token") @@ -210,7 +190,7 @@ fn config_works() -> anyhow::Result<()> { "abc123\n".to_string() ); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("registry.token") @@ -219,7 +199,7 @@ fn config_works() -> anyhow::Result<()> { assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("registry.token") @@ -230,7 +210,7 @@ fn config_works() -> anyhow::Result<()> { format!("{}\n", original_token.to_string().trim()) ); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("registry.url") @@ -238,18 +218,18 @@ fn config_works() -> anyhow::Result<()> { let original_url = String::from_utf8_lossy(&output.stdout); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("registry.url") - .arg("wapm.dev") + .arg("wasmer.wtf") .output()?; let output_str = String::from_utf8_lossy(&output.stdout); assert_eq!(output_str, "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("registry.url") @@ -258,10 +238,10 @@ fn config_works() -> anyhow::Result<()> { let output_str = String::from_utf8_lossy(&output.stdout); assert_eq!( output_str, - "https://registry.wapm.dev/graphql\n".to_string() + "https://registry.wasmer.wtf/graphql\n".to_string() ); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("registry.url") @@ -271,7 +251,7 @@ fn config_works() -> anyhow::Result<()> { let output_str = String::from_utf8_lossy(&output.stdout); assert_eq!(output_str, "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("registry.url") @@ -280,7 +260,7 @@ fn config_works() -> anyhow::Result<()> { let output_str = String::from_utf8_lossy(&output.stdout); assert_eq!(output_str, original_url.to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("telemetry.enabled") @@ -288,7 +268,7 @@ fn config_works() -> anyhow::Result<()> { let original_output = String::from_utf8_lossy(&output.stdout); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("telemetry.enabled") @@ -297,7 +277,7 @@ fn config_works() -> anyhow::Result<()> { assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("telemetry.enabled") @@ -308,7 +288,7 @@ fn config_works() -> anyhow::Result<()> { "true\n".to_string() ); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("telemetry.enabled") @@ -317,7 +297,7 @@ fn config_works() -> anyhow::Result<()> { assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("telemetry.enabled") @@ -328,7 +308,7 @@ fn config_works() -> anyhow::Result<()> { original_output.to_string() ); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("update-notifications.enabled") @@ -336,7 +316,7 @@ fn config_works() -> anyhow::Result<()> { let original_output = String::from_utf8_lossy(&output.stdout); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("update-notifications.enabled") @@ -345,7 +325,7 @@ fn config_works() -> anyhow::Result<()> { assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("update-notifications.enabled") @@ -356,7 +336,7 @@ fn config_works() -> anyhow::Result<()> { "true\n".to_string() ); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("set") .arg("update-notifications.enabled") @@ -365,7 +345,7 @@ fn config_works() -> anyhow::Result<()> { assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string()); - let output = Command::new(get_wasmer_path()) + let output = wasmer_cmd(&temp) .arg("config") .arg("get") .arg("update-notifications.enabled") diff --git a/tests/integration/cli/tests/create_exe.rs b/tests/integration/cli/tests/create_exe.rs index e368d422d6b..f46753bee29 100644 --- a/tests/integration/cli/tests/create_exe.rs +++ b/tests/integration/cli/tests/create_exe.rs @@ -1,25 +1,17 @@ //! Tests of the `wasmer create-exe` command. +use std::{ + fs, + io::prelude::*, + path::{Path, PathBuf}, + process::Command, +}; + use anyhow::{bail, Context}; -use std::fs; -use std::io::prelude::*; -use std::path::PathBuf; -use std::process::Command; +use assert_cmd::prelude::OutputAssertExt; use tempfile::TempDir; use wasmer_integration_tests_cli::*; -fn create_exe_wabt_path() -> String { - format!("{}/{}", C_ASSET_PATH, "wabt-1.0.37.wasmer") -} - -#[allow(dead_code)] -fn create_exe_python_wasmer() -> String { - format!("{}/{}", C_ASSET_PATH, "python-0.1.0.wasmer") -} - -fn create_exe_test_wasm_path() -> String { - format!("{}/{}", C_ASSET_PATH, "qjs.wasm") -} const JS_TEST_SRC_CODE: &[u8] = b"function greet(name) { return JSON.stringify('Hello, ' + name); }; print(greet('World'));\n"; @@ -49,7 +41,7 @@ impl Default for WasmerCreateExe { Self { current_dir: std::env::current_dir().unwrap(), wasmer_path: get_wasmer_path(), - wasm_path: PathBuf::from(create_exe_test_wasm_path()), + wasm_path: PathBuf::from(fixtures::qjs()), native_executable_path, compiler: Compiler::Cranelift, extra_cli_flags: vec![], @@ -124,7 +116,7 @@ impl Default for WasmerCreateObj { Self { current_dir: std::env::current_dir().unwrap(), wasmer_path: get_wasmer_path(), - wasm_path: PathBuf::from(create_exe_test_wasm_path()), + wasm_path: PathBuf::from(fixtures::qjs()), output_object_path, compiler: Compiler::Cranelift, extra_cli_flags: vec![], @@ -169,7 +161,7 @@ fn test_create_exe_with_pirita_works_1() { let wasm_out = path.join("out.obj"); let cmd = Command::new(get_wasmer_path()) .arg("create-obj") - .arg(create_exe_wabt_path()) + .arg(fixtures::wabt()) .arg("-o") .arg(&wasm_out) .output() @@ -187,7 +179,7 @@ fn test_create_exe_with_pirita_works_1() { let cmd = Command::new(get_wasmer_path()) .arg("create-obj") - .arg(create_exe_wabt_path()) + .arg(fixtures::wabt()) .arg("--atom") .arg("wasm2wat") .arg("-o") @@ -222,7 +214,7 @@ fn test_create_exe_with_precompiled_works_1() { let wasm_out = path.join("out.obj"); let _ = Command::new(get_wasmer_path()) .arg("create-obj") - .arg(create_exe_test_wasm_path()) + .arg(fixtures::qjs()) .arg("--prefix") .arg("sha123123") .arg("-o") @@ -244,7 +236,7 @@ fn test_create_exe_with_precompiled_works_1() { let _ = Command::new(get_wasmer_path()) .arg("create-obj") - .arg(create_exe_test_wasm_path()) + .arg(fixtures::qjs()) .arg("-o") .arg(&wasm_out) .output() @@ -276,7 +268,7 @@ fn create_exe_works() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + let wasm_path = operating_dir.join(fixtures::qjs()); #[cfg(not(windows))] let executable_path = operating_dir.join("wasm.out"); #[cfg(windows)] @@ -317,7 +309,7 @@ fn create_exe_works_multi_command_args_handling() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_wabt_path()); + let wasm_path = operating_dir.join(fixtures::wabt()); #[cfg(not(windows))] let executable_path = operating_dir.join("multicommand.out"); #[cfg(windows)] @@ -383,7 +375,7 @@ fn create_exe_works_multi_command_args_handling() -> anyhow::Result<()> { fn create_exe_works_underscore_module_name() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_wabt_path()); + let wasm_path = operating_dir.join(fixtures::wabt()); let atoms = &[ "wabt", @@ -450,7 +442,7 @@ fn create_exe_works_multi_command() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_wabt_path()); + let wasm_path = operating_dir.join(fixtures::wabt()); #[cfg(not(windows))] let executable_path = operating_dir.join("multicommand.out"); #[cfg(windows)] @@ -507,7 +499,7 @@ fn create_exe_works_with_file() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + let wasm_path = operating_dir.join(fixtures::qjs()); #[cfg(not(windows))] let executable_path = operating_dir.join("wasm.out"); #[cfg(windows)] @@ -568,7 +560,7 @@ fn create_obj(args: Vec) -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.as_path().join(create_exe_test_wasm_path()); + let wasm_path = operating_dir.as_path().join(fixtures::qjs()); let object_path = operating_dir.as_path().join("wasm"); let _output: Vec = WasmerCreateObj { @@ -600,7 +592,7 @@ fn create_exe_with_object_input(args: Vec) -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + let wasm_path = operating_dir.join(fixtures::qjs()); #[cfg(not(windows))] let object_path = operating_dir.join("wasm.o"); @@ -684,3 +676,170 @@ fn create_exe_with_object_input(args: Vec) -> anyhow::Result<()> { fn create_exe_with_object_input_default() -> anyhow::Result<()> { create_exe_with_object_input(vec![]) } + +/// TODO: on linux-musl, the packaging of libwasmer.a doesn't work properly +/// Tracked in https://github.com/wasmerio/wasmer/issues/3271 +#[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] +#[test] +fn test_wasmer_create_exe_pirita_works() { + // let temp_dir = Path::new("debug"); + // std::fs::create_dir_all(&temp_dir); + + use wasmer_integration_tests_cli::get_repo_root_path; + let temp_dir = TempDir::new().unwrap(); + let temp_dir = temp_dir.path().to_path_buf(); + let python_wasmer_path = temp_dir.join("python.wasmer"); + std::fs::copy(fixtures::python(), &python_wasmer_path).unwrap(); + let python_exe_output_path = temp_dir.join("python"); + + let native_target = target_lexicon::HOST; + let tmp_targz_path = get_repo_root_path().unwrap().join("link.tar.gz"); + + println!("compiling to target {native_target}"); + + let mut cmd = Command::new(get_wasmer_path()); + cmd.arg("create-exe"); + cmd.arg(&python_wasmer_path); + cmd.arg("--tarball"); + cmd.arg(&tmp_targz_path); + cmd.arg("--target"); + cmd.arg(format!("{native_target}")); + cmd.arg("-o"); + cmd.arg(&python_exe_output_path); + // change temp_dir to a local path and run this test again + // to output the compilation files into a debug folder + // + // cmd.arg("--debug-dir"); + // cmd.arg(&temp_dir); + + cmd.assert().success(); + + println!("compilation ok!"); + + if !python_exe_output_path.exists() { + panic!( + "python_exe_output_path {} does not exist", + python_exe_output_path.display() + ); + } + + println!("invoking command..."); + + let mut command = Command::new(&python_exe_output_path); + command.arg("-c"); + command.arg("print(\"hello\")"); + + command.assert().success().stdout("hello\n"); +} + +// FIXME: Fix and re-enable this test +// See https://github.com/wasmerio/wasmer/issues/3615 +#[test] +#[ignore] +fn test_cross_compile_python_windows() { + let temp_dir = TempDir::new().unwrap(); + + let targets: &[&str] = if cfg!(windows) { + &[ + "aarch64-darwin", + "x86_64-darwin", + "x86_64-linux-gnu", + "aarch64-linux-gnu", + ] + } else { + &[ + "aarch64-darwin", + "x86_64-darwin", + "x86_64-linux-gnu", + "aarch64-linux-gnu", + "x86_64-windows-gnu", + ] + }; + + let compilers: &[&str] = if cfg!(target_env = "musl") { + // MUSL has no support for LLVM in C-API + &["cranelift", "singlepass"] + } else { + &["cranelift", "singlepass", "llvm"] + }; + + // llvm-objdump --disassemble-all --demangle ./objects/wasmer_vm-50cb118b098c15db.wasmer_vm.60425a0a-cgu.12.rcgu.o + // llvm-objdump --macho --exports-trie ~/.wasmer/cache/wasmer-darwin-arm64/lib/libwasmer.dylib + let excluded_combinations = &[ + ("aarch64-darwin", "llvm"), // LLVM: aarch64 not supported relocation Arm64MovwG0 not supported + ("aarch64-linux-gnu", "llvm"), // LLVM: aarch64 not supported relocation Arm64MovwG0 not supported + // https://github.com/ziglang/zig/issues/13729 + ("x86_64-darwin", "llvm"), // undefined reference to symbol 'wasmer_vm_raise_trap' kind Unknown + ("x86_64-windows-gnu", "llvm"), // unimplemented symbol `wasmer_vm_raise_trap` kind Unknown + ]; + + for t in targets { + for c in compilers { + if excluded_combinations.contains(&(t, c)) { + continue; + } + println!("{t} target {c}"); + let python_wasmer_path = temp_dir.path().join(format!("{t}-python")); + + let tarball = match std::env::var("GITHUB_TOKEN") { + Ok(_) => Some(assert_tarball_is_present_local(t).unwrap()), + Err(_) => None, + }; + let mut cmd = Command::new(get_wasmer_path()); + + cmd.arg("create-exe"); + cmd.arg(fixtures::python()); + cmd.arg("--target"); + cmd.arg(t); + cmd.arg("-o"); + cmd.arg(python_wasmer_path.clone()); + cmd.arg(format!("--{c}")); + if std::env::var("GITHUB_TOKEN").is_ok() { + cmd.arg("--debug-dir"); + cmd.arg(format!("{t}-{c}")); + } + + if t.contains("x86_64") && *c == "singlepass" { + cmd.arg("-m"); + cmd.arg("avx"); + } + + if let Some(t) = tarball { + cmd.arg("--tarball"); + cmd.arg(t); + } + + let assert = cmd.assert().success(); + + if !python_wasmer_path.exists() { + let p = std::fs::read_dir(temp_dir.path()) + .unwrap() + .filter_map(|e| Some(e.ok()?.path())) + .collect::>(); + let output = assert.get_output(); + panic!("target {t} was not compiled correctly tempdir: {p:#?}, {output:?}",); + } + } + } +} + +fn assert_tarball_is_present_local(target: &str) -> Result { + let wasmer_dir = std::env::var("WASMER_DIR").expect("no WASMER_DIR set"); + let directory = match target { + "aarch64-darwin" => "wasmer-darwin-arm64.tar.gz", + "x86_64-darwin" => "wasmer-darwin-amd64.tar.gz", + "x86_64-linux-gnu" => "wasmer-linux-amd64.tar.gz", + "aarch64-linux-gnu" => "wasmer-linux-aarch64.tar.gz", + "x86_64-windows-gnu" => "wasmer-windows-gnu64.tar.gz", + _ => return Err(anyhow::anyhow!("unknown target {target}")), + }; + let libwasmer_cache_path = Path::new(&wasmer_dir).join("cache").join(directory); + if !libwasmer_cache_path.exists() { + return Err(anyhow::anyhow!( + "targz {} does not exist", + libwasmer_cache_path.display() + )); + } + println!("using targz {}", libwasmer_cache_path.display()); + Ok(libwasmer_cache_path) +} diff --git a/tests/integration/cli/tests/gen_c_header.rs b/tests/integration/cli/tests/gen_c_header.rs index af56cac610a..ee1a5a67aa6 100644 --- a/tests/integration/cli/tests/gen_c_header.rs +++ b/tests/integration/cli/tests/gen_c_header.rs @@ -1,22 +1,13 @@ -use std::path::PathBuf; -use std::process::Command; -use wasmer_integration_tests_cli::get_wasmer_path; -use wasmer_integration_tests_cli::C_ASSET_PATH; +use std::{path::PathBuf, process::Command}; -fn create_exe_wabt_path() -> String { - format!("{}/{}", C_ASSET_PATH, "wabt-1.0.37.wasmer") -} - -fn create_exe_test_wasm_path() -> String { - format!("{}/{}", C_ASSET_PATH, "qjs.wasm") -} +use wasmer_integration_tests_cli::{fixtures, get_wasmer_path}; #[test] fn gen_c_header_works() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_test_wasm_path()); + let wasm_path = operating_dir.join(fixtures::qjs()); let out_path = temp_dir.path().join("header.h"); let _ = Command::new(get_wasmer_path()) @@ -54,7 +45,7 @@ fn gen_c_header_works_pirita() -> anyhow::Result<()> { let temp_dir = tempfile::tempdir()?; let operating_dir: PathBuf = temp_dir.path().to_owned(); - let wasm_path = operating_dir.join(create_exe_wabt_path()); + let wasm_path = operating_dir.join(fixtures::wabt()); let out_path = temp_dir.path().join("header.h"); let _ = Command::new(get_wasmer_path()) diff --git a/tests/integration/cli/tests/init.rs b/tests/integration/cli/tests/init.rs index 8b2029c254c..3413f5b998d 100644 --- a/tests/integration/cli/tests/init.rs +++ b/tests/integration/cli/tests/init.rs @@ -1,24 +1,18 @@ -use anyhow::bail; +#[macro_use] +extern crate pretty_assertions; -use std::process::{Command, Stdio}; -use wasmer_integration_tests_cli::get_wasmer_path; +use assert_cmd::prelude::OutputAssertExt; +use tempfile::TempDir; -macro_rules! check_output { - ($output:expr) => { - let stdout_output = std::str::from_utf8(&$output.stdout).unwrap(); - let stderr_output = std::str::from_utf8(&$output.stdout).unwrap(); - if !$output.status.success() { - bail!("wasmer init failed with: stdout: {stdout_output}\n\nstderr: {stderr_output}"); - } - }; -} +use std::process::Command; +use wasmer_integration_tests_cli::get_wasmer_path; // Test that wasmer init without arguments works #[test] fn wasmer_init_works_1() -> anyhow::Result<()> { + let wasmer_dir = TempDir::new()?; let tempdir = tempfile::tempdir()?; - let path = tempdir.path(); - let path = path.join("testfirstproject"); + let path = tempdir.path().join("testfirstproject"); std::fs::create_dir_all(&path)?; if std::env::var("GITHUB_TOKEN").is_err() { @@ -33,36 +27,29 @@ fn wasmer_init_works_1() -> anyhow::Result<()> { if token.is_empty() { return Ok(()); } - let output = Command::new(get_wasmer_path()) + Command::new(get_wasmer_path()) .arg("login") - .arg("--registry") - .arg("wapm.dev") + .arg("--registry=wapm.dev") .arg(token) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .stdin(Stdio::null()) - .output()?; - check_output!(output); + .env("WASMER_DIR", wasmer_dir.path()) + .assert() + .success(); } println!("wasmer login ok!"); - let output = Command::new(get_wasmer_path()) + Command::new(get_wasmer_path()) .arg("init") .current_dir(&path) - .output()?; - check_output!(output); - - let read = std::fs::read_to_string(path.join("wasmer.toml")) - .unwrap() - .lines() - .collect::>() - .join("\n"); - let target = include_str!("./fixtures/init1.toml") - .lines() - .collect::>() - .join("\n"); - pretty_assertions::assert_eq!(read.trim(), target.trim()); + .env("WASMER_DIR", wasmer_dir.path()) + .assert() + .success(); + + assert_eq!( + std::fs::read_to_string(path.join("wasmer.toml")).unwrap(), + include_str!("./fixtures/init1.toml"), + ); + Ok(()) } @@ -91,45 +78,33 @@ fn wasmer_init_works_2() -> anyhow::Result<()> { if token.is_empty() { return Ok(()); } - let mut cmd = Command::new(get_wasmer_path()); - cmd.arg("login"); - cmd.arg("--registry"); - cmd.arg("wapm.dev"); - cmd.arg(token); - cmd.stdout(Stdio::inherit()); - cmd.stderr(Stdio::inherit()); - cmd.stdin(Stdio::null()); - let output = cmd.output()?; - check_output!(output); + Command::new(get_wasmer_path()) + .arg("login") + .arg("--registry=wapm.dev") + .arg(token) + .assert() + .success(); } println!("wasmer login ok!"); - let output = Command::new(get_wasmer_path()) + Command::new(get_wasmer_path()) .arg("init") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) .current_dir(&path) - .output()?; - check_output!(output); + .assert() + .success(); - pretty_assertions::assert_eq!( + assert_eq!( std::fs::read_to_string(path.join("Cargo.toml")).unwrap(), include_str!("./fixtures/init2.toml") ); println!("ok 1"); - let read = std::fs::read_to_string(path.join("wasmer.toml")) - .unwrap() - .lines() - .collect::>() - .join("\n"); - let target = include_str!("./fixtures/init4.toml") - .lines() - .collect::>() - .join("\n"); - pretty_assertions::assert_eq!(read.trim(), target.trim()); + assert_eq!( + std::fs::read_to_string(path.join("wasmer.toml")).unwrap(), + include_str!("./fixtures/init4.toml") + ); Ok(()) } diff --git a/tests/integration/cli/tests/login.rs b/tests/integration/cli/tests/login.rs index e06951fd5c8..1dd58b5d51f 100644 --- a/tests/integration/cli/tests/login.rs +++ b/tests/integration/cli/tests/login.rs @@ -1,53 +1,61 @@ -use anyhow::bail; +use assert_cmd::prelude::OutputAssertExt; +use predicates::str::contains; +use tempfile::TempDir; use std::process::Command; use wasmer_integration_tests_cli::get_wasmer_path; #[test] -fn login_works() -> anyhow::Result<()> { +fn login_works() { + let wasmer_dir = TempDir::new().unwrap(); + // running test locally: should always pass since // developers don't have access to WAPM_DEV_TOKEN if std::env::var("GITHUB_TOKEN").is_err() { - return Ok(()); + return; } let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").expect("WAPM_DEV_TOKEN env var not set"); // Special case: GitHub secrets aren't visible to outside collaborators if wapm_dev_token.is_empty() { - return Ok(()); + return; } - // FIXME: Change the registry to wasmer.wtf and all the associated terms with WAPM - let output = Command::new(get_wasmer_path()) + let assert = Command::new(get_wasmer_path()) .arg("login") - .arg("--registry") - .arg("wapm.dev") + .arg("--registry=wasmer.wtf") .arg(wapm_dev_token) - .output()?; + .env("WASMER_DIR", wasmer_dir.path()) + .assert(); - let stdout = std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"); + assert + .success() + .stdout(contains(r#"Login for Wasmer user "ciuser" saved"#)); +} - let stderr = std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes"); +#[test] +fn run_whoami_works() { + let wasmer_dir = TempDir::new().unwrap(); - if !output.status.success() { - bail!( - "wasmer login failed with: stdout: {}\n\nstderr: {}", - stdout, - stderr - ); + // running test locally: should always pass since + // developers don't have access to WAPM_DEV_TOKEN + if std::env::var("GITHUB_TOKEN").is_err() { + return; } - let stdout_output = std::str::from_utf8(&output.stdout).unwrap(); - let expected = "Done!\n✅ Login for Wasmer user \"ciuser\" saved\n"; - if stdout_output != expected { - println!("expected:"); - println!("{expected}"); - println!("got:"); - println!("{stdout}"); - println!("-----"); - println!("{stderr}"); - panic!("stdout incorrect"); + let ciuser_token = std::env::var("WAPM_DEV_TOKEN").expect("no CIUSER / WAPM_DEV_TOKEN token"); + // Special case: GitHub secrets aren't visible to outside collaborators + if ciuser_token.is_empty() { + return; } - Ok(()) + let assert = Command::new(get_wasmer_path()) + .arg("whoami") + .arg("--registry=wasmer.wtf") + .env("WASMER_TOKEN", &ciuser_token) + .env("WASMER_DIR", wasmer_dir.path()) + .assert() + .success(); + + assert.stdout( + "logged into registry \"https://registry.wasmer.wtf/graphql\" as user \"ciuser\"\n", + ); } diff --git a/tests/integration/cli/tests/publish.rs b/tests/integration/cli/tests/publish.rs index 3b0d09b2d40..1b583467c51 100644 --- a/tests/integration/cli/tests/publish.rs +++ b/tests/integration/cli/tests/publish.rs @@ -1,19 +1,15 @@ -use std::process::Stdio; -use wasmer_integration_tests_cli::{get_wasmer_path, C_ASSET_PATH}; - -fn create_exe_test_wasm_path() -> String { - format!("{}/{}", C_ASSET_PATH, "qjs.wasm") -} +use assert_cmd::prelude::OutputAssertExt; +use wasmer_integration_tests_cli::{fixtures, get_wasmer_path}; #[test] -fn wasmer_publish() -> anyhow::Result<()> { +fn wasmer_publish() { // Only run this test in the CI if std::env::var("GITHUB_TOKEN").is_err() { - return Ok(()); + return; } let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok(); - let tempdir = tempfile::tempdir()?; + let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path(); let username = "ciuser"; @@ -21,7 +17,7 @@ fn wasmer_publish() -> anyhow::Result<()> { let random2 = format!("{}", rand::random::()); let random3 = format!("{}", rand::random::()); - std::fs::copy(create_exe_test_wasm_path(), path.join("largewasmfile.wasm")).unwrap(); + std::fs::copy(fixtures::qjs(), path.join("largewasmfile.wasm")).unwrap(); std::fs::write( path.join("wasmer.toml"), include_str!("./fixtures/init6.toml") @@ -29,47 +25,39 @@ fn wasmer_publish() -> anyhow::Result<()> { .replace("RANDOMVERSION1", &random1) .replace("RANDOMVERSION2", &random2) .replace("RANDOMVERSION3", &random3), - )?; + ) + .unwrap(); let mut cmd = std::process::Command::new(get_wasmer_path()); - cmd.arg("publish"); - cmd.arg("--quiet"); - cmd.arg("--registry"); - cmd.arg("wapm.dev"); - cmd.arg(path); + cmd.arg("publish") + .arg("--quiet") + .arg("--registry=wasmer.wtf") + .arg(path); if let Some(token) = wapm_dev_token { // Special case: GitHub secrets aren't visible to outside collaborators if token.is_empty() { - return Ok(()); + return; } - cmd.arg("--token"); - cmd.arg(token); + cmd.arg("--token").arg(token); } - let output = cmd.stdin(Stdio::null()).output().unwrap(); - - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - assert_eq!(stdout, format!("Successfully published package `{username}/largewasmfile@{random1}.{random2}.{random3}`\n"), "failed to publish: {cmd:?}: {stderr}"); - - println!("wasmer publish ok! test done."); - - Ok(()) + cmd.assert().success().stdout(format!( + "Successfully published package `{username}/largewasmfile@{random1}.{random2}.{random3}`\n" + )); } // Runs a full integration test to test that the flow wasmer init - cargo build - // wasmer publish is working #[test] -fn wasmer_init_publish() -> anyhow::Result<()> { +fn wasmer_init_publish() { // Only run this test in the CI if std::env::var("GITHUB_TOKEN").is_err() { - return Ok(()); + return; } let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok(); - let tempdir = tempfile::tempdir()?; + let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path(); let username = "ciuser"; @@ -77,48 +65,33 @@ fn wasmer_init_publish() -> anyhow::Result<()> { let random2 = format!("{}", rand::random::()); let random3 = format!("{}", rand::random::()); - let mut cmd = std::process::Command::new("cargo"); - cmd.arg("init"); - cmd.arg("--bin"); - cmd.arg(path.join("randomversion")); - - let _ = cmd - .stdin(Stdio::null()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() - .unwrap(); - - let mut cmd = std::process::Command::new("cargo"); - cmd.arg("build"); - cmd.arg("--release"); - cmd.arg("--target"); - cmd.arg("wasm32-wasi"); - cmd.arg("--manifest-path"); - cmd.arg(path.join("randomversion").join("Cargo.toml")); - - let _ = cmd - .stdin(Stdio::null()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() - .unwrap(); + // Create a new Rust project and build it + std::process::Command::new("cargo") + .arg("init") + .arg("--bin") + .arg(path.join("randomversion")) + .assert() + .success(); + std::process::Command::new("cargo") + .arg("build") + .arg("--release") + .arg("--target") + .arg("wasm32-wasi") + .arg("--manifest-path") + .arg(path.join("randomversion").join("Cargo.toml")) + .assert() + .success(); // generate the wasmer.toml - let mut cmd = std::process::Command::new(get_wasmer_path()); - cmd.arg("init"); - cmd.arg("--namespace"); - cmd.arg(username); - cmd.arg("--version"); - cmd.arg(format!("{random1}.{random2}.{random3}")); - cmd.arg(path.join("randomversion")); - - let _ = cmd - .stdin(Stdio::null()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() - .unwrap(); + std::process::Command::new(get_wasmer_path()) + .arg("init") + .arg("--namespace") + .arg(username) + .arg("--version") + .arg(format!("{random1}.{random2}.{random3}")) + .arg(path.join("randomversion")) + .assert() + .success(); let s = std::fs::read_to_string(path.join("randomversion").join("wasmer.toml")).unwrap(); @@ -126,29 +99,22 @@ fn wasmer_init_publish() -> anyhow::Result<()> { // publish let mut cmd = std::process::Command::new(get_wasmer_path()); - cmd.arg("publish"); - cmd.arg("--quiet"); - cmd.arg("--registry"); - cmd.arg("wapm.dev"); - cmd.arg(path.join("randomversion")); + cmd.arg("publish") + .arg("--quiet") + .arg("--registry=wasmer.wtf") + .arg(path.join("randomversion")); if let Some(token) = wapm_dev_token { // Special case: GitHub secrets aren't visible to outside collaborators if token.is_empty() { - return Ok(()); + return; } - cmd.arg("--token"); - cmd.arg(token); + cmd.arg("--token").arg(token); } - let output = cmd.stdin(Stdio::null()).output().unwrap(); - - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - - assert_eq!(stdout, format!("Successfully published package `{username}/randomversion@{random1}.{random2}.{random3}`\n"), "failed to publish: {cmd:?}: {stderr}"); - - println!("wasmer init publish ok! test done."); + let assert = cmd.assert(); - Ok(()) + assert.success().stdout(format!( + "Successfully published package `{username}/randomversion@{random1}.{random2}.{random3}`\n" + )); } diff --git a/tests/integration/cli/tests/run.rs b/tests/integration/cli/tests/run.rs index 00c106a9ac8..616c32af455 100644 --- a/tests/integration/cli/tests/run.rs +++ b/tests/integration/cli/tests/run.rs @@ -1,25 +1,47 @@ //! Basic tests for the `run` subcommand -use assert_cmd::Command; +use std::{ + io::{ErrorKind, Read}, + path::Path, + process::{Child, Command, Stdio}, + time::{Duration, Instant}, +}; + +use assert_cmd::{assert::Assert, prelude::OutputAssertExt}; +use once_cell::sync::Lazy; use predicates::str::contains; -use std::path::{Path, PathBuf}; -use wasmer_integration_tests_cli::{get_wasmer_path, ASSET_PATH, C_ASSET_PATH}; - -fn wasi_test_python_path() -> PathBuf { - Path::new(C_ASSET_PATH).join("python-0.1.0.wasmer") -} - -fn wasi_test_wasm_path() -> PathBuf { - Path::new(C_ASSET_PATH).join("qjs.wasm") -} - -fn test_no_imports_wat_path() -> PathBuf { - Path::new(ASSET_PATH).join("fib.wat") -} - -fn test_no_start_wat_path() -> PathBuf { - Path::new(ASSET_PATH).join("no_start.wat") -} +use rand::Rng; +use reqwest::{blocking::Client, IntoUrl}; +use tempfile::TempDir; +use wasmer_integration_tests_cli::{asset_path, fixtures, get_wasmer_path}; + +const HTTP_GET_TIMEOUT: Duration = Duration::from_secs(5); + +static RUST_LOG: Lazy = Lazy::new(|| { + if cfg!(feature = "debug") { + "trace".to_string() + } else { + [ + "info", + "wasmer_wasix::resolve=debug", + "wasmer_wasix::runners=debug", + "wasmer_wasix=debug", + "virtual_fs::trace_fs=trace", + ] + .join(",") + } +}); + +/// A version of `$RUST_LOG` used for checking cache messages. +static CACHE_RUST_LOG: Lazy = Lazy::new(|| { + [ + "wasmer_wasix::runtime::resolver::wapm_source=debug", + "wasmer_wasix::runtime::resolver::web_source=debug", + "wasmer_wasix::runtime::package_loader::builtin_loader=debug", + "wasmer_wasix::runtime::module_cache::filesystem=debug", + ] + .join(",") +}); /// Ignored on Windows because running vendored packages does not work /// since Windows does not allow `::` characters in filenames (every other OS does) @@ -27,8 +49,8 @@ fn test_no_start_wat_path() -> PathBuf { /// The syntax for vendored package atoms has to be reworked for this to be fixed, see /// https://github.com/wasmerio/wasmer/issues/3535 // FIXME: Re-enable. See https://github.com/wasmerio/wasmer/issues/3717 -#[ignore] #[test] +#[ignore] fn test_run_customlambda() { let assert = Command::new(get_wasmer_path()) .arg("config") @@ -48,7 +70,7 @@ fn test_run_customlambda() { let assert = Command::new(get_wasmer_path()) .arg("run") - .arg("https://wapm.io/ciuser/customlambda") + .arg("https://wasmer.io/ciuser/customlambda") // TODO: this argument should not be necessary later // see https://github.com/wasmerio/wasmer/issues/3514 .arg("customlambda.py") @@ -60,7 +82,7 @@ fn test_run_customlambda() { // Run again to verify the caching let assert = Command::new(get_wasmer_path()) .arg("run") - .arg("https://wapm.io/ciuser/customlambda") + .arg("https://wasmer.io/ciuser/customlambda") // TODO: this argument should not be necessary later // see https://github.com/wasmerio/wasmer/issues/3514 .arg("customlambda.py") @@ -70,156 +92,11 @@ fn test_run_customlambda() { assert.stdout("139583862445\n"); } -#[allow(dead_code)] -fn assert_tarball_is_present_local(target: &str) -> Result { - let wasmer_dir = std::env::var("WASMER_DIR").expect("no WASMER_DIR set"); - let directory = match target { - "aarch64-darwin" => "wasmer-darwin-arm64.tar.gz", - "x86_64-darwin" => "wasmer-darwin-amd64.tar.gz", - "x86_64-linux-gnu" => "wasmer-linux-amd64.tar.gz", - "aarch64-linux-gnu" => "wasmer-linux-aarch64.tar.gz", - "x86_64-windows-gnu" => "wasmer-windows-gnu64.tar.gz", - _ => return Err(anyhow::anyhow!("unknown target {target}")), - }; - let libwasmer_cache_path = Path::new(&wasmer_dir).join("cache").join(directory); - if !libwasmer_cache_path.exists() { - return Err(anyhow::anyhow!( - "targz {} does not exist", - libwasmer_cache_path.display() - )); - } - println!("using targz {}", libwasmer_cache_path.display()); - Ok(libwasmer_cache_path) -} - -// FIXME: Fix and re-enable this test -// See https://github.com/wasmerio/wasmer/issues/3615 -// #[test] -#[allow(dead_code)] -fn test_cross_compile_python_windows() { - let temp_dir = tempfile::TempDir::new().unwrap(); - - #[cfg(not(windows))] - let targets = &[ - "aarch64-darwin", - "x86_64-darwin", - "x86_64-linux-gnu", - "aarch64-linux-gnu", - "x86_64-windows-gnu", - ]; - - #[cfg(windows)] - let targets = &[ - "aarch64-darwin", - "x86_64-darwin", - "x86_64-linux-gnu", - "aarch64-linux-gnu", - ]; - - // MUSL has no support for LLVM in C-API - #[cfg(target_env = "musl")] - let compilers = &["cranelift", "singlepass"]; - #[cfg(not(target_env = "musl"))] - let compilers = &["cranelift", "singlepass", "llvm"]; - - // llvm-objdump --disassemble-all --demangle ./objects/wasmer_vm-50cb118b098c15db.wasmer_vm.60425a0a-cgu.12.rcgu.o - // llvm-objdump --macho --exports-trie ~/.wasmer/cache/wasmer-darwin-arm64/lib/libwasmer.dylib - let excluded_combinations = &[ - ("aarch64-darwin", "llvm"), // LLVM: aarch64 not supported relocation Arm64MovwG0 not supported - ("aarch64-linux-gnu", "llvm"), // LLVM: aarch64 not supported relocation Arm64MovwG0 not supported - // https://github.com/ziglang/zig/issues/13729 - ("x86_64-darwin", "llvm"), // undefined reference to symbol 'wasmer_vm_raise_trap' kind Unknown - ("x86_64-windows-gnu", "llvm"), // unimplemented symbol `wasmer_vm_raise_trap` kind Unknown - ]; - - for t in targets { - for c in compilers { - if excluded_combinations.contains(&(t, c)) { - continue; - } - println!("{t} target {c}"); - let python_wasmer_path = temp_dir.path().join(format!("{t}-python")); - - let tarball = match std::env::var("GITHUB_TOKEN") { - Ok(_) => Some(assert_tarball_is_present_local(t).unwrap()), - Err(_) => None, - }; - let mut cmd = Command::new(get_wasmer_path()); - - cmd.arg("create-exe"); - cmd.arg(wasi_test_python_path()); - cmd.arg("--target"); - cmd.arg(t); - cmd.arg("-o"); - cmd.arg(python_wasmer_path.clone()); - cmd.arg(format!("--{c}")); - if std::env::var("GITHUB_TOKEN").is_ok() { - cmd.arg("--debug-dir"); - cmd.arg(format!("{t}-{c}")); - } - - if t.contains("x86_64") && *c == "singlepass" { - cmd.arg("-m"); - cmd.arg("avx"); - } - - if let Some(t) = tarball { - cmd.arg("--tarball"); - cmd.arg(t); - } - - let assert = cmd.assert().success(); - - if !python_wasmer_path.exists() { - let p = std::fs::read_dir(temp_dir.path()) - .unwrap() - .filter_map(|e| Some(e.ok()?.path())) - .collect::>(); - let output = assert.get_output(); - panic!("target {t} was not compiled correctly tempdir: {p:#?}, {output:?}",); - } - } - } -} - -#[test] -fn run_whoami_works() { - // running test locally: should always pass since - // developers don't have access to WAPM_DEV_TOKEN - if std::env::var("GITHUB_TOKEN").is_err() { - return; - } - - let ciuser_token = std::env::var("WAPM_DEV_TOKEN").expect("no CIUSER / WAPM_DEV_TOKEN token"); - // Special case: GitHub secrets aren't visible to outside collaborators - if ciuser_token.is_empty() { - return; - } - - Command::new(get_wasmer_path()) - .arg("login") - .arg("--registry") - .arg("wapm.dev") - .arg(ciuser_token) - .assert() - .success(); - - let assert = Command::new(get_wasmer_path()) - .arg("whoami") - .arg("--registry") - .arg("wapm.dev") - .assert() - .success(); - - assert - .stdout("logged into registry \"https://registry.wapm.dev/graphql\" as user \"ciuser\"\n"); -} - #[test] fn run_wasi_works() { let assert = Command::new(get_wasmer_path()) .arg("run") - .arg(wasi_test_wasm_path()) + .arg(fixtures::qjs()) .arg("--") .arg("-e") .arg("print(3 * (4 + 5))") @@ -229,68 +106,13 @@ fn run_wasi_works() { assert.stdout("27\n"); } -/// TODO: on linux-musl, the packaging of libwasmer.a doesn't work properly -/// Tracked in https://github.com/wasmerio/wasmer/issues/3271 -#[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] -#[test] -fn test_wasmer_create_exe_pirita_works() { - // let temp_dir = Path::new("debug"); - // std::fs::create_dir_all(&temp_dir); - - use wasmer_integration_tests_cli::get_repo_root_path; - let temp_dir = tempfile::TempDir::new().unwrap(); - let temp_dir = temp_dir.path().to_path_buf(); - let python_wasmer_path = temp_dir.join("python.wasmer"); - std::fs::copy(wasi_test_python_path(), &python_wasmer_path).unwrap(); - let python_exe_output_path = temp_dir.join("python"); - - let native_target = target_lexicon::HOST; - let tmp_targz_path = get_repo_root_path().unwrap().join("link.tar.gz"); - - println!("compiling to target {native_target}"); - - let mut cmd = Command::new(get_wasmer_path()); - cmd.arg("create-exe"); - cmd.arg(&python_wasmer_path); - cmd.arg("--tarball"); - cmd.arg(&tmp_targz_path); - cmd.arg("--target"); - cmd.arg(format!("{native_target}")); - cmd.arg("-o"); - cmd.arg(&python_exe_output_path); - // change temp_dir to a local path and run this test again - // to output the compilation files into a debug folder - // - // cmd.arg("--debug-dir"); - // cmd.arg(&temp_dir); - - cmd.assert().success(); - - println!("compilation ok!"); - - if !python_exe_output_path.exists() { - panic!( - "python_exe_output_path {} does not exist", - python_exe_output_path.display() - ); - } - - println!("invoking command..."); - - let mut command = Command::new(&python_exe_output_path); - command.arg("-c"); - command.arg("print(\"hello\")"); - - command.assert().success().stdout("hello\n"); -} - // FIXME: Re-enable. See https://github.com/wasmerio/wasmer/issues/3717 #[test] #[ignore] fn test_wasmer_run_pirita_works() { let temp_dir = tempfile::TempDir::new().unwrap(); let python_wasmer_path = temp_dir.path().join("python.wasmer"); - std::fs::copy(wasi_test_python_path(), &python_wasmer_path).unwrap(); + std::fs::copy(fixtures::python(), &python_wasmer_path).unwrap(); let assert = Command::new(get_wasmer_path()) .arg("run") @@ -310,7 +132,7 @@ fn test_wasmer_run_pirita_works() { fn test_wasmer_run_pirita_url_works() { let assert = Command::new(get_wasmer_path()) .arg("run") - .arg("https://wapm.dev/syrusakbary/python") + .arg("https://wasmer.wtf/syrusakbary/python") .arg("--") .arg("-c") .arg("print(\"hello\")") @@ -325,9 +147,9 @@ fn test_wasmer_run_works_with_dir() { let temp_dir = tempfile::TempDir::new().unwrap(); let qjs_path = temp_dir.path().join("qjs.wasm"); - std::fs::copy(wasi_test_wasm_path(), &qjs_path).unwrap(); + std::fs::copy(fixtures::qjs(), &qjs_path).unwrap(); std::fs::copy( - format!("{}/{}", C_ASSET_PATH, "qjs-wasmer.toml"), + fixtures::qjs_wasmer_toml(), temp_dir.path().join("wasmer.toml"), ) .unwrap(); @@ -360,8 +182,8 @@ fn test_wasmer_run_works_with_dir() { #[test] fn test_wasmer_run_works() { let assert = Command::new(get_wasmer_path()) - .arg("https://wapm.io/python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg("https://wasmer.io/python/python") + .arg(format!("--mapdir=.:{}", asset_path().display())) .arg("test.py") .assert() .success(); @@ -371,29 +193,20 @@ fn test_wasmer_run_works() { // same test again, but this time with "wasmer run ..." let assert = Command::new(get_wasmer_path()) .arg("run") - .arg("https://wapm.io/python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg("https://wasmer.io/python/python") + .arg(format!("--mapdir=.:{}", asset_path().display())) .arg("test.py") .assert() .success(); assert.stdout("hello\n"); - // set wapm.io as the current registry - let _ = Command::new(get_wasmer_path()) - .arg("login") - .arg("--registry") - .arg("wapm.io") - // will fail, but set wapm.io as the current registry regardless - .arg("öladkfjasöldfkjasdölfkj") - .assert() - .success(); - - // same test again, but this time without specifying the registry + // same test again, but this time without specifying the registry in the URL let assert = Command::new(get_wasmer_path()) .arg("run") .arg("python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg(format!("--mapdir=.:{}", asset_path().display())) + .arg("--registry=wasmer.io") .arg("test.py") .assert() .success(); @@ -404,7 +217,8 @@ fn test_wasmer_run_works() { let assert = Command::new(get_wasmer_path()) .arg("run") .arg("_/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) + .arg(format!("--mapdir=.:{}", asset_path().display())) + .arg("--registry=wasmer.io") .arg("test.py") .assert() .success(); @@ -416,7 +230,7 @@ fn test_wasmer_run_works() { fn run_no_imports_wasm_works() { Command::new(get_wasmer_path()) .arg("run") - .arg(test_no_imports_wat_path()) + .arg(fixtures::fib()) .assert() .success(); } @@ -438,111 +252,117 @@ fn run_wasi_works_non_existent() -> anyhow::Result<()> { Ok(()) } -// FIXME: Re-enable. See https://github.com/wasmerio/wasmer/issues/3717 -#[ignore] #[test] fn run_test_caching_works_for_packages() { - // set wapm.io as the current registry - Command::new(get_wasmer_path()) - .arg("login") - .arg("--registry") - .arg("wapm.io") - // will fail, but set wapm.io as the current registry regardless - .arg("öladkfjasöldfkjasdölfkj") - .assert() - .success(); + // we're testing the cache, so we don't want to reuse the current user's + // $WASMER_DIR + let wasmer_dir = TempDir::new().unwrap(); let assert = Command::new(get_wasmer_path()) .arg("python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) - .arg("test.py") - .assert() - .success(); - - assert.stdout("hello\n"); + .arg(format!("--mapdir=/app:{}", asset_path().display())) + .arg("--registry=wasmer.io") + .arg("/app/test.py") + .env("WASMER_CACHE_DIR", wasmer_dir.path()) + .env("RUST_LOG", &*CACHE_RUST_LOG) + .assert(); - let time = std::time::Instant::now(); + assert + .success() + .stderr(contains("wapm_source: Querying the GraphQL API")) + .stderr(contains("builtin_loader: Downloading a webc file")) + .stderr(contains("module_cache::filesystem: Saved to disk")); let assert = Command::new(get_wasmer_path()) .arg("python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) - .arg("test.py") + .arg(format!("--mapdir=/app:{}", asset_path().display())) + .arg("--registry=wasmer.io") + .arg("/app/test.py") + .env("WASMER_CACHE_DIR", wasmer_dir.path()) + .env("RUST_LOG", &*CACHE_RUST_LOG) .assert() .success(); - assert.stdout("hello\n"); - - // package should be cached - assert!(std::time::Instant::now() - time < std::time::Duration::from_secs(1)); + assert + .stderr(contains("wapm_source: Cache hit!")) + .stderr(contains("builtin_loader: Cache hit!")) + .stderr(contains("module_cache::filesystem: Cache hit!")); } #[test] fn run_test_caching_works_for_packages_with_versions() { - // set wapm.io as the current registry - Command::new(get_wasmer_path()) - .arg("login") - .arg("--registry") - .arg("wapm.io") - // will fail, but set wapm.io as the current registry regardless - .arg("öladkfjasöldfkjasdölfkj") - .assert() - .success(); + let wasmer_dir = TempDir::new().unwrap(); let assert = Command::new(get_wasmer_path()) .arg("python/python@0.1.0") - .arg(format!("--mapdir=/app:{}", ASSET_PATH)) + .arg(format!("--mapdir=/app:{}", asset_path().display())) + .arg("--registry=wasmer.io") .arg("/app/test.py") + .env("RUST_LOG", &*CACHE_RUST_LOG) + .env("WASMER_CACHE_DIR", wasmer_dir.path()) .assert() .success(); - assert.stdout("hello\n"); + assert + .success() + .stderr(contains("wapm_source: Querying the GraphQL API")) + .stderr(contains("builtin_loader: Downloading a webc file")) + .stderr(contains("module_cache::filesystem: Saved to disk")); let assert = Command::new(get_wasmer_path()) .arg("python/python@0.1.0") - .arg(format!("--mapdir=/app:{}", ASSET_PATH)) + .arg(format!("--mapdir=/app:{}", asset_path().display())) + .arg("--registry=wasmer.io") .arg("/app/test.py") - .env( - "RUST_LOG", - "wasmer_wasix::runtime::package_loader::builtin_loader=debug", - ) + .env("RUST_LOG", &*CACHE_RUST_LOG) + .env("WASMER_CACHE_DIR", wasmer_dir.path()) .assert(); assert .success() - // it should have ran like normal - .stdout("hello\n") - // we hit the cache while fetching the package - .stderr(contains( - "builtin_loader: Cache hit! pkg.name=\"python\" pkg.version=0.1.0", - )); + .stderr(contains("wapm_source: Cache hit!")) + .stderr(contains("builtin_loader: Cache hit!")) + .stderr(contains("module_cache::filesystem: Cache hit!")); } -// FIXME: Re-enable. See https://github.com/wasmerio/wasmer/issues/3717 -#[ignore] #[test] fn run_test_caching_works_for_urls() { + let wasmer_dir = TempDir::new().unwrap(); + let assert = Command::new(get_wasmer_path()) - .arg("https://wapm.io/python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) - .arg("test.py") + .arg("run") + .arg("https://wasmer.io/python/python") + .arg(format!("--mapdir=/app:{}", asset_path().display())) + .arg("/app/test.py") + .env("RUST_LOG", &*CACHE_RUST_LOG) + .env("WASMER_CACHE_DIR", wasmer_dir.path()) .assert() .success(); - assert.stdout("hello\n"); - - let time = std::time::Instant::now(); + assert + .success() + .stderr(contains("builtin_loader: Downloading a webc file")) + .stderr(contains("module_cache::filesystem: Saved to disk")); let assert = Command::new(get_wasmer_path()) - .arg("https://wapm.io/python/python") - .arg(format!("--mapdir=.:{}", ASSET_PATH)) - .arg("test.py") + .arg("run") + .arg("https://wasmer.io/python/python") + .arg(format!("--mapdir=/app:{}", asset_path().display())) + .arg("/app/test.py") + .env("RUST_LOG", &*CACHE_RUST_LOG) + .env("WASMER_CACHE_DIR", wasmer_dir.path()) .assert() .success(); - assert.stdout("hello\n"); - - // package should be cached - assert!(std::time::Instant::now() - time < std::time::Duration::from_secs(1)); + assert + // Got a cache hit downloading the *.webc file's metadata + .stderr(contains("web_source: Cache hit")) + // Cache hit downloading the *.webc file + .stderr(contains( + r#"builtin_loader: Cache hit! pkg.name="python" pkg.version=0.1.0"#, + )) + // Cache hit compiling the module + .stderr(contains("module_cache::filesystem: Cache hit!")); } // This test verifies that "wasmer run --invoke _start module.wat" @@ -587,7 +407,7 @@ fn run_invoke_works_with_nomain_wasi() { fn run_no_start_wasm_report_error() { let assert = Command::new(get_wasmer_path()) .arg("run") - .arg(test_no_start_wat_path()) + .arg(fixtures::wat_no_start()) .assert() .failure(); @@ -597,7 +417,7 @@ fn run_no_start_wasm_report_error() { // Test that wasmer can run a complex path #[test] fn test_wasmer_run_complex_url() { - let wasm_test_path = wasi_test_wasm_path(); + let wasm_test_path = fixtures::qjs(); let wasm_test_path = wasm_test_path.canonicalize().unwrap_or(wasm_test_path); let mut wasm_test_path = format!("{}", wasm_test_path.display()); if wasm_test_path.starts_with(r#"\\?\"#) { @@ -624,3 +444,593 @@ fn test_wasmer_run_complex_url() { .assert() .success(); } + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wasi_runner_on_disk() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::qjs()) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +/// See for more. +#[test] +fn wasi_runner_on_disk_mount_using_relative_directory_on_the_host() { + let temp = TempDir::new_in(env!("CARGO_TARGET_TMPDIR")).unwrap(); + std::fs::write(temp.path().join("main.py"), "print('Hello, World!')").unwrap(); + + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::python()) + .arg("--mapdir=/app:.") + .arg("--") + .arg("/app/main.py") + .env("RUST_LOG", &*RUST_LOG) + .current_dir(temp.path()) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wasi_runner_on_disk_with_mounted_directories() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("index.js"), "console.log('Hello, World!')").unwrap(); + + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::qjs()) + .arg(format!("--mapdir=/app:{}", temp.path().display())) + .arg("--") + .arg("/app/index.js") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wasi_runner_on_disk_with_mounted_directories_and_webc_volumes() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("main.py"), "print('Hello, World!')").unwrap(); + + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::python()) + .arg(format!("--mapdir=/app:{}", temp.path().display())) + .arg("--") + .arg("-B") + .arg("/app/main.py") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wasi_runner_on_disk_with_dependencies() { + let port = random_port(); + let mut cmd = Command::new(get_wasmer_path()); + cmd.arg("run") + .arg(fixtures::hello()) + .arg(format!("--env=SERVER_PORT={port}")) + .arg("--net") + .arg("--") + .arg("--log-level=info") + .env("RUST_LOG", &*RUST_LOG); + let mut child = JoinableChild::spawn(cmd); + child.wait_for_stderr("listening"); + + // Make sure we get the page we want + let html = reqwest::blocking::get(format!("http://localhost:{port}/")) + .unwrap() + .text() + .unwrap(); + assert!(html.contains("Hello World"), "{html}"); + + // and make sure our request was logged + child + .join() + .stderr(contains("incoming request: method=GET uri=/")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn webc_files_on_disk_with_multiple_commands_require_an_entrypoint_flag() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::wabt()) + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + let msg = r#"Unable to determine the WEBC file's entrypoint. Please choose one of ["wasm-interp", "wasm-strip", "wasm-validate", "wasm2wat", "wast2json", "wat2wasm"]"#; + assert.failure().stderr(contains(msg)); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wasi_runner_on_disk_with_env_vars() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::python()) + .arg("--env=SOME_VAR=Hello, World!") + .arg("--") + .arg("-B") + .arg("-c") + .arg("import os; print(os.environ['SOME_VAR'])") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wcgi_runner_on_disk() { + // Start the WCGI server in the background + let port = random_port(); + let mut cmd = Command::new(get_wasmer_path()); + cmd.arg("run") + .arg(format!("--addr=127.0.0.1:{port}")) + .arg(fixtures::static_server()) + .env("RUST_LOG", &*RUST_LOG); + + // Let's run the command and wait until the server has started + let mut child = JoinableChild::spawn(cmd); + child.wait_for_stdout("WCGI Server running"); + + // make the request + let body = http_get(format!("http://127.0.0.1:{port}/")).unwrap(); + assert!(body.contains("Index of /"), "{body}"); + + // Let's make sure 404s work too + let err = http_get(format!("http://127.0.0.1:{port}/this/does/not/exist.html")).unwrap_err(); + assert_eq!(err.status().unwrap(), reqwest::StatusCode::NOT_FOUND); + + // And kill the server, making sure it generated the expected logs + let assert = child.join(); + + assert + .stderr(contains("Starting the server")) + .stderr(contains( + "response generated method=GET uri=/ status_code=200 OK", + )) + .stderr(contains( + "response generated method=GET uri=/this/does/not/exist.html status_code=404 Not Found", + )); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wcgi_runner_on_disk_with_mounted_directories() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("file.txt"), "Hello, World!").unwrap(); + // Start the WCGI server in the background + let port = random_port(); + let mut cmd = Command::new(get_wasmer_path()); + cmd.arg("run") + .arg(format!("--addr=127.0.0.1:{port}")) + .arg(format!("--mapdir=/path/to:{}", temp.path().display())) + .arg(fixtures::static_server()) + .env("RUST_LOG", &*RUST_LOG); + + // Let's run the command and wait until the server has started + let mut child = JoinableChild::spawn(cmd); + child.wait_for_stdout("WCGI Server running"); + + let body = http_get(format!("http://127.0.0.1:{port}/path/to/file.txt")).unwrap(); + assert!(body.contains("Hello, World!"), "{body}"); + + // And kill the server, making sure it generated the expected logs + let assert = child.join(); + + assert + .stderr(contains("Starting the server")) + .stderr(contains( + "response generated method=GET uri=/path/to/file.txt status_code=200 OK", + )); +} + +/// See https://github.com/wasmerio/wasmer/issues/3794 +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn issue_3794_unable_to_mount_relative_paths() { + let temp = TempDir::new().unwrap(); + std::fs::write(temp.path().join("message.txt"), b"Hello, World!").unwrap(); + + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::coreutils()) + .arg(format!("--mapdir=./some-dir/:{}", temp.path().display())) + .arg("--command-name=cat") + .arg("--") + .arg("./some-dir/message.txt") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +#[cfg_attr( + windows, + ignore = "FIXME(Michael-F-Bryan): Temporarily broken on Windows - https://github.com/wasmerio/wasmer/issues/3929" +)] +fn merged_filesystem_contains_all_files() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::bash()) + .arg("--entrypoint=bash") + .arg("--use") + .arg(fixtures::coreutils()) + .arg("--use") + .arg(fixtures::python()) + .arg("--") + .arg("-c") + .arg("ls -l /usr/coreutils/*.md && ls -l /lib/python3.6/*.py") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert + .success() + .stdout(contains("/usr/coreutils/README.md")) + .stdout(contains("/lib/python3.6/this.py")); +} + +#[test] +fn run_a_wasi_executable() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::qjs()) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +fn wasm_file_with_no_abi() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::fib()) + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success(); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn error_if_no_start_function_found() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(fixtures::wat_no_start()) + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert + .failure() + .stderr(contains("The module doesn't contain a \"_start\" function")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn run_a_pre_compiled_wasm_file() { + let temp = TempDir::new().unwrap(); + let dest = temp.path().join("qjs.wasmu"); + let qjs = fixtures::qjs(); + // Make sure it is compiled + Command::new(get_wasmer_path()) + .arg("compile") + .arg("-o") + .arg(&dest) + .arg(&qjs) + .assert() + .success(); + assert!(dest.exists()); + + // Now we can try to run the compiled artifact + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg(&dest) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn wasmer_run_some_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 = Command::new(get_wasmer_path()) + .arg("run") + .arg(temp.path()) + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn run_quickjs_via_package_name() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg("saghul/quickjs") + .arg("--entrypoint=quickjs") + .arg("--registry=wasmer.io") + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +fn run_quickjs_via_url() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg("https://wasmer.io/saghul/quickjs") + .arg("--entrypoint=quickjs") + .arg("--") + .arg("--eval") + .arg("console.log('Hello, World!')") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + assert.success().stdout(contains("Hello, World!")); +} + +#[test] +#[cfg_attr( + all(target_env = "musl", target_os = "linux"), + ignore = "wasmer run-unstable segfaults on musl" +)] +#[cfg_attr( + windows, + ignore = "TODO(Michael-F-Bryan): Figure out why WasiFs::get_inode_at_path_inner() returns Errno::notcapable on Windows" +)] +fn run_bash_using_coreutils() { + let assert = Command::new(get_wasmer_path()) + .arg("run") + .arg("sharrattj/bash") + .arg("--entrypoint=bash") + .arg("--use=sharrattj/coreutils") + .arg("--registry=wasmer.io") + .arg("--") + .arg("-c") + .arg("ls /bin") + .env("RUST_LOG", &*RUST_LOG) + .assert(); + + // Note: the resulting filesystem should contain the main command as + // well as the commands from all the --use packages + + let some_expected_binaries = [ + "arch", "base32", "base64", "baseenc", "basename", "bash", "cat", + ] + .join("\n"); + assert.success().stdout(contains(some_expected_binaries)); +} + +/// A helper that wraps [`Child`] to make sure it gets terminated +/// when it is no longer needed. +struct JoinableChild { + command: Command, + child: Option, +} + +impl JoinableChild { + fn spawn(mut cmd: Command) -> Self { + let child = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + JoinableChild { + child: Some(child), + command: cmd, + } + } + + /// Keep reading lines from the child's stdout until a line containing the + /// desired text is found. + fn wait_for_stdout(&mut self, text: &str) -> String { + let stdout = self + .child + .as_mut() + .and_then(|child| child.stdout.as_mut()) + .unwrap(); + + wait_for(text, stdout) + } + + /// Keep reading lines from the child's stderr until a line containing the + /// desired text is found. + fn wait_for_stderr(&mut self, text: &str) -> String { + let stderr = self + .child + .as_mut() + .and_then(|child| child.stderr.as_mut()) + .unwrap(); + + wait_for(text, stderr) + } + + /// Kill the underlying [`Child`] and get an [`Assert`] we + /// can use to check it. + fn join(mut self) -> Assert { + let mut child = self.child.take().unwrap(); + child.kill().unwrap(); + child.wait_with_output().unwrap().assert() + } +} + +fn wait_for(text: &str, reader: &mut dyn Read) -> String { + let mut all_output = String::new(); + + loop { + let line = read_line(reader).unwrap(); + + if line.is_empty() { + eprintln!("=== All Output === "); + eprintln!("{all_output}"); + panic!("EOF before \"{text}\" was found"); + } + + let found = line.contains(text); + all_output.push_str(&line); + + if found { + return all_output; + } + } +} + +fn read_line(reader: &mut dyn Read) -> Result { + let mut line = Vec::new(); + + while !line.ends_with(&[b'\n']) { + let mut buffer = [0_u8]; + match reader.read_exact(&mut buffer) { + Ok(_) => { + line.push(buffer[0]); + } + Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, + Err(e) => return Err(e), + } + } + + let line = String::from_utf8(line).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?; + Ok(line) +} + +impl Drop for JoinableChild { + fn drop(&mut self) { + if let Some(mut child) = self.child.take() { + eprintln!("==== WARNING: Child was dropped before being joined ===="); + eprintln!("Command: {:?}", self.command); + + let _ = child.kill(); + + if let Some(mut stderr) = child.stderr.take() { + let mut buffer = String::new(); + if stderr.read_to_string(&mut buffer).is_ok() { + eprintln!("---- STDERR ----"); + eprintln!("{buffer}"); + } + } + + if let Some(mut stdout) = child.stdout.take() { + let mut buffer = String::new(); + if stdout.read_to_string(&mut buffer).is_ok() { + eprintln!("---- STDOUT ----"); + eprintln!("{buffer}"); + } + } + + if !std::thread::panicking() { + panic!("Child was dropped before being joined"); + } + } + } +} + +/// Send a GET request to a particular URL, automatically retrying (with +/// a timeout) if there are any connection errors. +fn http_get(url: impl IntoUrl) -> Result { + let start = Instant::now(); + let url = url.into_url().unwrap(); + + let client = Client::new(); + + while start.elapsed() < HTTP_GET_TIMEOUT { + match client.get(url.clone()).send() { + Ok(response) => { + return response.error_for_status()?.text(); + } + Err(e) if e.is_connect() => continue, + Err(other) => return Err(other), + } + } + + panic!("Didn't receive a response from \"{url}\" within the allocated time"); +} + +fn random_port() -> u16 { + rand::thread_rng().gen_range(10_000_u16..u16::MAX) +} diff --git a/tests/integration/cli/tests/run_unstable.rs b/tests/integration/cli/tests/run_unstable.rs deleted file mode 100644 index 4c418c234d9..00000000000 --- a/tests/integration/cli/tests/run_unstable.rs +++ /dev/null @@ -1,680 +0,0 @@ -use std::{ - io::{ErrorKind, Read}, - process::Stdio, - time::{Duration, Instant}, -}; - -use assert_cmd::{assert::Assert, prelude::OutputAssertExt}; -use once_cell::sync::Lazy; -use predicates::str::contains; -use rand::Rng; -use reqwest::{blocking::Client, IntoUrl}; -use tempfile::TempDir; -use wasmer_integration_tests_cli::get_wasmer_path; - -const HTTP_GET_TIMEOUT: Duration = Duration::from_secs(5); - -#[cfg(feature = "debug")] -static RUST_LOG: Lazy = Lazy::new(|| ["trace"].join(",")); - -#[cfg(not(feature = "debug"))] -static RUST_LOG: Lazy = Lazy::new(|| { - [ - "info", - "wasmer_wasix::resolve=debug", - "wasmer_wasix::runners=debug", - "wasmer_wasix=debug", - "virtual_fs::trace_fs=trace", - ] - .join(",") -}); - -fn wasmer_run_unstable(inherit_stderr: bool) -> std::process::Command { - let mut cmd = std::process::Command::new("cargo"); - cmd.arg("run") - .arg("--quiet") - .arg("--package=wasmer-cli") - .arg("--features=singlepass,cranelift,compiler") - .arg("--color=never") - .arg("--") - .arg("run"); - cmd.env("RUST_LOG", &*RUST_LOG); - if inherit_stderr { - cmd.stderr(Stdio::inherit()); - } - cmd -} - -mod webc_on_disk { - use super::*; - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wasi_runner() { - let assert = wasmer_run_unstable(true) - .arg(fixtures::qjs()) - .arg("--") - .arg("--eval") - .arg("console.log('Hello, World!')") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - /// See for more. - #[test] - fn wasi_runner_mount_using_relative_directory_on_the_host() { - let temp = TempDir::new_in(env!("CARGO_TARGET_TMPDIR")).unwrap(); - std::fs::write(temp.path().join("main.py"), "print('Hello, World!')").unwrap(); - - let assert = wasmer_run_unstable(true) - .arg(fixtures::python()) - .arg("--mapdir=/app:.") - .arg("--") - .arg("/app/main.py") - .current_dir(temp.path()) - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wasi_runner_with_mounted_directories() { - let temp = TempDir::new().unwrap(); - std::fs::write(temp.path().join("index.js"), "console.log('Hello, World!')").unwrap(); - - let assert = wasmer_run_unstable(true) - .arg(fixtures::qjs()) - .arg(format!("--mapdir=/app:{}", temp.path().display())) - .arg("--") - .arg("/app/index.js") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wasi_runner_with_mounted_directories_and_webc_volumes() { - let temp = TempDir::new().unwrap(); - std::fs::write(temp.path().join("main.py"), "print('Hello, World!')").unwrap(); - - let assert = wasmer_run_unstable(true) - .arg(fixtures::python()) - .arg(format!("--mapdir=/app:{}", temp.path().display())) - .arg("--") - .arg("-B") - .arg("/app/main.py") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wasi_runner_with_dependencies() { - let mut cmd = wasmer_run_unstable(false); - let port = random_port(); - cmd.arg(fixtures::hello()) - .arg(format!("--env=SERVER_PORT={port}")) - .arg("--net") - .arg("--") - .arg("--log-level=info"); - let mut child = JoinableChild::spawn(cmd); - child.wait_for_stderr("listening"); - - // Make sure we get the page we want - let html = reqwest::blocking::get(format!("http://localhost:{port}/")) - .unwrap() - .text() - .unwrap(); - assert!(html.contains("Hello World"), "{html}"); - - // and make sure our request was logged - child - .join() - .stderr(contains("incoming request: method=GET uri=/")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn webc_files_with_multiple_commands_require_an_entrypoint_flag() { - let assert = wasmer_run_unstable(false).arg(fixtures::wabt()).assert(); - - let msg = r#"Unable to determine the WEBC file's entrypoint. Please choose one of ["wasm-interp", "wasm-strip", "wasm-validate", "wasm2wat", "wast2json", "wat2wasm"]"#; - assert.failure().stderr(contains(msg)); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wasi_runner_with_env_vars() { - let assert = wasmer_run_unstable(true) - .arg(fixtures::python()) - .arg("--env=SOME_VAR=Hello, World!") - .arg("--") - .arg("-B") - .arg("-c") - .arg("import os; print(os.environ['SOME_VAR'])") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wcgi_runner() { - // Start the WCGI server in the background - let port = random_port(); - let mut cmd = wasmer_run_unstable(false); - cmd.arg(format!("--addr=127.0.0.1:{port}")) - .arg(fixtures::static_server()); - - // Let's run the command and wait until the server has started - let mut child = JoinableChild::spawn(cmd); - child.wait_for_stdout("WCGI Server running"); - - // make the request - let body = http_get(format!("http://127.0.0.1:{port}/")).unwrap(); - assert!(body.contains("Index of /"), "{body}"); - - // Let's make sure 404s work too - let err = - http_get(format!("http://127.0.0.1:{port}/this/does/not/exist.html")).unwrap_err(); - assert_eq!(err.status().unwrap(), reqwest::StatusCode::NOT_FOUND); - - // And kill the server, making sure it generated the expected logs - let assert = child.join(); - - assert - .stderr(contains("Starting the server")) - .stderr(contains("response generated method=GET uri=/ status_code=200 OK")) - .stderr(contains("response generated method=GET uri=/this/does/not/exist.html status_code=404 Not Found")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wcgi_runner_with_mounted_directories() { - let temp = TempDir::new().unwrap(); - std::fs::write(temp.path().join("file.txt"), "Hello, World!").unwrap(); - // Start the WCGI server in the background - let port = random_port(); - let mut cmd = wasmer_run_unstable(false); - cmd.arg(format!("--addr=127.0.0.1:{port}")) - .arg(format!("--mapdir=/path/to:{}", temp.path().display())) - .arg(fixtures::static_server()); - - // Let's run the command and wait until the server has started - let mut child = JoinableChild::spawn(cmd); - child.wait_for_stdout("WCGI Server running"); - - let body = http_get(format!("http://127.0.0.1:{port}/path/to/file.txt")).unwrap(); - assert!(body.contains("Hello, World!"), "{body}"); - - // And kill the server, making sure it generated the expected logs - let assert = child.join(); - - assert - .stderr(contains("Starting the server")) - .stderr(contains( - "response generated method=GET uri=/path/to/file.txt status_code=200 OK", - )); - } - - /// See https://github.com/wasmerio/wasmer/issues/3794 - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn issue_3794_unable_to_mount_relative_paths() { - let temp = TempDir::new().unwrap(); - std::fs::write(temp.path().join("message.txt"), b"Hello, World!").unwrap(); - - let assert = wasmer_run_unstable(true) - .arg(fixtures::coreutils()) - .arg(format!("--mapdir=./some-dir/:{}", temp.path().display())) - .arg("--command-name=cat") - .arg("--") - .arg("./some-dir/message.txt") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - #[cfg_attr( - windows, - ignore = "FIXME(Michael-F-Bryan): Temporarily broken on Windows - https://github.com/wasmerio/wasmer/issues/3929" - )] - fn merged_filesystem_contains_all_files() { - let assert = wasmer_run_unstable(true) - .arg(fixtures::bash()) - .arg("--entrypoint=bash") - .arg("--use") - .arg(fixtures::coreutils()) - .arg("--use") - .arg(fixtures::python()) - .arg("--") - .arg("-c") - .arg("ls -l /usr/coreutils/*.md && ls -l /lib/python3.6/*.py") - .assert(); - - assert - .success() - .stdout(contains("/usr/coreutils/README.md")) - .stdout(contains("/lib/python3.6/this.py")); - } -} - -mod wasm_on_disk { - use std::process::Command; - - use super::*; - use predicates::str::contains; - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn wasi_executable() { - let assert = wasmer_run_unstable(true) - .arg(fixtures::qjs()) - .arg("--") - .arg("--eval") - .arg("console.log('Hello, World!')") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn no_abi() { - let assert = wasmer_run_unstable(true).arg(fixtures::fib()).assert(); - - assert.success(); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn error_if_no_start_function_found() { - let assert = wasmer_run_unstable(false) - .arg(fixtures::wat_no_start()) - .assert(); - - assert - .failure() - .stderr(contains("The module doesn't contain a \"_start\" function")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn pre_compiled() { - let temp = TempDir::new().unwrap(); - let dest = temp.path().join("qjs.wasmu"); - let qjs = fixtures::qjs(); - // Make sure it is compiled - Command::new(get_wasmer_path()) - .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_run_unstable(true) - .arg(&dest) - .arg("--") - .arg("--eval") - .arg("console.log('Hello, World!')") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } -} - -mod local_directory { - use super::*; - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - 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_run_unstable(true) - .arg(temp.path()) - .arg("--") - .arg("--eval") - .arg("console.log('Hello, World!')") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } -} - -mod remote_webc { - use super::*; - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn quickjs_as_package_name() { - let assert = wasmer_run_unstable(true) - .arg("saghul/quickjs") - .arg("--entrypoint=quickjs") - .arg("--registry=wapm.io") - .arg("--") - .arg("--eval") - .arg("console.log('Hello, World!')") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - fn quickjs_as_url() { - let assert = wasmer_run_unstable(true) - .arg("https://wapm.io/saghul/quickjs") - .arg("--entrypoint=quickjs") - .arg("--") - .arg("--eval") - .arg("console.log('Hello, World!')") - .assert(); - - assert.success().stdout(contains("Hello, World!")); - } - - #[test] - #[cfg_attr( - all(target_env = "musl", target_os = "linux"), - ignore = "wasmer run-unstable segfaults on musl" - )] - #[cfg_attr( - windows, - ignore = "TODO(Michael-F-Bryan): Figure out why WasiFs::get_inode_at_path_inner() returns Errno::notcapable on Windows" - )] - fn bash_using_coreutils() { - let assert = wasmer_run_unstable(true) - .arg("sharrattj/bash") - .arg("--entrypoint=bash") - .arg("--use=sharrattj/coreutils") - .arg("--registry=wapm.io") - .arg("--") - .arg("-c") - .arg("ls /bin") - .assert(); - - // Note: the resulting filesystem should contain the main command as - // well as the commands from all the --use packages - - let some_expected_binaries = [ - "arch", "base32", "base64", "baseenc", "basename", "bash", "cat", - ] - .join("\n"); - assert.success().stdout(contains(some_expected_binaries)); - } -} - -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") - } - - pub fn coreutils() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("webc") - .join("coreutils-1.0.16-e27dbb4f-2ef2-4b44-b46a-ddd86497c6d7.webc") - } - - pub fn bash() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("webc") - .join("bash-1.0.16-f097441a-a80b-4e0d-87d7-684918ef4bb6.webc") - } - - /// A WEBC file containing `wat2wasm`, `wasm-validate`, and other helpful - /// WebAssembly-related commands. - pub fn wabt() -> PathBuf { - Path::new(C_ASSET_PATH).join("wabt-1.0.37.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") - } - - pub fn hello() -> PathBuf { - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("webc") - .join("hello-0.1.0-665d2ddc-80e6-4845-85d3-4587b1693bb7.webc") - } - - /// 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 JoinableChild { - command: std::process::Command, - child: Option, -} - -impl JoinableChild { - fn spawn(mut cmd: std::process::Command) -> Self { - let child = cmd - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - - JoinableChild { - child: Some(child), - command: cmd, - } - } - - /// Keep reading lines from the child's stdout until a line containing the - /// desired text is found. - fn wait_for_stdout(&mut self, text: &str) -> String { - let stdout = self - .child - .as_mut() - .and_then(|child| child.stdout.as_mut()) - .unwrap(); - - wait_for(text, stdout) - } - - /// Keep reading lines from the child's stderr until a line containing the - /// desired text is found. - fn wait_for_stderr(&mut self, text: &str) -> String { - let stderr = self - .child - .as_mut() - .and_then(|child| child.stderr.as_mut()) - .unwrap(); - - wait_for(text, stderr) - } - - /// Kill the underlying [`std::process::Child`] and get an [`Assert`] we - /// can use to check it. - fn join(mut self) -> Assert { - let mut child = self.child.take().unwrap(); - child.kill().unwrap(); - child.wait_with_output().unwrap().assert() - } -} - -fn wait_for(text: &str, reader: &mut dyn Read) -> String { - let mut all_output = String::new(); - - loop { - let line = read_line(reader).unwrap(); - - if line.is_empty() { - eprintln!("=== All Output === "); - eprintln!("{all_output}"); - panic!("EOF before \"{text}\" was found"); - } - - let found = line.contains(text); - all_output.push_str(&line); - - if found { - return all_output; - } - } -} - -fn read_line(reader: &mut dyn Read) -> Result { - let mut line = Vec::new(); - - while !line.ends_with(&[b'\n']) { - let mut buffer = [0_u8]; - match reader.read_exact(&mut buffer) { - Ok(_) => { - line.push(buffer[0]); - } - Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, - Err(e) => return Err(e), - } - } - - let line = String::from_utf8(line).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?; - Ok(line) -} - -impl Drop for JoinableChild { - fn drop(&mut self) { - if let Some(mut child) = self.child.take() { - eprintln!("==== WARNING: Child was dropped before being joined ===="); - eprintln!("Command: {:?}", self.command); - - let _ = child.kill(); - - if let Some(mut stderr) = child.stderr.take() { - let mut buffer = String::new(); - if stderr.read_to_string(&mut buffer).is_ok() { - eprintln!("---- STDERR ----"); - eprintln!("{buffer}"); - } - } - - if let Some(mut stdout) = child.stdout.take() { - let mut buffer = String::new(); - if stdout.read_to_string(&mut buffer).is_ok() { - eprintln!("---- STDOUT ----"); - eprintln!("{buffer}"); - } - } - - if !std::thread::panicking() { - panic!("Child was dropped before being joined"); - } - } - } -} - -/// Send a GET request to a particular URL, automatically retrying (with -/// a timeout) if there are any connection errors. -fn http_get(url: impl IntoUrl) -> Result { - let start = Instant::now(); - let url = url.into_url().unwrap(); - - let client = Client::new(); - - while start.elapsed() < HTTP_GET_TIMEOUT { - match client.get(url.clone()).send() { - Ok(response) => { - return response.error_for_status()?.text(); - } - Err(e) if e.is_connect() => continue, - Err(other) => return Err(other), - } - } - - panic!("Didn't receive a response from \"{url}\" within the allocated time"); -} - -fn random_port() -> u16 { - rand::thread_rng().gen_range(10_000_u16..u16::MAX) -}