Files
wasmtime/cranelift/filetests/src/test_wasm.rs
Trevor Elliott f04decc4a1 Use capstone to validate precise-output tests (#5780)
Use the capstone library to disassemble precise-output tests, in addition to pretty-printing their vcode.
2023-02-15 16:35:10 -08:00

135 lines
4.5 KiB
Rust

//! Test runner for `.wat` files to exercise CLIF-to-Wasm translations.
mod config;
mod env;
use anyhow::{bail, ensure, Context, Result};
use config::TestConfig;
use env::ModuleEnv;
use similar::TextDiff;
use std::{fmt::Write, path::Path};
/// Run one `.wat` test.
pub fn run(path: &Path, wat: &str) -> Result<()> {
debug_assert_eq!(path.extension().unwrap_or_default(), "wat");
// The test config source is the leading lines of the WAT file that are
// prefixed with `;;!`.
let config_lines: Vec<_> = wat
.lines()
.take_while(|l| l.starts_with(";;!"))
.map(|l| &l[3..])
.collect();
let config_text = config_lines.join("\n");
let config: TestConfig =
toml::from_str(&config_text).context("failed to parse the test configuration")?;
log::debug!("Wasm test config = {config:#?}");
config
.validate()
.context("test configuration is malformed")?;
let parsed = cranelift_reader::parse_sets_and_triple(&config.settings, &config.target)
.context("invalid ISA target or Cranelift settings")?;
let flags_or_isa = parsed.as_fisa();
ensure!(
flags_or_isa.isa.is_some(),
"Running `.wat` tests requires specifying an ISA"
);
let isa = flags_or_isa.isa.unwrap();
let mut env = ModuleEnv::new(isa, config.clone());
let wasm = wat::parse_str(wat).context("failed to parse the test WAT")?;
let mut validator = wasmparser::Validator::new_with_features(
cranelift_wasm::ModuleEnvironment::wasm_features(&env),
);
validator
.validate_all(&wasm)
.context("test WAT failed to validate")?;
cranelift_wasm::translate_module(&wasm, &mut env)
.context("failed to translate the test case into CLIF")?;
let mut actual = String::new();
for (_index, func) in env.inner.info.function_bodies.iter() {
if config.compile {
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
ctx.set_disasm(true);
let code = ctx
.compile(isa)
.map_err(|e| crate::pretty_anyhow_error(&e.func, e.inner))?;
writeln!(&mut actual, "function {}:", func.name).unwrap();
writeln!(&mut actual, "{}", code.vcode.as_ref().unwrap()).unwrap();
} else if config.optimize {
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
ctx.optimize(isa)
.map_err(|e| crate::pretty_anyhow_error(&ctx.func, e))?;
writeln!(&mut actual, "{}", ctx.func.display()).unwrap();
} else {
writeln!(&mut actual, "{}", func.display()).unwrap();
}
}
let actual = actual.trim();
log::debug!("=== actual ===\n{actual}");
// The test's expectation is the final comment.
let mut expected_lines: Vec<_> = wat
.lines()
.rev()
.take_while(|l| l.starts_with(";;"))
.map(|l| {
if l.starts_with(";; ") {
&l[3..]
} else {
&l[2..]
}
})
.collect();
expected_lines.reverse();
let expected = expected_lines.join("\n");
let expected = expected.trim();
log::debug!("=== expected ===\n{expected}");
if actual == expected {
return Ok(());
}
if std::env::var("CRANELIFT_TEST_BLESS").unwrap_or_default() == "1" {
let old_expectation_line_count = wat
.lines()
.rev()
.take_while(|l| l.starts_with(";;"))
.count();
let old_wat_line_count = wat.lines().count();
let new_wat_lines: Vec<_> = wat
.lines()
.take(old_wat_line_count - old_expectation_line_count)
.map(|l| l.to_string())
.chain(actual.lines().map(|l| {
if l.is_empty() {
";;".to_string()
} else {
format!(";; {l}")
}
}))
.collect();
let mut new_wat = new_wat_lines.join("\n");
new_wat.push('\n');
std::fs::write(path, new_wat)
.with_context(|| format!("failed to write file: {}", path.display()))?;
return Ok(());
}
bail!(
"Did not get the expected CLIF translation:\n\n\
{}\n\n\
Note: You can re-run with the `CRANELIFT_TEST_BLESS=1` environment\n\
variable set to update test expectations.",
TextDiff::from_lines(expected, actual)
.unified_diff()
.header("expected", "actual")
)
}