1 use std::{sync::OnceLock, time::Duration};
2
config() -> &'static TestConfig3 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.
open_scratch_directory(path: &str) -> Result<wasip1::Fd, String>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
create_file(dir_fd: wasip1::Fd, filename: &str)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 {
from_env() -> Self145 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 }
errno_expect_unix(&self) -> bool169 pub fn errno_expect_unix(&self) -> bool {
170 match self.errno_mode {
171 ErrnoMode::Unix | ErrnoMode::MacOS => true,
172 _ => false,
173 }
174 }
errno_expect_macos(&self) -> bool175 pub fn errno_expect_macos(&self) -> bool {
176 match self.errno_mode {
177 ErrnoMode::MacOS => true,
178 _ => false,
179 }
180 }
errno_expect_windows(&self) -> bool181 pub fn errno_expect_windows(&self) -> bool {
182 match self.errno_mode {
183 ErrnoMode::Windows => true,
184 _ => false,
185 }
186 }
fs_time_precision(&self) -> Duration187 pub fn fs_time_precision(&self) -> Duration {
188 Duration::from_nanos(self.fs_time_precision)
189 }
support_dangling_filesystem(&self) -> bool190 pub fn support_dangling_filesystem(&self) -> bool {
191 !self.no_dangling_filesystem
192 }
support_rename_dir_to_empty_dir(&self) -> bool193 pub fn support_rename_dir_to_empty_dir(&self) -> bool {
194 !self.no_rename_dir_to_empty_dir
195 }
support_rename_dir_onto_file(&self) -> bool196 pub fn support_rename_dir_onto_file(&self) -> bool {
197 self.rename_dir_onto_file
198 }
199 }
200