diff --git a/tests/rust/wasm32-wasip3/Cargo.lock b/tests/rust/wasm32-wasip3/Cargo.lock index fd650738..177033f0 100644 --- a/tests/rust/wasm32-wasip3/Cargo.lock +++ b/tests/rust/wasm32-wasip3/Cargo.lock @@ -26,6 +26,95 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -82,6 +171,18 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "prettyplease" version = "0.2.37" @@ -154,6 +255,12 @@ dependencies = [ "serde", ] +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "syn" version = "2.0.106" @@ -169,6 +276,7 @@ dependencies = [ name = "test-wasm32-wasip3" version = "0.1.0" dependencies = [ + "futures", "wit-bindgen", ] diff --git a/tests/rust/wasm32-wasip3/Cargo.toml b/tests/rust/wasm32-wasip3/Cargo.toml index b3674d7e..1eb6c6f9 100644 --- a/tests/rust/wasm32-wasip3/Cargo.toml +++ b/tests/rust/wasm32-wasip3/Cargo.toml @@ -5,3 +5,4 @@ edition = "2024" [dependencies] wit-bindgen = "0.48" +futures = "0.3.31" diff --git a/tests/rust/wasm32-wasip3/src/bin/filesystem-io.json b/tests/rust/wasm32-wasip3/src/bin/filesystem-io.json new file mode 100644 index 00000000..ff216a75 --- /dev/null +++ b/tests/rust/wasm32-wasip3/src/bin/filesystem-io.json @@ -0,0 +1,3 @@ +{ + "dirs": ["fs-tests.dir"] +} diff --git a/tests/rust/wasm32-wasip3/src/bin/filesystem-io.rs b/tests/rust/wasm32-wasip3/src/bin/filesystem-io.rs new file mode 100644 index 00000000..53126462 --- /dev/null +++ b/tests/rust/wasm32-wasip3/src/bin/filesystem-io.rs @@ -0,0 +1,226 @@ +use futures::join; +use std::process; +extern crate wit_bindgen; + +wit_bindgen::generate!({ + inline: r" + package test:test; + + world test { + include wasi:filesystem/imports@0.3.0-rc-2025-09-16; + include wasi:cli/command@0.3.0-rc-2025-09-16; + } +", + additional_derives: [PartialEq, Eq, Hash, Clone], + // Work around https://github.com/bytecodealliance/wasm-tools/issues/2285. + features:["clocks-timezone"], + generate_all +}); + +use wasi::filesystem::types::Descriptor; +use wasi::filesystem::types::{DescriptorFlags, ErrorCode, OpenFlags, PathFlags}; +use wit_bindgen::StreamResult; + +async fn pread(fd: &Descriptor, size: usize, offset: u64) -> Result, ErrorCode> { + let (mut rx, future) = fd.read_via_stream(offset); + let data = Vec::::with_capacity(size); + let mut bytes_read = 0; + let (mut result, mut data) = rx.read(data).await; + loop { + match result { + StreamResult::Complete(n) => { + assert!(n <= size - bytes_read); + bytes_read += n; + assert_eq!(data.len(), bytes_read); + if bytes_read == size { + break; + } + (result, data) = rx.read(data).await; + } + StreamResult::Dropped => { + // https://github.com/bytecodealliance/wit-bindgen/issues/1396 + assert!(data.len() >= bytes_read); + break; + } + StreamResult::Cancelled => { + panic!("who cancelled the stream?"); + } + } + } + drop(rx); + match future.await { + Ok(()) => Ok(data), + Err(err) => Err(err), + } +} + +async fn pwrite(fd: &Descriptor, offset: u64, data: &[u8]) -> Result { + let (mut tx, rx) = wit_stream::new(); + let future = fd.write_via_stream(rx, offset); + let len = data.len(); + let mut written: usize = 0; + let mut result: Result<(), ErrorCode> = Ok(()); + join! { + async { + let (mut result, mut buf) = tx.write(data.to_vec()).await; + loop { + match result { + StreamResult::Complete(n) => { + assert!(n <= len - written); + written += n; + assert_eq!(buf.remaining(), len - written); + if buf.remaining() != 0 { + (result, buf) = tx.write_buf(buf).await; + } else { + break; + } + } + StreamResult::Dropped => { + // https://github.com/bytecodealliance/wit-bindgen/issues/1396 + assert!(buf.remaining() <= len - written); + panic!("receiver dropped the stream?"); + } + StreamResult::Cancelled => { + break; + } + } + } + assert_eq!(buf.remaining(), len - written); + drop(tx); + }, + async { result = future.await; } + }; + match result { + Ok(()) => Ok(written), + Err(err) => Err(err), + } +} + +async fn pappend(fd: &Descriptor, data: &[u8]) -> Result { + let (mut tx, rx) = wit_stream::new(); + let future = fd.append_via_stream(rx); + let initial_size = fd.stat().await.unwrap().size as usize; + let len = data.len(); + let mut written: usize = 0; + let mut result: Result<(), ErrorCode> = Ok(()); + join! { + async { + let (mut result, mut buf) = tx.write(data.to_vec()).await; + loop { + match result { + StreamResult::Complete(n) => { + assert!(n <= len - written); + written += n; + assert_eq!(buf.remaining(), len - written); + assert_eq!(fd.stat().await.unwrap().size as usize, + initial_size + written); + if buf.remaining() != 0 { + (result, buf) = tx.write_buf(buf).await; + } else { + break; + } + } + StreamResult::Dropped => { + panic!("receiver dropped the stream?"); + } + StreamResult::Cancelled => { + break; + } + } + } + assert_eq!(buf.remaining(), len - written); + drop(tx); + }, + async { result = future.await; } + }; + match result { + Ok(()) => Ok(written), + Err(err) => Err(err), + } +} + +async fn read_to_eof(fd: &Descriptor, offset: u64) -> Vec { + let (stream, success) = fd.read_via_stream(offset); + let ret = stream.collect().await; + success.await.unwrap(); + ret +} + +async fn test_io(dir: &Descriptor) { + let open = |path: &str, oflags: OpenFlags, fdflags: DescriptorFlags| -> _ { + dir.open_at(PathFlags::empty(), path.to_string(), oflags, fdflags) + }; + let open_r = |path: &str| -> _ { open(path, OpenFlags::empty(), DescriptorFlags::READ) }; + let creat = |path: &str| -> _ { + open( + path, + OpenFlags::CREATE | OpenFlags::EXCLUSIVE, + DescriptorFlags::READ | DescriptorFlags::WRITE, + ) + }; + let rm = |path: &str| dir.unlink_file_at(path.to_string()); + + let a = open_r("a.txt").await.unwrap(); + + pread(&a, 0, 0).await.unwrap(); + pread(&a, 0, 1).await.unwrap(); + pread(&a, 0, 6).await.unwrap(); + pread(&a, 0, 7).await.unwrap(); + pread(&a, 0, 17).await.unwrap(); + + assert_eq!(&pread(&a, 1, 0).await.unwrap(), b"t"); + assert_eq!(&pread(&a, 1, 1).await.unwrap(), b"e"); + assert_eq!(&pread(&a, 1, 6).await.unwrap(), b"\n"); + assert_eq!(&pread(&a, 1, 7).await.unwrap(), b""); + assert_eq!(&pread(&a, 1, 17).await.unwrap(), b""); + + assert_eq!(&read_to_eof(&a, 0).await, b"test-a\n"); + assert_eq!(&read_to_eof(&a, 1).await, b"est-a\n"); + assert_eq!(&read_to_eof(&a, 6).await, b"\n"); + assert_eq!(&read_to_eof(&a, 7).await, b""); + assert_eq!(&read_to_eof(&a, 17).await, b""); + + // No-op on read-only fds. + a.sync_data().await.unwrap(); + a.sync().await.unwrap(); + + assert_eq!(pread(&a, 1, u64::MAX).await, Err(ErrorCode::Invalid)); + + let c = creat("c.cleanup").await.unwrap(); + assert_eq!(&read_to_eof(&c, 0).await, b""); + assert_eq!(pwrite(&c, 0, b"hello!").await, Ok(b"hello!".len())); + assert_eq!(&read_to_eof(&c, 0).await, b"hello!"); + assert_eq!(pwrite(&c, 0, b"byeee").await, Ok(b"byeee".len())); + assert_eq!(&read_to_eof(&c, 0).await, b"byeee!"); + assert_eq!(pappend(&c, b" laters!!").await, Ok(b" laters!!".len())); + assert_eq!(&read_to_eof(&c, 0).await, b"byeee! laters!!"); + c.sync_data().await.unwrap(); + assert_eq!( + &read_to_eof(&open_r("c.cleanup").await.unwrap(), 0).await, + b"byeee! laters!!" + ); + c.sync().await.unwrap(); + + rm("c.cleanup").await.unwrap(); +} + +struct Component; +export!(Component); +impl exports::wasi::cli::run::Guest for Component { + async fn run() -> Result<(), ()> { + match &wasi::filesystem::preopens::get_directories()[..] { + [(dir, dirname)] if dirname == "fs-tests.dir" => { + test_io(dir).await; + } + [..] => { + eprintln!("usage: run with one open dir named 'fs-tests.dir'"); + process::exit(1) + } + }; + Ok(()) + } +} + +fn main() { + unreachable!("main is a stub"); +}