1 //! Build program to generate a program which runs all the testsuites.
2 //!
3 //! By generating a separate `#[test]` test for each file, we allow cargo test
4 //! to automatically run the files in parallel.
5 
main()6 fn main() {
7     #[cfg(feature = "test_programs")]
8     wasi_tests::build_and_generate_tests()
9 }
10 
11 #[cfg(feature = "test_programs")]
12 mod wasi_tests {
13     use std::env;
14     use std::fs::{read_dir, File};
15     use std::io::{self, Write};
16     use std::path::{Path, PathBuf};
17     use std::process::{Command, Stdio};
18 
19     #[derive(Clone, Copy, Debug)]
20     enum PreopenType {
21         /// Preopens should be satisfied with real OS files.
22         OS,
23         /// Preopens should be satisfied with virtual files.
24         Virtual,
25     }
26 
build_and_generate_tests()27     pub(super) fn build_and_generate_tests() {
28         // Validate if any of test sources are present and if they changed
29         // This should always work since there is no submodule to init anymore
30         let bin_tests = std::fs::read_dir("wasi-tests/src/bin").unwrap();
31         for test in bin_tests {
32             if let Ok(test_file) = test {
33                 let test_file_path = test_file
34                     .path()
35                     .into_os_string()
36                     .into_string()
37                     .expect("test file path");
38                 println!("cargo:rerun-if-changed={}", test_file_path);
39             }
40         }
41         println!("cargo:rerun-if-changed=wasi-tests/Cargo.toml");
42         println!("cargo:rerun-if-changed=wasi-tests/src/lib.rs");
43         // Build tests to OUT_DIR (target/*/build/wasi-common-*/out/wasm32-wasi/release/*.wasm)
44         let out_dir = PathBuf::from(
45             env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
46         );
47         let mut out =
48             File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file");
49         build_tests("wasi-tests", &out_dir).expect("building tests");
50         test_directory(&mut out, "wasi-tests", &out_dir).expect("generating tests");
51     }
52 
build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()>53     fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> {
54         let mut cmd = Command::new("cargo");
55         cmd.args(&[
56             "build",
57             "--release",
58             "--target=wasm32-wasi",
59             "--target-dir",
60             out_dir.to_str().unwrap(),
61         ])
62         .stdout(Stdio::inherit())
63         .stderr(Stdio::inherit())
64         .current_dir(testsuite);
65         let output = cmd.output()?;
66 
67         let status = output.status;
68         if !status.success() {
69             panic!(
70                 "Building tests failed: exit code: {}",
71                 status.code().unwrap()
72             );
73         }
74 
75         Ok(())
76     }
77 
test_directory(out: &mut File, testsuite: &str, out_dir: &Path) -> io::Result<()>78     fn test_directory(out: &mut File, testsuite: &str, out_dir: &Path) -> io::Result<()> {
79         let mut dir_entries: Vec<_> = read_dir(out_dir.join("wasm32-wasi/release"))
80             .expect("reading testsuite directory")
81             .map(|r| r.expect("reading testsuite directory entry"))
82             .filter(|dir_entry| {
83                 let p = dir_entry.path();
84                 if let Some(ext) = p.extension() {
85                     // Only look at wast files.
86                     if ext == "wasm" {
87                         // Ignore files starting with `.`, which could be editor temporary files
88                         if let Some(stem) = p.file_stem() {
89                             if let Some(stemstr) = stem.to_str() {
90                                 if !stemstr.starts_with('.') {
91                                     return true;
92                                 }
93                             }
94                         }
95                     }
96                 }
97                 false
98             })
99             .collect();
100 
101         dir_entries.sort_by_key(|dir| dir.path());
102 
103         writeln!(
104             out,
105             "mod {} {{",
106             Path::new(testsuite)
107                 .file_stem()
108                 .expect("testsuite filename should have a stem")
109                 .to_str()
110                 .expect("testsuite filename should be representable as a string")
111                 .replace("-", "_")
112         )?;
113         writeln!(out, "    use super::{{runtime, utils, setup_log}};")?;
114         writeln!(out, "    use runtime::PreopenType;")?;
115         for dir_entry in dir_entries {
116             let test_path = dir_entry.path();
117             let stemstr = test_path
118                 .file_stem()
119                 .expect("file_stem")
120                 .to_str()
121                 .expect("to_str");
122 
123             if no_preopens(testsuite, stemstr) {
124                 write_testsuite_tests(out, &test_path, testsuite, PreopenType::OS)?;
125             } else {
126                 write_testsuite_tests(out, &test_path, testsuite, PreopenType::OS)?;
127                 write_testsuite_tests(out, &test_path, testsuite, PreopenType::Virtual)?;
128             }
129         }
130         writeln!(out, "}}")?;
131         Ok(())
132     }
133 
write_testsuite_tests( out: &mut File, path: &Path, testsuite: &str, preopen_type: PreopenType, ) -> io::Result<()>134     fn write_testsuite_tests(
135         out: &mut File,
136         path: &Path,
137         testsuite: &str,
138         preopen_type: PreopenType,
139     ) -> io::Result<()> {
140         let stemstr = path
141             .file_stem()
142             .expect("file_stem")
143             .to_str()
144             .expect("to_str");
145 
146         writeln!(out, "    #[test]")?;
147         let test_fn_name = format!(
148             "{}{}",
149             &stemstr.replace("-", "_"),
150             if let PreopenType::Virtual = preopen_type {
151                 "_virtualfs"
152             } else {
153                 ""
154             }
155         );
156         if ignore(testsuite, &test_fn_name) {
157             writeln!(out, "    #[ignore]")?;
158         }
159         writeln!(out, "    fn r#{}() -> anyhow::Result<()> {{", test_fn_name,)?;
160         writeln!(out, "        setup_log();")?;
161         writeln!(
162             out,
163             "        let path = std::path::Path::new(r#\"{}\"#);",
164             path.display()
165         )?;
166         writeln!(out, "        let data = wat::parse_file(path)?;")?;
167         writeln!(
168             out,
169             "        let bin_name = utils::extract_exec_name_from_path(path)?;"
170         )?;
171         let workspace = if no_preopens(testsuite, stemstr) {
172             "None"
173         } else {
174             match preopen_type {
175                 PreopenType::OS => {
176                     writeln!(
177                         out,
178                         "        let workspace = utils::prepare_workspace(&bin_name)?;"
179                     )?;
180                     "Some(workspace.path())"
181                 }
182                 PreopenType::Virtual => "Some(std::path::Path::new(&bin_name))",
183             }
184         };
185         writeln!(
186             out,
187             "        runtime::instantiate(&data, &bin_name, {}, {})",
188             workspace,
189             match preopen_type {
190                 PreopenType::OS => "PreopenType::OS",
191                 PreopenType::Virtual => "PreopenType::Virtual",
192             }
193         )?;
194         writeln!(out, "    }}")?;
195         writeln!(out)?;
196         Ok(())
197     }
198 
199     cfg_if::cfg_if! {
200         if #[cfg(not(windows))] {
201             /// Ignore tests that aren't supported yet.
202             fn ignore(testsuite: &str, name: &str) -> bool {
203                 if testsuite == "wasi-tests" {
204                     match name {
205                         // TODO: virtfs files cannot be poll_oneoff'd yet
206                         "poll_oneoff_virtualfs" => true,
207                         // TODO: virtfs does not support filetimes yet.
208                         "path_filestat_virtualfs" |
209                         "fd_filestat_set_virtualfs" => true,
210                         // TODO: virtfs does not support symlinks yet.
211                         "nofollow_errors_virtualfs" |
212                         "path_link_virtualfs" |
213                         "readlink_virtualfs" |
214                         "readlink_no_buffer_virtualfs" |
215                         "dangling_symlink_virtualfs" |
216                         "symlink_loop_virtualfs" |
217                         "path_symlink_trailing_slashes_virtualfs" => true,
218                         // TODO: virtfs does not support rename yet.
219                         "path_rename_trailing_slashes_virtualfs" |
220                         "path_rename_virtualfs" => true,
221                         _ => false,
222                     }
223                 } else {
224                     unreachable!()
225                 }
226             }
227         } else {
228             /// Ignore tests that aren't supported yet.
229             fn ignore(testsuite: &str, name: &str) -> bool {
230                 if testsuite == "wasi-tests" {
231                     match name {
232                         "readlink_no_buffer" => true,
233                         "dangling_symlink" => true,
234                         "symlink_loop" => true,
235                         "truncation_rights" => true,
236                         "dangling_fd" => true,
237                         // TODO: virtfs files cannot be poll_oneoff'd yet
238                         "poll_oneoff_virtualfs" => true,
239                         // TODO: virtfs does not support filetimes yet.
240                         "path_filestat_virtualfs" |
241                         "fd_filestat_set_virtualfs" => true,
242                         // TODO: virtfs does not support symlinks yet.
243                         "nofollow_errors_virtualfs" |
244                         "path_link_virtualfs" |
245                         "readlink_virtualfs" |
246                         "readlink_no_buffer_virtualfs" |
247                         "dangling_symlink_virtualfs" |
248                         "symlink_loop_virtualfs" |
249                         "path_symlink_trailing_slashes_virtualfs" => true,
250                         // TODO: virtfs does not support rename yet.
251                         "path_rename_trailing_slashes_virtualfs" |
252                         "path_rename_virtualfs" => true,
253                         _ => false,
254                     }
255                 } else {
256                     unreachable!()
257                 }
258             }
259         }
260     }
261 
262     /// Mark tests which do not require preopens
no_preopens(testsuite: &str, name: &str) -> bool263     fn no_preopens(testsuite: &str, name: &str) -> bool {
264         if testsuite == "wasi-tests" {
265             match name {
266                 "big_random_buf" => true,
267                 "clock_time_get" => true,
268                 "sched_yield" => true,
269                 _ => false,
270             }
271         } else {
272             unreachable!()
273         }
274     }
275 }
276