Simplify #[test] generation for *.wast files (#507)
This commit simplifies the build script slightly for generating tests by doing a few dull refactorings: * Leaves formatting to `rustfmt` * Extract bulk of code execution into a top-level shared `run_wast` function so each test is a one-liner * Use `anyhow` for errors both in the script and in tests
This commit is contained in:
@@ -38,6 +38,9 @@ libc = "0.2.60"
|
|||||||
rayon = "1.1"
|
rayon = "1.1"
|
||||||
wasm-webidl-bindings = "0.6"
|
wasm-webidl-bindings = "0.6"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
anyhow = "1.0.19"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"misc/wasmtime-rust",
|
"misc/wasmtime-rust",
|
||||||
|
|||||||
192
build.rs
192
build.rs
@@ -3,108 +3,105 @@
|
|||||||
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
//! By generating a separate `#[test]` test for each file, we allow cargo test
|
||||||
//! to automatically run the files in parallel.
|
//! to automatically run the files in parallel.
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{read_dir, File};
|
use std::fmt::Write;
|
||||||
use std::io::{self, Write};
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> anyhow::Result<()> {
|
||||||
let out_dir =
|
let out_dir = PathBuf::from(
|
||||||
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"));
|
env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
|
||||||
let mut out = File::create(out_dir.join("wast_testsuite_tests.rs"))
|
);
|
||||||
.expect("error generating test source file");
|
let mut out = String::new();
|
||||||
|
|
||||||
for strategy in &[
|
for strategy in &[
|
||||||
"Cranelift",
|
"Cranelift",
|
||||||
#[cfg(feature = "lightbeam")]
|
#[cfg(feature = "lightbeam")]
|
||||||
"Lightbeam",
|
"Lightbeam",
|
||||||
] {
|
] {
|
||||||
writeln!(out, "#[cfg(test)]").expect("generating tests");
|
writeln!(out, "#[cfg(test)]")?;
|
||||||
writeln!(out, "#[allow(non_snake_case)]").expect("generating tests");
|
writeln!(out, "#[allow(non_snake_case)]")?;
|
||||||
writeln!(out, "mod {} {{", strategy).expect("generating tests");
|
writeln!(out, "mod {} {{", strategy)?;
|
||||||
|
|
||||||
test_directory(&mut out, "misc_testsuite", strategy).expect("generating tests");
|
test_directory(&mut out, "misc_testsuite", strategy)?;
|
||||||
test_directory(&mut out, "spec_testsuite", strategy).expect("generating tests");
|
let spec_tests = test_directory(&mut out, "spec_testsuite", strategy)?;
|
||||||
// Skip running spec_testsuite tests if the submodule isn't checked out.
|
// Skip running spec_testsuite tests if the submodule isn't checked
|
||||||
if read_dir("spec_testsuite")
|
// out.
|
||||||
.expect("reading testsuite directory")
|
if spec_tests > 0 {
|
||||||
.next()
|
start_test_module(&mut out, "simd")?;
|
||||||
.is_some()
|
write_testsuite_tests(
|
||||||
{
|
|
||||||
test_file(
|
|
||||||
&mut out,
|
&mut out,
|
||||||
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_address.wast"]),
|
"spec_testsuite/proposals/simd/simd_address.wast",
|
||||||
|
"simd",
|
||||||
strategy,
|
strategy,
|
||||||
)
|
)?;
|
||||||
.expect("generating tests");
|
write_testsuite_tests(
|
||||||
test_file(
|
|
||||||
&mut out,
|
&mut out,
|
||||||
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_align.wast"]),
|
"spec_testsuite/proposals/simd/simd_align.wast",
|
||||||
|
"simd",
|
||||||
strategy,
|
strategy,
|
||||||
)
|
)?;
|
||||||
.expect("generating tests");
|
write_testsuite_tests(
|
||||||
test_file(
|
|
||||||
&mut out,
|
&mut out,
|
||||||
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_const.wast"]),
|
"spec_testsuite/proposals/simd/simd_const.wast",
|
||||||
|
"simd",
|
||||||
strategy,
|
strategy,
|
||||||
)
|
)?;
|
||||||
.expect("generating tests");
|
finish_test_module(&mut out)?;
|
||||||
|
|
||||||
let multi_value_suite = &to_os_path(&["spec_testsuite", "proposals", "multi-value"]);
|
test_directory(&mut out, "spec_testsuite/proposals/multi-value", strategy)
|
||||||
test_directory(&mut out, &multi_value_suite, strategy).expect("generating tests");
|
.expect("generating tests");
|
||||||
} else {
|
} else {
|
||||||
println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`.");
|
println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`.");
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(out, "}}").expect("generating tests");
|
writeln!(out, "}}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write out our auto-generated tests and opportunistically format them with
|
||||||
|
// `rustfmt` if it's installed.
|
||||||
|
let output = out_dir.join("wast_testsuite_tests.rs");
|
||||||
|
fs::write(&output, out)?;
|
||||||
|
drop(Command::new("rustfmt").arg(&output).status());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for creating OS-independent paths.
|
fn test_directory(
|
||||||
fn to_os_path(components: &[&str]) -> String {
|
out: &mut String,
|
||||||
let path: PathBuf = components.iter().collect();
|
path: impl AsRef<Path>,
|
||||||
path.display().to_string()
|
strategy: &str,
|
||||||
}
|
) -> anyhow::Result<usize> {
|
||||||
|
let path = path.as_ref();
|
||||||
fn test_directory(out: &mut File, path: &str, strategy: &str) -> io::Result<()> {
|
let mut dir_entries: Vec<_> = path
|
||||||
let mut dir_entries: Vec<_> = read_dir(path)
|
.read_dir()
|
||||||
.expect("reading testsuite directory")
|
.context(format!("failed to read {:?}", path))?
|
||||||
.map(|r| r.expect("reading testsuite directory entry"))
|
.map(|r| r.expect("reading testsuite directory entry"))
|
||||||
.filter(|dir_entry| {
|
.filter_map(|dir_entry| {
|
||||||
let p = dir_entry.path();
|
let p = dir_entry.path();
|
||||||
if let Some(ext) = p.extension() {
|
let ext = p.extension()?;
|
||||||
// Only look at wast files.
|
// Only look at wast files.
|
||||||
if ext == "wast" {
|
if ext != "wast" {
|
||||||
// Ignore files starting with `.`, which could be editor temporary files
|
return None;
|
||||||
if let Some(stem) = p.file_stem() {
|
|
||||||
if let Some(stemstr) = stem.to_str() {
|
|
||||||
if !stemstr.starts_with('.') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
false
|
// Ignore files starting with `.`, which could be editor temporary files
|
||||||
|
if p.file_stem()?.to_str()?.starts_with(".") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(p)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
dir_entries.sort_by_key(|dir| dir.path());
|
dir_entries.sort();
|
||||||
|
|
||||||
let testsuite = &extract_name(path);
|
let testsuite = &extract_name(path);
|
||||||
start_test_module(out, testsuite)?;
|
start_test_module(out, testsuite)?;
|
||||||
for dir_entry in dir_entries {
|
for entry in dir_entries.iter() {
|
||||||
write_testsuite_tests(out, &dir_entry.path(), testsuite, strategy)?;
|
write_testsuite_tests(out, entry, testsuite, strategy)?;
|
||||||
}
|
}
|
||||||
finish_test_module(out)
|
finish_test_module(out)?;
|
||||||
}
|
Ok(dir_entries.len())
|
||||||
|
|
||||||
fn test_file(out: &mut File, testfile: &str, strategy: &str) -> io::Result<()> {
|
|
||||||
let path = Path::new(testfile);
|
|
||||||
let testsuite = format!("single_test_{}", extract_name(path));
|
|
||||||
start_test_module(out, &testsuite)?;
|
|
||||||
write_testsuite_tests(out, path, &testsuite, strategy)?;
|
|
||||||
finish_test_module(out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a valid Rust identifier from the stem of a path.
|
/// Extract a valid Rust identifier from the stem of a path.
|
||||||
@@ -118,63 +115,38 @@ fn extract_name(path: impl AsRef<Path>) -> String {
|
|||||||
.replace("/", "_")
|
.replace("/", "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_test_module(out: &mut File, testsuite: &str) -> io::Result<()> {
|
fn start_test_module(out: &mut String, testsuite: &str) -> anyhow::Result<()> {
|
||||||
writeln!(out, " mod {} {{", testsuite)?;
|
writeln!(out, "mod {} {{", testsuite)?;
|
||||||
writeln!(
|
Ok(())
|
||||||
out,
|
|
||||||
" use super::super::{{native_isa, Path, WastContext, Compiler, Features, CompilationStrategy}};"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_test_module(out: &mut File) -> io::Result<()> {
|
fn finish_test_module(out: &mut String) -> anyhow::Result<()> {
|
||||||
writeln!(out, " }}")
|
out.push_str("}\n");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_testsuite_tests(
|
fn write_testsuite_tests(
|
||||||
out: &mut File,
|
out: &mut String,
|
||||||
path: &Path,
|
path: impl AsRef<Path>,
|
||||||
testsuite: &str,
|
testsuite: &str,
|
||||||
strategy: &str,
|
strategy: &str,
|
||||||
) -> io::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
let testname = extract_name(path);
|
let testname = extract_name(path);
|
||||||
|
|
||||||
writeln!(out, " #[test]")?;
|
writeln!(out, "#[test]")?;
|
||||||
if ignore(testsuite, &testname, strategy) {
|
if ignore(testsuite, &testname, strategy) {
|
||||||
writeln!(out, " #[ignore]")?;
|
writeln!(out, "#[ignore]")?;
|
||||||
}
|
}
|
||||||
writeln!(out, " fn r#{}() {{", &testname)?;
|
writeln!(out, "fn r#{}() -> anyhow::Result<()> {{", &testname)?;
|
||||||
writeln!(out, " let isa = native_isa();")?;
|
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
" let compiler = Compiler::new(isa, CompilationStrategy::{});",
|
"crate::run_wast(r#\"{}\"#, crate::CompilationStrategy::{})",
|
||||||
|
path.display(),
|
||||||
strategy
|
strategy
|
||||||
)?;
|
)?;
|
||||||
writeln!(
|
writeln!(out, "}}")?;
|
||||||
out,
|
|
||||||
" let features = Features {{ simd: {}, multi_value: {}, ..Default::default() }};",
|
|
||||||
testsuite.contains("simd"),
|
|
||||||
testsuite.contains("multi_value")
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
out,
|
|
||||||
" let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);"
|
|
||||||
)?;
|
|
||||||
writeln!(out, " wast_context")?;
|
|
||||||
writeln!(out, " .register_spectest()")?;
|
|
||||||
writeln!(
|
|
||||||
out,
|
|
||||||
" .expect(\"instantiating \\\"spectest\\\"\");"
|
|
||||||
)?;
|
|
||||||
writeln!(out, " wast_context")?;
|
|
||||||
write!(out, " .run_file(Path::new(\"")?;
|
|
||||||
// Write out the string with escape_debug to prevent special characters such
|
|
||||||
// as backslash from being reinterpreted.
|
|
||||||
for c in path.display().to_string().chars() {
|
|
||||||
write!(out, "{}", c.escape_debug())?;
|
|
||||||
}
|
|
||||||
writeln!(out, "\"))")?;
|
|
||||||
writeln!(out, " .expect(\"error running wast file\");",)?;
|
|
||||||
writeln!(out, " }}")?;
|
|
||||||
writeln!(out)?;
|
writeln!(out)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,24 @@ use wasmtime_wast::WastContext;
|
|||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||||
|
|
||||||
#[cfg(test)]
|
// Each of the tests included from `wast_testsuite_tests` will call this
|
||||||
|
// function which actually executes the `wast` test suite given the `strategy`
|
||||||
|
// to compile it.
|
||||||
|
fn run_wast(wast: &str, strategy: CompilationStrategy) -> anyhow::Result<()> {
|
||||||
|
let wast = Path::new(wast);
|
||||||
|
let isa = native_isa();
|
||||||
|
let compiler = Compiler::new(isa, strategy);
|
||||||
|
let features = Features {
|
||||||
|
simd: wast.iter().any(|s| s == "simd"),
|
||||||
|
multi_value: wast.iter().any(|s| s == "multi-value"),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);
|
||||||
|
wast_context.register_spectest()?;
|
||||||
|
wast_context.run_file(wast)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn native_isa() -> Box<dyn isa::TargetIsa> {
|
fn native_isa() -> Box<dyn isa::TargetIsa> {
|
||||||
let mut flag_builder = settings::builder();
|
let mut flag_builder = settings::builder();
|
||||||
flag_builder.enable("enable_verifier").unwrap();
|
flag_builder.enable("enable_verifier").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user