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
1 change: 1 addition & 0 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ jobs:
- { os: ubuntu-latest , target: x86_64-unknown-netbsd, features: "feat_os_unix", use-cross: use-cross , skip-tests: true , check-only: true }
- { os: ubuntu-latest , target: x86_64-unknown-redox , features: feat_os_unix_redox , use-cross: redoxer , skip-tests: true , check-only: true }
- { os: ubuntu-latest , target: wasm32-wasip1, default-features: false, features: feat_wasm, skip-tests: true }
- { os: ubuntu-latest , target: wasm32-wasip2, default-features: false, features: feat_wasm, skip-tests: true }
- { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_unix, workspace-tests: true } # M1 CPU
# PR #7964: chcon should not break build without the feature. cargo check is enough to detect it.
- { os: macos-latest , target: aarch64-apple-darwin , workspace-tests: true, check-only: true } # M1 CPU
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
- { os: macos-latest , features: all , workspace: true }
- { os: windows-latest , features: feat_os_windows }
- { os: ubuntu-latest , features: feat_wasm , target: wasm32-wasip1 }
- { os: ubuntu-latest , features: feat_wasm , target: wasm32-wasip2 }
steps:
- uses: actions/checkout@v6
with:
Expand Down
35 changes: 22 additions & 13 deletions .github/workflows/wasi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,48 +20,57 @@ jobs:
test_wasi:
name: Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { target: wasm32-wasip1, rust-flags: "--cfg wasi_runner" }
- { target: wasm32-wasip2, rust-flags: "--cfg wasi_runner --cfg wasip2_runner" }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1
targets: ${{ matrix.job.target }}
- uses: Swatinem/rust-cache@v2
with:
key: "${{ matrix.job.target }}"
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Run unit tests
env:
CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime
run: |
# Get all utilities and exclude ones that don't compile for wasm32-wasip1
# Get all utilities and exclude ones that don't compile for ${{ matrix.job.target }}
EXCLUDE="df|du|env|expr|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
cargo test --target ${{ matrix.job.target }} --no-default-features $UTILS
- name: Run integration tests via wasmtime
env:
RUSTFLAGS: --cfg wasi_runner
RUSTFLAGS: ${{ matrix.job.rust-flags }}
run: |
# Build the WASI binary
cargo build --target wasm32-wasip1 --no-default-features --features feat_wasm
cargo build --target ${{ matrix.job.target }} --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
# arch b2sum cksum cp csplit date dir dircolors fmt join
# ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink
# realpath rm rmdir seq sha1sum sha224sum sha256sum sha384sum
# realpath 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_BINARY_PATH="$(pwd)/target/${{ matrix.job.target }}/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_ln:: test_nl:: test_numfmt:: \
test_od:: test_paste:: test_printf:: test_shuf:: test_sum:: \
test_tee:: test_tr:: test_true:: test_truncate:: \
test_cat:: test_comm:: test_cut:: test_dirname:: test_echo:: \
test_expand:: test_factor:: test_false:: test_fold:: test_head:: \
test_link:: test_ln:: test_nl:: test_numfmt:: test_od:: \
test_paste:: test_printf:: test_rm:: test_rmdir:: test_shuf:: \
test_sum:: test_tee:: test_tr:: test_true:: test_truncate:: \
test_unexpand:: test_unlink:: test_wc::
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(fuzzing)',
'cfg(target_os, values("cygwin"))',
'cfg(wasi_runner)',
'cfg(wasip2_runner)',
] }
unused_qualifications = "warn"

Expand Down
2 changes: 1 addition & 1 deletion docs/src/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The platforms in tier 1 and the platforms that we test in CI are listed below.
| **FreeBSD** | `x86_64-unknown-freebsd` |
| **OpenBSD** | `x86_64-unknown-openbsd` |
| **Android** | `x86_64-linux-android` |
| **wasm32** | `wasm32-wasip1` |
| **wasm32** | `wasm32-wasip1` <br> `wasm32-wasip2` |

The platforms in tier 2 are more vague, but include:

Expand Down
6 changes: 5 additions & 1 deletion docs/src/wasi-test-gaps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 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.
Tests annotated with `#[cfg_attr(wasi_runner, ignore = "...")]` or `#[cfg_attr(wasip2_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/`

Expand Down Expand Up @@ -35,3 +35,7 @@ When stdin is a seekable file, wasmtime does not preserve the file position betw
## WASI: read_link on absolute paths fails under wasmtime via spawned test harness

