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