Skip to main content

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::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<()> {
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#{testname}(config: crate::Config) -> anyhow::Result<()> {{",
45    )?;
46    writeln!(out.buffer, "{body}")?;
47    writeln!(out.buffer, "}}")?;
48    writeln!(out.buffer)?;
49    Ok(())
50}
51
52pub fn test_directory(
53    out: &mut Testsuite,
54    path: impl AsRef<Path>,
55    processor: impl Fn(&mut Testsuite, PathBuf) -> Option<Test>,
56) -> anyhow::Result<()> {
57    let path = path.as_ref();
58    let mut dir_entries: Vec<_> = path
59        .read_dir()
60        .context(format!("failed to read {path:?}"))?
61        .map(|r| r.expect("reading testsuite directory entry"))
62        .filter_map(|dir_entry| processor(out, dir_entry.path()))
63        .collect();
64
65    dir_entries.sort();
66
67    for Test {
68        name: testname,
69        body,
70    } in dir_entries.iter()
71    {
72        out.path.push(testname.to_string());
73        write_test(out, testname, body).unwrap();
74        out.path.pop().unwrap();
75    }
76
77    Ok(())
78}
79
80/// Extract a valid Rust identifier from the stem of a path.
81pub fn extract_name(path: impl AsRef<Path>) -> String {
82    path.as_ref()
83        .file_stem()
84        .expect("filename should have a stem")
85        .to_str()
86        .expect("filename should be representable as a string")
87        .replace(['-', '/'], "_")
88}
89
90pub fn with_test_module(
91    out: &mut Testsuite,
92    testsuite: &str,
93    f: impl FnOnce(&mut Testsuite) -> anyhow::Result<()>,
94) -> anyhow::Result<()> {
95    out.path.push(testsuite.to_string());
96    out.buffer.push_str("mod ");
97    out.buffer.push_str(testsuite);
98    out.buffer.push_str(" {\n");
99
100    f(out)?;
101
102    out.buffer.push_str("}\n");
103    out.path.pop().unwrap();
104    Ok(())
105}