From 41779e357b3cb47fd027ebfc6b66408aebf89dbe Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Wed, 8 Apr 2026 18:58:39 +0200 Subject: [PATCH 1/5] tests: add WASI integration test support via wasmtime Add UUTESTS_WASM_RUNNER support to the test framework, enabling host-compiled integration tests to exercise a WASI binary through wasmtime. This runs ~1,230 existing tests across 26 tools without needing to compile the test harness for WASI. --- .github/workflows/wasi.yml | 85 ++++++++++++++++++++++++++- tests/by-util/test_basenc.rs | 16 +++--- tests/by-util/test_comm.rs | 68 ++++++++++++---------- tests/by-util/test_od.rs | 34 ++++++++--- tests/by-util/test_tr.rs | 20 +++++-- tests/by-util/test_wc.rs | 76 ++++++++++++++----------- tests/tests.rs | 8 ++- tests/uutests/src/lib/util.rs | 104 ++++++++++++++++++++++------------ 8 files changed, 286 insertions(+), 125 deletions(-) diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index ba5e5ac3472..2a8824792d3 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -32,7 +32,7 @@ jobs: run: | curl https://wasmtime.dev/install.sh -sSf | bash echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH - - name: Run tests + - name: Run unit tests env: CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime run: | @@ -40,3 +40,86 @@ jobs: EXCLUDE="dd|df|du|env|expr|mktemp|more|tac|test" UTILS=$(./util/show-utils.sh | tr ' ' '\n' | grep -vE "^($EXCLUDE)$" | sed 's/^/-p uu_/' | tr '\n' ' ') cargo test --target wasm32-wasip1 --no-default-features $UTILS + - name: Run integration tests via wasmtime + run: | + # Build the WASI binary + cargo build --target wasm32-wasip1 --no-default-features --features feat_wasm + # Run host-compiled integration tests against the WASI binary. + # TODO: add integration tests for these tools as WASI support is extended: + # arch b2sum cat cksum cp csplit date dir dircolors fmt join ln + # ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink + # realpath rm rmdir seq sha1sum sha224sum sha256sum sha384sum + # sha512sum shred sleep sort split tail touch tsort uname uniq + # vdir yes + UUTESTS_BINARY_PATH="$(pwd)/target/wasm32-wasip1/debug/coreutils.wasm" \ + UUTESTS_WASM_RUNNER=wasmtime \ + cargo test --test tests -- \ + test_base32:: test_base64:: test_basenc:: test_basename:: \ + test_comm:: test_cut:: test_dirname:: test_echo:: \ + test_expand:: test_factor:: test_false:: test_fold:: \ + test_head:: test_link:: test_nl:: test_numfmt:: \ + test_od:: test_paste:: test_printf:: test_shuf:: test_sum:: \ + test_tee:: test_tr:: test_true:: test_truncate:: \ + test_unexpand:: test_unlink:: test_wc:: \ + \ + `# WASI sandbox: host paths (/proc, /sys, /dev) not visible in guest` \ + --skip test_factor::test_parallel \ + --skip test_cut::test_too_large \ + --skip test_read_backwards_bytes_proc_fs_version \ + --skip test_read_backwards_bytes_proc_fs_modules \ + --skip test_read_backwards_lines_proc_fs_modules \ + --skip test_read_backwards_bytes_sys_kernel_profiling \ + --skip test_files_from_pseudo_filesystem \ + --skip test_files0_stops_after_stdout_write_error \ + --skip test_dev_zero_write_error_dev_full \ + --skip test_dev_zero_closed_pipe \ + --skip test_simd_respects_glibc_tunables \ + --skip test_comm::test_comm_anonymous_pipes \ + --skip test_comm::test_read_error \ + --skip test_base64::test_read_error \ + --skip test_expand::test_read_error \ + --skip test_unexpand::test_read_error \ + --skip test_od::test_skip_bytes_proc_file_without_seeking \ + --skip test_shuf::write_errors_are_reported \ + --skip test_tee::linux_only \ + --skip test_tee::test_readonly \ + \ + `# WASI spec: argv/filenames must be valid UTF-8` \ + --skip test_invalid_utf8_args \ + --skip test_dirname_non_utf8_paths \ + --skip test_trailing_dot_non_utf8 \ + --skip test_8bit_non_utf8_delimiter \ + --skip test_non_utf8_delimiter \ + --skip test_echo_invalid_unicode_in_arguments \ + --skip mb_invalid_unicode \ + --skip non_utf_8_input \ + --skip non_utf_8 \ + --skip test_octal_escape_ambiguous_followed_by_non_utf8 \ + --skip test_truncate_non_utf8_set \ + --skip test_output_lossy_utf8 \ + --skip non_utf8_paths \ + --skip non_utf8_filename \ + --skip non_utf8_name \ + --skip test_invalid_unicode_in_filename \ + --skip test_one_byte_section_delimiter \ + --skip test_section_delimiter_non_utf8 \ + --skip test_number_separator_non_utf8 \ + \ + `# WASI: no FIFO/mkfifo support` \ + --skip test_fifo_error_reference_file_only \ + --skip test_fifo_error_reference_and_size \ + --skip test_fifo_error_size_only \ + \ + `# WASI: no pipe/signal support` \ + --skip test_broken_pipe_no_error \ + --skip test_tee_output_not_buffered \ + \ + `# WASI: no subprocess spawning (test shells out or invokes another binary)` \ + --skip test_all_but_last_bytes_large_file_piped \ + --skip test_all_but_last_lines_large_file \ + --skip test_cmd_result_stderr_check_and_stderr_str_check \ + \ + `# WASI: stdin file position not preserved through wasmtime` \ + --skip test_od::test_read_bytes \ + --skip test_validate_stdin_offset_lines \ + --skip test_validate_stdin_offset_bytes diff --git a/tests/by-util/test_basenc.rs b/tests/by-util/test_basenc.rs index 0cce5ea8c66..1c8731c42cc 100644 --- a/tests/by-util/test_basenc.rs +++ b/tests/by-util/test_basenc.rs @@ -39,15 +39,15 @@ fn test_z85_not_padded_encode() { #[test] fn test_invalid_input() { - let error_message = if cfg!(windows) { - "basenc: .: Permission denied\n" + let cmd = new_ucmd!().args(&["--base32", "."]).fails(); + if cfg!(windows) { + cmd.stderr_only("basenc: .: Permission denied\n"); + } else if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + // wasi-libc may report a different error string than the host libc + cmd.stderr_contains("basenc: read error:"); } else { - "basenc: read error: Is a directory\n" - }; - new_ucmd!() - .args(&["--base32", "."]) - .fails() - .stderr_only(error_message); + cmd.stderr_only("basenc: read error: Is a directory\n"); + } } #[test] diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 6c6b88246f2..a4bfe8f5bf4 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -451,19 +451,23 @@ fn test_is_dir() { #[test] fn test_sorted() { - let expected_stderr = - "comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"; - let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.write("comm1", "1\n3"); at.write("comm2", "3\n2"); - scene - .ucmd() - .args(&["comm1", "comm2"]) - .fails_with_code(1) - .stdout_is("1\n\t\t3\n\t2\n") - .stderr_is(expected_stderr); + let cmd = scene.ucmd().args(&["comm1", "comm2"]).run(); + // WASI's strcoll (C locale only) may not detect unsorted input, + // but the comparison output is still correct. + if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + cmd.success().stdout_is("1\n\t\t3\n\t2\n"); + } else { + cmd.failure() + .code_is(1) + .stdout_is("1\n\t\t3\n\t2\n") + .stderr_is( + "comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n", + ); + } } #[test] @@ -490,16 +494,19 @@ fn test_both_inputs_out_of_order() { at.write("file_a", "3\n1\n0\n"); at.write("file_b", "3\n2\n0\n"); - scene - .ucmd() - .args(&["file_a", "file_b"]) - .fails_with_code(1) - .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") - .stderr_is( - "comm: file 1 is not in sorted order\n\ - comm: file 2 is not in sorted order\n\ - comm: input is not in sorted order\n", - ); + let cmd = scene.ucmd().args(&["file_a", "file_b"]).run(); + if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + cmd.success().stdout_is("\t\t3\n1\n0\n\t2\n\t0\n"); + } else { + cmd.failure() + .code_is(1) + .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); + } } #[test] @@ -509,16 +516,19 @@ fn test_both_inputs_out_of_order_last_pair() { at.write("file_a", "3\n1\n"); at.write("file_b", "3\n2\n"); - scene - .ucmd() - .args(&["file_a", "file_b"]) - .fails_with_code(1) - .stdout_is("\t\t3\n1\n\t2\n") - .stderr_is( - "comm: file 1 is not in sorted order\n\ - comm: file 2 is not in sorted order\n\ - comm: input is not in sorted order\n", - ); + let cmd = scene.ucmd().args(&["file_a", "file_b"]).run(); + if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + cmd.success().stdout_is("\t\t3\n1\n\t2\n"); + } else { + cmd.failure() + .code_is(1) + .stdout_is("\t\t3\n1\n\t2\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); + } } #[test] diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 823f2d1dc51..a9abd596a85 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -764,31 +764,47 @@ fn test_invalid_traditional_offsets_are_filenames() { #[test] fn test_traditional_offset_overflow_diagnosed() { + // The ERANGE message differs across platforms (e.g. "Result too large" + // on glibc vs "Result not representable" on wasi-libc), so when testing + // via a WASM runner we just check that stderr mentions the input value. + let wasm = std::env::var("UUTESTS_WASM_RUNNER").is_ok(); let erange = erange_message(); let long_octal = "7".repeat(255); let long_decimal = format!("{}.", "9".repeat(254)); let long_hex = format!("0x{}", "f".repeat(253)); - new_ucmd!() + let cmd = new_ucmd!() .arg("-") .arg(&long_octal) .pipe_in(Vec::::new()) - .fails_with_code(1) - .stderr_only(format!("od: {long_octal}: {erange}\n")); + .fails_with_code(1); + if wasm { + cmd.stderr_contains(&format!("od: {long_octal}:")); + } else { + cmd.stderr_only(format!("od: {long_octal}: {erange}\n")); + } - new_ucmd!() + let cmd = new_ucmd!() .arg("-") .arg(&long_decimal) .pipe_in(Vec::::new()) - .fails_with_code(1) - .stderr_only(format!("od: {long_decimal}: {erange}\n")); + .fails_with_code(1); + if wasm { + cmd.stderr_contains(&format!("od: {long_decimal}:")); + } else { + cmd.stderr_only(format!("od: {long_decimal}: {erange}\n")); + } - new_ucmd!() + let cmd = new_ucmd!() .arg("-") .arg(&long_hex) .pipe_in(Vec::::new()) - .fails_with_code(1) - .stderr_only(format!("od: {long_hex}: {erange}\n")); + .fails_with_code(1); + if wasm { + cmd.stderr_contains(&format!("od: {long_hex}:")); + } else { + cmd.stderr_only(format!("od: {long_hex}: {erange}\n")); + } } #[test] diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 37f1af97212..a9134b755bf 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -21,12 +21,20 @@ fn test_invalid_input() { .fails_with_code(1) .stderr_contains("tr: extra operand '<'"); #[cfg(unix)] - new_ucmd!() - .args(&["1", "1"]) - // will test "tr 1 1 < ." - .set_stdin(std::process::Stdio::from(std::fs::File::open(".").unwrap())) - .fails_with_code(1) - .stderr_contains("tr: read error: Is a directory"); + { + let cmd = new_ucmd!() + .args(&["1", "1"]) + // will test "tr 1 1 < ." + .set_stdin(std::process::Stdio::from(std::fs::File::open(".").unwrap())) + .fails_with_code(1); + if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + // On WASI the fluent translation key may appear instead of the + // translated text, but the OS error string is still present. + cmd.stderr_contains("Is a directory"); + } else { + cmd.stderr_contains("tr: read error: Is a directory"); + } + } } #[test] diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 2a3e9bebe44..f4b3e0597fd 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -432,21 +432,16 @@ fn test_file_bytes_dictate_width() { /// Test that getting counts from a directory is an error. #[test] fn test_read_from_directory_error() { - #[cfg(not(windows))] - const STDERR: &str = ".: Is a directory"; - #[cfg(windows)] - const STDERR: &str = ".: Permission denied"; - - #[cfg(not(windows))] - const STDOUT: &str = " 0 0 0 .\n"; - #[cfg(windows)] - const STDOUT: &str = ""; - - new_ucmd!() - .args(&["."]) - .fails() - .stderr_contains(STDERR) - .stdout_is(STDOUT); + let cmd = new_ucmd!().args(&["."]).fails(); + if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + // wasi-libc may report a different error string than the host libc + cmd.stderr_contains("wc: .:"); + } else if cfg!(windows) { + cmd.stderr_contains(".: Permission denied").stdout_is(""); + } else { + cmd.stderr_contains(".: Is a directory") + .stdout_is(" 0 0 0 .\n"); + } } #[cfg(unix)] @@ -455,15 +450,17 @@ fn test_read_error_order_with_stderr_to_stdout() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("ioerrdir"); - let expected = format!( - "{:>7} {:>7} {:>7} ioerrdir\nwc: ioerrdir: Is a directory\n", - 0, 0, 0 - ); - - ucmd.arg("ioerrdir") - .stderr_to_stdout() - .fails() - .stdout_only(expected); + let cmd = ucmd.arg("ioerrdir").stderr_to_stdout().fails(); + if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + // wasi-libc may report a different error string than the host libc + cmd.stdout_contains("wc: ioerrdir:"); + } else { + let expected = format!( + "{:>7} {:>7} {:>7} ioerrdir\nwc: ioerrdir: Is a directory\n", + 0, 0, 0 + ); + cmd.stdout_only(expected); + } } /// Test that getting counts from nonexistent file is an error. @@ -709,6 +706,12 @@ fn test_zero_length_files() { #[test] fn test_files0_errors_quoting() { + // Column padding differs on WASI (different terminal width detection) + let stdout = if std::env::var("UUTESTS_WASM_RUNNER").is_ok() { + " 0 0 0 total\n" + } else { + "0 0 0 total\n" + }; new_ucmd!() .args(&["--files0-from=files0 with nonexistent.txt"]) .fails() @@ -718,7 +721,7 @@ fn test_files0_errors_quoting() { "wc: 'this file does not exist.txt': No such file or directory\n", "wc: \"this files doesn't exist either.txt\": No such file or directory\n", )) - .stdout_is("0 0 0 total\n"); + .stdout_is(stdout); } #[test] @@ -790,7 +793,8 @@ fn test_files0_stops_after_stdout_write_error() { #[test] fn files0_from_dir() { // On Unix, `read(open("."))` fails. On Windows, `open(".")` fails. Thus, the errors happen in - // different contexts. + // different contexts. On WASI, the error string may differ (e.g., "Bad file descriptor"). + let wasm = std::env::var("UUTESTS_WASM_RUNNER").is_ok(); #[cfg(not(windows))] macro_rules! dir_err { ($p:literal) => { @@ -808,16 +812,22 @@ fn files0_from_dir() { #[cfg(not(windows))] const DOT_ERR: &str = dir_err!("."); - new_ucmd!() + let cmd = new_ucmd!() .args(&["--files0-from=dir with spaces"]) - .fails() - .stderr_only(dir_err!("'dir with spaces'")); + .fails(); + if wasm { + cmd.stderr_contains("wc: 'dir with spaces': read error:"); + } else { + cmd.stderr_only(dir_err!("'dir with spaces'")); + } // Those contexts have different rules about quoting in errors... - new_ucmd!() - .args(&["--files0-from=."]) - .fails() - .stderr_only(DOT_ERR); + let cmd = new_ucmd!().args(&["--files0-from=."]).fails(); + if wasm { + cmd.stderr_contains("wc: .: read error:"); + } else { + cmd.stderr_only(DOT_ERR); + } // That also means you cannot `< . wc --files0-from=-` on Windows. #[cfg(not(windows))] diff --git a/tests/tests.rs b/tests/tests.rs index 08c9bfb3a63..6fe07c56fbb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -10,9 +10,11 @@ pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); // Use the ctor attribute to run this function before any tests #[ctor::ctor] fn init() { - unsafe { - // Necessary for uutests to be able to find the binary - env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + // Allow overriding the binary path (e.g. to test a WASI binary via wasmtime) + if env::var("UUTESTS_BINARY_PATH").is_err() { + unsafe { + env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + } } } diff --git a/tests/uutests/src/lib/util.rs b/tests/uutests/src/lib/util.rs index 6e5e9690d24..e1aca8df4e0 100644 --- a/tests/uutests/src/lib/util.rs +++ b/tests/uutests/src/lib/util.rs @@ -736,10 +736,20 @@ impl CmdResult { /// 2. the command resulted in empty (zero-length) stdout stream output #[track_caller] pub fn usage_error>(&self, msg: T) -> &Self { + // When testing via a WASM runner, the binary sees only its filename + // (via --argv0), not the full host path. + let bin_display: Cow<'_, str> = if env::var("UUTESTS_WASM_RUNNER").is_ok() { + self.bin_path + .file_name() + .unwrap_or(self.bin_path.as_os_str()) + .to_string_lossy() + } else { + Cow::Owned(self.bin_path.display().to_string()) + }; self.stderr_only(format!( "{0}: {2}\nTry '{1} {0} --help' for more information.\n", self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command - self.bin_path.display(), + bin_display, msg.as_ref() )) } @@ -1817,55 +1827,77 @@ impl UCommand { } } - // unwrap is safe here because we have set `self.bin_path` before - let mut command = Command::new(self.bin_path.as_ref().unwrap()); - command.args(&self.args); - - // We use a temporary directory as working directory if not specified otherwise with - // `current_dir()`. If neither `current_dir` nor a temporary directory is available, then we - // create our own. - if let Some(current_dir) = &self.current_dir { - command.current_dir(current_dir); + // Resolve the working directory before building the command, since WASM + // runners need it as an absolute --dir argument. + let work_dir = if let Some(current_dir) = &self.current_dir { + std::path::absolute(current_dir).unwrap_or_else(|_| current_dir.clone()) } else if let Some(temp_dir) = &self.tmpd { - command.current_dir(temp_dir.path()); + temp_dir.path().to_path_buf() } else { let temp_dir = tempfile::tempdir().unwrap(); - self.current_dir = Some(temp_dir.path().into()); - command.current_dir(temp_dir.path()); + let path = temp_dir.path().to_path_buf(); + self.current_dir = Some(path.clone()); self.tmpd = Some(Rc::new(temp_dir)); - } + path + }; - command.env_clear(); + // If UUTESTS_WASM_RUNNER is set (e.g. "wasmtime"), run the binary through + // that runner so host-compiled tests can exercise a WASI binary. Only + // apply to the coreutils binary under test, not to helper commands like + // sh or seq that should run natively on the host. + let wasm_runner = env::var("UUTESTS_WASM_RUNNER").ok().filter(|_| { + self.bin_path.as_deref() == env::var("UUTESTS_BINARY_PATH").ok().map(PathBuf::from).as_deref() + }); - // Preserve PATH + // Collect environment variables for the command. + let mut cmd_env: Vec<(OsString, OsString)> = Vec::new(); + cmd_env.extend( + DEFAULT_ENV + .iter() + .map(|(k, v)| (OsString::from(k), OsString::from(v))), + ); if let Some(path) = env::var_os("PATH") { - command.env("PATH", path); + cmd_env.push(("PATH".into(), path)); } - if cfg!(windows) { // spell-checker:ignore (dll) rsaenh // %SYSTEMROOT% is required on Windows to initialize crypto provider - // ... and crypto provider is required for std::rand - // From `procmon`: RegQueryValue HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider\Image Path - // SUCCESS Type: REG_SZ, Length: 66, Data: %SystemRoot%\system32\rsaenh.dll" if let Some(systemroot) = env::var_os("SYSTEMROOT") { - command.env("SYSTEMROOT", systemroot); + cmd_env.push(("SYSTEMROOT".into(), systemroot)); } - } else { - // if someone is setting LD_PRELOAD, there's probably a good reason for it - if let Some(ld_preload) = env::var_os("LD_PRELOAD") { - command.env("LD_PRELOAD", ld_preload); + } else if let Some(ld_preload) = env::var_os("LD_PRELOAD") { + cmd_env.push(("LD_PRELOAD".into(), ld_preload)); + } + if let Some(llvm_profile) = env::var_os("LLVM_PROFILE_FILE") { + cmd_env.push(("LLVM_PROFILE_FILE".into(), llvm_profile)); + } + cmd_env.extend(self.env_vars.iter().cloned()); + + let mut command = if let Some(ref runner) = wasm_runner { + let bin = self.bin_path.as_ref().unwrap(); + let mut cmd = Command::new(runner); + // Map the working directory as the WASI guest's root. Only files + // under this directory are visible to the guest; tests using + // absolute host paths outside it must be skipped. + cmd.arg(format!("--dir={}::/", work_dir.display())); + cmd.arg("--argv0"); + cmd.arg(bin.file_name().unwrap_or(bin.as_os_str())); + // Forward env vars to the WASI guest via --env flags + for (key, val) in &cmd_env { + if let (Some(k), Some(v)) = (key.to_str(), val.to_str()) { + cmd.arg("--env"); + cmd.arg(format!("{k}={v}")); + } } - } - - // Forward the LLVM_PROFILE_FILE variable to the call, for coverage purposes. - if let Some(ld_preload) = env::var_os("LLVM_PROFILE_FILE") { - command.env("LLVM_PROFILE_FILE", ld_preload); - } - - command - .envs(DEFAULT_ENV) - .envs(self.env_vars.iter().cloned()); + cmd.arg(bin); + cmd + } else { + Command::new(self.bin_path.as_ref().unwrap()) + }; + command.args(&self.args); + command.current_dir(&work_dir); + command.env_clear(); + command.envs(cmd_env); if self.timeout.is_none() { self.timeout = Some(Duration::from_secs(30)); From 1396871ec2435db54e6750434fe64c60cce6543e Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 9 Apr 2026 10:34:59 +0200 Subject: [PATCH 2/5] Fix spelling and formatting --- .vscode/cspell.dictionaries/acronyms+names.wordlist.txt | 1 + .vscode/cspell.dictionaries/jargon.wordlist.txt | 1 + tests/by-util/test_comm.rs | 4 +--- tests/by-util/test_wc.rs | 4 +--- tests/uutests/src/lib/util.rs | 6 +++++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index 4de6f38f07a..a0e67dcb43b 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -74,3 +74,4 @@ Yargs # Product codspeed +wasmtime diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 16778da940d..c4577adb5b3 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -158,6 +158,7 @@ SIGTTOU sigttou sigusr strcasecmp +strcoll subcommand subexpression submodule diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index a4bfe8f5bf4..8f4b862fa52 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -464,9 +464,7 @@ fn test_sorted() { cmd.failure() .code_is(1) .stdout_is("1\n\t\t3\n\t2\n") - .stderr_is( - "comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n", - ); + .stderr_is("comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"); } } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index f4b3e0597fd..c3f972fe916 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -812,9 +812,7 @@ fn files0_from_dir() { #[cfg(not(windows))] const DOT_ERR: &str = dir_err!("."); - let cmd = new_ucmd!() - .args(&["--files0-from=dir with spaces"]) - .fails(); + let cmd = new_ucmd!().args(&["--files0-from=dir with spaces"]).fails(); if wasm { cmd.stderr_contains("wc: 'dir with spaces': read error:"); } else { diff --git a/tests/uutests/src/lib/util.rs b/tests/uutests/src/lib/util.rs index e1aca8df4e0..834023e4ca7 100644 --- a/tests/uutests/src/lib/util.rs +++ b/tests/uutests/src/lib/util.rs @@ -1846,7 +1846,11 @@ impl UCommand { // apply to the coreutils binary under test, not to helper commands like // sh or seq that should run natively on the host. let wasm_runner = env::var("UUTESTS_WASM_RUNNER").ok().filter(|_| { - self.bin_path.as_deref() == env::var("UUTESTS_BINARY_PATH").ok().map(PathBuf::from).as_deref() + self.bin_path.as_deref() + == env::var("UUTESTS_BINARY_PATH") + .ok() + .map(PathBuf::from) + .as_deref() }); // Collect environment variables for the command. From 45048e8e11bb0332d14d2efabb006cbf5b37571d Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 9 Apr 2026 10:49:11 +0200 Subject: [PATCH 3/5] Fix linter errors --- tests/by-util/test_od.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index a9abd596a85..59f6055c2a8 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -779,7 +779,7 @@ fn test_traditional_offset_overflow_diagnosed() { .pipe_in(Vec::::new()) .fails_with_code(1); if wasm { - cmd.stderr_contains(&format!("od: {long_octal}:")); + cmd.stderr_contains(format!("od: {long_octal}:")); } else { cmd.stderr_only(format!("od: {long_octal}: {erange}\n")); } @@ -790,7 +790,7 @@ fn test_traditional_offset_overflow_diagnosed() { .pipe_in(Vec::::new()) .fails_with_code(1); if wasm { - cmd.stderr_contains(&format!("od: {long_decimal}:")); + cmd.stderr_contains(format!("od: {long_decimal}:")); } else { cmd.stderr_only(format!("od: {long_decimal}: {erange}\n")); } @@ -801,7 +801,7 @@ fn test_traditional_offset_overflow_diagnosed() { .pipe_in(Vec::::new()) .fails_with_code(1); if wasm { - cmd.stderr_contains(&format!("od: {long_hex}:")); + cmd.stderr_contains(format!("od: {long_hex}:")); } else { cmd.stderr_only(format!("od: {long_hex}: {erange}\n")); } From 6e530d645e72517f767d3e5523ecc8e8d705c3a2 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 9 Apr 2026 14:15:51 +0200 Subject: [PATCH 4/5] move WASI skip list from workflow into test annotations --- .github/workflows/wasi.yml | 68 +++------------------------------- Cargo.toml | 1 + docs/src/wasi-test-gaps.md | 33 +++++++++++++++++ tests/by-util/test_base64.rs | 2 + tests/by-util/test_basename.rs | 1 + tests/by-util/test_basenc.rs | 1 + tests/by-util/test_comm.rs | 3 ++ tests/by-util/test_cut.rs | 3 ++ tests/by-util/test_dirname.rs | 2 + tests/by-util/test_echo.rs | 3 ++ tests/by-util/test_expand.rs | 2 + tests/by-util/test_factor.rs | 1 + tests/by-util/test_head.rs | 15 ++++++++ tests/by-util/test_nl.rs | 4 ++ tests/by-util/test_numfmt.rs | 1 + tests/by-util/test_od.rs | 5 +++ tests/by-util/test_paste.rs | 4 ++ tests/by-util/test_printf.rs | 2 + tests/by-util/test_shuf.rs | 3 ++ tests/by-util/test_sum.rs | 1 + tests/by-util/test_tee.rs | 4 +- tests/by-util/test_tr.rs | 3 ++ tests/by-util/test_truncate.rs | 4 ++ tests/by-util/test_unexpand.rs | 2 + tests/by-util/test_unlink.rs | 1 + tests/by-util/test_wc.rs | 3 ++ 26 files changed, 108 insertions(+), 64 deletions(-) create mode 100644 docs/src/wasi-test-gaps.md diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 2a8824792d3..8c3721d6ac5 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -41,10 +41,14 @@ jobs: UTILS=$(./util/show-utils.sh | tr ' ' '\n' | grep -vE "^($EXCLUDE)$" | sed 's/^/-p uu_/' | tr '\n' ' ') cargo test --target wasm32-wasip1 --no-default-features $UTILS - name: Run integration tests via wasmtime + env: + RUSTFLAGS: --cfg wasi_runner run: | # Build the WASI binary cargo build --target wasm32-wasip1 --no-default-features --features feat_wasm # Run host-compiled integration tests against the WASI binary. + # Tests incompatible with WASI are annotated with + # #[cfg_attr(wasi_runner, ignore)] in the test source files. # TODO: add integration tests for these tools as WASI support is extended: # arch b2sum cat cksum cp csplit date dir dircolors fmt join ln # ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink @@ -60,66 +64,4 @@ jobs: test_head:: test_link:: test_nl:: test_numfmt:: \ test_od:: test_paste:: test_printf:: test_shuf:: test_sum:: \ test_tee:: test_tr:: test_true:: test_truncate:: \ - test_unexpand:: test_unlink:: test_wc:: \ - \ - `# WASI sandbox: host paths (/proc, /sys, /dev) not visible in guest` \ - --skip test_factor::test_parallel \ - --skip test_cut::test_too_large \ - --skip test_read_backwards_bytes_proc_fs_version \ - --skip test_read_backwards_bytes_proc_fs_modules \ - --skip test_read_backwards_lines_proc_fs_modules \ - --skip test_read_backwards_bytes_sys_kernel_profiling \ - --skip test_files_from_pseudo_filesystem \ - --skip test_files0_stops_after_stdout_write_error \ - --skip test_dev_zero_write_error_dev_full \ - --skip test_dev_zero_closed_pipe \ - --skip test_simd_respects_glibc_tunables \ - --skip test_comm::test_comm_anonymous_pipes \ - --skip test_comm::test_read_error \ - --skip test_base64::test_read_error \ - --skip test_expand::test_read_error \ - --skip test_unexpand::test_read_error \ - --skip test_od::test_skip_bytes_proc_file_without_seeking \ - --skip test_shuf::write_errors_are_reported \ - --skip test_tee::linux_only \ - --skip test_tee::test_readonly \ - \ - `# WASI spec: argv/filenames must be valid UTF-8` \ - --skip test_invalid_utf8_args \ - --skip test_dirname_non_utf8_paths \ - --skip test_trailing_dot_non_utf8 \ - --skip test_8bit_non_utf8_delimiter \ - --skip test_non_utf8_delimiter \ - --skip test_echo_invalid_unicode_in_arguments \ - --skip mb_invalid_unicode \ - --skip non_utf_8_input \ - --skip non_utf_8 \ - --skip test_octal_escape_ambiguous_followed_by_non_utf8 \ - --skip test_truncate_non_utf8_set \ - --skip test_output_lossy_utf8 \ - --skip non_utf8_paths \ - --skip non_utf8_filename \ - --skip non_utf8_name \ - --skip test_invalid_unicode_in_filename \ - --skip test_one_byte_section_delimiter \ - --skip test_section_delimiter_non_utf8 \ - --skip test_number_separator_non_utf8 \ - \ - `# WASI: no FIFO/mkfifo support` \ - --skip test_fifo_error_reference_file_only \ - --skip test_fifo_error_reference_and_size \ - --skip test_fifo_error_size_only \ - \ - `# WASI: no pipe/signal support` \ - --skip test_broken_pipe_no_error \ - --skip test_tee_output_not_buffered \ - \ - `# WASI: no subprocess spawning (test shells out or invokes another binary)` \ - --skip test_all_but_last_bytes_large_file_piped \ - --skip test_all_but_last_lines_large_file \ - --skip test_cmd_result_stderr_check_and_stderr_str_check \ - \ - `# WASI: stdin file position not preserved through wasmtime` \ - --skip test_od::test_read_bytes \ - --skip test_validate_stdin_offset_lines \ - --skip test_validate_stdin_offset_bytes + test_unexpand:: test_unlink:: test_wc:: diff --git a/Cargo.toml b/Cargo.toml index d8f1b241a0d..7755617886d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -720,6 +720,7 @@ workspace = true unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(fuzzing)', 'cfg(target_os, values("cygwin"))', + 'cfg(wasi_runner)', ] } unused_qualifications = "warn" diff --git a/docs/src/wasi-test-gaps.md b/docs/src/wasi-test-gaps.md new file mode 100644 index 00000000000..18406243c9b --- /dev/null +++ b/docs/src/wasi-test-gaps.md @@ -0,0 +1,33 @@ +# WASI integration test gaps + +Tests annotated with `#[cfg_attr(wasi_runner, ignore = "...")]` are skipped when running integration tests against a WASI binary via wasmtime. This document tracks the reasons so that gaps in WASI support are visible in one place. + +To find all annotated tests: `grep -rn 'wasi_runner, ignore' tests/` + +## Tools not yet covered by integration tests + +arch, b2sum, cat, cksum, cp, csplit, date, dir, dircolors, fmt, join, ln, ls, md5sum, mkdir, mv, nproc, pathchk, pr, printenv, ptx, pwd, readlink, realpath, rm, rmdir, seq, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, shred, sleep, sort, split, tail, touch, tsort, uname, uniq, vdir, yes + +## WASI sandbox: host paths not visible + +The WASI guest only sees directories explicitly mapped with `--dir`. Host paths like `/proc`, `/sys`, and `/dev` are not accessible. Affected tests include those that read `/proc/version`, `/proc/modules`, `/proc/cpuinfo`, `/proc/self/mem`, `/sys/kernel/profiling`, `/dev/null`, `/dev/zero`, `/dev/full`, and tests that rely on anonymous pipes or Linux-specific I/O error paths. + +## WASI: argv/filenames must be valid UTF-8 + +The WASI specification requires that argv entries and filenames are valid UTF-8. Tests that pass non-UTF-8 bytes as arguments or create files with non-UTF-8 names cannot run under WASI. + +## WASI: no FIFO/mkfifo support + +WASI does not support creating or opening FIFOs (named pipes). Tests that use `mkfifo` are skipped. + +## WASI: no pipe/signal support + +WASI does not support Unix signals or pipe creation. Tests that rely on `SIGPIPE`, broken pipe detection, or pipe-based I/O are skipped. + +## WASI: no subprocess spawning + +WASI does not support spawning child processes. Tests that shell out to other commands or invoke a second binary are skipped. + +## WASI: stdin file position not preserved through wasmtime + +When stdin is a seekable file, wasmtime does not preserve the file position between the host and guest. Tests that validate stdin offset behavior after `head` reads are skipped. diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 7d02b62f74e..a8de3bb6972 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -20,6 +20,7 @@ fn test_version() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_base64_non_utf8_paths() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); @@ -277,6 +278,7 @@ cyBvdmVyIHRoZSBsYXp5IGRvZy4= #[test] #[cfg(all(target_os = "linux", not(target_env = "musl")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_read_error() { new_ucmd!() .arg("/proc/self/mem") diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 7ad5008b2e8..62b1481ca21 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -138,6 +138,7 @@ fn test_too_many_args_output() { #[cfg(any(unix, target_os = "redox"))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_invalid_utf8_args() { let param = uucore::os_str_from_bytes(b"/tmp/some-\xc0-file.k\xf3") .expect("Only unix platforms can test non-unicode names"); diff --git a/tests/by-util/test_basenc.rs b/tests/by-util/test_basenc.rs index 1c8731c42cc..ff3b7f43335 100644 --- a/tests/by-util/test_basenc.rs +++ b/tests/by-util/test_basenc.rs @@ -396,6 +396,7 @@ fn test_file() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_file_with_non_utf8_name() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 8f4b862fa52..e5432137bd4 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -658,6 +658,7 @@ fn test_comm_eintr_handling() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_output_lossy_utf8() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -683,6 +684,7 @@ fn test_output_lossy_utf8() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_comm_anonymous_pipes() { use std::{io::Write, os::fd::AsRawFd, process}; use uucore::pipes::pipe; @@ -722,6 +724,7 @@ fn test_comm_anonymous_pipes() { #[test] #[cfg(all(target_os = "linux", not(target_env = "musl")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_read_error() { new_ucmd!() .arg("/proc/self/mem") diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 160ad59c878..493ee747cf8 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -137,6 +137,7 @@ fn test_delimiter_with_byte_and_char() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_too_large() { new_ucmd!() .args(&["-b1-18446744073709551615", "/dev/null"]) @@ -569,6 +570,7 @@ fn test_multiple_mode_args() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_8bit_non_utf8_delimiter() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -635,6 +637,7 @@ fn test_failed_write_is_reported() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_cut_non_utf8_paths() { use std::fs::File; use std::io::Write; diff --git a/tests/by-util/test_dirname.rs b/tests/by-util/test_dirname.rs index 92350261d94..7c5684f17df 100644 --- a/tests/by-util/test_dirname.rs +++ b/tests/by-util/test_dirname.rs @@ -72,6 +72,7 @@ fn test_empty() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_dirname_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -177,6 +178,7 @@ fn test_trailing_dot_emoji() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_trailing_dot_non_utf8() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index c3c9400995d..2c73b418634 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -586,6 +586,7 @@ fn multibyte_escape_unicode() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn non_utf_8_hex_round_trip() { new_ucmd!() .args(&["-e", r"\xFF"]) @@ -610,6 +611,7 @@ fn nine_bit_octal() { #[test] #[cfg(target_family = "unix")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn non_utf_8() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -664,6 +666,7 @@ fn test_cmd_result_stdout_check_and_stdout_str_check() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no subprocess spawning")] fn test_cmd_result_stderr_check_and_stderr_str_check() { let ts = TestScenario::new("echo"); diff --git a/tests/by-util/test_expand.rs b/tests/by-util/test_expand.rs index cd84a82d280..ac65409ea7f 100644 --- a/tests/by-util/test_expand.rs +++ b/tests/by-util/test_expand.rs @@ -429,6 +429,7 @@ fn test_nonexisting_file() { #[test] #[cfg(all(target_os = "linux", not(target_env = "musl")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_read_error() { new_ucmd!() .arg("/proc/self/mem") @@ -438,6 +439,7 @@ fn test_read_error() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_expand_non_utf8_paths() { use std::os::unix::ffi::OsStringExt; use uutests::at_and_ucmd; diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 317ceabd1f6..bee47b5efdc 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -69,6 +69,7 @@ fn test_trim_null_chars() { #[test] #[cfg(feature = "sort")] #[cfg(not(target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_parallel() { use hex_literal::hex; use sha1::{Digest, Sha1}; diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index d954aa9d918..041b68f58a0 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -413,6 +413,7 @@ fn test_presume_input_pipe_5_chars() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no subprocess spawning")] fn test_all_but_last_bytes_large_file_piped() { // Validate print-all-but-last-n-bytes with a large piped-in (i.e. non-seekable) file. let scene = TestScenario::new(util_name!()); @@ -481,6 +482,7 @@ fn test_all_but_last_lines_large_file_presume_input_pipe() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no subprocess spawning")] fn test_all_but_last_lines_large_file() { // Create our fixtures on the fly. We need the input file to be at least double // the size of BUF_SIZE as specified in head.rs. Go for something a bit bigger @@ -554,6 +556,10 @@ fn test_all_but_last_lines_large_file() { not(target_os = "openbsd") ))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: stdin file position not preserved through wasmtime" +)] fn test_validate_stdin_offset_lines() { // A handful of unix-only tests to validate behavior when reading from stdin on a seekable // file. GNU-compatibility requires that the stdin file be left such that if another @@ -654,6 +660,10 @@ fn test_validate_stdin_offset_lines() { not(target_os = "openbsd") ))] #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: stdin file position not preserved through wasmtime" +)] fn test_validate_stdin_offset_bytes() { // A handful of unix-only tests to validate behavior when reading from stdin on a seekable // file. GNU-compatibility requires that the stdin file be left such that if another @@ -779,6 +789,7 @@ fn test_validate_stdin_offset_bytes() { not(target_os = "openbsd") ))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/proc) not visible")] fn test_read_backwards_bytes_proc_fs_version() { let ts = TestScenario::new(util_name!()); @@ -795,6 +806,7 @@ fn test_read_backwards_bytes_proc_fs_version() { not(target_os = "openbsd") ))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/proc) not visible")] fn test_read_backwards_bytes_proc_fs_modules() { let ts = TestScenario::new(util_name!()); @@ -815,6 +827,7 @@ fn test_read_backwards_bytes_proc_fs_modules() { not(target_os = "openbsd") ))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/proc) not visible")] fn test_read_backwards_lines_proc_fs_modules() { let ts = TestScenario::new(util_name!()); @@ -835,6 +848,7 @@ fn test_read_backwards_lines_proc_fs_modules() { not(target_os = "openbsd") ))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/sys) not visible")] fn test_read_backwards_bytes_sys_kernel_profiling() { let ts = TestScenario::new(util_name!()); // in case the kernel was not built with profiling support, e.g. WSL @@ -890,6 +904,7 @@ fn test_write_to_dev_full() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_head_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index dab5cc47f92..0801c322852 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -8,6 +8,7 @@ use uutests::{at_and_ucmd, new_ucmd, util::TestScenario, util_name}; #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_paths() { use std::os::unix::ffi::OsStringExt; let (at, mut ucmd) = at_and_ucmd!(); @@ -208,6 +209,7 @@ fn test_number_separator() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_number_separator_non_utf8() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; @@ -629,6 +631,7 @@ fn test_section_delimiter() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_section_delimiter_non_utf8() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; @@ -695,6 +698,7 @@ fn test_one_char_section_delimiter() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_one_byte_section_delimiter() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 22fa8385d19..0c61cb49fbc 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -1170,6 +1170,7 @@ fn test_zero_terminated_embedded_newline() { #[cfg(unix)] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_delimiter() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_od.rs b/tests/by-util/test_od.rs index 59f6055c2a8..4a8ad8f94e3 100644 --- a/tests/by-util/test_od.rs +++ b/tests/by-util/test_od.rs @@ -897,6 +897,7 @@ fn test_skip_bytes_prints_after_consuming_multiple_inputs() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/proc) not visible")] fn test_skip_bytes_proc_file_without_seeking() { let proc_path = Path::new("/proc/version"); if !proc_path.exists() { @@ -934,6 +935,10 @@ fn test_skip_bytes_error() { } #[test] +#[cfg_attr( + wasi_runner, + ignore = "WASI: stdin file position not preserved through wasmtime" +)] fn test_read_bytes() { let scene = TestScenario::new(util_name!()); let fixtures = &scene.fixtures; diff --git a/tests/by-util/test_paste.rs b/tests/by-util/test_paste.rs index 0bba5bd7a5d..ce5fec2f19b 100644 --- a/tests/by-util/test_paste.rs +++ b/tests/by-util/test_paste.rs @@ -417,6 +417,7 @@ fn test_data() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_delimiter() { let (at, mut ucmd) = at_and_ucmd!(); at.write("f1", "1\n2\n"); @@ -432,6 +433,7 @@ fn test_non_utf8_delimiter() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_paste_non_utf8_paths() { let (at, mut ucmd) = at_and_ucmd!(); @@ -466,6 +468,7 @@ fn make_broken_pipe() -> std::fs::File { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/dev) not visible")] fn test_dev_zero_write_error_dev_full() { use std::fs::File; @@ -482,6 +485,7 @@ fn test_dev_zero_write_error_dev_full() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/dev) not visible")] fn test_dev_zero_closed_pipe() { new_ucmd!() .arg("/dev/zero") diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 81427fae5a4..1c8d77c9a1b 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1336,6 +1336,7 @@ fn mb_input() { #[test] #[cfg(target_family = "unix")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn mb_invalid_unicode() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -1424,6 +1425,7 @@ fn positional_format_specifiers() { #[test] #[cfg(target_family = "unix")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn non_utf_8_input() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_shuf.rs b/tests/by-util/test_shuf.rs index d9fd4d2399e..1f406eab188 100644 --- a/tests/by-util/test_shuf.rs +++ b/tests/by-util/test_shuf.rs @@ -383,6 +383,7 @@ fn test_echo_separators_in_arguments() { #[cfg(unix)] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_echo_invalid_unicode_in_arguments() { use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; @@ -398,6 +399,7 @@ fn test_echo_invalid_unicode_in_arguments() { #[cfg(any(unix, target_os = "wasi"))] #[cfg(not(target_os = "macos"))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_invalid_unicode_in_filename() { use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; @@ -853,6 +855,7 @@ fn test_range_repeat_empty_minus_one() { // This test fails if we forget to flush the `BufWriter`. #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn write_errors_are_reported() { new_ucmd!() .arg("-i1-10") diff --git a/tests/by-util/test_sum.rs b/tests/by-util/test_sum.rs index d4e4e8c5548..995169e2819 100644 --- a/tests/by-util/test_sum.rs +++ b/tests/by-util/test_sum.rs @@ -85,6 +85,7 @@ fn test_invalid_metadata() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_sum_non_utf8_paths() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index ae3e34dcd14..f35c19eb027 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -125,6 +125,7 @@ fn test_tee_multiple_append_flags() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_readonly() { let (at, mut ucmd) = at_and_ucmd!(); let content_tee = "hello"; @@ -146,6 +147,7 @@ fn test_readonly() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no pipe/signal support")] fn test_tee_output_not_buffered() { // POSIX says: The tee utility shall not buffer output @@ -191,7 +193,7 @@ fn test_tee_output_not_buffered() { handle.join().unwrap(); } -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", not(wasi_runner)))] mod linux_only { use uutests::util::{AtPath, CmdResult, UCommand}; diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index a9134b755bf..77fdb3404c6 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1504,6 +1504,7 @@ fn check_complement_set2_too_big() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_truncate_non_utf8_set() { let stdin = b"\x01amp\xfe\xff"; let set1 = OsStr::from_bytes(b"a\xfe\xffz"); // spell-checker:disable-line @@ -1595,6 +1596,7 @@ fn test_non_digit_repeat() { #[test] #[cfg(unix)] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_octal_escape_ambiguous_followed_by_non_utf8() { // This case does not trigger the panic let set1 = OsStr::from_bytes(b"\\501a"); @@ -1629,6 +1631,7 @@ fn test_failed_write_is_reported() { } #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no pipe/signal support")] fn test_broken_pipe_no_error() { new_ucmd!() .args(&["e", "a"]) diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index a875d158bd1..2cd2c477d50 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -439,6 +439,7 @@ fn test_negative_size_with_space() { #[cfg(not(windows))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no FIFO/mkfifo support")] fn test_fifo_error_size_only() { let (at, mut ucmd) = at_and_ucmd!(); at.mkfifo("fifo"); @@ -450,6 +451,7 @@ fn test_fifo_error_size_only() { #[cfg(not(windows))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no FIFO/mkfifo support")] fn test_fifo_error_reference_file_only() { let (at, mut ucmd) = at_and_ucmd!(); at.mkfifo("fifo"); @@ -462,6 +464,7 @@ fn test_fifo_error_reference_file_only() { #[cfg(not(windows))] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI: no FIFO/mkfifo support")] fn test_fifo_error_reference_and_size() { let (at, mut ucmd) = at_and_ucmd!(); at.mkfifo("fifo"); @@ -474,6 +477,7 @@ fn test_fifo_error_reference_and_size() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_truncate_non_utf8_paths() { use std::os::unix::ffi::OsStrExt; let ts = TestScenario::new(util_name!()); diff --git a/tests/by-util/test_unexpand.rs b/tests/by-util/test_unexpand.rs index c6d481f53c0..fef63fc4a41 100644 --- a/tests/by-util/test_unexpand.rs +++ b/tests/by-util/test_unexpand.rs @@ -285,6 +285,7 @@ fn test_one_nonexisting_file() { #[test] #[cfg(all(target_os = "linux", not(target_env = "musl")))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_read_error() { new_ucmd!() .arg("/proc/self/mem") @@ -294,6 +295,7 @@ fn test_read_error() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_non_utf8_filename() { use std::os::unix::ffi::OsStringExt; diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index 7a2b50ac3c4..310b1ee970c 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -78,6 +78,7 @@ fn test_unlink_symlink() { #[test] #[cfg(target_os = "linux")] +#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")] fn test_unlink_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index c3f972fe916..023e577b1ea 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -474,6 +474,7 @@ fn test_read_from_nonexistent_file() { #[test] #[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths (/proc) not visible")] fn test_files_from_pseudo_filesystem() { use pretty_assertions::assert_ne; let result = new_ucmd!().arg("-c").arg("/proc/cpuinfo").succeeds(); @@ -768,6 +769,7 @@ fn test_files0_progressive_stream() { #[cfg(target_os = "linux")] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_files0_stops_after_stdout_write_error() { use std::fs::OpenOptions; @@ -870,6 +872,7 @@ fn test_invalid_byte_sequence_word_count() { #[cfg(unix)] #[test] +#[cfg_attr(wasi_runner, ignore = "WASI sandbox: host paths not visible")] fn test_simd_respects_glibc_tunables() { // Ensure debug output reflects that SIMD paths are disabled via GLIBC_TUNABLES let debug_output = new_ucmd!() From 7d666b966e4bf4a997cdba33a5bcf67c8a8aa861 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Thu, 9 Apr 2026 22:08:06 +0200 Subject: [PATCH 5/5] Update jargon.wordlist.txt --- .vscode/cspell.dictionaries/jargon.wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index c4577adb5b3..1c3e6525c58 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -135,6 +135,7 @@ ROOTFS reparse rposition seedable +seekable semver semiprime semiprimes