16506567cSGuy Bedford use std::{sync::OnceLock, time::Duration};
2f4be3606SAlex Crichton 
config() -> &'static TestConfig3f4be3606SAlex Crichton pub fn config() -> &'static TestConfig {
4f4be3606SAlex Crichton     static TESTCONFIG: OnceLock<TestConfig> = OnceLock::new();
5f4be3606SAlex Crichton     TESTCONFIG.get_or_init(TestConfig::from_env)
6f4be3606SAlex Crichton }
7f4be3606SAlex Crichton 
8f4be3606SAlex Crichton // The `wasi` crate version 0.9.0 and beyond, doesn't
9f4be3606SAlex Crichton // seem to define these constants, so we do it ourselves.
10953adeedSAlex Crichton pub const STDIN_FD: wasip1::Fd = 0x0;
11953adeedSAlex Crichton pub const STDOUT_FD: wasip1::Fd = 0x1;
12953adeedSAlex Crichton pub const STDERR_FD: wasip1::Fd = 0x2;
13f4be3606SAlex Crichton 
14f4be3606SAlex Crichton /// Opens a fresh file descriptor for `path` where `path` should be a preopened
15f4be3606SAlex Crichton /// directory.
open_scratch_directory(path: &str) -> Result<wasip1::Fd, String>16953adeedSAlex Crichton pub fn open_scratch_directory(path: &str) -> Result<wasip1::Fd, String> {
17f4be3606SAlex Crichton     unsafe {
18f4be3606SAlex Crichton         for i in 3.. {
19953adeedSAlex Crichton             let stat = match wasip1::fd_prestat_get(i) {
20f4be3606SAlex Crichton                 Ok(s) => s,
21f4be3606SAlex Crichton                 Err(_) => break,
22f4be3606SAlex Crichton             };
23953adeedSAlex Crichton             if stat.tag != wasip1::PREOPENTYPE_DIR.raw() {
24f4be3606SAlex Crichton                 continue;
25f4be3606SAlex Crichton             }
26f4be3606SAlex Crichton             let mut dst = Vec::with_capacity(stat.u.dir.pr_name_len);
27953adeedSAlex Crichton             if wasip1::fd_prestat_dir_name(i, dst.as_mut_ptr(), dst.capacity()).is_err() {
28f4be3606SAlex Crichton                 continue;
29f4be3606SAlex Crichton             }
30f4be3606SAlex Crichton             dst.set_len(stat.u.dir.pr_name_len);
31f4be3606SAlex Crichton             if dst == path.as_bytes() {
32953adeedSAlex Crichton                 return Ok(
33953adeedSAlex Crichton                     wasip1::path_open(i, 0, ".", wasip1::OFLAGS_DIRECTORY, 0, 0, 0)
34953adeedSAlex Crichton                         .expect("failed to open dir"),
35953adeedSAlex Crichton                 );
36f4be3606SAlex Crichton             }
37f4be3606SAlex Crichton         }
38f4be3606SAlex Crichton 
39f4be3606SAlex Crichton         Err(format!("failed to find scratch dir"))
40f4be3606SAlex Crichton     }
41f4be3606SAlex Crichton }
42f4be3606SAlex Crichton 
create_file(dir_fd: wasip1::Fd, filename: &str)43953adeedSAlex Crichton pub unsafe fn create_file(dir_fd: wasip1::Fd, filename: &str) {
44*073aedabSAlex Crichton     unsafe {
45953adeedSAlex Crichton         let file_fd = wasip1::path_open(dir_fd, 0, filename, wasip1::OFLAGS_CREAT, 0, 0, 0)
46953adeedSAlex Crichton             .expect("creating a file");
47f4be3606SAlex Crichton         assert!(file_fd > STDERR_FD, "file descriptor range check",);
48953adeedSAlex Crichton         wasip1::fd_close(file_fd).expect("closing a file");
49f4be3606SAlex Crichton     }
50*073aedabSAlex Crichton }
51f4be3606SAlex Crichton 
526506567cSGuy Bedford // Small workaround to get the crate's macros, through the
53f4be3606SAlex Crichton // `#[macro_export]` attribute below, also available from this module.
546506567cSGuy Bedford pub use crate::{assert_errno, assert_fs_time_eq};
55f4be3606SAlex Crichton 
56f4be3606SAlex Crichton #[macro_export]
57f4be3606SAlex Crichton macro_rules! assert_errno {
58f4be3606SAlex Crichton     ($s:expr, windows => $i:expr, $( $rest:tt )+) => {
59f4be3606SAlex Crichton         let e = $s;
60f4be3606SAlex Crichton         if $crate::preview1::config().errno_expect_windows() {
61f4be3606SAlex Crichton             assert_errno!(e, $i);
62f4be3606SAlex Crichton         } else {
63f4be3606SAlex Crichton             assert_errno!(e, $($rest)+, $i);
64f4be3606SAlex Crichton         }
65f4be3606SAlex Crichton     };
66f4be3606SAlex Crichton     ($s:expr, macos => $i:expr, $( $rest:tt )+) => {
67f4be3606SAlex Crichton         let e = $s;
68f4be3606SAlex Crichton         if $crate::preview1::config().errno_expect_macos() {
69f4be3606SAlex Crichton             assert_errno!(e, $i);
70f4be3606SAlex Crichton         } else {
71f4be3606SAlex Crichton             assert_errno!(e, $($rest)+, $i);
72f4be3606SAlex Crichton         }
73f4be3606SAlex Crichton     };
74f4be3606SAlex Crichton     ($s:expr, unix => $i:expr, $( $rest:tt )+) => {
75f4be3606SAlex Crichton         let e = $s;
76f4be3606SAlex Crichton         if $crate::preview1::config().errno_expect_unix() {
77f4be3606SAlex Crichton             assert_errno!(e, $i);
78f4be3606SAlex Crichton         } else {
79f4be3606SAlex Crichton             assert_errno!(e, $($rest)+, $i);
80f4be3606SAlex Crichton         }
81f4be3606SAlex Crichton     };
82f4be3606SAlex Crichton     ($s:expr, $( $i:expr ),+) => {
83f4be3606SAlex Crichton         let e = $s;
84f4be3606SAlex Crichton         {
85f4be3606SAlex Crichton             // Pretty printing infrastructure
86f4be3606SAlex Crichton             struct Alt<'a>(&'a [&'static str]);
87f4be3606SAlex Crichton             impl<'a> std::fmt::Display for Alt<'a> {
88f4be3606SAlex Crichton                 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
89f4be3606SAlex Crichton                     let l = self.0.len();
90f4be3606SAlex Crichton                     if l == 0 {
91f4be3606SAlex Crichton                         unreachable!()
92f4be3606SAlex Crichton                     } else if l == 1 {
93f4be3606SAlex Crichton                         f.write_str(self.0[0])
94f4be3606SAlex Crichton                     } else if l == 2 {
95f4be3606SAlex Crichton                         f.write_str(self.0[0])?;
96f4be3606SAlex Crichton                         f.write_str(" or ")?;
97f4be3606SAlex Crichton                         f.write_str(self.0[1])
98f4be3606SAlex Crichton                     } else {
99f4be3606SAlex Crichton                         for (ix, s) in self.0.iter().enumerate() {
100f4be3606SAlex Crichton                             if ix == l - 1 {
101f4be3606SAlex Crichton                                 f.write_str("or ")?;
102f4be3606SAlex Crichton                                 f.write_str(s)?;
103f4be3606SAlex Crichton                             } else {
104f4be3606SAlex Crichton                                 f.write_str(s)?;
105f4be3606SAlex Crichton                                 f.write_str(", ")?;
106f4be3606SAlex Crichton                             }
107f4be3606SAlex Crichton                         }
108f4be3606SAlex Crichton                         Ok(())
109f4be3606SAlex Crichton                     }
110f4be3606SAlex Crichton                 }
111f4be3606SAlex Crichton             }
112f4be3606SAlex Crichton             assert!( $( e == $i || )+ false,
113f4be3606SAlex Crichton                 "expected errno {}; got {}",
114f4be3606SAlex Crichton                 Alt(&[ $( $i.name() ),+ ]),
115f4be3606SAlex Crichton                 e.name()
116f4be3606SAlex Crichton             )
117f4be3606SAlex Crichton         }
118f4be3606SAlex Crichton     };
119f4be3606SAlex Crichton }
120f4be3606SAlex Crichton 
1216506567cSGuy Bedford #[macro_export]
1226506567cSGuy Bedford macro_rules! assert_fs_time_eq {
1236506567cSGuy Bedford     ($l:expr, $r:expr, $n:literal) => {
1246506567cSGuy Bedford         let diff = if $l > $r { $l - $r } else { $r - $l };
1256506567cSGuy Bedford         assert!(diff < $crate::preview1::config().fs_time_precision(), $n);
1266506567cSGuy Bedford     };
1276506567cSGuy Bedford }
1286506567cSGuy Bedford 
129f4be3606SAlex Crichton pub struct TestConfig {
130f4be3606SAlex Crichton     errno_mode: ErrnoMode,
1316506567cSGuy Bedford     fs_time_precision: u64,
132f4be3606SAlex Crichton     no_dangling_filesystem: bool,
133f4be3606SAlex Crichton     no_rename_dir_to_empty_dir: bool,
134b98f75aaSAlex Crichton     rename_dir_onto_file: bool,
135f4be3606SAlex Crichton }
136f4be3606SAlex Crichton 
137f4be3606SAlex Crichton enum ErrnoMode {
138f4be3606SAlex Crichton     Unix,
139f4be3606SAlex Crichton     MacOS,
140f4be3606SAlex Crichton     Windows,
141f4be3606SAlex Crichton     Permissive,
142f4be3606SAlex Crichton }
143f4be3606SAlex Crichton 
144f4be3606SAlex Crichton impl TestConfig {
from_env() -> Self145f4be3606SAlex Crichton     pub fn from_env() -> Self {
146f4be3606SAlex Crichton         let errno_mode = if std::env::var("ERRNO_MODE_UNIX").is_ok() {
147f4be3606SAlex Crichton             ErrnoMode::Unix
148f4be3606SAlex Crichton         } else if std::env::var("ERRNO_MODE_MACOS").is_ok() {
149f4be3606SAlex Crichton             ErrnoMode::MacOS
150f4be3606SAlex Crichton         } else if std::env::var("ERRNO_MODE_WINDOWS").is_ok() {
151f4be3606SAlex Crichton             ErrnoMode::Windows
152f4be3606SAlex Crichton         } else {
153f4be3606SAlex Crichton             ErrnoMode::Permissive
154f4be3606SAlex Crichton         };
1556506567cSGuy Bedford         let fs_time_precision = match std::env::var("FS_TIME_PRECISION") {
1566506567cSGuy Bedford             Ok(p) => p.parse().unwrap(),
1576506567cSGuy Bedford             Err(_) => 100,
1586506567cSGuy Bedford         };
159f4be3606SAlex Crichton         let no_dangling_filesystem = std::env::var("NO_DANGLING_FILESYSTEM").is_ok();
160f4be3606SAlex Crichton         let no_rename_dir_to_empty_dir = std::env::var("NO_RENAME_DIR_TO_EMPTY_DIR").is_ok();
161f4be3606SAlex Crichton         TestConfig {
162f4be3606SAlex Crichton             errno_mode,
1636506567cSGuy Bedford             fs_time_precision,
164f4be3606SAlex Crichton             no_dangling_filesystem,
165f4be3606SAlex Crichton             no_rename_dir_to_empty_dir,
166b98f75aaSAlex Crichton             rename_dir_onto_file: std::env::var("RENAME_DIR_ONTO_FILE").is_ok(),
167f4be3606SAlex Crichton         }
168f4be3606SAlex Crichton     }
errno_expect_unix(&self) -> bool169f4be3606SAlex Crichton     pub fn errno_expect_unix(&self) -> bool {
170f4be3606SAlex Crichton         match self.errno_mode {
171f4be3606SAlex Crichton             ErrnoMode::Unix | ErrnoMode::MacOS => true,
172f4be3606SAlex Crichton             _ => false,
173f4be3606SAlex Crichton         }
174f4be3606SAlex Crichton     }
errno_expect_macos(&self) -> bool175f4be3606SAlex Crichton     pub fn errno_expect_macos(&self) -> bool {
176f4be3606SAlex Crichton         match self.errno_mode {
177f4be3606SAlex Crichton             ErrnoMode::MacOS => true,
178f4be3606SAlex Crichton             _ => false,
179f4be3606SAlex Crichton         }
180f4be3606SAlex Crichton     }
errno_expect_windows(&self) -> bool181f4be3606SAlex Crichton     pub fn errno_expect_windows(&self) -> bool {
182f4be3606SAlex Crichton         match self.errno_mode {
183f4be3606SAlex Crichton             ErrnoMode::Windows => true,
184f4be3606SAlex Crichton             _ => false,
185f4be3606SAlex Crichton         }
186f4be3606SAlex Crichton     }
fs_time_precision(&self) -> Duration1876506567cSGuy Bedford     pub fn fs_time_precision(&self) -> Duration {
1886506567cSGuy Bedford         Duration::from_nanos(self.fs_time_precision)
1896506567cSGuy Bedford     }
support_dangling_filesystem(&self) -> bool190f4be3606SAlex Crichton     pub fn support_dangling_filesystem(&self) -> bool {
191f4be3606SAlex Crichton         !self.no_dangling_filesystem
192f4be3606SAlex Crichton     }
support_rename_dir_to_empty_dir(&self) -> bool193f4be3606SAlex Crichton     pub fn support_rename_dir_to_empty_dir(&self) -> bool {
194f4be3606SAlex Crichton         !self.no_rename_dir_to_empty_dir
195f4be3606SAlex Crichton     }
support_rename_dir_onto_file(&self) -> bool196b98f75aaSAlex Crichton     pub fn support_rename_dir_onto_file(&self) -> bool {
197b98f75aaSAlex Crichton         self.rename_dir_onto_file
198b98f75aaSAlex Crichton     }
199f4be3606SAlex Crichton }
200