From ae948a834dcc09093f820dfcc3835e86fa7070e4 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 10 Jun 2026 13:01:02 +0000 Subject: [PATCH 01/19] feat(wasix): build wasm_tests Rust fixtures with cargo-wasix Replace direct rustc invocation with cargo wasix build, using ephemeral Cargo.toml files for single-file fixtures and supporting explicit Cargo projects with ## directives and BuildBin selection. Co-authored-by: Cursor --- lib/wasix/tests/wasm_tests/README.md | 21 ++- lib/wasix/tests/wasm_tests/mod.rs | 228 ++++++++++++++++++++++----- 2 files changed, 210 insertions(+), 39 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/README.md b/lib/wasix/tests/wasm_tests/README.md index 0a0d19bfd262..217863a58b93 100644 --- a/lib/wasix/tests/wasm_tests/README.md +++ b/lib/wasix/tests/wasm_tests/README.md @@ -10,9 +10,18 @@ The `wasix-wasm` integration test target discovers tests automatically from this directory. Any subdirectory that contains one of these primary files is treated as a test fixture: +- `build.sh` or other `*.sh` primary sources +- `Cargo.toml` - `main.c` - `main.cpp` -- `$name.sh` +- `*.rs` + +Discovery precedence within a fixture directory is: + +1. `*.sh` shell primary sources +2. `Cargo.toml` (full Cargo project) +3. `main.c` / `main.cpp` +4. `*.rs` (each Rust source is an independent test) The harness builds each discovered fixture, runs the resulting `main` module through the WASIX runner, and registers one test per configuration each of the enabled engines. @@ -24,6 +33,9 @@ The supported directives are documented in [`../wasm_tests.rs`](../wasm_tests.rs If a fixture has more than one `.sh` file, each shell file is treated as a primary source, where `build.sh` is the default shell source name. +Fixtures with an explicit `Cargo.toml` are built as full Cargo projects. Directives +can be placed in `##Directive: Args` comments in the manifest. + The former `tests/wasi-fyi` shell suite now lives in [`wasi_fyi/`](./wasi_fyi/) as Rust primary sources with inline directives. @@ -34,7 +46,10 @@ The harness then builds them as follows: - `main.c` is compiled with `CC`, or `wasixcc` if `CC` is unset. - `main.cpp` is compiled with `CXX`, or `wasix++` if `CXX` is unset. -- `*.rs` is compiled with `rustc --target=wasm32-wasip1`. +- `*.rs` is built with `cargo wasix build` using an ephemeral `Cargo.toml` generated + in the build directory. +- `Cargo.toml` fixtures are built with `cargo wasix build` and the selected binary + artifact is copied to `main`. - `build.sh` and other shell primary sources are executed with `bash`; the harness sets `CC=wasixcc`, `CXX=wasix++`, and `WASIXCC_DISCARD_UNSUPPORTED_FLAGS=yes`. @@ -50,7 +65,7 @@ the primary source, for example `//#BuildEnv: WASIXCC_PIC=1` in C/C++ sources or These tests run through the normal `wasix` integration test target, so standard Cargo and nextest filtering both work. Before running the suite, make sure `wasixcc` is installed and available in your shell environment. -Rust `.rs` tests also require `rustc` with the `wasm32-wasip1` target installed. +Rust fixtures also require `cargo-wasix` on `PATH` (`cargo install cargo-wasix`). On macOS, this suite collects and runs the LLVM variants only because Cranelift exception-handling support is still incomplete there: diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index 0fbdbb2b2146..dc21f78fec51 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -5,7 +5,7 @@ //! own arguments, environment setup, expected exit status, and output/file checks. //! //! Directives use `//#Directive: Args` in C/C++/Rust sources and -//! `##Directive: Args` in shell sources. +//! `##Directive: Args` in shell sources and `Cargo.toml` comments. //! //! Supported directives: //! @@ -21,6 +21,9 @@ //! //! `BuildEnv:{key}={value}` sets an environment variable before building. //! +//! `BuildBin:{name}` selects which Cargo binary to copy to `main` after a +//! `cargo wasix build` (for fixtures with multiple `[[bin]]` targets). +//! //! The harness also sets `WASMER_BACKEND` to the engine name (`cranelift`, `v8`, //! etc.) before every build so shell scripts can tune compile-time parameters per backend. //! @@ -186,6 +189,7 @@ struct Config { expected_stderr: Vec, arguments: Vec, build_env: Vec<(String, String)>, + build_bin_name: Option, env: Vec<(String, String)>, stdin: Option>, ignored: Option, @@ -218,6 +222,7 @@ impl Config { is_abstract: false, arguments: Vec::new(), build_env: Vec::new(), + build_bin_name: None, env: Vec::new(), nonzero_exit_code: false, expected_exit_code: 0, @@ -266,19 +271,8 @@ fn parse_configs(default_config: &Config) -> Result> { let mut config = default_config.clone(); let mut build_env = Vec::new(); - let directive_prefix = match src_filename - .extension() - .expect("extension expected") - .to_str() - .expect("must be valid string") - { - "c" | "cpp" | "rs" => "//#", - "sh" => "##", - suffix => bail!("unexpected extension '{suffix}' of a primary source: {src_filename:?}"), - }; - for (i, line) in source.lines().enumerate() { - if let Some(rest) = line.trim().strip_prefix(directive_prefix) { + if let Some(rest) = default_config.source.parse_directive_line(line) { process_directive( rest, &mut build_env, @@ -395,6 +389,11 @@ fn process_directive( ensure!(!key.is_empty(), "BuildEnv key must not be empty"); build_env.push((key.to_owned(), value.trim().to_owned())); } + "BuildBin" => { + let name = arg.trim(); + ensure!(!name.is_empty(), "BuildBin name must not be empty"); + config.build_bin_name = Some(name.to_owned()); + } "Env" => { let (key, value) = arg .split_once('=') @@ -575,18 +574,140 @@ fn read_fixture_bytes(test_src_dir: &Path, arg: &str, directive: &str) -> Result .with_context(|| format!("failed to read {directive} {}", path.display())) } -fn rustc_command(toolchain: Option<&str>) -> Command { - if let Some(toolchain) = toolchain { - // rustc +version multiplexing is unsupported on Windows, use the documented approach: - // https://rust-lang.github.io/rustup/concepts/toolchains.html#custom-toolchains - let mut cmd = Command::new("rustup"); - cmd.arg("run").arg(toolchain).arg("rustc"); - cmd +fn parse_cargo_toml_directive_line(line: &str) -> Option<&str> { + let line = line.trim(); + let rest = line.strip_prefix("##")?; + let rest = rest.trim(); + if rest.is_empty() || !rest.contains(':') { + None } else { - Command::new("rustc") + Some(rest) } } +const CARGO_WASIX_ARTIFACT_DIR: &str = "target/wasm32-wasmer-wasi/debug"; + +fn cargo_wasix_build_command(build_dir: &Path) -> Command { + let mut cmd = Command::new("cargo"); + cmd.arg("wasix") + .arg("build") + .current_dir(build_dir); + cmd +} + +fn write_ephemeral_cargo_toml(build_dir: &Path, source_filename: &str) -> Result<()> { + let manifest = format!( + r#"[package] +name = "main" +version = "0.0.0" +edition = "2021" + +[[bin]] +name = "main" +path = "{source_filename}" + +[workspace] +"# + ); + fs::write(build_dir.join("Cargo.toml"), manifest) + .with_context(|| format!("failed to write {}", build_dir.join("Cargo.toml").display())) +} + +fn ensure_standalone_cargo_workspace(build_dir: &Path) -> Result<()> { + let manifest_path = build_dir.join("Cargo.toml"); + let contents = fs::read_to_string(&manifest_path) + .with_context(|| format!("failed to read {}", manifest_path.display()))?; + if contents.contains("[workspace]") { + return Ok(()); + } + fs::write(&manifest_path, format!("{contents}\n[workspace]\n")) + .with_context(|| format!("failed to update {}", manifest_path.display())) +} + +fn cargo_bin_name_from_manifest(manifest_path: &Path, build_dir: &Path) -> Result { + let contents = fs::read_to_string(manifest_path) + .with_context(|| format!("failed to read {}", manifest_path.display()))?; + let manifest: toml::Value = + toml::from_str(&contents).context("failed to parse Cargo.toml")?; + + if let Some(bins) = manifest.get("bin").and_then(|bins| bins.as_array()) { + ensure!( + !bins.is_empty(), + "Cargo.toml defines an empty [[bin]] list in {}", + manifest_path.display() + ); + if bins.len() == 1 { + return Ok(bins[0] + .get("name") + .and_then(|name| name.as_str()) + .context("[[bin]] is missing name")? + .to_owned()); + } + + let package_name = manifest + .get("package") + .and_then(|package| package.get("name")) + .and_then(|name| name.as_str()); + + if let Some(name) = package_name { + if bins.iter().any(|bin| { + bin.get("name") + .and_then(|name| name.as_str()) + .is_some_and(|bin_name| bin_name == name) + }) { + return Ok(name.to_owned()); + } + } + + if let Some(bin) = bins.iter().find(|bin| { + bin.get("name") + .and_then(|name| name.as_str()) + .is_some_and(|name| name == "main") + }) { + return Ok(bin + .get("name") + .and_then(|name| name.as_str()) + .expect("main bin has a name") + .to_owned()); + } + + bail!( + "Cargo.toml in {} defines multiple [[bin]] targets; add a ##BuildBin: name directive", + manifest_path.display() + ); + } + + let package_name = manifest + .get("package") + .and_then(|package| package.get("name")) + .and_then(|name| name.as_str()) + .with_context(|| format!("missing package.name in {}", manifest_path.display()))?; + + if build_dir.join("src/main.rs").exists() { + return Ok(package_name.to_owned()); + } + + bail!( + "could not determine Cargo binary name for {}; add src/main.rs or a ##BuildBin: name directive", + manifest_path.display() + ) +} + +fn copy_cargo_wasix_artifact(build_dir: &Path, bin_name: &str) -> Result { + let wasm = build_dir + .join(CARGO_WASIX_ARTIFACT_DIR) + .join(format!("{bin_name}.wasm")); + ensure!( + wasm.exists(), + "expected cargo-wasix artifact at {}", + wasm.display() + ); + let main_path = build_dir.join("main"); + fs::copy(&wasm, &main_path) + .with_context(|| format!("failed to copy {} to {}", wasm.display(), main_path.display()))?; + Ok(main_path) +} + fn run_build_script(config: &Config) -> anyhow::Result { // First, copy the test source directory to the 'build' subfolder that will // be unique for each configuration of a test. @@ -624,7 +745,8 @@ fn run_build_script(config: &Config) -> anyhow::Result { std::env::var("CXX").unwrap_or_else(|_| "wasix++".to_string()) } PrimarySource::BashScript(_) => unreachable!("handled above"), - PrimarySource::RustSourceFile(_) => unreachable!("handled below"), + PrimarySource::RustSourceFile(_) + | PrimarySource::CargoProject => unreachable!("handled below"), }; let mut cmd = Command::new(&compiler); cmd.arg(&primary_source) @@ -635,16 +757,12 @@ fn run_build_script(config: &Config) -> anyhow::Result { cmd } PrimarySource::RustSourceFile(filename) => { - let primary_source = build_test_path.join(filename); - let source = std::fs::read_to_string(&primary_source) - .with_context(|| format!("Failed to read {}", primary_source.display()))?; - let mut cmd = rustc_command(source.contains("#![feature(").then_some("nightly")); - cmd.arg("--target=wasm32-wasip1") - .arg("-o") - .arg("main") - .arg(&primary_source) - .current_dir(&build_test_path); - cmd + write_ephemeral_cargo_toml(&build_test_path, filename)?; + cargo_wasix_build_command(&build_test_path) + } + PrimarySource::CargoProject => { + ensure_standalone_cargo_workspace(&build_test_path)?; + cargo_wasix_build_command(&build_test_path) } }; @@ -660,7 +778,24 @@ fn run_build_script(config: &Config) -> anyhow::Result { anyhow::bail!("Build failed for {}", build_test_path.display()); } - Ok(build_test_path.join("main")) + let main_path = match &config.source { + PrimarySource::RustSourceFile(_) => copy_cargo_wasix_artifact(&build_test_path, "main")?, + PrimarySource::CargoProject => { + let bin_name = match &config.build_bin_name { + Some(name) => name.clone(), + None => cargo_bin_name_from_manifest( + &build_test_path.join("Cargo.toml"), + &build_test_path, + )?, + }; + copy_cargo_wasix_artifact(&build_test_path, &bin_name)? + } + PrimarySource::BashScript(_) + | PrimarySource::CSourceFile(_) + | PrimarySource::CppSourceFile(_) => build_test_path.join("main"), + }; + + Ok(main_path) } struct CopyHostTreeActions { @@ -1035,13 +1170,14 @@ fn run_integration_test(config: Config) -> Result { Ok(libtest_mimic::Completion::Completed) } -const PRIMARY_SOURCE_FILES: &[&str] = &["main.c", "main.cpp", "build.sh"]; +const PRIMARY_SOURCE_FILES: &[&str] = &["main.c", "main.cpp", "build.sh", "Cargo.toml"]; #[derive(Debug, Clone, PartialEq, Eq)] enum PrimarySource { CSourceFile(String), CppSourceFile(String), RustSourceFile(String), + CargoProject, BashScript(String), } @@ -1070,6 +1206,7 @@ impl PrimarySource { .to_string() } } + Self::CargoProject => "default".to_owned(), } } @@ -1079,6 +1216,7 @@ impl PrimarySource { | Self::CppSourceFile(filename) | Self::RustSourceFile(filename) => filename.clone(), Self::BashScript(filename) => filename.clone(), + Self::CargoProject => "Cargo.toml".to_owned(), } } @@ -1089,6 +1227,17 @@ impl PrimarySource { } Self::RustSourceFile(_) => false, Self::BashScript(filename) => filename == "build.sh", + Self::CargoProject => true, + } + } + + fn parse_directive_line<'a>(&self, line: &'a str) -> Option<&'a str> { + match self { + Self::CargoProject => parse_cargo_toml_directive_line(line), + Self::BashScript(_) => line.trim().strip_prefix("##"), + Self::CSourceFile(_) | Self::CppSourceFile(_) | Self::RustSourceFile(_) => { + line.trim().strip_prefix("//#") + } } } } @@ -1111,6 +1260,10 @@ fn identify_primary_sources(test_src_dir: &Path) -> Result> { return Ok(shell_sources); } + if test_src_dir.join("Cargo.toml").is_file() { + return Ok(vec![PrimarySource::CargoProject]); + } + for file in ["main.c", "main.cpp"] { let path = test_src_dir.join(file); if path.exists() { @@ -1145,7 +1298,7 @@ fn identify_primary_sources(test_src_dir: &Path) -> Result> { bail!( "{} must contain {}", test_src_dir.display(), - "main.c, main.cpp, build.sh, or *.rs" + "build.sh, Cargo.toml, main.c, main.cpp, or *.rs" ); } @@ -1228,7 +1381,10 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { // WASIXCC toolchain does not cover Windows yet. if cfg!(target_os = "windows") - && !matches!(config.source, PrimarySource::RustSourceFile(..)) + && !matches!( + config.source, + PrimarySource::RustSourceFile(..) | PrimarySource::CargoProject + ) { continue; } From fc05f6d2c9f2361db4d184d70a053e24fb7fb712 Mon Sep 17 00:00:00 2001 From: Arshia001 Date: Wed, 10 Jun 2026 17:08:40 +0400 Subject: [PATCH 02/19] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/wasix/tests/wasm_tests/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index dc21f78fec51..cad9c6fbfe03 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -591,7 +591,9 @@ fn cargo_wasix_build_command(build_dir: &Path) -> Command { let mut cmd = Command::new("cargo"); cmd.arg("wasix") .arg("build") - .current_dir(build_dir); + .current_dir(build_dir) + // Ensure deterministic output location regardless of the caller environment. + .env("CARGO_TARGET_DIR", build_dir.join("target")); cmd } From 24b18eb880b62ab03b67fc7d02ceac574eba10cf Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 10 Jun 2026 13:10:17 +0000 Subject: [PATCH 03/19] Fix style --- lib/wasix/tests/wasm_tests/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index cad9c6fbfe03..e27f67de74e0 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -629,8 +629,7 @@ fn ensure_standalone_cargo_workspace(build_dir: &Path) -> Result<()> { fn cargo_bin_name_from_manifest(manifest_path: &Path, build_dir: &Path) -> Result { let contents = fs::read_to_string(manifest_path) .with_context(|| format!("failed to read {}", manifest_path.display()))?; - let manifest: toml::Value = - toml::from_str(&contents).context("failed to parse Cargo.toml")?; + let manifest: toml::Value = toml::from_str(&contents).context("failed to parse Cargo.toml")?; if let Some(bins) = manifest.get("bin").and_then(|bins| bins.as_array()) { ensure!( @@ -705,8 +704,13 @@ fn copy_cargo_wasix_artifact(build_dir: &Path, bin_name: &str) -> Result anyhow::Result { std::env::var("CXX").unwrap_or_else(|_| "wasix++".to_string()) } PrimarySource::BashScript(_) => unreachable!("handled above"), - PrimarySource::RustSourceFile(_) - | PrimarySource::CargoProject => unreachable!("handled below"), + PrimarySource::RustSourceFile(_) | PrimarySource::CargoProject => { + unreachable!("handled below") + } }; let mut cmd = Command::new(&compiler); cmd.arg(&primary_source) From 815bb7d5274b62aae676e083cf7b84ff9effb27a Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 11 Jun 2026 08:28:51 +0000 Subject: [PATCH 04/19] Simplify wasm_tests cargo-wasix harness per review feedback. Drop BuildBin and multi-bin selection, use edition 2024, and let cargo wasix build surface workspace and artifact errors directly. Co-authored-by: Cursor --- lib/wasix/tests/wasm_tests/README.md | 2 +- lib/wasix/tests/wasm_tests/mod.rs | 116 ++++----------------------- 2 files changed, 18 insertions(+), 100 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/README.md b/lib/wasix/tests/wasm_tests/README.md index 217863a58b93..b9cb3a620f7e 100644 --- a/lib/wasix/tests/wasm_tests/README.md +++ b/lib/wasix/tests/wasm_tests/README.md @@ -48,7 +48,7 @@ The harness then builds them as follows: - `main.cpp` is compiled with `CXX`, or `wasix++` if `CXX` is unset. - `*.rs` is built with `cargo wasix build` using an ephemeral `Cargo.toml` generated in the build directory. -- `Cargo.toml` fixtures are built with `cargo wasix build` and the selected binary +- `Cargo.toml` fixtures are built with `cargo wasix build` and the single binary artifact is copied to `main`. - `build.sh` and other shell primary sources are executed with `bash`; the harness sets `CC=wasixcc`, `CXX=wasix++`, and diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index e27f67de74e0..d5e64ad3dcd6 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -21,9 +21,6 @@ //! //! `BuildEnv:{key}={value}` sets an environment variable before building. //! -//! `BuildBin:{name}` selects which Cargo binary to copy to `main` after a -//! `cargo wasix build` (for fixtures with multiple `[[bin]]` targets). -//! //! The harness also sets `WASMER_BACKEND` to the engine name (`cranelift`, `v8`, //! etc.) before every build so shell scripts can tune compile-time parameters per backend. //! @@ -189,7 +186,6 @@ struct Config { expected_stderr: Vec, arguments: Vec, build_env: Vec<(String, String)>, - build_bin_name: Option, env: Vec<(String, String)>, stdin: Option>, ignored: Option, @@ -222,7 +218,6 @@ impl Config { is_abstract: false, arguments: Vec::new(), build_env: Vec::new(), - build_bin_name: None, env: Vec::new(), nonzero_exit_code: false, expected_exit_code: 0, @@ -389,11 +384,6 @@ fn process_directive( ensure!(!key.is_empty(), "BuildEnv key must not be empty"); build_env.push((key.to_owned(), value.trim().to_owned())); } - "BuildBin" => { - let name = arg.trim(); - ensure!(!name.is_empty(), "BuildBin name must not be empty"); - config.build_bin_name = Some(name.to_owned()); - } "Env" => { let (key, value) = arg .split_once('=') @@ -575,14 +565,8 @@ fn read_fixture_bytes(test_src_dir: &Path, arg: &str, directive: &str) -> Result } fn parse_cargo_toml_directive_line(line: &str) -> Option<&str> { - let line = line.trim(); - let rest = line.strip_prefix("##")?; - let rest = rest.trim(); - if rest.is_empty() || !rest.contains(':') { - None - } else { - Some(rest) - } + let rest = line.trim().strip_prefix("##")?.trim(); + rest.split_once(':').map(|_| rest) } const CARGO_WASIX_ARTIFACT_DIR: &str = "target/wasm32-wasmer-wasi/debug"; @@ -602,7 +586,7 @@ fn write_ephemeral_cargo_toml(build_dir: &Path, source_filename: &str) -> Result r#"[package] name = "main" version = "0.0.0" -edition = "2021" +edition = "2024" [[bin]] name = "main" @@ -615,94 +599,36 @@ path = "{source_filename}" .with_context(|| format!("failed to write {}", build_dir.join("Cargo.toml").display())) } -fn ensure_standalone_cargo_workspace(build_dir: &Path) -> Result<()> { - let manifest_path = build_dir.join("Cargo.toml"); - let contents = fs::read_to_string(&manifest_path) - .with_context(|| format!("failed to read {}", manifest_path.display()))?; - if contents.contains("[workspace]") { - return Ok(()); - } - fs::write(&manifest_path, format!("{contents}\n[workspace]\n")) - .with_context(|| format!("failed to update {}", manifest_path.display())) -} - -fn cargo_bin_name_from_manifest(manifest_path: &Path, build_dir: &Path) -> Result { +fn cargo_bin_name_from_manifest(manifest_path: &Path) -> Result { let contents = fs::read_to_string(manifest_path) .with_context(|| format!("failed to read {}", manifest_path.display()))?; let manifest: toml::Value = toml::from_str(&contents).context("failed to parse Cargo.toml")?; if let Some(bins) = manifest.get("bin").and_then(|bins| bins.as_array()) { ensure!( - !bins.is_empty(), - "Cargo.toml defines an empty [[bin]] list in {}", - manifest_path.display() - ); - if bins.len() == 1 { - return Ok(bins[0] - .get("name") - .and_then(|name| name.as_str()) - .context("[[bin]] is missing name")? - .to_owned()); - } - - let package_name = manifest - .get("package") - .and_then(|package| package.get("name")) - .and_then(|name| name.as_str()); - - if let Some(name) = package_name { - if bins.iter().any(|bin| { - bin.get("name") - .and_then(|name| name.as_str()) - .is_some_and(|bin_name| bin_name == name) - }) { - return Ok(name.to_owned()); - } - } - - if let Some(bin) = bins.iter().find(|bin| { - bin.get("name") - .and_then(|name| name.as_str()) - .is_some_and(|name| name == "main") - }) { - return Ok(bin - .get("name") - .and_then(|name| name.as_str()) - .expect("main bin has a name") - .to_owned()); - } - - bail!( - "Cargo.toml in {} defines multiple [[bin]] targets; add a ##BuildBin: name directive", + bins.len() == 1, + "expected exactly one [[bin]] in {}", manifest_path.display() ); + return Ok(bins[0] + .get("name") + .and_then(|name| name.as_str()) + .context("[[bin]] is missing name")? + .to_owned()); } - let package_name = manifest + manifest .get("package") .and_then(|package| package.get("name")) .and_then(|name| name.as_str()) - .with_context(|| format!("missing package.name in {}", manifest_path.display()))?; - - if build_dir.join("src/main.rs").exists() { - return Ok(package_name.to_owned()); - } - - bail!( - "could not determine Cargo binary name for {}; add src/main.rs or a ##BuildBin: name directive", - manifest_path.display() - ) + .context("missing package.name") + .map(str::to_owned) } fn copy_cargo_wasix_artifact(build_dir: &Path, bin_name: &str) -> Result { let wasm = build_dir .join(CARGO_WASIX_ARTIFACT_DIR) .join(format!("{bin_name}.wasm")); - ensure!( - wasm.exists(), - "expected cargo-wasix artifact at {}", - wasm.display() - ); let main_path = build_dir.join("main"); fs::copy(&wasm, &main_path).with_context(|| { format!( @@ -767,10 +693,7 @@ fn run_build_script(config: &Config) -> anyhow::Result { write_ephemeral_cargo_toml(&build_test_path, filename)?; cargo_wasix_build_command(&build_test_path) } - PrimarySource::CargoProject => { - ensure_standalone_cargo_workspace(&build_test_path)?; - cargo_wasix_build_command(&build_test_path) - } + PrimarySource::CargoProject => cargo_wasix_build_command(&build_test_path), }; for (k, v) in &config.build_env { @@ -788,13 +711,8 @@ fn run_build_script(config: &Config) -> anyhow::Result { let main_path = match &config.source { PrimarySource::RustSourceFile(_) => copy_cargo_wasix_artifact(&build_test_path, "main")?, PrimarySource::CargoProject => { - let bin_name = match &config.build_bin_name { - Some(name) => name.clone(), - None => cargo_bin_name_from_manifest( - &build_test_path.join("Cargo.toml"), - &build_test_path, - )?, - }; + let bin_name = + cargo_bin_name_from_manifest(&build_test_path.join("Cargo.toml"))?; copy_cargo_wasix_artifact(&build_test_path, &bin_name)? } PrimarySource::BashScript(_) From ca66b234056da86f13b1b84ceb47ad800cbca60f Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 11 Jun 2026 08:32:59 +0000 Subject: [PATCH 05/19] Style fixes -.- --- lib/wasix/tests/wasm_tests/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index 040a016fc35b..627ecb1d5313 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -775,8 +775,7 @@ fn run_build_script(config: &Config) -> anyhow::Result { let main_path = match &config.source { PrimarySource::RustSourceFile(_) => copy_cargo_wasix_artifact(&build_test_path, "main")?, PrimarySource::CargoProject => { - let bin_name = - cargo_bin_name_from_manifest(&build_test_path.join("Cargo.toml"))?; + let bin_name = cargo_bin_name_from_manifest(&build_test_path.join("Cargo.toml"))?; copy_cargo_wasix_artifact(&build_test_path, &bin_name)? } PrimarySource::BashScript(_) From c4e49b4e9bd9c89c691b5e7678978c1231f70d02 Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Thu, 11 Jun 2026 13:34:09 +0200 Subject: [PATCH 06/19] fix compilation error --- lib/wasix/tests/wasm_tests/wasi_fyi/ported_close_preopen_fd.rs | 2 +- lib/wasix/tests/wasm_tests/wasi_wast/close_preopen_fd.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/ported_close_preopen_fd.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/ported_close_preopen_fd.rs index 0989636fcab5..bcfe62e75043 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/ported_close_preopen_fd.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/ported_close_preopen_fd.rs @@ -4,7 +4,7 @@ // mapdir: hamlet:test_fs/hamlet #[link(wasm_import_module = "wasi_unstable")] -extern "C" { +unsafe extern "C" { fn fd_close(fd: u32) -> u16; fn fd_fdstat_set_flags(fd: u32, flags: u16) -> u16; } diff --git a/lib/wasix/tests/wasm_tests/wasi_wast/close_preopen_fd.rs b/lib/wasix/tests/wasm_tests/wasi_wast/close_preopen_fd.rs index 9b24fa7e2713..f66ff4b1c3ce 100644 --- a/lib/wasix/tests/wasm_tests/wasi_wast/close_preopen_fd.rs +++ b/lib/wasix/tests/wasm_tests/wasi_wast/close_preopen_fd.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; #[cfg(target_os = "wasi")] #[link(wasm_import_module = "wasi_unstable")] -extern "C" { +unsafe extern "C" { fn fd_close(fd: u32) -> u16; fn fd_fdstat_set_flags(fd: u32, flags: u16) -> u16; } From f6e793e3cd17a1fe4b10037fd6ecca9ac5f91fba Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Thu, 11 Jun 2026 13:12:17 +0000 Subject: [PATCH 07/19] Install cargo-wasix in CI + pin rust toolchain tag --- .github/ci-constants.env | 2 ++ .github/workflows/copilot-setup-steps.yml | 6 ++++++ .github/workflows/test.yaml | 17 +++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/.github/ci-constants.env b/.github/ci-constants.env index 18da91161fa4..9a24959acc35 100644 --- a/.github/ci-constants.env +++ b/.github/ci-constants.env @@ -1,3 +1,5 @@ # Shared CI constants. Loaded into GITHUB_ENV by workflows that need them. # Pinned wasix-libc sysroot (wasix-org/wasix-libc release tag). WASIX_LIBC_SYSROOT_TAG=v2026-06-09.1 +# Pinned WASIX rust toolchain (wasix-org/rust release tag). +WASIX_RUST_TOOLCHAIN_TAG=v2026-06-09.1+rust-1.90 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 6483727c20d4..e24c1fd0341d 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -81,6 +81,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: WASIXCC_SYSROOT_PREFIX=~/.wasixcc/sysroot-v2026-05-12.1 wasixccenv download-sysroot v2026-05-12.1 + - name: Install cargo-wasix + uses: wasix-org/cargo-wasix@main + with: + toolchain-version: ${{ env.WASIX_RUST_TOOLCHAIN_TAG }} - name: Install wasm-tools run: | cargo install --locked wasm-tools @@ -95,6 +99,8 @@ jobs: wasm-tools --version # Check wasixcc is installed wasixcc --version + # Check cargo-wasix is installed + cargo wasix --version # Check wasm-opt is available wasm-opt --version # Check that the repo exists diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e862bc05a83b..6ea0e7d7063a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -113,6 +113,18 @@ jobs: echo "Pinned sysroot ($WASIX_LIBC_SYSROOT_TAG) is older than latest release ($latest)" exit 1 fi + - name: Verify pinned wasix rust toolchain is up to date + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + source .github/ci-constants.env + latest=$(gh release list --exclude-pre-releases --order desc -R wasix-org/rust --json tagName -q '.[0].tagName') + newest=$(printf '%s\n' "$latest" "$WASIX_RUST_TOOLCHAIN_TAG" | sort -V | tail -1) + if [ "$WASIX_RUST_TOOLCHAIN_TAG" != "$newest" ]; then + echo "Pinned rust toolchain ($WASIX_RUST_TOOLCHAIN_TAG) is older than latest release ($latest)" + exit 1 + fi cargo_deny: name: cargo-deny runs-on: ubuntu-22.04 @@ -692,6 +704,11 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: WASIXCC_SYSROOT_PREFIX=~/.wasixcc/sysroot-v2026-05-12.1 wasixccenv download-sysroot v2026-05-12.1 if: matrix.metadata.build != 'windows-x64' + - name: Install cargo-wasix + if: matrix.stage.make == 'test-all' + uses: wasix-org/cargo-wasix@main + with: + toolchain-version: ${{ env.WASIX_RUST_TOOLCHAIN_TAG }} - name: Install LLVM shell: bash if: matrix.metadata.llvm_url From 90150a98991db420734123936706b80e45676b6d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 17 Jul 2026 10:57:49 +0000 Subject: [PATCH 08/19] test(wasix): drop Singlepass from the wasm_tests harness Singlepass lacks exception-handling support, and the WASIX toolchain (cargo-wasix with the exnref sysroot) now emits EH opcodes even for fixtures that never touched exceptions under wasm32-wasip1. WASIX and Singlepass aren't expected to play nicely, so remove the engine from the harness instead of skipping fixtures piecemeal. Co-Authored-By: Claude Fable 5 --- lib/wasix/tests/wasm_tests/mod.rs | 35 +--------------------------- lib/wasix/tests/wasm_tests/runner.rs | 8 ------- 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index 809e4ac6998a..0aeda190aa61 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -43,7 +43,7 @@ //! `Ignored:{reason}` marks the configuration as ignored with the given reason. //! //! `SkipEngine:{engine}:{reason}` marks the configuration as ignored for -//! a given engine (LLVM, Cranelift, V8, Singlepass). +//! a given engine (LLVM, Cranelift, V8). //! //! `UnixOnly:{bool}` ignores the configuration on non-Unix hosts when true. //! @@ -140,8 +140,6 @@ pub enum Engine { Cranelift, #[cfg(feature = "llvm")] LLVM, - #[cfg(feature = "singlepass")] - Singlepass, #[cfg(feature = "v8")] V8, } @@ -163,8 +161,6 @@ impl Engine { Self::Cranelift => "cranelift", #[cfg(feature = "llvm")] Self::LLVM => "llvm", - #[cfg(feature = "singlepass")] - Self::Singlepass => "singlepass", #[cfg(feature = "v8")] Self::V8 => "v8", } @@ -458,16 +454,6 @@ fn process_directive( None } } - "singlepass" => { - #[cfg(feature = "singlepass")] - { - Some(Engine::Singlepass) - } - #[cfg(not(feature = "singlepass"))] - { - None - } - } _ => bail!("unsupported engine: '{engine}'"), } { config.skipped_engines.push((engine, reason.to_owned())); @@ -1327,8 +1313,6 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { let mut supported_engines = vec![]; #[cfg(feature = "llvm")] supported_engines.push(Engine::LLVM); - #[cfg(feature = "singlepass")] - supported_engines.push(Engine::Singlepass); #[cfg(feature = "v8")] supported_engines.push(Engine::V8); @@ -1353,23 +1337,6 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { .unwrap_or_else(|| &default_file_systems) { for engine in &supported_engines { - // In general, the WASIX tests expect support for more advanced WebAssembly extensions (like exception handling), - // but we can still run selectively some tests with Singlepass. - #[cfg(feature = "singlepass")] - { - let test_name = entry - .path() - .file_name() - .expect("must be valid filename") - .to_string_lossy() - .to_string(); - if *engine == Engine::Singlepass - && !["wasi_fyi", "wasi_wast"].contains(&test_name.as_str()) - { - continue; - } - } - // WASIXCC toolchain does not cover Windows yet. if cfg!(target_os = "windows") && !matches!( diff --git a/lib/wasix/tests/wasm_tests/runner.rs b/lib/wasix/tests/wasm_tests/runner.rs index e94679a8246d..4ebb53a462a7 100644 --- a/lib/wasix/tests/wasm_tests/runner.rs +++ b/lib/wasix/tests/wasm_tests/runner.rs @@ -266,8 +266,6 @@ fn create_engine_for_wasm(wasm_bytes: &[u8], engine: Engine) -> wasmer::Engine { Engine::Cranelift => wasmer::BackendKind::Cranelift, #[cfg(feature = "llvm")] Engine::LLVM => wasmer::BackendKind::LLVM, - #[cfg(feature = "singlepass")] - Engine::Singlepass => wasmer::BackendKind::Singlepass, #[cfg(feature = "v8")] Engine::V8 => wasmer::BackendKind::V8, }; @@ -287,12 +285,6 @@ fn create_engine_for_wasm(wasm_bytes: &[u8], engine: Engine) -> wasmer::Engine { config.num_threads(NonZero::new(1).unwrap()); EngineBuilder::new(config) } - #[cfg(feature = "singlepass")] - Engine::Singlepass => { - let mut config = wasmer::sys::Singlepass::default(); - config.num_threads(NonZero::new(1).unwrap()); - EngineBuilder::new(config) - } #[cfg(feature = "v8")] Engine::V8 => return wasmer::v8::engine::Engine::new().into(), }; From 24e4e8868affbb3843f5237d8d3047e112ddeb10 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 17 Jul 2026 11:14:12 +0000 Subject: [PATCH 09/19] test(wasix): prebuild Rust wasm_tests fixtures once in CI The WASIX Rust toolchain is not published for every platform we run tests on (notably aarch64-linux and musl hosts), so Rust fixtures can't be compiled there. Since the produced wasm is host-independent, build the fixtures once on linux-x64 and share them with all test jobs: - WASM_TESTS_BUILD_ONLY_DIR= builds each Rust fixture through its Cranelift trial (artifacts are engine-independent) and exports the wasm to without running tests, via make build-wasm-tests-fixtures. - WASM_TESTS_PREBUILT_DIR= makes the suite consume those artifacts instead of invoking cargo wasix build, so test hosts no longer need cargo-wasix or the WASIX Rust toolchain. CI gains a build_wasm_tests_fixtures job that uploads the artifacts; the test-all jobs download them and drop the cargo-wasix install step (which was also broken in containerized jobs due to a hardcoded runner path in the action). C/C++/bash fixtures keep building on each host with wasixcc as before. Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yaml | 76 +++++++++++++++++++++++++-- Makefile | 6 +++ lib/wasix/tests/wasm_tests/README.md | 12 +++++ lib/wasix/tests/wasm_tests/mod.rs | 77 +++++++++++++++++++++++++++- 4 files changed, 166 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6ea0e7d7063a..e5c82f10c146 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -570,10 +570,72 @@ jobs: path: build-capi.tar.gz if-no-files-found: ignore retention-days: 2 + build_wasm_tests_fixtures: + # The WASIX Rust toolchain is not published for every platform we test on, + # so the Rust wasm_tests fixtures are prebuilt here once (wasm is + # host-independent) and shared with the test jobs as an artifact. + name: Build Rust wasm_tests fixtures + runs-on: ubuntu-22.04 + needs: setup + steps: + - uses: actions/checkout@v6 + with: + submodules: true + - name: Load CI constants + shell: bash + run: grep -v '^#' .github/ci-constants.env >> $GITHUB_ENV + - uses: ./.github/actions/load_toolchain + id: load_toolchain + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ steps.load_toolchain.outputs.rust_toolchain }} + - name: Install Nextest + uses: taiki-e/install-action@nextest + - name: Install wasixcc + uses: wasix-org/wasixcc@v0.4.3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + version: v0.4.3 + sysroot-tag: ${{ env.WASIX_LIBC_SYSROOT_TAG }} + - name: Install older wasix-libc for compatibility testing + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: WASIXCC_SYSROOT_PREFIX=~/.wasixcc/sysroot-v2026-05-12.1 wasixccenv download-sysroot v2026-05-12.1 + - name: Install cargo-wasix + uses: wasix-org/cargo-wasix@main + with: + toolchain-version: ${{ env.WASIX_RUST_TOOLCHAIN_TAG }} + - name: Cache + # TODO: v3 is unable to Restore the cache for some reason + uses: whywaita/actions-cache-s3@v2 + with: + path: | + ~/.cargo/* + ./target/* + key: cache-v${{ env.S3_CACHE_VERSION }}-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-wasmer-wasm-tests-fixtures + aws-s3-bucket: wasmer-rust-artifacts-cache + aws-access-key-id: ${{ secrets.CLOUDFLARE_ARTIFACTS_CACHE_ACCESS_TOKEN }} + aws-secret-access-key: ${{ secrets.CLOUDFLARE_ARTIFACTS_CACHE_ACCESS_KEY }} + aws-region: auto + aws-endpoint: https://1541b1e8a3fc6ad155ce67ef38899700.r2.cloudflarestorage.com + aws-s3-bucket-endpoint: false + aws-s3-force-path-style: true + - name: Build fixtures + shell: bash + run: make build-wasm-tests-fixtures + env: + WASM_TESTS_BUILD_ONLY_DIR: ${{ github.workspace }}/wasm-tests-prebuilt + - name: Upload fixtures + uses: actions/upload-artifact@v4 + with: + name: wasm-tests-prebuilt + path: wasm-tests-prebuilt + if-no-files-found: error test: name: ${{ matrix.stage.description }} - ${{ matrix.metadata.build }} runs-on: ${{ matrix.metadata.os }} - needs: setup + needs: [setup, build_wasm_tests_fixtures] strategy: fail-fast: false matrix: @@ -704,11 +766,16 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: WASIXCC_SYSROOT_PREFIX=~/.wasixcc/sysroot-v2026-05-12.1 wasixccenv download-sysroot v2026-05-12.1 if: matrix.metadata.build != 'windows-x64' - - name: Install cargo-wasix + - name: Download prebuilt Rust wasm_tests fixtures if: matrix.stage.make == 'test-all' - uses: wasix-org/cargo-wasix@main + uses: actions/download-artifact@v4 with: - toolchain-version: ${{ env.WASIX_RUST_TOOLCHAIN_TAG }} + name: wasm-tests-prebuilt + path: wasm-tests-prebuilt + - name: Use prebuilt Rust wasm_tests fixtures + if: matrix.stage.make == 'test-all' + shell: bash + run: echo "WASM_TESTS_PREBUILT_DIR=$GITHUB_WORKSPACE/wasm-tests-prebuilt" >> $GITHUB_ENV - name: Install LLVM shell: bash if: matrix.metadata.llvm_url @@ -766,6 +833,7 @@ jobs: - test_build_docs_rs - build_linux_riscv64 - build + - build_wasm_tests_fixtures - test if: ${{ always() }} steps: diff --git a/Makefile b/Makefile index 97d717968dab..7e1a5b16c0a9 100644 --- a/Makefile +++ b/Makefile @@ -575,6 +575,12 @@ build-capi-headless-ios: # intentionally not using nextest as it runs tests in separate processes test-wast: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --release $(compiler_features) --locked +# Build the Rust wasm_tests fixtures without running them, exporting the wasm +# artifacts to WASM_TESTS_BUILD_ONLY_DIR. Requires cargo-wasix and the WASIX +# Rust toolchain; platforms without one can then run test-all with +# WASM_TESTS_PREBUILT_DIR pointing at the exported directory. +build-wasm-tests-fixtures: + $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) -p wasmer-wasix --test wasm_tests --locked test-all: $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(test_compiler_features) --features experimental-async,experimental-host-interrupt --locked && \ $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --manifest-path lib/virtual-net/Cargo.toml --release $(virtual_net_test_features) --locked && \ diff --git a/lib/wasix/tests/wasm_tests/README.md b/lib/wasix/tests/wasm_tests/README.md index b9cb3a620f7e..896d91466fb8 100644 --- a/lib/wasix/tests/wasm_tests/README.md +++ b/lib/wasix/tests/wasm_tests/README.md @@ -67,5 +67,17 @@ Cargo and nextest filtering both work. Before running the suite, make sure `wasixcc` is installed and available in your shell environment. Rust fixtures also require `cargo-wasix` on `PATH` (`cargo install cargo-wasix`). +The WASIX Rust toolchain is not published for every platform, so the Rust +fixtures can alternatively be prebuilt on a supported host and reused: + +- `WASM_TESTS_BUILD_ONLY_DIR=` builds the Rust fixtures into `` + without running any tests (CI runs this through + `make build-wasm-tests-fixtures` on linux-x64). The artifacts are + engine-independent and are built through the Cranelift trials, so this must + run on a host that collects them (i.e. not macOS). +- `WASM_TESTS_PREBUILT_DIR=` makes the suite consume those prebuilt + artifacts instead of invoking `cargo wasix build`, removing the need for + `cargo-wasix` and the WASIX Rust toolchain on the test host. + On macOS, this suite collects and runs the LLVM variants only because Cranelift exception-handling support is still incomplete there: diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index 0aeda190aa61..d6257c348f7a 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -247,6 +247,13 @@ impl Config { } fn full_test_name(&self) -> String { + format!("{}/{}", self.engine_independent_name(), self.engine) + } + + /// Identifies this configuration's build inputs. Unlike `full_test_name`, + /// this excludes the engine: Rust fixture builds don't depend on it, so + /// prebuilt artifacts are shared across engines. + fn engine_independent_name(&self) -> String { let mut parts = vec!["wasm".to_owned(), self.test_name.clone()]; if !self.source.is_default() { parts.push(self.source.config_name()); @@ -258,10 +265,13 @@ impl Config { if let Some(sysroot_version) = &self.sysroot_version { parts.push(sysroot_version.to_string()); } - parts.push(self.engine.to_string()); parts.join("/") } + fn prebuilt_wasm_path(&self, root: &Path) -> PathBuf { + root.join(self.engine_independent_name()).join("main.wasm") + } + fn set_sysroot(&mut self, sysroot_version: &'static str) -> Result<()> { let sysroot_path = dirs::home_dir() .ok_or_else(|| anyhow!("cannot expand home dir"))? @@ -707,6 +717,23 @@ fn run_build_script(config: &Config) -> anyhow::Result { ) })?; + // Rust fixtures can be prebuilt elsewhere (see `build_fixture_only`), + // which lets platforms without a WASIX Rust toolchain run the tests. + if config.source.is_rust() { + if let Some(prebuilt_root) = env_var_path("WASM_TESTS_PREBUILT_DIR") { + let prebuilt = config.prebuilt_wasm_path(&prebuilt_root); + let main_path = build_test_path.join("main"); + fs::copy(&prebuilt, &main_path).with_context(|| { + format!( + "failed to copy prebuilt Rust fixture {} — \ + ensure the fixture prebuild ran with a matching configuration", + prebuilt.display() + ) + })?; + return Ok(main_path); + } + } + let mut cmd = match &config.source { PrimarySource::BashScript(filename) => { let mut cmd = Command::new("bash"); @@ -1021,10 +1048,54 @@ fn configure_mapped_directories( Ok(()) } +fn env_var_path(name: &str) -> Option { + std::env::var_os(name) + .filter(|value| !value.is_empty()) + .map(PathBuf::from) +} + +/// Build the Rust fixture for this configuration and export the resulting +/// wasm to `output_root` without running the test. CI uses this to prebuild +/// fixtures on a host that has the WASIX Rust toolchain; hosts without one +/// consume the artifacts via `WASM_TESTS_PREBUILT_DIR`. +fn build_fixture_only(config: &Config, output_root: &Path) -> Result { + if !config.source.is_rust() { + return Ok(libtest_mimic::Completion::ignored_with( + "build-only: not a Rust fixture", + )); + } + // Artifacts are engine-independent, so build each configuration once + // through its Cranelift trial. This requires the build-only run to happen + // on a host that collects Cranelift trials (i.e. not macOS). + if config.engine != Engine::Cranelift { + return Ok(libtest_mimic::Completion::ignored_with( + "build-only: built by the cranelift variant", + )); + } + if let Some(reason) = minimal_libc_skip_reason(config)? { + return Ok(libtest_mimic::Completion::ignored_with(reason)); + } + + let wasm = run_build_script(config)?; + let dest = config.prebuilt_wasm_path(output_root); + create_dir_all(dest.parent().expect("prebuilt path must have a parent"))?; + fs::copy(&wasm, &dest).with_context(|| { + format!( + "failed to export {} to {}", + wasm.display(), + dest.display() + ) + })?; + Ok(libtest_mimic::Completion::Completed) +} + fn run_integration_test(config: Config) -> Result { if let Some(reason) = &config.ignored { return Ok(libtest_mimic::Completion::ignored_with(reason.clone())); } + if let Some(output_root) = env_var_path("WASM_TESTS_BUILD_ONLY_DIR") { + return build_fixture_only(&config, &output_root); + } if !cfg!(unix) && config.unix_only { return Ok(libtest_mimic::Completion::ignored_with("Unix only")); } @@ -1208,6 +1279,10 @@ impl PrimarySource { } } + fn is_rust(&self) -> bool { + matches!(self, Self::RustSourceFile(_) | Self::CargoProject) + } + fn parse_directive_line<'a>(&self, line: &'a str) -> Option<&'a str> { match self { Self::CargoProject => parse_cargo_toml_directive_line(line), From ff0e519969048b9f185534fc06ec5a0aeced48d4 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 17 Jul 2026 11:25:37 +0000 Subject: [PATCH 10/19] ci: drop obsolete wasm32-wasip1 target install from test jobs The step only served the old direct-rustc wasm_tests fixture builds, which now use prebuilt artifacts (or cargo-wasix locally). The CLI integration tests install the target themselves in the Makefile. Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e5c82f10c146..30aa28caeb36 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -746,12 +746,6 @@ jobs: with: toolchain: ${{ steps.load_toolchain.outputs.rust_toolchain }} target: ${{ matrix.metadata.target }} - - name: Install Rust WASI targets - if: matrix.stage.make == 'test-all' - shell: bash - run: | - rustup target add wasm32-wasip1 - rustup toolchain install nightly --profile minimal --target wasm32-wasip1 - name: Install Nextest uses: taiki-e/install-action@nextest - name: Install wasixcc From 396e1cf02ce1da084050338791024e03e1609ee1 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 17 Jul 2026 13:56:53 +0000 Subject: [PATCH 11/19] test(wasix): fix fixture edition clash, fmt, and smoke test in build-only mode - Generate ephemeral fixture manifests with edition 2021: fixtures ported from the old direct-rustc build (edition 2015) use non-unsafe extern blocks, which edition 2024 rejects. - Skip the dynamic_runtime_hooks smoke test in build-only mode; it builds a C fixture with wasixcc and needs nothing prebuilt. - rustfmt fixes. Co-Authored-By: Claude Fable 5 --- lib/wasix/tests/wasm_tests/mod.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index 4941233794f1..7c17c438a759 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -650,7 +650,9 @@ fn write_ephemeral_cargo_toml(build_dir: &Path, source_filename: &str) -> Result r#"[package] name = "main" version = "0.0.0" -edition = "2024" +# 2021 rather than 2024 so fixtures ported from the old direct-rustc build +# (which used edition 2015) can keep their non-unsafe extern blocks. +edition = "2021" [[bin]] name = "main" @@ -1083,13 +1085,8 @@ fn build_fixture_only(config: &Config, output_root: &Path) -> Result Date: Fri, 17 Jul 2026 14:14:26 +0000 Subject: [PATCH 12/19] test(wasix): restore Singlepass via a wasip1 toolchain axis for Rust fixtures Review feedback (#6698): the wasi_fyi/wasi_wast suites were the only non-spec Singlepass coverage, and switching them to the WASIX toolchain (whose output uses EH opcodes Singlepass lacks) silently dropped it. Instead of a directory carve-out, model the build toolchain as an axis: single-file Rust fixtures default to building with both cargo-wasix (runs on every engine except Singlepass) and rustc --target wasm32-wasip1 (runs on Singlepass only), overridable per fixture with a new Toolchains directive. Singlepass therefore never sees WASIX-built wasm by construction, and no fixture edits are needed. The wasip1 build path restores the old direct-rustc invocation, including nightly selection for #![feature(...)] sources. wasip1 variants build locally on every platform (the rustup target is universal), so the prebuilt-artifact pipeline stays scoped to the WASIX toolchain and existing trial names and artifact keys are unchanged; new trials appear as .../wasip1/singlepass. Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yaml | 8 + lib/wasix/tests/wasm_tests/README.md | 13 +- lib/wasix/tests/wasm_tests/mod.rs | 222 ++++++++++++++++++++++----- lib/wasix/tests/wasm_tests/runner.rs | 8 + 4 files changed, 207 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bc64adb000c8..13b30599ae16 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -758,6 +758,14 @@ jobs: with: toolchain: ${{ steps.load_toolchain.outputs.rust_toolchain }} target: ${{ matrix.metadata.target }} + # For the wasip1-toolchain (Singlepass) variants of the Rust wasm_tests + # fixtures, which build locally on every platform. + - name: Install Rust WASI targets + if: matrix.stage.make == 'test-all' + shell: bash + run: | + rustup target add wasm32-wasip1 + rustup toolchain install nightly --profile minimal --target wasm32-wasip1 - name: Install Nextest uses: taiki-e/install-action@nextest - name: Install wasixcc diff --git a/lib/wasix/tests/wasm_tests/README.md b/lib/wasix/tests/wasm_tests/README.md index 896d91466fb8..2cac88767087 100644 --- a/lib/wasix/tests/wasm_tests/README.md +++ b/lib/wasix/tests/wasm_tests/README.md @@ -67,8 +67,17 @@ Cargo and nextest filtering both work. Before running the suite, make sure `wasixcc` is installed and available in your shell environment. Rust fixtures also require `cargo-wasix` on `PATH` (`cargo install cargo-wasix`). -The WASIX Rust toolchain is not published for every platform, so the Rust -fixtures can alternatively be prebuilt on a supported host and reused: +Single-file Rust fixtures build with two toolchains by default (see the +`Toolchains` directive): `wasix` (cargo-wasix), which runs on every engine +except Singlepass, and `wasip1` (`rustc --target wasm32-wasip1`, nightly when +the source uses `#![feature(...)]`), which runs on Singlepass only — the WASIX +toolchain emits exception-handling opcodes Singlepass does not support. The +wasip1 variants need `rustup target add wasm32-wasip1` (plus the same target on +nightly). + +The WASIX Rust toolchain is not published for every platform, so the +wasix-toolchain fixtures can alternatively be prebuilt on a supported host and +reused: - `WASM_TESTS_BUILD_ONLY_DIR=` builds the Rust fixtures into `` without running any tests (CI runs this through diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index 7c17c438a759..cb6e0e7e153f 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -43,7 +43,12 @@ //! `Ignored:{reason}` marks the configuration as ignored with the given reason. //! //! `SkipEngine:{engine}:{reason}` marks the configuration as ignored for -//! a given engine (LLVM, Cranelift, V8). +//! a given engine (LLVM, Cranelift, V8, Singlepass). +//! +//! `Toolchains:{list}` selects which toolchains build a single-file Rust +//! fixture: `wasix` (cargo-wasix; runs on every engine except Singlepass) and +//! `wasip1` (rustc with the wasm32-wasip1 target; runs on Singlepass only). +//! Defaults to `wasix,wasip1` for single-file Rust fixtures. //! //! `UnixOnly:{bool}` ignores the configuration on non-Unix hosts when true. //! @@ -144,10 +149,43 @@ pub enum Engine { Cranelift, #[cfg(feature = "llvm")] LLVM, + #[cfg(feature = "singlepass")] + Singlepass, #[cfg(feature = "v8")] V8, } +/// Which toolchain builds a Rust fixture. The WASIX toolchain emits +/// exception-handling opcodes, so its output cannot run on Singlepass; the +/// plain `wasm32-wasip1` rustup target can, and is available on every host +/// platform. +#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumString)] +#[strum(ascii_case_insensitive, serialize_all = "lowercase")] +enum Toolchain { + Wasix, + Wasip1, +} + +impl Toolchain { + /// Engines that can run this toolchain's output. Singlepass lacks + /// exception-handling support, so it only runs the wasip1-built variant; + /// conversely the other engines only run the WASIX-built variant since + /// running both toolchains' output there would duplicate coverage. + fn supports_engine(self, engine: Engine) -> bool { + #[cfg(feature = "singlepass")] + let is_singlepass = engine == Engine::Singlepass; + #[cfg(not(feature = "singlepass"))] + let is_singlepass = { + let _ = engine; + false + }; + match self { + Self::Wasix => !is_singlepass, + Self::Wasip1 => is_singlepass, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumIter, strum::EnumString)] #[strum(ascii_case_insensitive, serialize_all = "lowercase")] enum FileSystemKind { @@ -165,6 +203,8 @@ impl Engine { Self::Cranelift => "cranelift", #[cfg(feature = "llvm")] Self::LLVM => "llvm", + #[cfg(feature = "singlepass")] + Self::Singlepass => "singlepass", #[cfg(feature = "v8")] Self::V8 => "v8", } @@ -181,6 +221,8 @@ struct Config { test_name: String, config_name: String, engine: Engine, + toolchain: Toolchain, + toolchains: Option>, selected_file_system: FileSystemKind, file_systems: Option>, is_abstract: bool, @@ -221,6 +263,8 @@ impl Config { test_name, config_name: "default".to_owned(), engine: Engine::Cranelift, + toolchain: Toolchain::Wasix, + toolchains: None, file_systems: None, selected_file_system: FileSystemKind::Host, is_abstract: false, @@ -269,6 +313,9 @@ impl Config { if let Some(sysroot_version) = &self.sysroot_version { parts.push(sysroot_version.to_string()); } + if self.toolchain != Toolchain::Wasix { + parts.push(self.toolchain.to_string()); + } parts.join("/") } @@ -458,6 +505,16 @@ fn process_directive( } } "cranelift" => Some(Engine::Cranelift), + "singlepass" => { + #[cfg(feature = "singlepass")] + { + Some(Engine::Singlepass) + } + #[cfg(not(feature = "singlepass"))] + { + None + } + } "v8" => { #[cfg(feature = "v8")] { @@ -524,6 +581,28 @@ fn process_directive( "DefaultMappedDirectories" => { config.default_mapped_directories = arg.parse::()?; } + "Toolchains" => { + let toolchains = arg + .split(',') + .map(|toolchain| { + toolchain + .trim() + .parse::() + .map_err(|_| anyhow!("unsupported toolchain: '{toolchain}'")) + }) + .collect::>>()?; + ensure!( + !toolchains.is_empty(), + "at least one toolchain must be selected" + ); + if toolchains.contains(&Toolchain::Wasip1) { + ensure!( + matches!(config.source, PrimarySource::RustSourceFile(_)), + "the wasip1 toolchain is only supported for single-file Rust fixtures" + ); + } + config.toolchains = Some(toolchains); + } "FileSystems" => { config.file_systems = Some(if arg == "all" { FileSystemKind::iter().collect() @@ -635,6 +714,31 @@ fn parse_cargo_toml_directive_line(line: &str) -> Option<&str> { const CARGO_WASIX_ARTIFACT_DIR: &str = "target/wasm32-wasmer-wasi/debug"; +fn rustc_command(toolchain: Option<&str>) -> Command { + if let Some(toolchain) = toolchain { + // rustc +version multiplexing is unsupported on Windows, use the documented approach: + // https://rust-lang.github.io/rustup/concepts/toolchains.html#custom-toolchains + let mut cmd = Command::new("rustup"); + cmd.arg("run").arg(toolchain).arg("rustc"); + cmd + } else { + Command::new("rustc") + } +} + +fn rustc_wasip1_build_command(build_dir: &Path, source_filename: &str) -> Result { + let primary_source = build_dir.join(source_filename); + let source = std::fs::read_to_string(&primary_source) + .with_context(|| format!("Failed to read {}", primary_source.display()))?; + let mut cmd = rustc_command(source.contains("#![feature(").then_some("nightly")); + cmd.arg("--target=wasm32-wasip1") + .arg("-o") + .arg("main") + .arg(&primary_source) + .current_dir(build_dir); + Ok(cmd) +} + fn cargo_wasix_build_command(build_dir: &Path) -> Command { let mut cmd = Command::new("cargo"); cmd.arg("wasix") @@ -723,9 +827,11 @@ fn run_build_script(config: &Config) -> anyhow::Result { ) })?; - // Rust fixtures can be prebuilt elsewhere (see `build_fixture_only`), - // which lets platforms without a WASIX Rust toolchain run the tests. - if config.source.is_rust() { + // WASIX-toolchain Rust fixtures can be prebuilt elsewhere (see + // `build_fixture_only`), which lets platforms without a WASIX Rust + // toolchain run the tests. The wasip1 toolchain is available everywhere, + // so those variants always build locally. + if config.source.is_rust() && config.toolchain == Toolchain::Wasix { if let Some(prebuilt_root) = env_var_path("WASM_TESTS_PREBUILT_DIR") { let prebuilt = config.prebuilt_wasm_path(&prebuilt_root); let main_path = build_test_path.join("main"); @@ -772,10 +878,13 @@ fn run_build_script(config: &Config) -> anyhow::Result { .env("WASIXCC_DISCARD_UNSUPPORTED_FLAGS", "yes"); cmd } - PrimarySource::RustSourceFile(filename) => { - write_ephemeral_cargo_toml(&build_test_path, filename)?; - cargo_wasix_build_command(&build_test_path) - } + PrimarySource::RustSourceFile(filename) => match config.toolchain { + Toolchain::Wasix => { + write_ephemeral_cargo_toml(&build_test_path, filename)?; + cargo_wasix_build_command(&build_test_path) + } + Toolchain::Wasip1 => rustc_wasip1_build_command(&build_test_path, filename)?, + }, PrimarySource::CargoProject => cargo_wasix_build_command(&build_test_path), }; @@ -791,15 +900,21 @@ fn run_build_script(config: &Config) -> anyhow::Result { anyhow::bail!("Build failed for {}", build_test_path.display()); } - let main_path = match &config.source { - PrimarySource::RustSourceFile(_) => copy_cargo_wasix_artifact(&build_test_path, "main")?, - PrimarySource::CargoProject => { + let main_path = match (&config.source, config.toolchain) { + (PrimarySource::RustSourceFile(_), Toolchain::Wasix) => { + copy_cargo_wasix_artifact(&build_test_path, "main")? + } + (PrimarySource::CargoProject, _) => { let bin_name = cargo_bin_name_from_manifest(&build_test_path.join("Cargo.toml"))?; copy_cargo_wasix_artifact(&build_test_path, &bin_name)? } - PrimarySource::BashScript(_) - | PrimarySource::CSourceFile(_) - | PrimarySource::CppSourceFile(_) => build_test_path.join("main"), + (PrimarySource::RustSourceFile(_), Toolchain::Wasip1) + | ( + PrimarySource::BashScript(_) + | PrimarySource::CSourceFile(_) + | PrimarySource::CppSourceFile(_), + _, + ) => build_test_path.join("main"), }; Ok(main_path) @@ -1070,6 +1185,11 @@ fn build_fixture_only(config: &Config, output_root: &Path) -> Result) -> Result<()> { let mut supported_engines = vec![Engine::Cranelift]; #[cfg(feature = "llvm")] supported_engines.push(Engine::LLVM); + #[cfg(feature = "singlepass")] + supported_engines.push(Engine::Singlepass); #[cfg(feature = "v8")] supported_engines.push(Engine::V8); let default_file_systems = vec![FileSystemKind::Host]; for primary_source in primary_sources { + // Single-file Rust fixtures historically built with wasm32-wasip1; + // keep that variant alive since it is the only way to run them on + // Singlepass (the WASIX toolchain emits EH opcodes it lacks). + let default_toolchains = match &primary_source { + PrimarySource::RustSourceFile(_) => vec![Toolchain::Wasix, Toolchain::Wasip1], + _ => vec![Toolchain::Wasix], + }; + let configs = parse_configs(&Config::new( primary_source, entry.path().to_path_buf(), @@ -1417,40 +1547,48 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { .as_ref() .unwrap_or(&default_file_systems) { - for engine in &supported_engines { - // WASIXCC toolchain does not cover Windows yet. - if cfg!(target_os = "windows") - && !matches!( - config.source, - PrimarySource::RustSourceFile(..) | PrimarySource::CargoProject - ) - { - continue; - } + for toolchain in config.toolchains.as_ref().unwrap_or(&default_toolchains) { + for engine in &supported_engines { + if !toolchain.supports_engine(*engine) { + continue; + } - for sysroot in TESTED_LIBC_VERSIONS { - // For performance reasons, run the wasix-libc compatibility tests - // only with the Cranelift compiler. - if sysroot.is_some() - && (*engine != Engine::Cranelift || cfg!(target_os = "windows")) + // WASIXCC toolchain does not cover Windows yet. + if cfg!(target_os = "windows") + && !matches!( + config.source, + PrimarySource::RustSourceFile(..) | PrimarySource::CargoProject + ) { continue; } - let mut config = config.clone(); - config.engine = *engine; - config.selected_file_system = *file_system; - if let Some(sysroot_version) = sysroot { - config.set_sysroot(sysroot_version)?; + for sysroot in TESTED_LIBC_VERSIONS { + // For performance reasons, run the wasix-libc compatibility tests + // only with the Cranelift compiler. + if sysroot.is_some() + && (*engine != Engine::Cranelift || cfg!(target_os = "windows")) + { + continue; + } + + let mut config = config.clone(); + config.engine = *engine; + config.toolchain = *toolchain; + config.selected_file_system = *file_system; + if let Some(sysroot_version) = sysroot { + config.set_sysroot(sysroot_version)?; + } + + tests.push(libtest_mimic::Trial::ignorable_test( + config.full_test_name(), + move || { + run_integration_test(config).map_err(|e| { + libtest_mimic::Failed::from(format!("{e:?}")) + }) + }, + )); } - - tests.push(libtest_mimic::Trial::ignorable_test( - config.full_test_name(), - move || { - run_integration_test(config) - .map_err(|e| libtest_mimic::Failed::from(format!("{e:?}"))) - }, - )); } } } diff --git a/lib/wasix/tests/wasm_tests/runner.rs b/lib/wasix/tests/wasm_tests/runner.rs index 9ecd650048e4..4171f94c6dd5 100644 --- a/lib/wasix/tests/wasm_tests/runner.rs +++ b/lib/wasix/tests/wasm_tests/runner.rs @@ -268,6 +268,8 @@ fn create_engine_for_wasm(wasm_bytes: &[u8], engine: Engine) -> wasmer::Engine { Engine::Cranelift => wasmer::BackendKind::Cranelift, #[cfg(feature = "llvm")] Engine::LLVM => wasmer::BackendKind::LLVM, + #[cfg(feature = "singlepass")] + Engine::Singlepass => wasmer::BackendKind::Singlepass, #[cfg(feature = "v8")] Engine::V8 => wasmer::BackendKind::V8, }; @@ -287,6 +289,12 @@ fn create_engine_for_wasm(wasm_bytes: &[u8], engine: Engine) -> wasmer::Engine { config.num_threads(NonZero::new(1).unwrap()); EngineBuilder::new(config) } + #[cfg(feature = "singlepass")] + Engine::Singlepass => { + let mut config = wasmer::sys::Singlepass::default(); + config.num_threads(NonZero::new(1).unwrap()); + EngineBuilder::new(config) + } #[cfg(feature = "v8")] Engine::V8 => return wasmer::v8::engine::Engine::new().into(), }; From 7572f548e022576a952d042277b2176b62385714 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 17 Jul 2026 14:17:13 +0000 Subject: [PATCH 13/19] chore: default WASM_TESTS_BUILD_ONLY_DIR in the fixtures make target Local runs of build-wasm-tests-fixtures no longer require exporting the variable: it defaults to target/wasm-tests-prebuilt, and the target prints where the artifacts were exported. An explicit env var (as CI sets) still takes precedence. Co-Authored-By: Claude Fable 5 --- Makefile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 7d6c22a4af9b..8f6a01c0b75c 100644 --- a/Makefile +++ b/Makefile @@ -578,11 +578,14 @@ build-capi-headless-ios: test-wast: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --release $(compiler_features) --locked # Build the Rust wasm_tests fixtures without running them, exporting the wasm -# artifacts to WASM_TESTS_BUILD_ONLY_DIR. Requires cargo-wasix and the WASIX -# Rust toolchain; platforms without one can then run test-all with -# WASM_TESTS_PREBUILT_DIR pointing at the exported directory. +# artifacts to WASM_TESTS_BUILD_ONLY_DIR (defaults to a directory under +# target/). Requires cargo-wasix and the WASIX Rust toolchain; platforms +# without one can then run test-all with WASM_TESTS_PREBUILT_DIR pointing at +# the exported directory. +build-wasm-tests-fixtures: WASM_TESTS_BUILD_ONLY_DIR ?= $(CURDIR)/target/wasm-tests-prebuilt build-wasm-tests-fixtures: - $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) -p wasmer-wasix --test wasm_tests --locked + WASM_TESTS_BUILD_ONLY_DIR="$(WASM_TESTS_BUILD_ONLY_DIR)" $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) -p wasmer-wasix --test wasm_tests --locked + @echo "Rust wasm_tests fixtures exported to $(WASM_TESTS_BUILD_ONLY_DIR)" test-all: $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(test_compiler_features) --features experimental-async,experimental-host-interrupt --locked && \ $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --manifest-path lib/virtual-net/Cargo.toml --release $(virtual_net_test_features) --locked && \ From 23d2d3381a6fa1000e2f05fb6888bd16d9229c61 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 17 Jul 2026 14:24:56 +0000 Subject: [PATCH 14/19] chore: bump WASIX rust toolchain pin, lint wasm_tests Rust fixtures - Bump WASIX_RUST_TOOLCHAIN_TAG to v2026-07-07.3+rust-1.96; the lint job requires the pin to match the latest wasix-org/rust release. - Add a rustfmt check for the wasm_tests Rust fixtures to lint-formatting, mirroring the clang-format check for C fixtures: they are not part of any crate, so cargo fmt does not cover them. Format the existing fixtures accordingly (all wasi_wast/wasi_fyi tests re-run green afterwards, quine included). Co-Authored-By: Claude Fable 5 --- .github/ci-constants.env | 2 +- Makefile | 3 +++ .../tests/wasm_tests/wasi_fyi/env_args-many.rs | 12 ++++++------ .../tests/wasm_tests/wasi_fyi/env_args-none.rs | 6 +++--- .../tests/wasm_tests/wasi_fyi/env_args-some.rs | 8 ++++---- .../tests/wasm_tests/wasi_fyi/env_vars-none.rs | 4 ++-- .../tests/wasm_tests/wasi_fyi/env_vars-some.rs | 6 +++--- .../tests/wasm_tests/wasi_fyi/fs_file_create.rs | 8 +++++--- .../wasm_tests/wasi_fyi/fs_seek_append_mode.rs | 2 +- .../wasm_tests/wasi_fyi/fs_write-and-seek.rs | 2 +- .../wasm_tests/wasi_fyi/io_stderr-beowulf.rs | 6 +++++- .../tests/wasm_tests/wasi_fyi/io_stderr-hello.rs | 6 +++++- .../wasm_tests/wasi_fyi/io_stdout-beowulf.rs | 6 +++++- .../tests/wasm_tests/wasi_fyi/io_stdout-hello.rs | 6 +++++- .../wasm_tests/wasi_fyi/ported_poll_oneoff.rs | 6 +----- .../tests/wasm_tests/wasi_fyi/process_exit-0.rs | 2 +- .../tests/wasm_tests/wasi_fyi/process_exit-1.rs | 2 +- .../wasm_tests/wasi_fyi/process_exit-120.rs | 2 +- lib/wasix/tests/wasm_tests/wasi_wast/fd_close.rs | 4 ++-- lib/wasix/tests/wasm_tests/wasi_wast/fd_read.rs | 4 ++-- .../tests/wasm_tests/wasi_wast/fd_rename_path.rs | 5 +++-- lib/wasix/tests/wasm_tests/wasi_wast/fseek.rs | 4 ++-- lib/wasix/tests/wasm_tests/wasi_wast/inode.rs | 16 +++++++++++----- .../tests/wasm_tests/wasi_wast/path_rename.rs | 1 - .../tests/wasm_tests/wasi_wast/poll_oneoff.rs | 16 +++++++++------- .../wasi_wast/wasi_sees_virtual_root.rs | 4 +--- 26 files changed, 83 insertions(+), 60 deletions(-) diff --git a/.github/ci-constants.env b/.github/ci-constants.env index b17217b0c25e..ea45ac9b9df3 100644 --- a/.github/ci-constants.env +++ b/.github/ci-constants.env @@ -2,4 +2,4 @@ # Pinned wasix-libc sysroot (wasix-org/wasix-libc release tag). WASIX_LIBC_SYSROOT_TAG=v2026-07-03.1 # Pinned WASIX rust toolchain (wasix-org/rust release tag). -WASIX_RUST_TOOLCHAIN_TAG=v2026-07-03.1+rust-1.90 +WASIX_RUST_TOOLCHAIN_TAG=v2026-07-07.3+rust-1.96 diff --git a/Makefile b/Makefile index 8f6a01c0b75c..9cd4f5d0011c 100644 --- a/Makefile +++ b/Makefile @@ -923,6 +923,9 @@ lint-package-crate: lint-formatting: cargo fmt --all -- --check cargo fmt --manifest-path fuzz/Cargo.toml -- --check + # The wasm_tests Rust fixtures are not part of any crate, so `cargo fmt` + # does not cover them. + cd lib/wasix/tests/wasm_tests && find . -path ./build -prune -o -type f -name '*.rs' -exec rustfmt --edition 2024 --check {} + lint: lint-yamlfmt lint-clang-format lint-formatting lint-packages lint-taplo diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-many.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-many.rs index 03604659f888..541842d0e028 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-many.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-many.rs @@ -4,10 +4,10 @@ use std::env; fn main() { - let args = env::args().collect::>(); - assert_eq!(args.len(), 4); - assert_eq!(args[0], "env_args-many.wasm"); - assert_eq!(args[1], "none"); - assert_eq!(args[2], "some"); - assert_eq!(args[3], "many"); + let args = env::args().collect::>(); + assert_eq!(args.len(), 4); + assert_eq!(args[0], "env_args-many.wasm"); + assert_eq!(args[1], "none"); + assert_eq!(args[2], "some"); + assert_eq!(args[3], "many"); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-none.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-none.rs index c21b9d628455..984436d90427 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-none.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-none.rs @@ -4,7 +4,7 @@ use std::env; fn main() { - let args = env::args().collect::>(); - assert_eq!(args.len(), 1); - assert_eq!(args[0], "env_args-none.wasm"); + let args = env::args().collect::>(); + assert_eq!(args.len(), 1); + assert_eq!(args[0], "env_args-none.wasm"); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-some.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-some.rs index 15b260ee1752..a798c3ef5bd1 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-some.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/env_args-some.rs @@ -4,8 +4,8 @@ use std::env; fn main() { - let args = env::args().collect::>(); - assert_eq!(args.len(), 2); - assert_eq!(args[0], "env_args-some.wasm"); - assert_eq!(args[1], "some"); + let args = env::args().collect::>(); + assert_eq!(args.len(), 2); + assert_eq!(args[0], "env_args-some.wasm"); + assert_eq!(args[1], "some"); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-none.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-none.rs index a4165bae05fa..ce6ed0835881 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-none.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-none.rs @@ -2,6 +2,6 @@ use std::env; fn main() { - let vars = env::vars().collect::>(); - assert_eq!(vars.len(), 0); + let vars = env::vars().collect::>(); + assert_eq!(vars.len(), 0); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-some.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-some.rs index c2780ba4fc9d..3e74edca8003 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-some.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/env_vars-some.rs @@ -3,7 +3,7 @@ use std::env; fn main() { - let vars = env::vars().collect::>(); - assert_eq!(vars.len(), 1); - assert_eq!(vars[0], ("SOME".to_string(), "some".to_string())); + let vars = env::vars().collect::>(); + assert_eq!(vars.len(), 1); + assert_eq!(vars[0], ("SOME".to_string(), "some".to_string())); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/fs_file_create.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/fs_file_create.rs index 798d2cfae5e0..e58358fddb2a 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/fs_file_create.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/fs_file_create.rs @@ -3,8 +3,10 @@ use std::fs; fn main() { assert!(fs::File::create("/fyi/fs_file_create.dir/new_file").is_ok()); - assert!(fs::metadata("/fyi/fs_file_create.dir/new_file") - .unwrap() - .is_file()); + assert!( + fs::metadata("/fyi/fs_file_create.dir/new_file") + .unwrap() + .is_file() + ); assert!(fs::remove_file("/fyi/fs_file_create.dir/new_file").is_ok()); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/fs_seek_append_mode.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/fs_seek_append_mode.rs index 2b06683f4f1b..bd0f95180d04 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/fs_seek_append_mode.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/fs_seek_append_mode.rs @@ -1,7 +1,7 @@ //#AbstractConfigFile: wasi-fyi.config use std::fs::OpenOptions; -use std::io::prelude::*; use std::io::SeekFrom; +use std::io::prelude::*; fn main() { let mut file = OpenOptions::new() diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/fs_write-and-seek.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/fs_write-and-seek.rs index 1341256f2947..7153ce485063 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/fs_write-and-seek.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/fs_write-and-seek.rs @@ -1,5 +1,5 @@ //#AbstractConfigFile: wasi-fyi.config -use std::fs::{metadata, OpenOptions}; +use std::fs::{OpenOptions, metadata}; use std::io::{Seek, SeekFrom, Write}; fn main() { diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-beowulf.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-beowulf.rs index e77d99d33b4e..3accb8ea60bf 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-beowulf.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-beowulf.rs @@ -4,5 +4,9 @@ use std::io; use std::io::Write; fn main() { - assert!(io::stderr().write_all(include_bytes!("io_stderr-beowulf.stderr")).is_ok()); + assert!( + io::stderr() + .write_all(include_bytes!("io_stderr-beowulf.stderr")) + .is_ok() + ); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-hello.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-hello.rs index d0ca0d6597a2..381363b4b210 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-hello.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stderr-hello.rs @@ -4,5 +4,9 @@ use std::io; use std::io::Write; fn main() { - assert!(io::stderr().write_all(include_bytes!("io_stderr-hello.stderr")).is_ok()); + assert!( + io::stderr() + .write_all(include_bytes!("io_stderr-hello.stderr")) + .is_ok() + ); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-beowulf.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-beowulf.rs index d5f504c665cd..e9fff7682d30 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-beowulf.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-beowulf.rs @@ -4,5 +4,9 @@ use std::io; use std::io::Write; fn main() { - assert!(io::stdout().write_all(include_bytes!("io_stdout-beowulf.stdout")).is_ok()); + assert!( + io::stdout() + .write_all(include_bytes!("io_stdout-beowulf.stdout")) + .is_ok() + ); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-hello.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-hello.rs index eea186f1b406..7334f42fc937 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-hello.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/io_stdout-hello.rs @@ -4,5 +4,9 @@ use std::io; use std::io::Write; fn main() { - assert!(io::stdout().write_all(include_bytes!("io_stdout-hello.stdout")).is_ok()); + assert!( + io::stdout() + .write_all(include_bytes!("io_stdout-hello.stdout")) + .is_ok() + ); } diff --git a/lib/wasix/tests/wasm_tests/wasi_fyi/ported_poll_oneoff.rs b/lib/wasix/tests/wasm_tests/wasi_fyi/ported_poll_oneoff.rs index 5547235a1cc5..90c61100f4e4 100644 --- a/lib/wasix/tests/wasm_tests/wasi_fyi/ported_poll_oneoff.rs +++ b/lib/wasix/tests/wasm_tests/wasi_fyi/ported_poll_oneoff.rs @@ -132,11 +132,7 @@ fn poll(fds: &[u32], read: &[bool], write: &[bool]) -> Result u32 { fn main() { #[cfg(not(target_os = "wasi"))] let mut base = PathBuf::from("test_fs/hamlet"); -#[cfg(target_os = "wasi")] -let mut base = PathBuf::from("hamlet"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("hamlet"); base.push("act3/scene4.txt"); let mut file = fs::File::open(&base).expect("Could not open file"); diff --git a/lib/wasix/tests/wasm_tests/wasi_wast/fd_rename_path.rs b/lib/wasix/tests/wasm_tests/wasi_wast/fd_rename_path.rs index 6995476dbf03..182b5e50b53a 100644 --- a/lib/wasix/tests/wasm_tests/wasi_wast/fd_rename_path.rs +++ b/lib/wasix/tests/wasm_tests/wasi_wast/fd_rename_path.rs @@ -6,14 +6,15 @@ use std::path::PathBuf; fn main() { let mut idx = 0; - fs::create_dir_all(PathBuf::from("test_fs/wasitests")).expect("cannot create the parent directory"); + fs::create_dir_all(PathBuf::from("test_fs/wasitests")) + .expect("cannot create the parent directory"); let old_path = loop { let old_path = PathBuf::from(format!("test_fs/wasitests/dirtorename-{}", idx)); if fs::create_dir(old_path.clone()).ok().is_some() { break old_path; } - idx+=1; + idx += 1; if idx > 10 { panic!("too many try at creating the folder"); } diff --git a/lib/wasix/tests/wasm_tests/wasi_wast/fseek.rs b/lib/wasix/tests/wasm_tests/wasi_wast/fseek.rs index 54709573d48d..86c85942e91e 100644 --- a/lib/wasix/tests/wasm_tests/wasi_wast/fseek.rs +++ b/lib/wasix/tests/wasm_tests/wasi_wast/fseek.rs @@ -9,8 +9,8 @@ use std::path::PathBuf; fn main() { #[cfg(not(target_os = "wasi"))] let mut base = PathBuf::from("test_fs/hamlet"); -#[cfg(target_os = "wasi")] -let mut base = PathBuf::from("hamlet"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("hamlet"); base.push("act1/scene3.txt"); diff --git a/lib/wasix/tests/wasm_tests/wasi_wast/inode.rs b/lib/wasix/tests/wasm_tests/wasi_wast/inode.rs index 74fa425512fc..fc9aa82129e1 100644 --- a/lib/wasix/tests/wasm_tests/wasi_wast/inode.rs +++ b/lib/wasix/tests/wasm_tests/wasi_wast/inode.rs @@ -14,16 +14,22 @@ use std::os::wasi::fs::MetadataExt; fn main() { #[cfg(target = "wasi")] { - let meta1 = fs::metadata("test_fs/hamlet/act1/scene1.txt").expect("could not find src file"); - let meta2 = fs::metadata("test_fs/hamlet/act1/scene2.txt").expect("could not find src file"); + let meta1 = + fs::metadata("test_fs/hamlet/act1/scene1.txt").expect("could not find src file"); + let meta2 = + fs::metadata("test_fs/hamlet/act1/scene2.txt").expect("could not find src file"); if meta1.dev() == meta2.dev() && meta1.ino() == meta2.ino() { println!("Warning, different files from same folder have same dev/inod"); } - let meta3 = fs::metadata("test_fs/hamlet/act2/scene1.txt").expect("could not find src file"); + let meta3 = + fs::metadata("test_fs/hamlet/act2/scene1.txt").expect("could not find src file"); if meta1.dev() == meta3.dev() && meta1.ino() == meta3.ino() { - println!("Warning, different files from different folder with same name have same dev/inod"); + println!( + "Warning, different files from different folder with same name have same dev/inod" + ); } - let meta4 = fs::metadata("test_fs/hamlet/act1/../act1/scene1.txt").expect("could not find src file"); + let meta4 = fs::metadata("test_fs/hamlet/act1/../act1/scene1.txt") + .expect("could not find src file"); if meta1.dev() != meta4.dev() || meta1.ino() != meta4.ino() { println!("Warning, same files have different dev/inod"); } diff --git a/lib/wasix/tests/wasm_tests/wasi_wast/path_rename.rs b/lib/wasix/tests/wasm_tests/wasi_wast/path_rename.rs index fc4f1b73add8..7f35ab0e7103 100644 --- a/lib/wasix/tests/wasm_tests/wasi_wast/path_rename.rs +++ b/lib/wasix/tests/wasm_tests/wasi_wast/path_rename.rs @@ -123,7 +123,6 @@ fn run_with_toplevel_dir_overwrite() { println!("The original file does not still exist!"); } - if !file_to_rename_to.exists() { println!("The moved file does not exist!"); return; diff --git a/lib/wasix/tests/wasm_tests/wasi_wast/poll_oneoff.rs b/lib/wasix/tests/wasm_tests/wasi_wast/poll_oneoff.rs index 58106f5587fc..cbdee0a75d75 100644 --- a/lib/wasix/tests/wasm_tests/wasi_wast/poll_oneoff.rs +++ b/lib/wasix/tests/wasm_tests/wasi_wast/poll_oneoff.rs @@ -131,11 +131,7 @@ fn poll(fds: &[u32], read: &[bool], write: &[bool]) -> Result>(); roots.sort(); From 509259e042fe4c7d6b885eea61f2f875cb033d15 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Mon, 20 Jul 2026 07:34:16 +0000 Subject: [PATCH 15/19] fix(ci): keep wasm_tests .stdin fixtures LF on Windows checkouts The io_stdin-* tests compare bytes read from stdin at runtime against a copy of the same fixture baked in via include_bytes! at build time. The wasm fixtures are cross-built on Linux (LF), while the Windows runner checks out *.stdin with core.autocrlf=true, feeding CRLF into stdin and failing the assertion. Mark *.stdin as binary so the fixtures are checked out byte-identically on every platform, matching the existing scene*.txt rule. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 6486387fd29b..4a30688065f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ CHANGELOG.md merge=union *.wast linguist-vendored *.wat linguist-vendored scene*.txt -text +*.stdin -text From a2f3fa6e9ea411a894ca1d8c0cbeb444f8616e6d Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 5 Aug 2026 13:54:17 +0000 Subject: [PATCH 16/19] test(wasix): run Rust wasm_tests fixtures only where a toolchain exists Revert the prebuild-and-download pipeline for the Rust wasm_tests fixtures (WASM_TESTS_BUILD_ONLY_DIR / WASM_TESTS_PREBUILT_DIR, the build_wasm_tests_fixtures CI job and the build-wasm-tests-fixtures make target). Fixtures build on the test host again, via cargo-wasix for the wasix toolchain and rustc for the wasip1 variants. Instead of shipping artifacts to hosts that cannot build them, only collect Rust fixtures where wasix-org/rust publishes a toolchain: gnu Linux on x86_64 and aarch64, plus macOS on aarch64. musl has no toolchain at all. Windows is left out deliberately even though a toolchain exists for it, since wasixcc does not cover Windows either and the rest of the suite already skips that host. CI installs cargo-wasix and the wasm32-wasip1 targets on exactly those three builds, keyed off a new `wasix_rust_toolchain` matrix flag. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/test.yaml | 89 ++++-------------------- Makefile | 9 --- lib/wasix/tests/wasm_tests/README.md | 17 ++--- lib/wasix/tests/wasm_tests/mod.rs | 100 ++++++--------------------- 4 files changed, 40 insertions(+), 175 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5bd6f3d687c3..a393dfb5406b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -575,72 +575,10 @@ jobs: path: build-capi.tar.gz if-no-files-found: ignore retention-days: 2 - build_wasm_tests_fixtures: - # The WASIX Rust toolchain is not published for every platform we test on, - # so the Rust wasm_tests fixtures are prebuilt here once (wasm is - # host-independent) and shared with the test jobs as an artifact. - name: Build Rust wasm_tests fixtures - runs-on: ubuntu-22.04 - needs: setup - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - name: Load CI constants - shell: bash - run: grep -v '^#' .github/ci-constants.env >> $GITHUB_ENV - - uses: ./.github/actions/load_toolchain - id: load_toolchain - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ steps.load_toolchain.outputs.rust_toolchain }} - - name: Install Nextest - uses: taiki-e/install-action@nextest - - name: Install wasixcc - uses: wasix-org/wasixcc@v0.4.3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - version: v0.4.3 - sysroot-tag: ${{ env.WASIX_LIBC_SYSROOT_TAG }} - - name: Install older wasix-libc for compatibility testing - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: WASIXCC_SYSROOT_PREFIX=~/.wasixcc/sysroot-v2026-05-12.1 wasixccenv download-sysroot v2026-05-12.1 - - name: Install cargo-wasix - uses: wasix-org/cargo-wasix@main - with: - toolchain-version: ${{ env.WASIX_RUST_TOOLCHAIN_TAG }} - - name: Cache - # TODO: v3 is unable to Restore the cache for some reason - uses: whywaita/actions-cache-s3@v2 - with: - path: | - ~/.cargo/* - ./target/* - key: cache-v${{ env.S3_CACHE_VERSION }}-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-wasmer-wasm-tests-fixtures - aws-s3-bucket: wasmer-rust-artifacts-cache - aws-access-key-id: ${{ secrets.CLOUDFLARE_ARTIFACTS_CACHE_ACCESS_TOKEN }} - aws-secret-access-key: ${{ secrets.CLOUDFLARE_ARTIFACTS_CACHE_ACCESS_KEY }} - aws-region: auto - aws-endpoint: https://1541b1e8a3fc6ad155ce67ef38899700.r2.cloudflarestorage.com - aws-s3-bucket-endpoint: false - aws-s3-force-path-style: true - - name: Build fixtures - shell: bash - run: make build-wasm-tests-fixtures - env: - WASM_TESTS_BUILD_ONLY_DIR: ${{ github.workspace }}/wasm-tests-prebuilt - - name: Upload fixtures - uses: actions/upload-artifact@v4 - with: - name: wasm-tests-prebuilt - path: wasm-tests-prebuilt - if-no-files-found: error test: name: ${{ matrix.stage.description }} - ${{ matrix.metadata.build }} runs-on: ${{ matrix.metadata.os }} - needs: [setup, build_wasm_tests_fixtures] + needs: setup strategy: fail-fast: false matrix: @@ -662,20 +600,26 @@ jobs: - description: "CLI integ. tests" make: test-integration-cli-ci metadata: + # `wasix_rust_toolchain` marks the hosts wasix-org/rust publishes a + # toolchain for, i.e. the ones that collect the Rust wasm_tests + # fixtures (see lib/wasix/tests/wasm_tests/README.md). - build: linux-x64 os: ubuntu-22.04 target: x86_64-unknown-linux-gnu exe: "" + wasix_rust_toolchain: true llvm_url: "https://github.com/wasmerio/llvm-custom-builds/releases/download/22.x/llvm-linux-amd64.tar.xz" - build: linux-arm64 os: ubuntu-22.04-arm target: aarch64-unknown-linux-gnu exe: "" + wasix_rust_toolchain: true llvm_url: "https://github.com/wasmerio/llvm-custom-builds/releases/download/22.x/llvm-linux-aarch64.tar.xz" - build: macos-arm os: depot-macos-14 target: aarch64-apple-darwin exe: "" + wasix_rust_toolchain: true llvm_url: "https://github.com/wasmerio/llvm-custom-builds/releases/download/22.x/llvm-darwin-aarch64.tar.xz" - build: windows-x64 os: windows-2022 @@ -759,9 +703,9 @@ jobs: toolchain: ${{ steps.load_toolchain.outputs.rust_toolchain }} target: ${{ matrix.metadata.target }} # For the wasip1-toolchain (Singlepass) variants of the Rust wasm_tests - # fixtures, which build locally on every platform. + # fixtures. - name: Install Rust WASI targets - if: matrix.stage.make == 'test-all' + if: matrix.stage.make == 'test-all' && matrix.metadata.wasix_rust_toolchain shell: bash run: | rustup target add wasm32-wasip1 @@ -780,16 +724,12 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: WASIXCC_SYSROOT_PREFIX=~/.wasixcc/sysroot-v2026-05-12.1 wasixccenv download-sysroot v2026-05-12.1 if: matrix.metadata.build != 'windows-x64' - - name: Download prebuilt Rust wasm_tests fixtures - if: matrix.stage.make == 'test-all' - uses: actions/download-artifact@v4 + # For the wasix-toolchain variants of the Rust wasm_tests fixtures. + - name: Install cargo-wasix + if: matrix.stage.make == 'test-all' && matrix.metadata.wasix_rust_toolchain + uses: wasix-org/cargo-wasix@main with: - name: wasm-tests-prebuilt - path: wasm-tests-prebuilt - - name: Use prebuilt Rust wasm_tests fixtures - if: matrix.stage.make == 'test-all' - shell: bash - run: echo "WASM_TESTS_PREBUILT_DIR=$GITHUB_WORKSPACE/wasm-tests-prebuilt" >> $GITHUB_ENV + toolchain-version: ${{ env.WASIX_RUST_TOOLCHAIN_TAG }} - name: Install LLVM shell: bash if: matrix.metadata.llvm_url @@ -847,7 +787,6 @@ jobs: - test_build_docs_rs - build_linux_riscv64 - build - - build_wasm_tests_fixtures - test if: ${{ always() }} steps: diff --git a/Makefile b/Makefile index 1b4385971ff3..5ef89ada2160 100644 --- a/Makefile +++ b/Makefile @@ -582,15 +582,6 @@ build-capi-headless-ios: # intentionally not using nextest as it runs tests in separate processes test-wast: $(CARGO_BINARY) test $(CARGO_TARGET_FLAG) --release $(compiler_features) --locked -# Build the Rust wasm_tests fixtures without running them, exporting the wasm -# artifacts to WASM_TESTS_BUILD_ONLY_DIR (defaults to a directory under -# target/). Requires cargo-wasix and the WASIX Rust toolchain; platforms -# without one can then run test-all with WASM_TESTS_PREBUILT_DIR pointing at -# the exported directory. -build-wasm-tests-fixtures: WASM_TESTS_BUILD_ONLY_DIR ?= $(CURDIR)/target/wasm-tests-prebuilt -build-wasm-tests-fixtures: - WASM_TESTS_BUILD_ONLY_DIR="$(WASM_TESTS_BUILD_ONLY_DIR)" $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) -p wasmer-wasix --test wasm_tests --locked - @echo "Rust wasm_tests fixtures exported to $(WASM_TESTS_BUILD_ONLY_DIR)" test-all: $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --workspace --release $(exclude_tests) --exclude wasmer-c-api-test-runner --exclude wasmer-capi-examples-runner $(test_compiler_features) --features $(test_all_features) --locked && \ $(CARGO_BINARY) nextest run $(CARGO_TARGET_FLAG) --manifest-path lib/virtual-net/Cargo.toml --release $(virtual_net_test_features) --locked && \ diff --git a/lib/wasix/tests/wasm_tests/README.md b/lib/wasix/tests/wasm_tests/README.md index 2cac88767087..0ae2d26788e1 100644 --- a/lib/wasix/tests/wasm_tests/README.md +++ b/lib/wasix/tests/wasm_tests/README.md @@ -75,18 +75,11 @@ toolchain emits exception-handling opcodes Singlepass does not support. The wasip1 variants need `rustup target add wasm32-wasip1` (plus the same target on nightly). -The WASIX Rust toolchain is not published for every platform, so the -wasix-toolchain fixtures can alternatively be prebuilt on a supported host and -reused: - -- `WASM_TESTS_BUILD_ONLY_DIR=` builds the Rust fixtures into `` - without running any tests (CI runs this through - `make build-wasm-tests-fixtures` on linux-x64). The artifacts are - engine-independent and are built through the Cranelift trials, so this must - run on a host that collects them (i.e. not macOS). -- `WASM_TESTS_PREBUILT_DIR=` makes the suite consume those prebuilt - artifacts instead of invoking `cargo wasix build`, removing the need for - `cargo-wasix` and the WASIX Rust toolchain on the test host. +The WASIX Rust toolchain is not published for every platform, so Rust fixtures +(both toolchain variants) are only collected on hosts that have one: gnu Linux +on x86_64 and aarch64, and macOS on aarch64. They are skipped everywhere else — +notably on musl, which has no toolchain, and on Windows, which `wasixcc` does +not cover either, so no fixture kind runs there at all. On macOS, this suite collects and runs the LLVM variants only because Cranelift exception-handling support is still incomplete there: diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index cb6e0e7e153f..9b888f749832 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -105,6 +105,22 @@ mod runner; const TESTED_LIBC_VERSIONS: &[Option<&str>] = &[None, Some("v2026-05-12.1")]; +/// Whether Rust fixtures are collected on this host. +/// +/// Building them needs the WASIX Rust toolchain, which wasix-org/rust only +/// publishes for a few hosts; musl hosts in particular have no toolchain at +/// all. Windows is excluded on purpose even though a toolchain exists for it: +/// wasixcc does not cover Windows either, so the suite as a whole does not run +/// there. +const COLLECT_RUST_FIXTURES: bool = cfg!(any( + all( + target_os = "linux", + target_env = "gnu", + any(target_arch = "x86_64", target_arch = "aarch64") + ), + all(target_os = "macos", target_arch = "aarch64"), +)); + fn should_emit_colour() -> bool { std::io::stdout().is_terminal() || std::env::var("CARGO_TERM_COLOR").as_deref() == Ok("always") @@ -295,13 +311,6 @@ impl Config { } fn full_test_name(&self) -> String { - format!("{}/{}", self.engine_independent_name(), self.engine) - } - - /// Identifies this configuration's build inputs. Unlike `full_test_name`, - /// this excludes the engine: Rust fixture builds don't depend on it, so - /// prebuilt artifacts are shared across engines. - fn engine_independent_name(&self) -> String { let mut parts = vec!["wasm".to_owned(), self.test_name.clone()]; if !self.source.is_default() { parts.push(self.source.config_name()); @@ -316,13 +325,10 @@ impl Config { if self.toolchain != Toolchain::Wasix { parts.push(self.toolchain.to_string()); } + parts.push(self.engine.to_string()); parts.join("/") } - fn prebuilt_wasm_path(&self, root: &Path) -> PathBuf { - root.join(self.engine_independent_name()).join("main.wasm") - } - fn set_sysroot(&mut self, sysroot_version: &'static str) -> Result<()> { let sysroot_path = dirs::home_dir() .ok_or_else(|| anyhow!("cannot expand home dir"))? @@ -827,25 +833,6 @@ fn run_build_script(config: &Config) -> anyhow::Result { ) })?; - // WASIX-toolchain Rust fixtures can be prebuilt elsewhere (see - // `build_fixture_only`), which lets platforms without a WASIX Rust - // toolchain run the tests. The wasip1 toolchain is available everywhere, - // so those variants always build locally. - if config.source.is_rust() && config.toolchain == Toolchain::Wasix { - if let Some(prebuilt_root) = env_var_path("WASM_TESTS_PREBUILT_DIR") { - let prebuilt = config.prebuilt_wasm_path(&prebuilt_root); - let main_path = build_test_path.join("main"); - fs::copy(&prebuilt, &main_path).with_context(|| { - format!( - "failed to copy prebuilt Rust fixture {} — \ - ensure the fixture prebuild ran with a matching configuration", - prebuilt.display() - ) - })?; - return Ok(main_path); - } - } - let mut cmd = match &config.source { PrimarySource::BashScript(filename) => { let mut cmd = Command::new("bash"); @@ -1169,54 +1156,10 @@ fn configure_mapped_directories( Ok(()) } -fn env_var_path(name: &str) -> Option { - std::env::var_os(name) - .filter(|value| !value.is_empty()) - .map(PathBuf::from) -} - -/// Build the Rust fixture for this configuration and export the resulting -/// wasm to `output_root` without running the test. CI uses this to prebuild -/// fixtures on a host that has the WASIX Rust toolchain; hosts without one -/// consume the artifacts via `WASM_TESTS_PREBUILT_DIR`. -fn build_fixture_only(config: &Config, output_root: &Path) -> Result { - if !config.source.is_rust() { - return Ok(libtest_mimic::Completion::ignored_with( - "build-only: not a Rust fixture", - )); - } - if config.toolchain != Toolchain::Wasix { - return Ok(libtest_mimic::Completion::ignored_with( - "build-only: wasip1 fixtures build on every host", - )); - } - // Artifacts are engine-independent, so build each configuration once - // through its Cranelift trial. This requires the build-only run to happen - // on a host that collects Cranelift trials (i.e. not macOS). - if config.engine != Engine::Cranelift { - return Ok(libtest_mimic::Completion::ignored_with( - "build-only: built by the cranelift variant", - )); - } - if let Some(reason) = minimal_libc_skip_reason(config)? { - return Ok(libtest_mimic::Completion::ignored_with(reason)); - } - - let wasm = run_build_script(config)?; - let dest = config.prebuilt_wasm_path(output_root); - create_dir_all(dest.parent().expect("prebuilt path must have a parent"))?; - fs::copy(&wasm, &dest) - .with_context(|| format!("failed to export {} to {}", wasm.display(), dest.display()))?; - Ok(libtest_mimic::Completion::Completed) -} - fn run_integration_test(config: Config) -> Result { if let Some(reason) = &config.ignored { return Ok(libtest_mimic::Completion::ignored_with(reason.clone())); } - if let Some(output_root) = env_var_path("WASM_TESTS_BUILD_ONLY_DIR") { - return build_fixture_only(&config, &output_root); - } if !cfg!(unix) && config.unix_only { return Ok(libtest_mimic::Completion::ignored_with("Unix only")); } @@ -1526,6 +1469,10 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { let default_file_systems = vec![FileSystemKind::Host]; for primary_source in primary_sources { + if primary_source.is_rust() && !COLLECT_RUST_FIXTURES { + continue; + } + // Single-file Rust fixtures historically built with wasm32-wasip1; // keep that variant alive since it is the only way to run them on // Singlepass (the WASIX toolchain emits EH opcodes it lacks). @@ -1608,11 +1555,6 @@ fn run_dynamic_runtime_hook_smoke( "WASIXCC toolchain does not cover Windows yet", )); } - if env_var_path("WASM_TESTS_BUILD_ONLY_DIR").is_some() { - return Ok(libtest_mimic::Completion::ignored_with( - "build-only: not a Rust fixture", - )); - } let source_dir = tests_dir.join("dynamic_library/simple-dynamic-lib"); let config = Config::new( From 24e7cc9615560ec511c7b0c80688fc103d43b3b6 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 5 Aug 2026 14:26:24 +0000 Subject: [PATCH 17/19] chore: bump pinned WASIX rust toolchain to v2026-08-05.1+rust-1.97 The release adds an aarch64-unknown-linux-gnu toolchain, which the linux-arm64 test job needs now that Rust wasm_tests fixtures build on the test host again. Co-Authored-By: Claude Opus 5 (1M context) --- .github/ci-constants.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci-constants.env b/.github/ci-constants.env index 94f2adb2313b..21e6c5922bf5 100644 --- a/.github/ci-constants.env +++ b/.github/ci-constants.env @@ -2,4 +2,4 @@ # Pinned wasix-libc sysroot (wasix-org/wasix-libc release tag). WASIX_LIBC_SYSROOT_TAG=v2026-07-30.1 # Pinned WASIX rust toolchain (wasix-org/rust release tag). -WASIX_RUST_TOOLCHAIN_TAG=v2026-07-07.3+rust-1.96 +WASIX_RUST_TOOLCHAIN_TAG=v2026-08-05.1+rust-1.97 From 8dd23327f6ea213d0f732c2b9dd611284d73fb6e Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 7 Aug 2026 10:53:05 +0000 Subject: [PATCH 18/19] chore: bump pinned WASIX rust toolchain to v2026-08-06.1+rust-1.97 The lint job requires the pin to be at least the latest wasix-org/rust release, and v2026-08-06.1 landed after the previous bump. Same rust 1.97 base, and it still publishes all four host toolchains including aarch64-unknown-linux-gnu. Co-Authored-By: Claude Opus 5 (1M context) --- .github/ci-constants.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ci-constants.env b/.github/ci-constants.env index 21e6c5922bf5..6db4329db72a 100644 --- a/.github/ci-constants.env +++ b/.github/ci-constants.env @@ -2,4 +2,4 @@ # Pinned wasix-libc sysroot (wasix-org/wasix-libc release tag). WASIX_LIBC_SYSROOT_TAG=v2026-07-30.1 # Pinned WASIX rust toolchain (wasix-org/rust release tag). -WASIX_RUST_TOOLCHAIN_TAG=v2026-08-05.1+rust-1.97 +WASIX_RUST_TOOLCHAIN_TAG=v2026-08-06.1+rust-1.97 From d31f4ca58a25324ac087c1e7a5b06694de9e2be6 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 7 Aug 2026 12:46:47 +0000 Subject: [PATCH 19/19] test(wasix): address review on the Rust toolchain axis Review feedback (#6698): - Run the wasip1 fixture variants on every engine again, not just Singlepass. Only the WASIX toolchain's exception-handling output is restricted, so that constraint now lives in engine_runs_wasix_output. - Gate on the WASIX Rust toolchain per toolchain rather than per fixture: wasip1 builds with a plain rustup target and works everywhere, so single-file Rust fixtures keep running on musl and Windows. Only the cargo-wasix variants are skipped, and the constant is renamed WASIX_RUST_TOOLCHAIN_AVAILABLE to say so. - Stop gating the wasm32-wasip1 target install in CI on the platforms that have a WASIX Rust toolchain; the target is available everywhere. - Rename Toolchain to RustToolchain and the config fields to rust_toolchain/rust_toolchains. - Move the toolchain iteration to the innermost loop, where it reads as the per-fixture axis it is. - Shorten the platform-support comment. Also skip the wasix-libc compatibility variants for wasip1: those builds go through rustc and never read the sysroot, so they only duplicated the default variant. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/test.yaml | 6 +- lib/wasix/tests/wasm_tests/README.md | 25 ++-- lib/wasix/tests/wasm_tests/mod.rs | 167 +++++++++++++++------------ 3 files changed, 110 insertions(+), 88 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0a3c83c8f24d..f8693ea6174f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -714,10 +714,10 @@ jobs: with: toolchain: ${{ steps.load_toolchain.outputs.rust_toolchain }} target: ${{ matrix.metadata.target }} - # For the wasip1-toolchain (Singlepass) variants of the Rust wasm_tests - # fixtures. + # For the wasip1-toolchain variants of the Rust wasm_tests fixtures, which + # build on every platform we support. - name: Install Rust WASI targets - if: matrix.stage.make == 'test-all' && matrix.metadata.wasix_rust_toolchain + if: matrix.stage.make == 'test-all' shell: bash run: | rustup target add wasm32-wasip1 diff --git a/lib/wasix/tests/wasm_tests/README.md b/lib/wasix/tests/wasm_tests/README.md index 0ae2d26788e1..4f97506131e7 100644 --- a/lib/wasix/tests/wasm_tests/README.md +++ b/lib/wasix/tests/wasm_tests/README.md @@ -67,19 +67,20 @@ Cargo and nextest filtering both work. Before running the suite, make sure `wasixcc` is installed and available in your shell environment. Rust fixtures also require `cargo-wasix` on `PATH` (`cargo install cargo-wasix`). -Single-file Rust fixtures build with two toolchains by default (see the +Single-file Rust fixtures build with two Rust toolchains by default (see the `Toolchains` directive): `wasix` (cargo-wasix), which runs on every engine -except Singlepass, and `wasip1` (`rustc --target wasm32-wasip1`, nightly when -the source uses `#![feature(...)]`), which runs on Singlepass only — the WASIX -toolchain emits exception-handling opcodes Singlepass does not support. The -wasip1 variants need `rustup target add wasm32-wasip1` (plus the same target on -nightly). - -The WASIX Rust toolchain is not published for every platform, so Rust fixtures -(both toolchain variants) are only collected on hosts that have one: gnu Linux -on x86_64 and aarch64, and macOS on aarch64. They are skipped everywhere else — -notably on musl, which has no toolchain, and on Windows, which `wasixcc` does -not cover either, so no fixture kind runs there at all. +except Singlepass — the WASIX toolchain emits exception-handling opcodes +Singlepass does not support — and `wasip1` (`rustc --target wasm32-wasip1`, +nightly when the source uses `#![feature(...)]`), which runs on every engine. +The wasip1 variants need `rustup target add wasm32-wasip1` (plus the same +target on nightly). + +We do not publish the WASIX Rust toolchain for every platform yet, so the +`wasix` variants are only collected on hosts that have one: gnu Linux on x86_64 +and aarch64, and macOS on aarch64. They are skipped on musl, which has no +toolchain, and on Windows, which `wasixcc` does not cover either. The `wasip1` +variants build everywhere, so single-file Rust fixtures keep running on those +hosts. On macOS, this suite collects and runs the LLVM variants only because Cranelift exception-handling support is still incomplete there: diff --git a/lib/wasix/tests/wasm_tests/mod.rs b/lib/wasix/tests/wasm_tests/mod.rs index ddc0409c1368..8c1b28d2bd4a 100644 --- a/lib/wasix/tests/wasm_tests/mod.rs +++ b/lib/wasix/tests/wasm_tests/mod.rs @@ -45,9 +45,9 @@ //! `SkipEngine:{engine}:{reason}` marks the configuration as ignored for //! a given engine (LLVM, Cranelift, V8, Singlepass). //! -//! `Toolchains:{list}` selects which toolchains build a single-file Rust +//! `Toolchains:{list}` selects which Rust toolchains build a single-file Rust //! fixture: `wasix` (cargo-wasix; runs on every engine except Singlepass) and -//! `wasip1` (rustc with the wasm32-wasip1 target; runs on Singlepass only). +//! `wasip1` (rustc with the wasm32-wasip1 target; runs on every engine). //! Defaults to `wasix,wasip1` for single-file Rust fixtures. //! //! `UnixOnly:{bool}` ignores the configuration on non-Unix hosts when true. @@ -105,14 +105,12 @@ mod runner; const TESTED_LIBC_VERSIONS: &[Option<&str>] = &[None, Some("v2026-05-12.1")]; -/// Whether Rust fixtures are collected on this host. -/// -/// Building them needs the WASIX Rust toolchain, which wasix-org/rust only -/// publishes for a few hosts; musl hosts in particular have no toolchain at -/// all. Windows is excluded on purpose even though a toolchain exists for it: -/// wasixcc does not cover Windows either, so the suite as a whole does not run -/// there. -const COLLECT_RUST_FIXTURES: bool = cfg!(any( +/// Whether the WASIX Rust toolchain is published for this host: we don't +/// provide it for every platform yet. Only the `cargo wasix` variants of the +/// Rust fixtures are gated on it; their `wasm32-wasip1` counterparts build +/// everywhere. Windows is left out on purpose, since `wasixcc` does not cover +/// it either. +const WASIX_RUST_TOOLCHAIN_AVAILABLE: bool = cfg!(any( all( target_os = "linux", target_env = "gnu", @@ -172,33 +170,38 @@ pub enum Engine { V8, } -/// Which toolchain builds a Rust fixture. The WASIX toolchain emits +/// Which Rust toolchain builds a Rust fixture. The WASIX toolchain emits /// exception-handling opcodes, so its output cannot run on Singlepass; the /// plain `wasm32-wasip1` rustup target can, and is available on every host /// platform. #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumString)] #[strum(ascii_case_insensitive, serialize_all = "lowercase")] -enum Toolchain { +enum RustToolchain { Wasix, Wasip1, } -impl Toolchain { - /// Engines that can run this toolchain's output. Singlepass lacks - /// exception-handling support, so it only runs the wasip1-built variant; - /// conversely the other engines only run the WASIX-built variant since - /// running both toolchains' output there would duplicate coverage. +/// Whether `engine` can run wasm produced by a WASIX toolchain (cargo-wasix +/// for Rust fixtures, wasixcc for the rest). Singlepass lacks exception +/// handling, which that output relies on. +fn engine_runs_wasix_output(engine: Engine) -> bool { + #[cfg(feature = "singlepass")] + let is_singlepass = engine == Engine::Singlepass; + #[cfg(not(feature = "singlepass"))] + let is_singlepass = { + let _ = engine; + false + }; + !is_singlepass +} + +impl RustToolchain { + /// Engines that can run this toolchain's output. wasip1-built wasm runs on + /// every engine; WASIX-built wasm cannot run on Singlepass. fn supports_engine(self, engine: Engine) -> bool { - #[cfg(feature = "singlepass")] - let is_singlepass = engine == Engine::Singlepass; - #[cfg(not(feature = "singlepass"))] - let is_singlepass = { - let _ = engine; - false - }; match self { - Self::Wasix => !is_singlepass, - Self::Wasip1 => is_singlepass, + Self::Wasix => engine_runs_wasix_output(engine), + Self::Wasip1 => true, } } } @@ -239,8 +242,8 @@ struct Config { test_name: String, config_name: String, engine: Engine, - toolchain: Toolchain, - toolchains: Option>, + rust_toolchain: RustToolchain, + rust_toolchains: Option>, selected_file_system: FileSystemKind, file_systems: Option>, is_abstract: bool, @@ -284,8 +287,8 @@ impl Config { engine: Engine::V8, #[cfg(not(target_os = "windows"))] engine: Engine::Cranelift, - toolchain: Toolchain::Wasix, - toolchains: None, + rust_toolchain: RustToolchain::Wasix, + rust_toolchains: None, file_systems: None, selected_file_system: FileSystemKind::Host, is_abstract: false, @@ -327,8 +330,8 @@ impl Config { if let Some(sysroot_version) = &self.sysroot_version { parts.push(sysroot_version.to_string()); } - if self.toolchain != Toolchain::Wasix { - parts.push(self.toolchain.to_string()); + if self.rust_toolchain != RustToolchain::Wasix { + parts.push(self.rust_toolchain.to_string()); } parts.push(self.engine.to_string()); parts.join("/") @@ -602,26 +605,26 @@ fn process_directive( config.default_mapped_directories = arg.parse::()?; } "Toolchains" => { - let toolchains = arg + let rust_toolchains = arg .split(',') .map(|toolchain| { toolchain .trim() - .parse::() + .parse::() .map_err(|_| anyhow!("unsupported toolchain: '{toolchain}'")) }) .collect::>>()?; ensure!( - !toolchains.is_empty(), + !rust_toolchains.is_empty(), "at least one toolchain must be selected" ); - if toolchains.contains(&Toolchain::Wasip1) { + if rust_toolchains.contains(&RustToolchain::Wasip1) { ensure!( matches!(config.source, PrimarySource::RustSourceFile(_)), "the wasip1 toolchain is only supported for single-file Rust fixtures" ); } - config.toolchains = Some(toolchains); + config.rust_toolchains = Some(rust_toolchains); } "FileSystems" => { config.file_systems = Some(if arg == "all" { @@ -879,12 +882,12 @@ fn run_build_script(config: &Config) -> anyhow::Result { .env("WASIXCC_DISCARD_UNSUPPORTED_FLAGS", "yes"); cmd } - PrimarySource::RustSourceFile(filename) => match config.toolchain { - Toolchain::Wasix => { + PrimarySource::RustSourceFile(filename) => match config.rust_toolchain { + RustToolchain::Wasix => { write_ephemeral_cargo_toml(&build_test_path, filename)?; cargo_wasix_build_command(&build_test_path) } - Toolchain::Wasip1 => rustc_wasip1_build_command(&build_test_path, filename)?, + RustToolchain::Wasip1 => rustc_wasip1_build_command(&build_test_path, filename)?, }, PrimarySource::CargoProject => cargo_wasix_build_command(&build_test_path), }; @@ -901,15 +904,15 @@ fn run_build_script(config: &Config) -> anyhow::Result { anyhow::bail!("Build failed for {}", build_test_path.display()); } - let main_path = match (&config.source, config.toolchain) { - (PrimarySource::RustSourceFile(_), Toolchain::Wasix) => { + let main_path = match (&config.source, config.rust_toolchain) { + (PrimarySource::RustSourceFile(_), RustToolchain::Wasix) => { copy_cargo_wasix_artifact(&build_test_path, "main")? } (PrimarySource::CargoProject, _) => { let bin_name = cargo_bin_name_from_manifest(&build_test_path.join("Cargo.toml"))?; copy_cargo_wasix_artifact(&build_test_path, &bin_name)? } - (PrimarySource::RustSourceFile(_), Toolchain::Wasip1) + (PrimarySource::RustSourceFile(_), RustToolchain::Wasip1) | ( PrimarySource::BashScript(_) | PrimarySource::CSourceFile(_) @@ -1485,16 +1488,16 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { let default_file_systems = vec![FileSystemKind::Host]; for primary_source in primary_sources { - if primary_source.is_rust() && !COLLECT_RUST_FIXTURES { - continue; - } - // Single-file Rust fixtures historically built with wasm32-wasip1; - // keep that variant alive since it is the only way to run them on - // Singlepass (the WASIX toolchain emits EH opcodes it lacks). - let default_toolchains = match &primary_source { - PrimarySource::RustSourceFile(_) => vec![Toolchain::Wasix, Toolchain::Wasip1], - _ => vec![Toolchain::Wasix], + // keep that variant alongside the cargo-wasix one, since it is the + // only one Singlepass can run. Every other source has no toolchain + // axis: it is built by wasixcc, or by cargo-wasix for Cargo + // projects, whose output behaves like the WASIX toolchain's. + let default_rust_toolchains = match &primary_source { + PrimarySource::RustSourceFile(_) => { + vec![RustToolchain::Wasix, RustToolchain::Wasip1] + } + _ => vec![RustToolchain::Wasix], }; let configs = parse_configs(&Config::new( @@ -1510,37 +1513,55 @@ fn collect_tests(tests: &mut Vec) -> Result<()> { .as_ref() .unwrap_or(&default_file_systems) { - for toolchain in config.toolchains.as_ref().unwrap_or(&default_toolchains) { - for engine in &supported_engines { - if !toolchain.supports_engine(*engine) { + for engine in &supported_engines { + // WASIXCC toolchain does not cover Windows yet. + if cfg!(target_os = "windows") + && !matches!(config.source, PrimarySource::RustSourceFile(..)) + { + continue; + } + + for sysroot in TESTED_LIBC_VERSIONS { + // For performance reasons, run the wasix-libc compatibility tests + // only with the Cranelift compiler. + if sysroot.is_some() { + #[cfg(target_os = "windows")] continue; + #[cfg(not(target_os = "windows"))] + if *engine != Engine::Cranelift { + continue; + } } - // WASIXCC toolchain does not cover Windows yet. - if cfg!(target_os = "windows") - && !matches!( - config.source, - PrimarySource::RustSourceFile(..) | PrimarySource::CargoProject - ) + for rust_toolchain in config + .rust_toolchains + .as_ref() + .unwrap_or(&default_rust_toolchains) { - continue; - } + if !rust_toolchain.supports_engine(*engine) { + continue; + } + + // wasip1 builds go through rustc and never see + // the wasix-libc sysroot, so the compatibility + // variants would just duplicate the default one. + if sysroot.is_some() && *rust_toolchain == RustToolchain::Wasip1 { + continue; + } - for sysroot in TESTED_LIBC_VERSIONS { - // For performance reasons, run the wasix-libc compatibility tests - // only with the Cranelift compiler. - if sysroot.is_some() { - #[cfg(target_os = "windows")] + // We don't publish the WASIX Rust toolchain for + // every platform yet; the wasip1 variants build + // anywhere. + if config.source.is_rust() + && *rust_toolchain == RustToolchain::Wasix + && !WASIX_RUST_TOOLCHAIN_AVAILABLE + { continue; - #[cfg(not(target_os = "windows"))] - if *engine != Engine::Cranelift { - continue; - } } let mut config = config.clone(); config.engine = *engine; - config.toolchain = *toolchain; + config.rust_toolchain = *rust_toolchain; config.selected_file_system = *file_system; if let Some(sysroot_version) = sysroot { config.set_sysroot(sysroot_version)?;