`fs::read_link` on an absolute path inside the sandbox (e.g. `/file2`) returns `EPERM` when the WASI binary is launched through `std::process::Command` from the test harness, even though the same call works when wasmtime is invoked directly. This breaks `uucore::fs::canonicalize` for symlink sources, so tests that rely on following a symlink to compute a relative path are skipped.

## WASI Preview2: exit with code has not been implemented

Until the [wasi:cli/exit#exit-with-code](https://github.com/WebAssembly/WASI/blob/a1fc383d01eabaf3fac01de03c0ab1a01bfdd099/proposals/cli/wit/exit.wit#L16) will be made available in Rust stable toolchain, we will have to consider error exit code only as 1.
11 changes: 8 additions & 3 deletions src/uu/yes/src/yes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,20 @@ pub fn uu_app() -> Command {
fn args_into_buffer<'a>(i: impl Iterator<Item = &'a OsString>) -> UResult<Vec<u8>> {
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "wasi")]
#[cfg(all(target_os = "wasi", target_env = "p1"))]
use std::os::wasi::ffi::OsStrExt;

let mut buf = Vec::with_capacity(BUF_SIZE);
// On Unix (and wasi), OsStrs are just &[u8]'s underneath...
#[cfg(any(unix, target_os = "wasi"))]
// On Unix (and wasip1), OsStrs are just &[u8]'s underneath...
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
for part in itertools::intersperse(i.map(|a| a.as_bytes()), b" ") {
buf.extend_from_slice(part);
}
// On WASI Preview2, OsStrs are guaranteed to be UTF-8 encoded
#[cfg(all(target_os = "wasi", target_env = "p2"))]
for part in itertools::intersperse(i.map(|a| a.as_encoded_bytes()), b" ") {
buf.extend_from_slice(part);
}
// But, on Windows, we must hop through a String.
#[cfg(not(any(unix, target_os = "wasi")))]
for part in itertools::intersperse(i.map(|a| a.to_str()), Some(" ")) {
Expand Down
15 changes: 12 additions & 3 deletions src/uucore/src/lib/features/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,12 +759,21 @@ pub fn are_hardlinks_or_one_way_symlink_to_same_file(source: &Path, target: &Pat
pub fn path_ends_with_terminator(path: &Path) -> bool {
#[cfg(unix)]
use std::os::unix::prelude::OsStrExt;
#[cfg(target_os = "wasi")]
#[cfg(all(target_os = "wasi", target_env = "p1"))]
use std::os::wasi::ffi::OsStrExt;
path.as_os_str()

#[cfg(all(target_os = "wasi", target_env = "p2"))]
return path
.as_os_str()
.as_encoded_bytes()
.last()
.is_some_and(|&byte| byte == b'/');
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
return path
.as_os_str()
.as_bytes()
.last()
.is_some_and(|&byte| byte == b'/')
.is_some_and(|&byte| byte == b'/');
}

#[cfg(windows)]
Expand Down
23 changes: 14 additions & 9 deletions src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ use std::io::{BufRead, BufReader};
use std::iter;
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(target_os = "wasi")]
#[cfg(all(target_os = "wasi", target_env = "p1"))]
use std::os::wasi::ffi::{OsStrExt, OsStringExt};
use std::str;
use std::str::Utf8Chunk;
Expand Down Expand Up @@ -454,9 +454,12 @@ impl error::UError for NonUtf8OsStrError {}
/// and fails on other platforms if the string can't be coerced to UTF-8.
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
return Ok(os_string.as_bytes());

#[cfg(all(target_os = "wasi", target_env = "p2"))]
return Ok(os_string.as_encoded_bytes());

#[cfg(not(any(unix, target_os = "wasi")))]
os_string
.to_str()
Expand All @@ -471,8 +474,10 @@ pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
/// This is always lossless on unix platforms,
/// and wraps [`OsStr::to_string_lossy`] on non-unix platforms.
pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
return Cow::from(os_string.as_bytes());
#[cfg(all(target_os = "wasi", target_env = "p2"))]
return Cow::from(os_string.as_encoded_bytes());

#[cfg(not(any(unix, target_os = "wasi")))]
match os_string.to_string_lossy() {
Expand All @@ -488,10 +493,10 @@ pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
/// and fails on other platforms if the bytes can't be parsed as UTF-8.
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));

