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