revmc_cli_tests/
lib.rs

1//! CLI tests runner.
2
3use tester as test;
4
5use std::{
6    fs,
7    path::{Path, PathBuf},
8    process::Command,
9    sync::Arc,
10};
11use test::{ShouldPanic, TestDesc, TestDescAndFn, TestFn, TestName, TestType};
12use walkdir::{DirEntry, WalkDir};
13
14/// Run all tests with the given command.
15pub fn run_tests(cmd: &'static Path) -> i32 {
16    let args = std::env::args().collect::<Vec<_>>();
17    let mut opts = match test::test::parse_opts(&args) {
18        Some(Ok(o)) => o,
19        Some(Err(msg)) => {
20            eprintln!("error: {msg}");
21            return 101;
22        }
23        None => return 0,
24    };
25
26    /*
27    // Condense output if not explicitly requested.
28    let requested_pretty = || args.iter().any(|x| x.contains("--format"));
29    if opts.format == test::OutputFormat::Pretty && !requested_pretty() {
30        opts.format = test::OutputFormat::Terse;
31    }
32    */
33    // [`tester`] currently (0.9.1) uses `num_cpus::get_physical`;
34    // use all available threads instead.
35    if opts.test_threads.is_none() {
36        opts.test_threads = std::thread::available_parallelism().map(|x| x.get()).ok();
37    }
38
39    let mut tests = Vec::new();
40    make_tests(cmd, &mut tests);
41    tests.sort_by(|a, b| a.desc.name.as_slice().cmp(b.desc.name.as_slice()));
42
43    if opts.list {
44        // The only way to call `list_tests_console` is through `test_main`.
45        test::test_main(&args, tests, Some(opts.options));
46        return 0;
47    }
48
49    match test::run_tests_console(&opts, tests) {
50        Ok(true) => 0,
51        Ok(false) => {
52            eprintln!("Some tests failed");
53            1
54        }
55        Err(e) => {
56            eprintln!("I/O failure during tests: {e}");
57            101
58        }
59    }
60}
61
62fn make_tests(cmd: &'static Path, tests: &mut Vec<TestDescAndFn>) {
63    let config = Arc::new(Config::new(cmd));
64
65    let codegen = config.root.join("tests/codegen");
66    for entry in collect_tests(&codegen) {
67        let config = config.clone();
68        let path = entry.path().to_path_buf();
69        let stripped = path.strip_prefix(config.root).unwrap();
70        let name = stripped.display().to_string();
71        tests.push(TestDescAndFn {
72            desc: TestDesc {
73                name: TestName::DynTestName(name),
74                allow_fail: false,
75                ignore: false,
76                should_panic: ShouldPanic::No,
77                test_type: TestType::Unknown,
78            },
79            testfn: TestFn::DynTestFn(Box::new(move || run_test(&config, &path))),
80        });
81    }
82}
83
84fn collect_tests(root: &Path) -> impl Iterator<Item = DirEntry> {
85    WalkDir::new(root)
86        .sort_by_file_name()
87        .into_iter()
88        .map(Result::unwrap)
89        .filter(|e| e.file_type().is_file())
90}
91
92fn run_test(config: &Config, path: &Path) {
93    let test_name = path.file_stem().unwrap().to_str().unwrap();
94
95    // let s = fs::read_to_string(path).unwrap();
96    // let comment = if path.extension() == Some("evm".as_ref()) { "#"} else { "//"};
97
98    // let lines = s.lines().map(str::trim).filter(|s| !s.is_empty());
99    // let comments = lines.filter_map(|s| s.strip_prefix(comment)).map(str::trim_start);
100    // let mut commands = comments.filter_map(|s| s.strip_prefix("RUN:")).map(str::trim_start);
101    // let command = commands.next().expect("no `RUN:` directive provided");
102    // assert!(commands.next().is_none(), "multiple `RUN:` directives provided");
103
104    let build_dir = &config.build_base;
105
106    let mut compiler = Command::new(config.cmd);
107    fs::create_dir_all(build_dir).unwrap();
108    compiler.arg(path).arg("-o").arg(build_dir);
109    // eprintln!("running compiler: {compiler:?}");
110    let output = compiler.output().expect("failed to run test");
111    assert!(
112        output.status.success(),
113        "compiler failed with {}:\n{}",
114        output.status,
115        String::from_utf8_lossy(&output.stderr)
116    );
117    let out_dir = build_dir.join(test_name);
118    assert!(out_dir.exists(), "no output produced");
119
120    let input_path = out_dir.join("opt.ll");
121
122    let mut filecheck = Command::new(config.filecheck.as_deref().unwrap_or("FileCheck".as_ref()));
123    filecheck.arg(path).arg("--input-file").arg(&input_path);
124    // eprintln!("running filecheck: {filecheck:?}");
125    let output = filecheck.output().expect("failed to run FileCheck");
126    assert!(
127        output.status.success(),
128        "FileCheck failed with {}:\n{}",
129        output.status,
130        String::from_utf8_lossy(&output.stderr)
131    );
132}
133
134struct Config {
135    cmd: &'static Path,
136    root: &'static Path,
137    build_base: PathBuf,
138    filecheck: Option<PathBuf>,
139}
140
141impl Config {
142    fn new(cmd: &'static Path) -> Self {
143        let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
144        let build_base = root.join("target/tester");
145        fs::create_dir_all(&build_base).unwrap();
146        Self { root, cmd, build_base, filecheck: None }
147    }
148}