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