1 use std::{sync::OnceLock, time::Duration};
2 
3 pub fn config() -> &'static TestConfig {
4     static TESTCONFIG: OnceLock<TestConfig> = OnceLock::new();
5     TESTCONFIG.get_or_init(TestConfig::from_env)
6 }
7 
8 // The `wasi` crate version 0.9.0 and beyond, doesn't
9 // seem to define these constants, so we do it ourselves.
10 pub const STDIN_FD: wasi::Fd = 0x0;
11 pub const STDOUT_FD: wasi::Fd = 0x1;
12 pub const STDERR_FD: wasi::Fd = 0x2;
13 
14 /// Opens a fresh file descriptor for `path` where `path` should be a preopened
15 /// directory.
16 pub fn open_scratch_directory(path: &str) -> Result<wasi::Fd, String> {
17     unsafe {
18         for i in 3.. {
19             let stat = match wasi::fd_prestat_get(i) {
20                 Ok(s) => s,
21                 Err(_) => break,
22             };
23             if stat.tag != wasi::PREOPENTYPE_DIR.raw() {
24                 continue;
25             }
26             let mut dst = Vec::with_capacity(stat.u.dir.pr_name_len);
27             if wasi::fd_prestat_dir_name(i, dst.as_mut_ptr(), dst.capacity()).is_err() {
28                 continue;
29             }
30             dst.set_len(stat.u.dir.pr_name_len);
31             if dst == path.as_bytes() {
32                 return Ok(wasi::path_open(i, 0, ".", wasi::OFLAGS_DIRECTORY, 0, 0, 0)
33                     .expect("failed to open dir"));
34             }
35         }
36 
37         Err(format!("failed to find scratch dir"))
38     }
39 }
40 
41 pub unsafe fn create_file(dir_fd: wasi::Fd, filename: &str) {
42     let file_fd =
43         wasi::path_open(dir_fd, 0, filename, wasi::OFLAGS_CREAT, 0, 0, 0).expect("creating a file");
44     assert!(file_fd > STDERR_FD, "file descriptor range check",);
45     wasi::fd_close(file_fd).expect("closing a file");
46 }
47 
48 // Small workaround to get the crate's macros, through the
49 // `#[macro_export]` attribute below, also available from this module.
50 pub use crate::{assert_errno, assert_fs_time_eq};
51 
52 #[macro_export]
53 macro_rules! assert_errno {
54     ($s:expr, windows => $i:expr, $( $rest:tt )+) => {
55         let e = $s;
56         if $crate::preview1::config().errno_expect_windows() {
57             assert_errno!(e, $i);
58         } else {
59             assert_errno!(e, $($rest)+, $i);
60         }
61     };
62     ($s:expr, macos => $i:expr, $( $rest:tt )+) => {
63         let e = $s;
64         if $crate::preview1::config().errno_expect_macos() {
65             assert_errno!(e, $i);
66         } else {
67             assert_errno!(e, $($rest)+, $i);
68         }
69     };
70     ($s:expr, unix => $i:expr, $( $rest:tt )+) => {
71         let e = $s;
72         if $crate::preview1::config().errno_expect_unix() {
73             assert_errno!(e, $i);
74         } else {
75             assert_errno!(e, $($rest)+, $i);
76         }
77     };
78     ($s:expr, $( $i:expr ),+) => {
79         let e = $s;
80         {
81             // Pretty printing infrastructure
82             struct Alt<'a>(&'a [&'static str]);
83             impl<'a> std::fmt::Display for Alt<'a> {
84                 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
85                     let l = self.0.len();
86                     if l == 0 {
87                         unreachable!()
88                     } else if l == 1 {
89                         f.write_str(self.0[0])
90                     } else if l == 2 {
91                         f.write_str(self.0[0])?;
92                         f.write_str(" or ")?;
93                         f.write_str(self.0[1])
94                     } else {
95                         for (ix, s) in self.0.iter().enumerate() {
96                             if ix == l - 1 {
97                                 f.write_str("or ")?;
98                                 f.write_str(s)?;
99                             } else {
100                                 f.write_str(s)?;
101                                 f.write_str(", ")?;
102                             }
103                         }
104                         Ok(())
105                     }
106                 }
107             }
108             assert!( $( e == $i || )+ false,
109                 "expected errno {}; got {}",
110                 Alt(&[ $( $i.name() ),+ ]),
111                 e.name()
112             )
113         }
114     };
115 }
116 
117 #[macro_export]
118 macro_rules! assert_fs_time_eq {
119     ($l:expr, $r:expr, $n:literal) => {
120         let diff = if $l > $r { $l - $r } else { $r - $l };
121         assert!(diff < $crate::preview1::config().fs_time_precision(), $n);
122     };
123 }
124 
125 pub struct TestConfig {
126     errno_mode: ErrnoMode,
127     fs_time_precision: u64,
128     no_dangling_filesystem: bool,
129     no_rename_dir_to_empty_dir: bool,
130     no_fdflags_sync_support: bool,
131 }
132 
133 enum ErrnoMode {
134     Unix,
135     MacOS,
136     Windows,
137     Permissive,
138 }
139 
140 impl TestConfig {
141     pub fn from_env() -> Self {
142         let errno_mode = if std::env::var("ERRNO_MODE_UNIX").is_ok() {
143             ErrnoMode::Unix
144         } else if std::env::var("ERRNO_MODE_MACOS").is_ok() {
145             ErrnoMode::MacOS
146         } else if std::env::var("ERRNO_MODE_WINDOWS").is_ok() {
147             ErrnoMode::Windows
148         } else {
149             ErrnoMode::Permissive
150         };
151         let fs_time_precision = match std::env::var("FS_TIME_PRECISION") {
152             Ok(p) => p.parse().unwrap(),
153             Err(_) => 100,
154         };
155         let no_dangling_filesystem = std::env::var("NO_DANGLING_FILESYSTEM").is_ok();
156         let no_rename_dir_to_empty_dir = std::env::var("NO_RENAME_DIR_TO_EMPTY_DIR").is_ok();
157         let no_fdflags_sync_support = std::env::var("NO_FDFLAGS_SYNC_SUPPORT").is_ok();
158         TestConfig {
159             errno_mode,
160             fs_time_precision,
161             no_dangling_filesystem,
162             no_rename_dir_to_empty_dir,
163             no_fdflags_sync_support,
164         }
165     }
166     pub fn errno_expect_unix(&self) -> bool {
167         match self.errno_mode {
168             ErrnoMode::Unix | ErrnoMode::MacOS => true,
169             _ => false,
170         }
171     }
172     pub fn errno_expect_macos(&self) -> bool {
173         match self.errno_mode {
174             ErrnoMode::MacOS => true,
175             _ => false,
176         }
177     }
178     pub fn errno_expect_windows(&self) -> bool {
179         match self.errno_mode {
180             ErrnoMode::Windows => true,
181             _ => false,
182         }
183     }
184     pub fn fs_time_precision(&self) -> Duration {
185         Duration::from_nanos(self.fs_time_precision)
186     }
187     pub fn support_dangling_filesystem(&self) -> bool {
188         !self.no_dangling_filesystem
189     }
190     pub fn support_rename_dir_to_empty_dir(&self) -> bool {
191         !self.no_rename_dir_to_empty_dir
192     }
193     pub fn support_fdflags_sync(&self) -> bool {
194         !self.no_fdflags_sync_support
195     }
196 }
197