From 543fc6f877aea1ede31914cbeeaf85aaef69ca14 Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Thu, 8 Dec 2022 22:16:54 +0000 Subject: [PATCH 1/7] Copy some of the rust WASI tests from wasmtime --- tests/rust/.gitignore | 1 + tests/rust/Cargo.lock | 30 ++++ tests/rust/Cargo.toml | 11 ++ tests/rust/src/bin/big_random_buf.rs | 15 ++ tests/rust/src/bin/clock_time_get.rs | 17 ++ tests/rust/src/bin/close_preopen.rs | 71 +++++++++ tests/rust/src/bin/dangling_fd.rs | 53 ++++++ tests/rust/src/bin/dangling_symlink.rs | 52 ++++++ tests/rust/src/bin/directory_seek.rs | 71 +++++++++ tests/rust/src/bin/fd_advise.rs | 75 +++++++++ tests/rust/src/bin/fd_filestat_get.rs | 25 +++ tests/rust/src/bin/fd_filestat_set.rs | 73 +++++++++ tests/rust/src/bin/fd_flags_set.rs | 164 +++++++++++++++++++ tests/rust/src/bin/fd_readdir.rs | 213 +++++++++++++++++++++++++ tests/rust/src/config.rs | 69 ++++++++ tests/rust/src/lib.rs | 130 +++++++++++++++ 16 files changed, 1070 insertions(+) create mode 100644 tests/rust/.gitignore create mode 100644 tests/rust/Cargo.lock create mode 100644 tests/rust/Cargo.toml create mode 100644 tests/rust/src/bin/big_random_buf.rs create mode 100644 tests/rust/src/bin/clock_time_get.rs create mode 100644 tests/rust/src/bin/close_preopen.rs create mode 100644 tests/rust/src/bin/dangling_fd.rs create mode 100644 tests/rust/src/bin/dangling_symlink.rs create mode 100644 tests/rust/src/bin/directory_seek.rs create mode 100644 tests/rust/src/bin/fd_advise.rs create mode 100644 tests/rust/src/bin/fd_filestat_get.rs create mode 100644 tests/rust/src/bin/fd_filestat_set.rs create mode 100644 tests/rust/src/bin/fd_flags_set.rs create mode 100644 tests/rust/src/bin/fd_readdir.rs create mode 100644 tests/rust/src/config.rs create mode 100644 tests/rust/src/lib.rs diff --git a/tests/rust/.gitignore b/tests/rust/.gitignore new file mode 100644 index 00000000..9f970225 --- /dev/null +++ b/tests/rust/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/tests/rust/Cargo.lock b/tests/rust/Cargo.lock new file mode 100644 index 00000000..fc5aa2c5 --- /dev/null +++ b/tests/rust/Cargo.lock @@ -0,0 +1,30 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi_tests" +version = "0.1.0" +dependencies = [ + "libc", + "once_cell", + "wasi", +] diff --git a/tests/rust/Cargo.toml b/tests/rust/Cargo.toml new file mode 100644 index 00000000..d4004eab --- /dev/null +++ b/tests/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wasi_tests" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2.65" +wasi = "0.10.2" +once_cell = "1.12" \ No newline at end of file diff --git a/tests/rust/src/bin/big_random_buf.rs b/tests/rust/src/bin/big_random_buf.rs new file mode 100644 index 00000000..ad40497f --- /dev/null +++ b/tests/rust/src/bin/big_random_buf.rs @@ -0,0 +1,15 @@ +fn test_big_random_buf() { + let mut buf = Vec::new(); + buf.resize(1024, 0); + unsafe { + wasi::random_get(buf.as_mut_ptr(), 1024).expect("failed to call random_get"); + } + // Chances are pretty good that at least *one* byte will be non-zero in + // any meaningful random function producing 1024 u8 values. + assert!(buf.iter().any(|x| *x != 0), "random_get returned all zeros"); +} + +fn main() { + // Run the tests. + test_big_random_buf() +} diff --git a/tests/rust/src/bin/clock_time_get.rs b/tests/rust/src/bin/clock_time_get.rs new file mode 100644 index 00000000..fcfcd582 --- /dev/null +++ b/tests/rust/src/bin/clock_time_get.rs @@ -0,0 +1,17 @@ +unsafe fn test_clock_time_get() { + // Test that clock_time_get succeeds. Even in environments where it's not + // desirable to expose high-precision timers, it should still succeed. + // clock_res_get is where information about precision can be provided. + wasi::clock_time_get(wasi::CLOCKID_MONOTONIC, 1).expect("precision 1 should work"); + + let first_time = + wasi::clock_time_get(wasi::CLOCKID_MONOTONIC, 0).expect("precision 0 should work"); + + let time = wasi::clock_time_get(wasi::CLOCKID_MONOTONIC, 0).expect("re-fetch time should work"); + assert!(first_time <= time, "CLOCK_MONOTONIC should be monotonic"); +} + +fn main() { + // Run the tests. + unsafe { test_clock_time_get() } +} diff --git a/tests/rust/src/bin/close_preopen.rs b/tests/rust/src/bin/close_preopen.rs new file mode 100644 index 00000000..9066f064 --- /dev/null +++ b/tests/rust/src/bin/close_preopen.rs @@ -0,0 +1,71 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, open_scratch_directory}; + +unsafe fn test_close_preopen(dir_fd: wasi::Fd) { + let pre_fd: wasi::Fd = (libc::STDERR_FILENO + 1) as wasi::Fd; + + assert!(dir_fd > pre_fd, "dir_fd number"); + + // Try to close a preopened directory handle. + assert_errno!( + wasi::fd_close(pre_fd) + .expect_err("closing a preopened file descriptor") + .raw_error(), + wasi::ERRNO_NOTSUP + ); + + // Try to renumber over a preopened directory handle. + assert_errno!( + wasi::fd_renumber(dir_fd, pre_fd) + .expect_err("renumbering over a preopened file descriptor") + .raw_error(), + wasi::ERRNO_NOTSUP + ); + + // Ensure that dir_fd is still open. + let dir_fdstat = wasi::fd_fdstat_get(dir_fd).expect("failed fd_fdstat_get"); + assert_eq!( + dir_fdstat.fs_filetype, + wasi::FILETYPE_DIRECTORY, + "expected the scratch directory to be a directory", + ); + + // Try to renumber a preopened directory handle. + assert_errno!( + wasi::fd_renumber(pre_fd, dir_fd) + .expect_err("renumbering over a preopened file descriptor") + .raw_error(), + wasi::ERRNO_NOTSUP + ); + + // Ensure that dir_fd is still open. + let dir_fdstat = wasi::fd_fdstat_get(dir_fd).expect("failed fd_fdstat_get"); + assert_eq!( + dir_fdstat.fs_filetype, + wasi::FILETYPE_DIRECTORY, + "expected the scratch directory to be a directory", + ); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_close_preopen(dir_fd) } +} diff --git a/tests/rust/src/bin/dangling_fd.rs b/tests/rust/src/bin/dangling_fd.rs new file mode 100644 index 00000000..3d29c4fc --- /dev/null +++ b/tests/rust/src/bin/dangling_fd.rs @@ -0,0 +1,53 @@ +use std::{env, process}; +use wasi_tests::{open_scratch_directory, TESTCONFIG}; + +unsafe fn test_dangling_fd(dir_fd: wasi::Fd) { + if TESTCONFIG.support_dangling_filesystem() { + // Create a file, open it, delete it without closing the handle, + // and then try creating it again + let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); + wasi::fd_close(fd).unwrap(); + let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("failed to open"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink"); + let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); + wasi::fd_close(fd).unwrap(); + + // Now, repeat the same process but for a directory + wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir"); + let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect("failed to open dir"); + assert!( + subdir_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::path_remove_directory(dir_fd, "subdir").expect("failed to remove dir 2"); + wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir 2"); + } +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_dangling_fd(dir_fd) } +} diff --git a/tests/rust/src/bin/dangling_symlink.rs b/tests/rust/src/bin/dangling_symlink.rs new file mode 100644 index 00000000..bfc9095d --- /dev/null +++ b/tests/rust/src/bin/dangling_symlink.rs @@ -0,0 +1,52 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, open_scratch_directory, TESTCONFIG}; + +unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) { + if TESTCONFIG.support_dangling_filesystem() { + // First create a dangling symlink. + wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + + // Try to open it as a directory with O_NOFOLLOW. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect_err("opening a dangling symlink as a directory") + .raw_error(), + wasi::ERRNO_NOTDIR, + wasi::ERRNO_LOOP + ); + + // Try to open it as a file with O_NOFOLLOW. + assert_errno!( + wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0) + .expect_err("opening a dangling symlink as a file") + .raw_error(), + wasi::ERRNO_LOOP + ); + + // Clean up. + wasi::path_unlink_file(dir_fd, "symlink").expect("failed to remove file"); + } +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_dangling_symlink(dir_fd) } +} diff --git a/tests/rust/src/bin/directory_seek.rs b/tests/rust/src/bin/directory_seek.rs new file mode 100644 index 00000000..ab4b0dbc --- /dev/null +++ b/tests/rust/src/bin/directory_seek.rs @@ -0,0 +1,71 @@ +use std::{env, process}; +use wasi_tests::{assert_errno, open_scratch_directory}; + +unsafe fn test_directory_seek(dir_fd: wasi::Fd) { + // Create a directory in the scratch directory. + wasi::path_create_directory(dir_fd, "dir").expect("failed to make directory"); + + // Open the directory and attempt to request rights for seeking. + let fd = wasi::path_open( + dir_fd, + 0, + "dir", + wasi::OFLAGS_DIRECTORY, + wasi::RIGHTS_FD_SEEK, + 0, + 0, + ) + .expect("failed to open file"); + assert!( + fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Attempt to seek. + assert_errno!( + wasi::fd_seek(fd, 0, wasi::WHENCE_CUR) + .expect_err("seek on a directory") + .raw_error(), + wasi::ERRNO_BADF + ); + + // Check if we obtained the right to seek. + let fdstat = wasi::fd_fdstat_get(fd).expect("failed to fdstat"); + assert_eq!( + fdstat.fs_filetype, + wasi::FILETYPE_DIRECTORY, + "expected the scratch directory to be a directory", + ); + assert_eq!( + (fdstat.fs_rights_base & wasi::RIGHTS_FD_SEEK), + 0, + "directory does NOT have the seek right", + ); + + // Clean up. + wasi::fd_close(fd).expect("failed to close fd"); + wasi::path_remove_directory(dir_fd, "dir").expect("failed to remove dir"); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_directory_seek(dir_fd) } +} diff --git a/tests/rust/src/bin/fd_advise.rs b/tests/rust/src/bin/fd_advise.rs new file mode 100644 index 00000000..62346fdc --- /dev/null +++ b/tests/rust/src/bin/fd_advise.rs @@ -0,0 +1,75 @@ +use std::{env, process}; +use wasi_tests::{open_scratch_directory, TESTCONFIG}; + +unsafe fn test_fd_advise(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_ADVISE + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_FILESTAT_SET_SIZE + | wasi::RIGHTS_FD_ALLOCATE, + 0, + 0, + ) + .expect("failed to open file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Check file size + let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat"); + assert_eq!(stat.size, 0, "file size should be 0"); + + // set_size it bigger + wasi::fd_filestat_set_size(file_fd, 100).expect("setting size"); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 2"); + assert_eq!(stat.size, 100, "file size should be 100"); + + // Advise the kernel + wasi::fd_advise(file_fd, 10, 50, wasi::ADVICE_NORMAL).expect("failed advise"); + + // Advise shouldnt change size + let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 3"); + assert_eq!(stat.size, 100, "file size should be 100"); + + if TESTCONFIG.support_fd_allocate() { + // Use fd_allocate to expand size to 200: + wasi::fd_allocate(file_fd, 100, 100).expect("allocating size"); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed to fdstat 3"); + assert_eq!(stat.size, 200, "file size should be 200"); + } + + wasi::fd_close(file_fd).expect("failed to close"); + wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink"); +} +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_fd_advise(dir_fd) } +} diff --git a/tests/rust/src/bin/fd_filestat_get.rs b/tests/rust/src/bin/fd_filestat_get.rs new file mode 100644 index 00000000..d2ba27a1 --- /dev/null +++ b/tests/rust/src/bin/fd_filestat_get.rs @@ -0,0 +1,25 @@ +unsafe fn test_fd_filestat_get() { + + let stat = wasi::fd_filestat_get(libc::STDIN_FILENO as u32).expect("failed filestat 0"); + assert_eq!(stat.size, 0, "stdio size should be 0"); + assert_eq!(stat.atim, 0, "stdio atim should be 0"); + assert_eq!(stat.mtim, 0, "stdio mtim should be 0"); + assert_eq!(stat.ctim, 0, "stdio ctim should be 0"); + + let stat = wasi::fd_filestat_get(libc::STDOUT_FILENO as u32).expect("failed filestat 1"); + assert_eq!(stat.size, 0, "stdio size should be 0"); + assert_eq!(stat.atim, 0, "stdio atim should be 0"); + assert_eq!(stat.mtim, 0, "stdio mtim should be 0"); + assert_eq!(stat.ctim, 0, "stdio ctim should be 0"); + + let stat = wasi::fd_filestat_get(libc::STDERR_FILENO as u32).expect("failed filestat 2"); + assert_eq!(stat.size, 0, "stdio size should be 0"); + assert_eq!(stat.atim, 0, "stdio atim should be 0"); + assert_eq!(stat.mtim, 0, "stdio mtim should be 0"); + assert_eq!(stat.ctim, 0, "stdio ctim should be 0"); +} + +fn main() { + // Run the tests. + unsafe { test_fd_filestat_get() } +} diff --git a/tests/rust/src/bin/fd_filestat_set.rs b/tests/rust/src/bin/fd_filestat_set.rs new file mode 100644 index 00000000..424831b3 --- /dev/null +++ b/tests/rust/src/bin/fd_filestat_set.rs @@ -0,0 +1,73 @@ +use std::{env, process}; +use wasi_tests::open_scratch_directory; + +unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { + // Create a file in the scratch directory. + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_FILESTAT_SET_SIZE + | wasi::RIGHTS_FD_FILESTAT_SET_TIMES, + 0, + 0, + ) + .expect("failed to create file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + // Check file size + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat"); + assert_eq!(stat.size, 0, "file size should be 0"); + + // Check fd_filestat_set_size + wasi::fd_filestat_set_size(file_fd, 100).expect("fd_filestat_set_size"); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 2"); + assert_eq!(stat.size, 100, "file size should be 100"); + + // Check fd_filestat_set_times + let old_atim = stat.atim; + let new_mtim = stat.mtim - 100; + wasi::fd_filestat_set_times(file_fd, new_mtim, new_mtim, wasi::FSTFLAGS_MTIM) + .expect("fd_filestat_set_times"); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat 3"); + assert_eq!(stat.size, 100, "file size should remain unchanged at 100"); + assert_eq!(stat.mtim, new_mtim, "mtim should change"); + assert_eq!(stat.atim, old_atim, "atim should not change"); + + // let status = wasi_fd_filestat_set_times(file_fd, new_mtim, new_mtim, wasi::FILESTAT_SET_MTIM | wasi::FILESTAT_SET_MTIM_NOW); + // assert_eq!(status, wasi::EINVAL, "ATIM & ATIM_NOW can't both be set"); + + wasi::fd_close(file_fd).expect("failed to close fd"); + wasi::path_unlink_file(dir_fd, "file").expect("failed to remove dir"); +} +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_fd_filestat_set(dir_fd) } +} diff --git a/tests/rust/src/bin/fd_flags_set.rs b/tests/rust/src/bin/fd_flags_set.rs new file mode 100644 index 00000000..17586ef0 --- /dev/null +++ b/tests/rust/src/bin/fd_flags_set.rs @@ -0,0 +1,164 @@ +use std::{env, process}; +use wasi_tests::open_scratch_directory; + +unsafe fn test_fd_fdstat_set_flags(dir_fd: wasi::Fd) { + const FILE_NAME: &str = "file"; + let data = &[0u8; 100]; + + let file_fd = wasi::path_open( + dir_fd, + 0, + FILE_NAME, + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_SEEK + | wasi::RIGHTS_FD_TELL + | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS, + 0, + wasi::FDFLAGS_APPEND, + ) + .expect("opening a file"); + + // Write some data and then verify the written data + assert_eq!( + wasi::fd_write( + file_fd, + &[wasi::Ciovec { + buf: data.as_ptr(), + buf_len: data.len(), + }], + ) + .expect("writing to a file"), + data.len(), + "should write {} bytes", + data.len(), + ); + + wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET).expect("seeking file"); + + let buffer = &mut [0u8; 100]; + + assert_eq!( + wasi::fd_read( + file_fd, + &[wasi::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }] + ) + .expect("reading file"), + buffer.len(), + "should read {} bytes", + buffer.len() + ); + + assert_eq!(&data[..], &buffer[..]); + + let data = &[1u8; 100]; + + // Seek back to the start to ensure we're in append-only mode + wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET).expect("seeking file"); + + assert_eq!( + wasi::fd_write( + file_fd, + &[wasi::Ciovec { + buf: data.as_ptr(), + buf_len: data.len(), + }], + ) + .expect("writing to a file"), + data.len(), + "should write {} bytes", + data.len(), + ); + + wasi::fd_seek(file_fd, 100, wasi::WHENCE_SET).expect("seeking file"); + + assert_eq!( + wasi::fd_read( + file_fd, + &[wasi::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }] + ) + .expect("reading file"), + buffer.len(), + "should read {} bytes", + buffer.len() + ); + + assert_eq!(&data[..], &buffer[..]); + + wasi::fd_fdstat_set_flags(file_fd, 0).expect("disabling flags"); + + // Overwrite some existing data to ensure the append mode is now off + wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET).expect("seeking file"); + + let data = &[2u8; 100]; + + assert_eq!( + wasi::fd_write( + file_fd, + &[wasi::Ciovec { + buf: data.as_ptr(), + buf_len: data.len(), + }], + ) + .expect("writing to a file"), + data.len(), + "should write {} bytes", + data.len(), + ); + + wasi::fd_seek(file_fd, 0, wasi::WHENCE_SET).expect("seeking file"); + + assert_eq!( + wasi::fd_read( + file_fd, + &[wasi::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }] + ) + .expect("reading file"), + buffer.len(), + "should read {} bytes", + buffer.len() + ); + + assert_eq!(&data[..], &buffer[..]); + + wasi::fd_close(file_fd).expect("close file"); + + let stat = wasi::path_filestat_get(dir_fd, 0, FILE_NAME).expect("stat path"); + + assert_eq!(stat.size, 200, "expected a file size of 200"); + + wasi::path_unlink_file(dir_fd, FILE_NAME).expect("unlinking file"); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + unsafe { + test_fd_fdstat_set_flags(dir_fd); + } +} diff --git a/tests/rust/src/bin/fd_readdir.rs b/tests/rust/src/bin/fd_readdir.rs new file mode 100644 index 00000000..87aa112c --- /dev/null +++ b/tests/rust/src/bin/fd_readdir.rs @@ -0,0 +1,213 @@ +use std::{env, mem, process, slice, str}; +use wasi_tests::open_scratch_directory; + +const BUF_LEN: usize = 256; + +struct DirEntry { + dirent: wasi::Dirent, + name: String, +} + +// Manually reading the output from fd_readdir is tedious and repetitive, +// so encapsulate it into an iterator +struct ReadDir<'a> { + buf: &'a [u8], +} + +impl<'a> ReadDir<'a> { + fn from_slice(buf: &'a [u8]) -> Self { + Self { buf } + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = DirEntry; + + fn next(&mut self) -> Option { + unsafe { + if self.buf.len() < mem::size_of::() { + return None; + } + + // Read the data + let dirent_ptr = self.buf.as_ptr() as *const wasi::Dirent; + let dirent = dirent_ptr.read_unaligned(); + + if self.buf.len() < mem::size_of::() + dirent.d_namlen as usize { + return None; + } + + let name_ptr = dirent_ptr.offset(1) as *const u8; + // NOTE Linux syscall returns a NUL-terminated name, but WASI doesn't + let namelen = dirent.d_namlen as usize; + let slice = slice::from_raw_parts(name_ptr, namelen); + let name = str::from_utf8(slice).expect("invalid utf8").to_owned(); + + // Update the internal state + let delta = mem::size_of_val(&dirent) + namelen; + self.buf = &self.buf[delta..]; + + DirEntry { dirent, name }.into() + } + } +} + +/// Return the entries plus a bool indicating EOF. +unsafe fn exec_fd_readdir(fd: wasi::Fd, cookie: wasi::Dircookie) -> (Vec, bool) { + let mut buf: [u8; BUF_LEN] = [0; BUF_LEN]; + let bufused = + wasi::fd_readdir(fd, buf.as_mut_ptr(), BUF_LEN, cookie).expect("failed fd_readdir"); + assert!(bufused <= BUF_LEN); + + let sl = slice::from_raw_parts(buf.as_ptr(), bufused); + let dirs: Vec<_> = ReadDir::from_slice(sl).collect(); + let eof = bufused < BUF_LEN; + (dirs, eof) +} + +unsafe fn test_fd_readdir(dir_fd: wasi::Fd) { + let stat = wasi::fd_filestat_get(dir_fd).expect("failed filestat"); + + // Check the behavior in an empty directory + let (mut dirs, eof) = exec_fd_readdir(dir_fd, 0); + assert!(eof, "expected to read the entire directory"); + dirs.sort_by_key(|d| d.name.clone()); + assert_eq!(dirs.len(), 2, "expected two entries in an empty directory"); + let mut dirs = dirs.into_iter(); + + // the first entry should be `.` + let dir = dirs.next().expect("first entry is None"); + assert_eq!(dir.name, ".", "first name"); + assert_eq!(dir.dirent.d_type, wasi::FILETYPE_DIRECTORY, "first type"); + assert_eq!(dir.dirent.d_ino, stat.ino); + assert_eq!(dir.dirent.d_namlen, 1); + + // the second entry should be `..` + let dir = dirs.next().expect("second entry is None"); + assert_eq!(dir.name, "..", "second name"); + assert_eq!(dir.dirent.d_type, wasi::FILETYPE_DIRECTORY, "second type"); + + assert!( + dirs.next().is_none(), + "the directory should be seen as empty" + ); + + // Add a file and check the behavior + let file_fd = wasi::path_open( + dir_fd, + 0, + "file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_FD_FILESTAT_GET, + 0, + 0, + ) + .expect("failed to create file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + + let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat"); + wasi::fd_close(file_fd).expect("closing a file"); + + // Execute another readdir + let (mut dirs, eof) = exec_fd_readdir(dir_fd, 0); + assert!(eof, "expected to read the entire directory"); + assert_eq!(dirs.len(), 3, "expected three entries"); + // Save the data about the last entry. We need to do it before sorting. + let lastfile_cookie = dirs[1].dirent.d_next; + let lastfile_name = dirs[2].name.clone(); + dirs.sort_by_key(|d| d.name.clone()); + let mut dirs = dirs.into_iter(); + + let dir = dirs.next().expect("first entry is None"); + assert_eq!(dir.name, ".", "first name"); + let dir = dirs.next().expect("second entry is None"); + assert_eq!(dir.name, "..", "second name"); + let dir = dirs.next().expect("third entry is None"); + // check the file info + assert_eq!(dir.name, "file", "file name doesn't match"); + assert_eq!( + dir.dirent.d_type, + wasi::FILETYPE_REGULAR_FILE, + "type for the real file" + ); + assert_eq!(dir.dirent.d_ino, stat.ino); + + // check if cookie works as expected + let (dirs, eof) = exec_fd_readdir(dir_fd, lastfile_cookie); + assert!(eof, "expected to read the entire directory"); + assert_eq!(dirs.len(), 1, "expected one entry"); + assert_eq!(dirs[0].name, lastfile_name, "name of the only entry"); + + wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); +} + +unsafe fn test_fd_readdir_lots(dir_fd: wasi::Fd) { + // Add a file and check the behavior + for count in 0..1000 { + let file_fd = wasi::path_open( + dir_fd, + 0, + &format!("file.{}", count), + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_FD_FILESTAT_GET, + 0, + 0, + ) + .expect("failed to create file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::fd_close(file_fd).expect("closing a file"); + } + + // Count the entries to ensure that we see the correct number. + let mut total = 0; + let mut cookie = 0; + loop { + let (dirs, eof) = exec_fd_readdir(dir_fd, cookie); + total += dirs.len(); + if eof { + break; + } + cookie = dirs[dirs.len() - 1].dirent.d_next; + } + assert_eq!(total, 1002, "expected 1000 entries plus . and .."); + + for count in 0..1000 { + wasi::path_unlink_file(dir_fd, &format!("file.{}", count)).expect("removing a file"); + } +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {} ", prog); + process::exit(1); + }; + + // Open scratch directory + let dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("{}", err); + process::exit(1) + } + }; + + // Run the tests. + unsafe { test_fd_readdir(dir_fd) } + unsafe { test_fd_readdir_lots(dir_fd) } +} diff --git a/tests/rust/src/config.rs b/tests/rust/src/config.rs new file mode 100644 index 00000000..b2d095c5 --- /dev/null +++ b/tests/rust/src/config.rs @@ -0,0 +1,69 @@ +pub struct TestConfig { + errno_mode: ErrnoMode, + no_dangling_filesystem: bool, + no_fd_allocate: bool, + no_rename_dir_to_empty_dir: bool, + no_fdflags_sync_support: bool, +} + +enum ErrnoMode { + Unix, + MacOS, + Windows, + Permissive, +} + +impl TestConfig { + pub fn from_env() -> Self { + let errno_mode = if std::env::var("ERRNO_MODE_UNIX").is_ok() { + ErrnoMode::Unix + } else if std::env::var("ERRNO_MODE_MACOS").is_ok() { + ErrnoMode::MacOS + } else if std::env::var("ERRNO_MODE_WINDOWS").is_ok() { + ErrnoMode::Windows + } else { + ErrnoMode::Permissive + }; + let no_dangling_filesystem = std::env::var("NO_DANGLING_FILESYSTEM").is_ok(); + let no_fd_allocate = std::env::var("NO_FD_ALLOCATE").is_ok(); + let no_rename_dir_to_empty_dir = std::env::var("NO_RENAME_DIR_TO_EMPTY_DIR").is_ok(); + let no_fdflags_sync_support = std::env::var("NO_FDFLAGS_SYNC_SUPPORT").is_ok(); + TestConfig { + errno_mode, + no_dangling_filesystem, + no_fd_allocate, + no_rename_dir_to_empty_dir, + no_fdflags_sync_support, + } + } + pub fn errno_expect_unix(&self) -> bool { + match self.errno_mode { + ErrnoMode::Unix | ErrnoMode::MacOS => true, + _ => false, + } + } + pub fn errno_expect_macos(&self) -> bool { + match self.errno_mode { + ErrnoMode::MacOS => true, + _ => false, + } + } + pub fn errno_expect_windows(&self) -> bool { + match self.errno_mode { + ErrnoMode::Windows => true, + _ => false, + } + } + pub fn support_dangling_filesystem(&self) -> bool { + !self.no_dangling_filesystem + } + pub fn support_fd_allocate(&self) -> bool { + !self.no_fd_allocate + } + pub fn support_rename_dir_to_empty_dir(&self) -> bool { + !self.no_rename_dir_to_empty_dir + } + pub fn support_fdflags_sync(&self) -> bool { + !self.no_fdflags_sync_support + } +} diff --git a/tests/rust/src/lib.rs b/tests/rust/src/lib.rs new file mode 100644 index 00000000..bddd7c3c --- /dev/null +++ b/tests/rust/src/lib.rs @@ -0,0 +1,130 @@ +pub mod config; +use once_cell::sync::Lazy; + +pub static TESTCONFIG: Lazy = Lazy::new(config::TestConfig::from_env); + +// The `wasi` crate version 0.9.0 and beyond, doesn't +// seem to define these constants, so we do it ourselves. +pub const STDIN_FD: wasi::Fd = 0x0; +pub const STDOUT_FD: wasi::Fd = 0x1; +pub const STDERR_FD: wasi::Fd = 0x2; + +/// Opens a fresh file descriptor for `path` where `path` should be a preopened +/// directory. +pub fn open_scratch_directory(path: &str) -> Result { + unsafe { + for i in 3.. { + let stat = match wasi::fd_prestat_get(i) { + Ok(s) => s, + Err(_) => break, + }; + if stat.tag != wasi::PREOPENTYPE_DIR { + continue; + } + let mut dst = Vec::with_capacity(stat.u.dir.pr_name_len); + if wasi::fd_prestat_dir_name(i, dst.as_mut_ptr(), dst.capacity()).is_err() { + continue; + } + dst.set_len(stat.u.dir.pr_name_len); + if dst == path.as_bytes() { + let (base, inherit) = fd_get_rights(i); + return Ok( + wasi::path_open(i, 0, ".", wasi::OFLAGS_DIRECTORY, base, inherit, 0) + .expect("failed to open dir"), + ); + } + } + + Err(format!("failed to find scratch dir")) + } +} + +pub unsafe fn create_file(dir_fd: wasi::Fd, filename: &str) { + let file_fd = + wasi::path_open(dir_fd, 0, filename, wasi::OFLAGS_CREAT, 0, 0, 0).expect("creating a file"); + assert!( + file_fd > libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + wasi::fd_close(file_fd).expect("closing a file"); +} + +// Returns: (rights_base, rights_inheriting) +pub unsafe fn fd_get_rights(fd: wasi::Fd) -> (wasi::Rights, wasi::Rights) { + let fdstat = wasi::fd_fdstat_get(fd).expect("fd_fdstat_get failed"); + (fdstat.fs_rights_base, fdstat.fs_rights_inheriting) +} + +pub unsafe fn drop_rights(fd: wasi::Fd, drop_base: wasi::Rights, drop_inheriting: wasi::Rights) { + let (current_base, current_inheriting) = fd_get_rights(fd); + + let new_base = current_base & !drop_base; + let new_inheriting = current_inheriting & !drop_inheriting; + + wasi::fd_fdstat_set_rights(fd, new_base, new_inheriting).expect("dropping fd rights"); +} + +#[macro_export] +macro_rules! assert_errno { + ($s:expr, windows => $i:expr, $( $rest:tt )+) => { + let e = $s; + if $crate::TESTCONFIG.errno_expect_windows() { + assert_errno!(e, $i); + } else { + assert_errno!(e, $($rest)+, $i); + } + }; + ($s:expr, macos => $i:expr, $( $rest:tt )+) => { + let e = $s; + if $crate::TESTCONFIG.errno_expect_macos() { + assert_errno!(e, $i); + } else { + assert_errno!(e, $($rest)+, $i); + } + }; + ($s:expr, unix => $i:expr, $( $rest:tt )+) => { + let e = $s; + if $crate::TESTCONFIG.errno_expect_unix() { + assert_errno!(e, $i); + } else { + assert_errno!(e, $($rest)+, $i); + } + }; + ($s:expr, $( $i:expr ),+) => { + let e = $s; + { + // Pretty printing infrastructure + struct Alt<'a>(&'a [&'static str]); + impl<'a> std::fmt::Display for Alt<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let l = self.0.len(); + if l == 0 { + unreachable!() + } else if l == 1 { + f.write_str(self.0[0]) + } else if l == 2 { + f.write_str(self.0[0])?; + f.write_str(" or ")?; + f.write_str(self.0[1]) + } else { + for (ix, s) in self.0.iter().enumerate() { + if ix == l - 1 { + f.write_str("or ")?; + f.write_str(s)?; + } else { + f.write_str(s)?; + f.write_str(", ")?; + } + } + Ok(()) + } + } + } + assert!( $( e == $i || )+ false, + "expected errno {}; got {}", + Alt(&[ $( wasi::errno_name($i) ),+ ]), + wasi::errno_name(e), + ) + } + }; +} From 6f19d90b8f741a747cfd45a95be015b73e6e516d Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Thu, 8 Dec 2022 22:29:57 +0000 Subject: [PATCH 2/7] Add build script --- tests/rust/build.sh | 5 +++++ tests/rust/testsuite/.gitignore | 1 + 2 files changed, 6 insertions(+) create mode 100755 tests/rust/build.sh create mode 100644 tests/rust/testsuite/.gitignore diff --git a/tests/rust/build.sh b/tests/rust/build.sh new file mode 100755 index 00000000..40a0003f --- /dev/null +++ b/tests/rust/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cargo build --target=wasm32-wasi + +cp target/wasm32-wasi/debug/*.wasm testsuite/ \ No newline at end of file diff --git a/tests/rust/testsuite/.gitignore b/tests/rust/testsuite/.gitignore new file mode 100644 index 00000000..94a2dd14 --- /dev/null +++ b/tests/rust/testsuite/.gitignore @@ -0,0 +1 @@ +*.json \ No newline at end of file From 0ece56a85d9c065072575a54b4b7c79943608a3a Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Thu, 8 Dec 2022 22:51:35 +0000 Subject: [PATCH 3/7] Correct order of arguments in the wasmtime adapter --- adapters/wasmtime.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/wasmtime.sh b/adapters/wasmtime.sh index 1f99fdef..d66955a0 100755 --- a/adapters/wasmtime.sh +++ b/adapters/wasmtime.sh @@ -37,4 +37,4 @@ while [[ $# -gt 0 ]]; do esac done -wasmtime $TEST_FILE "${PROG_ARGS[@]}" "${ARGS[@]}" \ No newline at end of file +wasmtime $TEST_FILE "${ARGS[@]}" "${PROG_ARGS[@]}" \ No newline at end of file From c6cd0cf90db19ce7884b6ab262a13d6a405ce54c Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Thu, 8 Dec 2022 22:41:22 +0000 Subject: [PATCH 4/7] Add test configuration files --- tests/rust/testsuite/.gitignore | 1 - tests/rust/testsuite/close_preopen.json | 4 ++++ tests/rust/testsuite/dangling_fd.json | 4 ++++ tests/rust/testsuite/dangling_symlink.json | 4 ++++ tests/rust/testsuite/directory_seek.json | 4 ++++ tests/rust/testsuite/fd_advise.json | 4 ++++ tests/rust/testsuite/fd_filestat_set.json | 4 ++++ tests/rust/testsuite/fd_flags_set.json | 4 ++++ tests/rust/testsuite/fd_readdir.json | 4 ++++ tests/rust/testsuite/fs-tests.dir/.keep | 0 tests/rust/testsuite/manifest.json | 3 +++ 11 files changed, 35 insertions(+), 1 deletion(-) delete mode 100644 tests/rust/testsuite/.gitignore create mode 100644 tests/rust/testsuite/close_preopen.json create mode 100644 tests/rust/testsuite/dangling_fd.json create mode 100644 tests/rust/testsuite/dangling_symlink.json create mode 100644 tests/rust/testsuite/directory_seek.json create mode 100644 tests/rust/testsuite/fd_advise.json create mode 100644 tests/rust/testsuite/fd_filestat_set.json create mode 100644 tests/rust/testsuite/fd_flags_set.json create mode 100644 tests/rust/testsuite/fd_readdir.json create mode 100644 tests/rust/testsuite/fs-tests.dir/.keep create mode 100644 tests/rust/testsuite/manifest.json diff --git a/tests/rust/testsuite/.gitignore b/tests/rust/testsuite/.gitignore deleted file mode 100644 index 94a2dd14..00000000 --- a/tests/rust/testsuite/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.json \ No newline at end of file diff --git a/tests/rust/testsuite/close_preopen.json b/tests/rust/testsuite/close_preopen.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/close_preopen.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/dangling_fd.json b/tests/rust/testsuite/dangling_fd.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/dangling_fd.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/dangling_symlink.json b/tests/rust/testsuite/dangling_symlink.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/dangling_symlink.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/directory_seek.json b/tests/rust/testsuite/directory_seek.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/directory_seek.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/fd_advise.json b/tests/rust/testsuite/fd_advise.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/fd_advise.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/fd_filestat_set.json b/tests/rust/testsuite/fd_filestat_set.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/fd_filestat_set.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/fd_flags_set.json b/tests/rust/testsuite/fd_flags_set.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/fd_flags_set.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/fd_readdir.json b/tests/rust/testsuite/fd_readdir.json new file mode 100644 index 00000000..ef083796 --- /dev/null +++ b/tests/rust/testsuite/fd_readdir.json @@ -0,0 +1,4 @@ +{ + "dirs": ["fs-tests.dir"], + "args": ["fs-tests.dir"] +} \ No newline at end of file diff --git a/tests/rust/testsuite/fs-tests.dir/.keep b/tests/rust/testsuite/fs-tests.dir/.keep new file mode 100644 index 00000000..e69de29b diff --git a/tests/rust/testsuite/manifest.json b/tests/rust/testsuite/manifest.json new file mode 100644 index 00000000..af75c1f5 --- /dev/null +++ b/tests/rust/testsuite/manifest.json @@ -0,0 +1,3 @@ +{ + "name": "WASI Rust tests" +} \ No newline at end of file From 1acde463c4769da4c95f2a70d1bd33abf257d303 Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Thu, 8 Dec 2022 22:42:38 +0000 Subject: [PATCH 5/7] Add .cleanup suffix for files created in tests so the're automatically cleaned up by the test runner --- tests/rust/src/bin/dangling_fd.rs | 22 +++++++++------- tests/rust/src/bin/dangling_symlink.rs | 9 ++++--- tests/rust/src/bin/directory_seek.rs | 7 +++--- tests/rust/src/bin/fd_advise.rs | 5 ++-- tests/rust/src/bin/fd_filestat_set.rs | 5 ++-- tests/rust/src/bin/fd_flags_set.rs | 2 +- tests/rust/src/bin/fd_readdir.rs | 35 +++++++++++++++++++++++++- 7 files changed, 63 insertions(+), 22 deletions(-) diff --git a/tests/rust/src/bin/dangling_fd.rs b/tests/rust/src/bin/dangling_fd.rs index 3d29c4fc..79ae3b6e 100644 --- a/tests/rust/src/bin/dangling_fd.rs +++ b/tests/rust/src/bin/dangling_fd.rs @@ -3,29 +3,33 @@ use wasi_tests::{open_scratch_directory, TESTCONFIG}; unsafe fn test_dangling_fd(dir_fd: wasi::Fd) { if TESTCONFIG.support_dangling_filesystem() { + const FILE_NAME: &str = "dangling_fd_file.cleanup"; + const DIR_NAME: &str = "dangling_fd_subdir.cleanup"; // Create a file, open it, delete it without closing the handle, // and then try creating it again - let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); + let fd = wasi::path_open(dir_fd, 0, FILE_NAME, wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); wasi::fd_close(fd).unwrap(); - let file_fd = wasi::path_open(dir_fd, 0, "file", 0, 0, 0, 0).expect("failed to open"); + let file_fd = + wasi::path_open(dir_fd, 0, FILE_NAME, 0, 0, 0, 0).expect("failed to open"); assert!( file_fd > libc::STDERR_FILENO as wasi::Fd, "file descriptor range check", ); - wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink"); - let fd = wasi::path_open(dir_fd, 0, "file", wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); + wasi::path_unlink_file(dir_fd, FILE_NAME).expect("failed to unlink"); + let fd = wasi::path_open(dir_fd, 0, FILE_NAME, wasi::OFLAGS_CREAT, 0, 0, 0).unwrap(); wasi::fd_close(fd).unwrap(); // Now, repeat the same process but for a directory - wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir"); - let subdir_fd = wasi::path_open(dir_fd, 0, "subdir", wasi::OFLAGS_DIRECTORY, 0, 0, 0) - .expect("failed to open dir"); + wasi::path_create_directory(dir_fd, DIR_NAME).expect("failed to create dir"); + let subdir_fd = + wasi::path_open(dir_fd, 0, DIR_NAME, wasi::OFLAGS_DIRECTORY, 0, 0, 0) + .expect("failed to open dir"); assert!( subdir_fd > libc::STDERR_FILENO as wasi::Fd, "file descriptor range check", ); - wasi::path_remove_directory(dir_fd, "subdir").expect("failed to remove dir 2"); - wasi::path_create_directory(dir_fd, "subdir").expect("failed to create dir 2"); + wasi::path_remove_directory(dir_fd, DIR_NAME).expect("failed to remove dir 2"); + wasi::path_create_directory(dir_fd, DIR_NAME).expect("failed to create dir 2"); } } diff --git a/tests/rust/src/bin/dangling_symlink.rs b/tests/rust/src/bin/dangling_symlink.rs index bfc9095d..ea4dc5ed 100644 --- a/tests/rust/src/bin/dangling_symlink.rs +++ b/tests/rust/src/bin/dangling_symlink.rs @@ -3,12 +3,13 @@ use wasi_tests::{assert_errno, open_scratch_directory, TESTCONFIG}; unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) { if TESTCONFIG.support_dangling_filesystem() { + const SYMLINK_NAME: &str = "dangling_symlink_symlink.cleanup"; // First create a dangling symlink. - wasi::path_symlink("target", dir_fd, "symlink").expect("creating a symlink"); + wasi::path_symlink("target", dir_fd, SYMLINK_NAME).expect("creating a symlink"); // Try to open it as a directory with O_NOFOLLOW. assert_errno!( - wasi::path_open(dir_fd, 0, "symlink", wasi::OFLAGS_DIRECTORY, 0, 0, 0) + wasi::path_open(dir_fd, 0, SYMLINK_NAME, wasi::OFLAGS_DIRECTORY, 0, 0, 0) .expect_err("opening a dangling symlink as a directory") .raw_error(), wasi::ERRNO_NOTDIR, @@ -17,14 +18,14 @@ unsafe fn test_dangling_symlink(dir_fd: wasi::Fd) { // Try to open it as a file with O_NOFOLLOW. assert_errno!( - wasi::path_open(dir_fd, 0, "symlink", 0, 0, 0, 0) + wasi::path_open(dir_fd, 0, SYMLINK_NAME, 0, 0, 0, 0) .expect_err("opening a dangling symlink as a file") .raw_error(), wasi::ERRNO_LOOP ); // Clean up. - wasi::path_unlink_file(dir_fd, "symlink").expect("failed to remove file"); + wasi::path_unlink_file(dir_fd, SYMLINK_NAME).expect("failed to remove file"); } } diff --git a/tests/rust/src/bin/directory_seek.rs b/tests/rust/src/bin/directory_seek.rs index ab4b0dbc..0ae20abe 100644 --- a/tests/rust/src/bin/directory_seek.rs +++ b/tests/rust/src/bin/directory_seek.rs @@ -2,14 +2,15 @@ use std::{env, process}; use wasi_tests::{assert_errno, open_scratch_directory}; unsafe fn test_directory_seek(dir_fd: wasi::Fd) { + const DIR_NAME: &str = "directory_seek_dir.cleanup"; // Create a directory in the scratch directory. - wasi::path_create_directory(dir_fd, "dir").expect("failed to make directory"); + wasi::path_create_directory(dir_fd, DIR_NAME).expect("failed to make directory"); // Open the directory and attempt to request rights for seeking. let fd = wasi::path_open( dir_fd, 0, - "dir", + DIR_NAME, wasi::OFLAGS_DIRECTORY, wasi::RIGHTS_FD_SEEK, 0, @@ -44,7 +45,7 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) { // Clean up. wasi::fd_close(fd).expect("failed to close fd"); - wasi::path_remove_directory(dir_fd, "dir").expect("failed to remove dir"); + wasi::path_remove_directory(dir_fd, DIR_NAME).expect("failed to remove dir"); } fn main() { diff --git a/tests/rust/src/bin/fd_advise.rs b/tests/rust/src/bin/fd_advise.rs index 62346fdc..40fb74e7 100644 --- a/tests/rust/src/bin/fd_advise.rs +++ b/tests/rust/src/bin/fd_advise.rs @@ -2,11 +2,12 @@ use std::{env, process}; use wasi_tests::{open_scratch_directory, TESTCONFIG}; unsafe fn test_fd_advise(dir_fd: wasi::Fd) { + const FILE_NAME: &str = "fd_advise_file.cleanup"; // Create a file in the scratch directory. let file_fd = wasi::path_open( dir_fd, 0, - "file", + FILE_NAME, wasi::OFLAGS_CREAT, wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE @@ -49,7 +50,7 @@ unsafe fn test_fd_advise(dir_fd: wasi::Fd) { } wasi::fd_close(file_fd).expect("failed to close"); - wasi::path_unlink_file(dir_fd, "file").expect("failed to unlink"); + wasi::path_unlink_file(dir_fd, FILE_NAME).expect("failed to unlink"); } fn main() { let mut args = env::args(); diff --git a/tests/rust/src/bin/fd_filestat_set.rs b/tests/rust/src/bin/fd_filestat_set.rs index 424831b3..84b9b1a8 100644 --- a/tests/rust/src/bin/fd_filestat_set.rs +++ b/tests/rust/src/bin/fd_filestat_set.rs @@ -2,11 +2,12 @@ use std::{env, process}; use wasi_tests::open_scratch_directory; unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { + const FILE_NAME: &str = "fd_filestat_set_file.cleanup"; // Create a file in the scratch directory. let file_fd = wasi::path_open( dir_fd, 0, - "file", + FILE_NAME, wasi::OFLAGS_CREAT, wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE @@ -47,7 +48,7 @@ unsafe fn test_fd_filestat_set(dir_fd: wasi::Fd) { // assert_eq!(status, wasi::EINVAL, "ATIM & ATIM_NOW can't both be set"); wasi::fd_close(file_fd).expect("failed to close fd"); - wasi::path_unlink_file(dir_fd, "file").expect("failed to remove dir"); + wasi::path_unlink_file(dir_fd, FILE_NAME).expect("failed to remove dir"); } fn main() { let mut args = env::args(); diff --git a/tests/rust/src/bin/fd_flags_set.rs b/tests/rust/src/bin/fd_flags_set.rs index 17586ef0..0ef3b586 100644 --- a/tests/rust/src/bin/fd_flags_set.rs +++ b/tests/rust/src/bin/fd_flags_set.rs @@ -2,7 +2,7 @@ use std::{env, process}; use wasi_tests::open_scratch_directory; unsafe fn test_fd_fdstat_set_flags(dir_fd: wasi::Fd) { - const FILE_NAME: &str = "file"; + const FILE_NAME: &str = "fd_flags_set_file.cleanup"; let data = &[0u8; 100]; let file_fd = wasi::path_open( diff --git a/tests/rust/src/bin/fd_readdir.rs b/tests/rust/src/bin/fd_readdir.rs index 87aa112c..99c50dc3 100644 --- a/tests/rust/src/bin/fd_readdir.rs +++ b/tests/rust/src/bin/fd_readdir.rs @@ -1,4 +1,5 @@ use std::{env, mem, process, slice, str}; +use wasi::path_create_directory; use wasi_tests::open_scratch_directory; const BUF_LEN: usize = 256; @@ -52,6 +53,28 @@ impl<'a> Iterator for ReadDir<'a> { } } +unsafe fn create_tmp_dir(dir_fd: wasi::Fd, name: &str) -> wasi::Fd { + path_create_directory(dir_fd, name).expect("failed to create dir"); + wasi::path_open( + dir_fd, + 0, + name, + wasi::OFLAGS_DIRECTORY, + wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_PATH_CREATE_FILE + | wasi::RIGHTS_PATH_OPEN + | wasi::RIGHTS_PATH_UNLINK_FILE, + wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_SEEK, + 0, + ) + .expect("failed to open dir") +} + /// Return the entries plus a bool indicating EOF. unsafe fn exec_fd_readdir(fd: wasi::Fd, cookie: wasi::Dircookie) -> (Vec, bool) { let mut buf: [u8; BUF_LEN] = [0; BUF_LEN]; @@ -199,7 +222,7 @@ fn main() { }; // Open scratch directory - let dir_fd = match open_scratch_directory(&arg) { + let base_dir_fd = match open_scratch_directory(&arg) { Ok(dir_fd) => dir_fd, Err(err) => { eprintln!("{}", err); @@ -207,7 +230,17 @@ fn main() { } }; + const DIR_NAME: &str = "fd_readdir_dir.cleanup"; + let dir_fd; + unsafe { + dir_fd = create_tmp_dir(base_dir_fd, DIR_NAME); + } + // Run the tests. unsafe { test_fd_readdir(dir_fd) } unsafe { test_fd_readdir_lots(dir_fd) } + + unsafe { + wasi::path_remove_directory(base_dir_fd, DIR_NAME).expect("failed to remove dir") + } } From c49ea8c49b0d5d260545a391c1504766e4eace92 Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Tue, 3 Jan 2023 17:51:48 +0000 Subject: [PATCH 6/7] Use `ISDIR` and `NOTCAPABLE` expected value for directory_seek test The return value is not documented so we allow for a few reasonable error codes here. --- tests/rust/src/bin/directory_seek.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/rust/src/bin/directory_seek.rs b/tests/rust/src/bin/directory_seek.rs index 0ae20abe..ccc460fa 100644 --- a/tests/rust/src/bin/directory_seek.rs +++ b/tests/rust/src/bin/directory_seek.rs @@ -27,6 +27,8 @@ unsafe fn test_directory_seek(dir_fd: wasi::Fd) { wasi::fd_seek(fd, 0, wasi::WHENCE_CUR) .expect_err("seek on a directory") .raw_error(), + wasi::ERRNO_ISDIR, + wasi::ERRNO_NOTCAPABLE, wasi::ERRNO_BADF ); From 0b3931406275b18a4e98ab135243361f7deae3c7 Mon Sep 17 00:00:00 2001 From: Marcin Kolny Date: Tue, 3 Jan 2023 17:52:53 +0000 Subject: [PATCH 7/7] Set some flags for build scripts So we fail the script if one of the test doesn't compile --- tests/c/build.sh | 1 + tests/rust/build.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/c/build.sh b/tests/c/build.sh index 01188f5f..3372ffbb 100755 --- a/tests/c/build.sh +++ b/tests/c/build.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -ueo pipefail CC=${CC:=clang} diff --git a/tests/rust/build.sh b/tests/rust/build.sh index 40a0003f..3db1e359 100755 --- a/tests/rust/build.sh +++ b/tests/rust/build.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -ueo pipefail cargo build --target=wasm32-wasi