1 #![expect(unsafe_op_in_unsafe_fn, reason = "old code, not worth updating yet")]
2
3 use std::{env, mem::MaybeUninit, process};
4 use test_programs::preview1::{assert_errno, open_scratch_directory};
5
6 const CLOCK_ID: wasip1::Userdata = 0x0123_45678;
7
poll_oneoff_impl( r#in: &[wasip1::Subscription], ) -> Result<Vec<wasip1::Event>, wasip1::Errno>8 unsafe fn poll_oneoff_impl(
9 r#in: &[wasip1::Subscription],
10 ) -> Result<Vec<wasip1::Event>, wasip1::Errno> {
11 let mut out: Vec<wasip1::Event> = Vec::new();
12 out.resize_with(r#in.len(), || {
13 MaybeUninit::<wasip1::Event>::zeroed().assume_init()
14 });
15 let size = wasip1::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())?;
16 out.truncate(size);
17 Ok(out)
18 }
19
20 /// Repeatedly call `poll_oneoff` until all the subscriptions in `in` have
21 /// seen their events occur.
poll_oneoff_with_retry( r#in: &[wasip1::Subscription], ) -> Result<Vec<wasip1::Event>, wasip1::Errno>22 unsafe fn poll_oneoff_with_retry(
23 r#in: &[wasip1::Subscription],
24 ) -> Result<Vec<wasip1::Event>, wasip1::Errno> {
25 let mut subscriptions = r#in.to_vec();
26 let mut events = Vec::new();
27 while !subscriptions.is_empty() {
28 let mut out: Vec<wasip1::Event> = Vec::new();
29 out.resize_with(subscriptions.len(), || {
30 MaybeUninit::<wasip1::Event>::zeroed().assume_init()
31 });
32 let size = wasip1::poll_oneoff(
33 subscriptions.as_ptr(),
34 out.as_mut_ptr(),
35 subscriptions.len(),
36 )?;
37 out.truncate(size);
38
39 // Append the events from this `poll` to the result.
40 events.extend_from_slice(&out);
41
42 // Assuming userdata fields are unique, filter out any subscriptions
43 // whose event has occurred.
44 subscriptions.retain(|sub| !out.iter().any(|event| event.userdata == sub.userdata));
45 }
46 Ok(events)
47 }
48
test_empty_poll()49 unsafe fn test_empty_poll() {
50 let r#in = [];
51 let mut out: Vec<wasip1::Event> = Vec::new();
52 assert_errno!(
53 wasip1::poll_oneoff(r#in.as_ptr(), out.as_mut_ptr(), r#in.len())
54 .expect_err("empty poll_oneoff should fail"),
55 wasip1::ERRNO_INVAL
56 );
57 }
58
test_timeout()59 unsafe fn test_timeout() {
60 let timeout = 5_000_000u64; // 5 milliseconds
61 let clock = wasip1::SubscriptionClock {
62 id: wasip1::CLOCKID_MONOTONIC,
63 timeout,
64 precision: 0,
65 flags: 0,
66 };
67 let r#in = [wasip1::Subscription {
68 userdata: CLOCK_ID,
69 u: wasip1::SubscriptionU {
70 tag: wasip1::EVENTTYPE_CLOCK.raw(),
71 u: wasip1::SubscriptionUU { clock },
72 },
73 }];
74 let before = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
75 let out = poll_oneoff_impl(&r#in).unwrap();
76 let after = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
77 assert_eq!(out.len(), 1, "should return 1 event");
78 let event = &out[0];
79 assert_errno!(event.error, wasip1::ERRNO_SUCCESS);
80 assert_eq!(
81 event.type_,
82 wasip1::EVENTTYPE_CLOCK,
83 "the event.type should equal clock"
84 );
85 assert_eq!(
86 event.userdata, CLOCK_ID,
87 "the event.userdata should contain clock_id specified by the user"
88 );
89 assert!(
90 after - before >= timeout,
91 "poll_oneoff should sleep for the specified interval of {timeout}. before: {before}, after: {after}"
92 );
93 }
94
95 // Like test_timeout, but uses `CLOCKID_REALTIME`, as WASI libc's sleep
96 // functions do.
test_sleep()97 unsafe fn test_sleep() {
98 let timeout = 5_000_000u64; // 5 milliseconds
99 let clock = wasip1::SubscriptionClock {
100 id: wasip1::CLOCKID_REALTIME,
101 timeout,
102 precision: 0,
103 flags: 0,
104 };
105 let r#in = [wasip1::Subscription {
106 userdata: CLOCK_ID,
107 u: wasip1::SubscriptionU {
108 tag: wasip1::EVENTTYPE_CLOCK.raw(),
109 u: wasip1::SubscriptionUU { clock },
110 },
111 }];
112 let before = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
113 let out = poll_oneoff_impl(&r#in).unwrap();
114 let after = wasip1::clock_time_get(wasip1::CLOCKID_MONOTONIC, 0).unwrap();
115 assert_eq!(out.len(), 1, "should return 1 event");
116 let event = &out[0];
117 assert_errno!(event.error, wasip1::ERRNO_SUCCESS);
118 assert_eq!(
119 event.type_,
120 wasip1::EVENTTYPE_CLOCK,
121 "the event.type should equal clock"
122 );
123 assert_eq!(
124 event.userdata, CLOCK_ID,
125 "the event.userdata should contain clock_id specified by the user"
126 );
127 assert!(
128 after - before >= timeout,
129 "poll_oneoff should sleep for the specified interval of {timeout}. before: {before}, after: {after}"
130 );
131 }
132
test_fd_readwrite( readable_fd: wasip1::Fd, writable_fd: wasip1::Fd, error_code: wasip1::Errno, )133 unsafe fn test_fd_readwrite(
134 readable_fd: wasip1::Fd,
135 writable_fd: wasip1::Fd,
136 error_code: wasip1::Errno,
137 ) {
138 let r#in = [
139 wasip1::Subscription {
140 userdata: 1,
141 u: wasip1::SubscriptionU {
142 tag: wasip1::EVENTTYPE_FD_READ.raw(),
143 u: wasip1::SubscriptionUU {
144 fd_read: wasip1::SubscriptionFdReadwrite {
145 file_descriptor: readable_fd,
146 },
147 },
148 },
149 },
150 wasip1::Subscription {
151 userdata: 2,
152 u: wasip1::SubscriptionU {
153 tag: wasip1::EVENTTYPE_FD_WRITE.raw(),
154 u: wasip1::SubscriptionUU {
155 fd_write: wasip1::SubscriptionFdReadwrite {
156 file_descriptor: writable_fd,
157 },
158 },
159 },
160 },
161 ];
162 let out = poll_oneoff_with_retry(&r#in).unwrap();
163 assert_eq!(out.len(), 2, "should return 2 events, got: {out:?}");
164
165 let (read, write) = if out[0].userdata == 1 {
166 (&out[0], &out[1])
167 } else {
168 (&out[1], &out[0])
169 };
170 assert_eq!(
171 read.userdata, 1,
172 "the event.userdata should contain fd userdata specified by the user"
173 );
174 assert_errno!(read.error, error_code);
175 assert_eq!(
176 read.type_,
177 wasip1::EVENTTYPE_FD_READ,
178 "the event.type_ should equal FD_READ"
179 );
180 assert_eq!(
181 write.userdata, 2,
182 "the event.userdata should contain fd userdata specified by the user"
183 );
184 assert_errno!(write.error, error_code);
185 assert_eq!(
186 write.type_,
187 wasip1::EVENTTYPE_FD_WRITE,
188 "the event.type_ should equal FD_WRITE"
189 );
190 }
191
test_fd_readwrite_valid_fd(dir_fd: wasip1::Fd)192 unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasip1::Fd) {
193 // Create a file in the scratch directory.
194 let nonempty_file = wasip1::path_open(
195 dir_fd,
196 0,
197 "readable_file",
198 wasip1::OFLAGS_CREAT,
199 wasip1::RIGHTS_FD_WRITE,
200 0,
201 0,
202 )
203 .expect("create writable file");
204 // Write to file
205 let contents = &[1u8];
206 let ciovec = wasip1::Ciovec {
207 buf: contents.as_ptr() as *const _,
208 buf_len: contents.len(),
209 };
210 wasip1::fd_write(nonempty_file, &[ciovec]).expect("write");
211 wasip1::fd_close(nonempty_file).expect("close");
212
213 // Now open the file for reading
214 let readable_fd =
215 wasip1::path_open(dir_fd, 0, "readable_file", 0, wasip1::RIGHTS_FD_READ, 0, 0)
216 .expect("opening a readable file");
217
218 assert!(
219 readable_fd > libc::STDERR_FILENO as wasip1::Fd,
220 "file descriptor range check",
221 );
222 // Create a file in the scratch directory.
223 let writable_fd = wasip1::path_open(
224 dir_fd,
225 0,
226 "writable_file",
227 wasip1::OFLAGS_CREAT,
228 wasip1::RIGHTS_FD_WRITE,
229 0,
230 0,
231 )
232 .expect("opening a writable file");
233 assert!(
234 writable_fd > libc::STDERR_FILENO as wasip1::Fd,
235 "file descriptor range check",
236 );
237
238 test_fd_readwrite(readable_fd, writable_fd, wasip1::ERRNO_SUCCESS);
239
240 wasip1::fd_close(readable_fd).expect("closing readable_file");
241 wasip1::fd_close(writable_fd).expect("closing writable_file");
242 wasip1::path_unlink_file(dir_fd, "readable_file").expect("removing readable_file");
243 wasip1::path_unlink_file(dir_fd, "writable_file").expect("removing writable_file");
244 }
245
test_fd_readwrite_invalid_fd()246 unsafe fn test_fd_readwrite_invalid_fd() {
247 let fd_readwrite = wasip1::SubscriptionFdReadwrite {
248 file_descriptor: wasip1::Fd::max_value(),
249 };
250 let r#in = [
251 wasip1::Subscription {
252 userdata: 1,
253 u: wasip1::SubscriptionU {
254 tag: wasip1::EVENTTYPE_FD_READ.raw(),
255 u: wasip1::SubscriptionUU {
256 fd_read: fd_readwrite,
257 },
258 },
259 },
260 wasip1::Subscription {
261 userdata: 2,
262 u: wasip1::SubscriptionU {
263 tag: wasip1::EVENTTYPE_FD_WRITE.raw(),
264 u: wasip1::SubscriptionUU {
265 fd_write: fd_readwrite,
266 },
267 },
268 },
269 ];
270 let err = poll_oneoff_impl(&r#in).unwrap_err();
271 assert_eq!(err, wasip1::ERRNO_BADF)
272 }
273
test_poll_oneoff(dir_fd: wasip1::Fd)274 unsafe fn test_poll_oneoff(dir_fd: wasip1::Fd) {
275 test_timeout();
276 test_sleep();
277 test_empty_poll();
278 test_fd_readwrite_valid_fd(dir_fd);
279 test_fd_readwrite_invalid_fd();
280 }
main()281 fn main() {
282 let mut args = env::args();
283 let prog = args.next().unwrap();
284 let arg = if let Some(arg) = args.next() {
285 arg
286 } else {
287 eprintln!("usage: {prog} <scratch directory>");
288 process::exit(1);
289 };
290
291 // Open scratch directory
292 let dir_fd = match open_scratch_directory(&arg) {
293 Ok(dir_fd) => dir_fd,
294 Err(err) => {
295 eprintln!("{err}");
296 process::exit(1)
297 }
298 };
299
300 // Run the tests.
301 unsafe { test_poll_oneoff(dir_fd) }
302 }
303