test_generator/
lib.rs

1//! Build library 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//!
6//! > This program is inspired/forked from:
7//! > https://github.com/bytecodealliance/wasmtime/blob/master/build.rs
8mod processors;
9
10pub use crate::processors::{wasi_processor, wast_processor};
11use anyhow::Context;
12use std::fmt::Write;
13use std::path::{Path, PathBuf};
14
15pub struct Testsuite {
16    pub buffer: String,
17    pub path: Vec<String>,
18}
19
20#[derive(PartialEq, Eq, PartialOrd, Ord)]
21pub struct Test {
22    pub name: String,
23    pub body: String,
24}
25
26pub fn test_directory_module(
27    out: &mut Testsuite,
28    path: impl AsRef<Path>,
29    processor: impl Fn(&mut Testsuite, PathBuf) -> Option<Test>,
30) -> anyhow::Result<usize> {
31    let path = path.as_ref();
32    let testsuite = &extract_name(path);
33    with_test_module(out, testsuite, |out| test_directory(out, path, processor))
34}
35
36fn write_test(out: &mut Testsuite, testname: &str, body: &str) -> anyhow::Result<()> {
37    writeln!(
38        out.buffer,
39        "#[compiler_test({})]",
40        out.path[..out.path.len() - 1].join("::")
41    )?;
42    writeln!(
43        out.buffer,
44        "fn r#{}(config: crate::Config) -> anyhow::Result<()> {{",
45        &testname
46    )?;
47    writeln!(out.buffer, "{body}")?;
48    writeln!(out.buffer, "}}")?;
49    writeln!(out.buffer)?;
50    Ok(())
51}
52
53pub fn test_directory(
54    out: &mut Testsuite,
55    path: impl AsRef<Path>,
56    processor: impl Fn(&mut Testsuite, PathBuf) -> Option<Test>,
57) -> anyhow::Result<usize> {
58    let path = path.as_ref();
59    let mut dir_entries: Vec<_> = path
60        .read_dir()
61        .context(format!("failed to read {path:?}"))?
62        .map(|r| r.expect("reading testsuite directory entry"))
63        .filter_map(|dir_entry| processor(out, dir_entry.path()))
64        .collect();
65
66    dir_entries.sort();
67
68    for Test {
69        name: testname,
70        body,
71    } in dir_entries.iter()
72    {
73        out.path.push(testname.to_string());
74        write_test(out, testname, body).unwrap();
75        out.path.pop().unwrap();
76    }
77
78    Ok(dir_entries.len())
79}
80
81/// Extract a valid Rust identifier from the stem of a path.
82pub fn extract_name(path: impl AsRef<Path>) -> String {
83    path.as_ref()
84        .file_stem()
85        .expect("filename should have a stem")
86        .to_str()
87        .expect("filename should be representable as a string")
88        .replace(['-', '/'], "_")
89}
90
91pub fn with_test_module<T>(
92    out: &mut Testsuite,
93    testsuite: &str,
94    f: impl FnOnce(&mut Testsuite) -> anyhow::Result<T>,
95) -> anyhow::Result<T> {
96    out.path.push(testsuite.to_string());
97    out.buffer.push_str("mod ");
98    out.buffer.push_str(testsuite);
99    out.buffer.push_str(" {\n");
100
101    let result = f(out)?;
102
103    out.buffer.push_str("}\n");
104    out.path.pop().unwrap();
105    Ok(result)
106}