Skip to content

Commit a70bca8

Browse files
authored
Merge pull request #135 from wingo/filesystem-io
wasi:filesystem@0.3.0-rc-2025-09-16: Add tests for read/write/append
2 parents 571a8fc + d730bb9 commit a70bca8

File tree

4 files changed

+338
-0
lines changed

4 files changed

+338
-0
lines changed

tests/rust/wasm32-wasip3/Cargo.lock

Lines changed: 108 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/rust/wasm32-wasip3/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ edition = "2024"
55

66
[dependencies]
77
wit-bindgen = "0.48"
8+
futures = "0.3.31"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dirs": ["fs-tests.dir"]
3+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
use futures::join;
2+
use std::process;
3+
extern crate wit_bindgen;
4+
5+
wit_bindgen::generate!({
6+
inline: r"
7+
package test:test;
8+
9+
world test {
10+
include wasi:filesystem/imports@0.3.0-rc-2025-09-16;
11+
include wasi:cli/command@0.3.0-rc-2025-09-16;
12+
}
13+
",
14+
additional_derives: [PartialEq, Eq, Hash, Clone],
15+
// Work around https://github.com/bytecodealliance/wasm-tools/issues/2285.
16+
features:["clocks-timezone"],
17+
generate_all
18+
});
19+
20+
use wasi::filesystem::types::Descriptor;
21+
use wasi::filesystem::types::{DescriptorFlags, ErrorCode, OpenFlags, PathFlags};
22+
use wit_bindgen::StreamResult;
23+
24+
async fn pread(fd: &Descriptor, size: usize, offset: u64) -> Result<Vec<u8>, ErrorCode> {
25+
let (mut rx, future) = fd.read_via_stream(offset);
26+
let data = Vec::<u8>::with_capacity(size);
27+
let mut bytes_read = 0;
28+
let (mut result, mut data) = rx.read(data).await;
29+
loop {
30+
match result {
31+
StreamResult::Complete(n) => {
32+
assert!(n <= size - bytes_read);
33+
bytes_read += n;
34+
assert_eq!(data.len(), bytes_read);
35+
if bytes_read == size {
36+
break;
37+
}
38+
(result, data) = rx.read(data).await;
39+
}
40+
StreamResult::Dropped => {
41+
// https://github.com/bytecodealliance/wit-bindgen/issues/1396
42+
assert!(data.len() >= bytes_read);
43+
break;
44+
}
45+
StreamResult::Cancelled => {
46+
panic!("who cancelled the stream?");
47+
}
48+
}
49+
}
50+
drop(rx);
51+
match future.await {
52+
Ok(()) => Ok(data),
53+
Err(err) => Err(err),
54+
}
55+
}
56+
57+
async fn pwrite(fd: &Descriptor, offset: u64, data: &[u8]) -> Result<usize, ErrorCode> {
58+
let (mut tx, rx) = wit_stream::new();
59+
let future = fd.write_via_stream(rx, offset);
60+
let len = data.len();
61+
let mut written: usize = 0;
62+
let mut result: Result<(), ErrorCode> = Ok(());
63+
join! {
64+
async {
65+
let (mut result, mut buf) = tx.write(data.to_vec()).await;
66+
loop {
67+
match result {
68+
StreamResult::Complete(n) => {
69+
assert!(n <= len - written);
70+
written += n;
71+
assert_eq!(buf.remaining(), len - written);
72+
if buf.remaining() != 0 {
73+
(result, buf) = tx.write_buf(buf).await;
74+
} else {
75+
break;
76+
}
77+
}
78+
StreamResult::Dropped => {
79+
// https://github.com/bytecodealliance/wit-bindgen/issues/1396
80+
assert!(buf.remaining() <= len - written);
81+
panic!("receiver dropped the stream?");
82+
}
83+
StreamResult::Cancelled => {
84+
break;
85+
}
86+
}
87+
}
88+
assert_eq!(buf.remaining(), len - written);
89+
drop(tx);
90+
},
91+
async { result = future.await; }
92+
};
93+
match result {
94+
Ok(()) => Ok(written),
95+
Err(err) => Err(err),
96+
}
97+
}
98+
99+
async fn pappend(fd: &Descriptor, data: &[u8]) -> Result<usize, ErrorCode> {
100+
let (mut tx, rx) = wit_stream::new();
101+
let future = fd.append_via_stream(rx);
102+
let initial_size = fd.stat().await.unwrap().size as usize;
103+
let len = data.len();
104+
let mut written: usize = 0;
105+
let mut result: Result<(), ErrorCode> = Ok(());
106+
join! {
107+
async {
108+
let (mut result, mut buf) = tx.write(data.to_vec()).await;
109+
loop {
110+
match result {
111+
StreamResult::Complete(n) => {
112+
assert!(n <= len - written);
113+
written += n;
114+
assert_eq!(buf.remaining(), len - written);
115+
assert_eq!(fd.stat().await.unwrap().size as usize,
116+
initial_size + written);
117+
if buf.remaining() != 0 {
118+
(result, buf) = tx.write_buf(buf).await;
119+
} else {
120+
break;
121+
}
122+
}
123+
StreamResult::Dropped => {
124+
panic!("receiver dropped the stream?");
125+
}
126+
StreamResult::Cancelled => {
127+
break;
128+
}
129+
}
130+
}
131+
assert_eq!(buf.remaining(), len - written);
132+
drop(tx);
133+
},
134+
async { result = future.await; }
135+
};
136+
match result {
137+
Ok(()) => Ok(written),
138+
Err(err) => Err(err),
139+
}
140+
}
141+
142+
async fn read_to_eof(fd: &Descriptor, offset: u64) -> Vec<u8> {
143+
let (stream, success) = fd.read_via_stream(offset);
144+
let ret = stream.collect().await;
145+
success.await.unwrap();
146+
ret
147+
}
148+
149+
async fn test_io(dir: &Descriptor) {
150+
let open = |path: &str, oflags: OpenFlags, fdflags: DescriptorFlags| -> _ {
151+
dir.open_at(PathFlags::empty(), path.to_string(), oflags, fdflags)
152+
};
153+
let open_r = |path: &str| -> _ { open(path, OpenFlags::empty(), DescriptorFlags::READ) };
154+
let creat = |path: &str| -> _ {
155+
open(
156+
path,
157+
OpenFlags::CREATE | OpenFlags::EXCLUSIVE,
158+
DescriptorFlags::READ | DescriptorFlags::WRITE,
159+
)
160+
};
161+
let rm = |path: &str| dir.unlink_file_at(path.to_string());
162+
163+
let a = open_r("a.txt").await.unwrap();
164+
165+
pread(&a, 0, 0).await.unwrap();
166+
pread(&a, 0, 1).await.unwrap();
167+
pread(&a, 0, 6).await.unwrap();
168+
pread(&a, 0, 7).await.unwrap();
169+
pread(&a, 0, 17).await.unwrap();
170+
171+
assert_eq!(&pread(&a, 1, 0).await.unwrap(), b"t");
172+
assert_eq!(&pread(&a, 1, 1).await.unwrap(), b"e");
173+
assert_eq!(&pread(&a, 1, 6).await.unwrap(), b"\n");
174+
assert_eq!(&pread(&a, 1, 7).await.unwrap(), b"");
175+
assert_eq!(&pread(&a, 1, 17).await.unwrap(), b"");
176+
177+
assert_eq!(&read_to_eof(&a, 0).await, b"test-a\n");
178+
assert_eq!(&read_to_eof(&a, 1).await, b"est-a\n");
179+
assert_eq!(&read_to_eof(&a, 6).await, b"\n");
180+
assert_eq!(&read_to_eof(&a, 7).await, b"");
181+
assert_eq!(&read_to_eof(&a, 17).await, b"");
182+
183+
// No-op on read-only fds.
184+
a.sync_data().await.unwrap();
185+
a.sync().await.unwrap();
186+
187+
assert_eq!(pread(&a, 1, u64::MAX).await, Err(ErrorCode::Invalid));
188+
189+
let c = creat("c.cleanup").await.unwrap();
190+
assert_eq!(&read_to_eof(&c, 0).await, b"");
191+
assert_eq!(pwrite(&c, 0, b"hello!").await, Ok(b"hello!".len()));
192+
assert_eq!(&read_to_eof(&c, 0).await, b"hello!");
193+
assert_eq!(pwrite(&c, 0, b"byeee").await, Ok(b"byeee".len()));
194+
assert_eq!(&read_to_eof(&c, 0).await, b"byeee!");
195+
assert_eq!(pappend(&c, b" laters!!").await, Ok(b" laters!!".len()));
196+
assert_eq!(&read_to_eof(&c, 0).await, b"byeee! laters!!");
197+
c.sync_data().await.unwrap();
198+
assert_eq!(
199+
&read_to_eof(&open_r("c.cleanup").await.unwrap(), 0).await,
200+
b"byeee! laters!!"
201+
);
202+
c.sync().await.unwrap();
203+
204+
rm("c.cleanup").await.unwrap();
205+
}
206+
207+
struct Component;
208+
export!(Component);
209+
impl exports::wasi::cli::run::Guest for Component {
210+
async fn run() -> Result<(), ()> {
211+
match &wasi::filesystem::preopens::get_directories()[..] {
212+
[(dir, dirname)] if dirname == "fs-tests.dir" => {
213+
test_io(dir).await;
214+
}
215+
[..] => {
216+
eprintln!("usage: run with one open dir named 'fs-tests.dir'");
217+
process::exit(1)
218+
}
219+
};
220+
Ok(())
221+
}
222+
}
223+
224+
fn main() {
225+
unreachable!("main is a stub");
226+
}

0 commit comments

Comments
 (0)