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