#[cfg(not(any(unix, target_os = "wasi")))]
#[cfg(not(any(unix, all(target_os = "wasi", target_env = "p1"))))]
Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
|_| error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
)?)))
Expand All @@ -503,10 +508,10 @@ pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
/// and fails on other platforms if the bytes can't be parsed as UTF-8.
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
return Ok(OsString::from_vec(vec));

#[cfg(not(any(unix, target_os = "wasi")))]
#[cfg(not(any(unix, all(target_os = "wasi", target_env = "p1"))))]
Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
})?))
Expand All @@ -518,9 +523,9 @@ pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
/// and fails on other platforms if the bytes can't be parsed as UTF-8.
#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
pub fn os_string_to_vec(s: OsString) -> error::UResult<Vec<u8>> {
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
let v = s.into_vec();
#[cfg(not(any(unix, target_os = "wasi")))]
#[cfg(not(any(unix, all(target_os = "wasi", target_env = "p1"))))]
let v = s
.into_string()
.map_err(|_| {
Expand Down
8 changes: 6 additions & 2 deletions src/uucore/src/lib/mods/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use std::io::{self, BufWriter, Stdout, StdoutLock, Write as _};

#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "wasi")]
#[cfg(all(target_os = "wasi", target_env = "p1"))]
use std::os::wasi::ffi::OsStrExt;

// These used to be defined here, but they live in their own crate now.
Expand Down Expand Up @@ -76,10 +76,14 @@ pub trait OsWrite: io::Write {
/// On Windows, if the OS string is not valid Unicode, an error of kind
/// [`io::ErrorKind::InvalidData`] is returned.
fn write_all_os(&mut self, buf: &OsStr) -> io::Result<()> {
#[cfg(any(unix, target_os = "wasi"))]
#[cfg(any(unix, all(target_os = "wasi", target_env = "p1")))]
{
self.write_all(buf.as_bytes())
}
#[cfg(all(target_os = "wasi", target_env = "p2"))]
{
self.write_all(buf.as_encoded_bytes())
}

#[cfg(not(any(unix, target_os = "wasi")))]
{
Expand Down
20 changes: 16 additions & 4 deletions tests/by-util/test_cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ fn test_no_options_big_input() {

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore)]
fn test_fifo_symlink() {
use std::io::Write;
use std::thread;
Expand Down Expand Up @@ -138,6 +139,7 @@ fn test_closes_file_descriptors() {

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore)]
fn test_broken_pipe() {
let mut cmd = new_ucmd!();
let mut child = cmd
Expand Down Expand Up @@ -514,6 +516,7 @@ fn test_squeeze_blank_before_numbering() {
/// This tests reading from Unix character devices
#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore)]
fn test_dev_random() {
#[cfg(any(target_os = "linux", target_os = "android"))]
const DEV_RANDOM: &str = "/dev/urandom";
Expand Down Expand Up @@ -603,10 +606,15 @@ fn test_domain_socket() {
s.ucmd()
.args(&[socket_path])
.fails()
.stderr_contains("No such device or address");
.stderr_contains(if cfg!(wasi_runner) {
"No such file or directory"
} else {
"No such device or address"
});
}

#[test]
#[cfg_attr(wasi_runner, ignore)]
fn test_write_to_self_empty() {
// it's ok if the input file is also the output file if it's empty
let s = TestScenario::new(util_name!());
Expand All @@ -622,6 +630,7 @@ fn test_write_to_self_empty() {
}

#[test]
#[cfg_attr(wasi_runner, ignore)]
fn test_write_to_self() {
let s = TestScenario::new(util_name!());
let file_path = s.fixtures.plus("first_file");
Expand Down Expand Up @@ -678,6 +687,7 @@ fn test_successful_write_to_read_write_self() {
///
/// `cat fx fx3 1<>fx3`
#[test]
#[cfg_attr(wasi_runner, ignore)]
fn test_failed_write_to_read_write_self() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("fx", "g");
Expand Down Expand Up @@ -708,9 +718,11 @@ fn test_error_loop() {
at.symlink_file("2", "1");
at.symlink_file("3", "2");
at.symlink_file("1", "3");
ucmd.arg("1")
.fails()
.stderr_is("cat: 1: Too many levels of symbolic links\n");
ucmd.arg("1").fails().stderr_is(if cfg!(wasi_runner) {
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 is a regression :/

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.

We were not running test_cat for WASI so I just saw that it returns that. If you want, I can ignore this test for wasi_runner

"cat: 1: Operation not permitted\n"
} else {
"cat: 1: Too many levels of symbolic links\n"
});
}

#[test]
Expand Down
Loading
Loading