Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion .github/workflows/wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,36 @@ 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: |
# Get all utilities and exclude ones that don't compile for wasm32-wasip1
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
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
# 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 -- \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this long list isn't ideal
we should #ignore them for wasi in the .rs files

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can change this if you feel strongly about it, but then we lose the centralized tracking of what's not yet supported, but soon could be.

Or, if a separate centralized list is maintained somewhere, it would need to be kept up to date with the #ignore annotations.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub has search feature.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can "grep" it easily in the code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tradeoff is that there would not be one place where the coverage is understandable at a glance, and that information would need to be aggregated from many files.

I'm happy to make whatever changes you require, but just wanted to make the tradeoff clear. Having a centralized account of gaps in support would be helpful for me, since I hope to expand WASI support for these tools over time.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to test annotations in the latest commit. Does this look good to you now?

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::
1 change: 1 addition & 0 deletions .vscode/cspell.dictionaries/acronyms+names.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,4 @@ Yargs

# Product
codspeed
wasmtime
2 changes: 2 additions & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ ROOTFS
reparse
rposition
seedable
seekable
semver
semiprime
semiprimes
Expand All @@ -158,6 +159,7 @@ SIGTTOU
sigttou
sigusr
strcasecmp
strcoll
subcommand
subexpression
submodule
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
33 changes: 33 additions & 0 deletions docs/src/wasi-test-gaps.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions tests/by-util/test_base64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!();
Expand Down Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions tests/by-util/test_basename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
17 changes: 9 additions & 8 deletions tests/by-util/test_basenc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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!();
Expand Down
69 changes: 40 additions & 29 deletions tests/by-util/test_comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,19 +451,21 @@ 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]
Expand All @@ -490,16 +492,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]
Expand All @@ -509,16 +514,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]
Expand Down Expand Up @@ -650,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;
Expand All @@ -675,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;
Expand Down Expand Up @@ -714,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")
Expand Down
3 changes: 3 additions & 0 deletions tests/by-util/test_cut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions tests/by-util/test_dirname.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions tests/by-util/test_echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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;
Expand Down Expand Up @@ -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");

Expand Down
2 changes: 2 additions & 0 deletions tests/by-util/test_expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/by-util/test_factor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
Loading