Files
wasmtime/winch/filetests/src/lib.rs
Kevin Rizzo da03ff47f1 winch: Adding support for integration tests (#5588)
* Adding in the foundations for Winch `filetests`

This commit adds two new crates into the Winch workspace:
`filetests` and `test-macros`. The intent is to mimic the
structure of Cranelift `filetests`, but in a simpler way.

* Updates to documentation

This commits adds a high level document to outline how to test Winch
through the `winch-tools` utility. It also updates some inline
documentation which gets propagated to the CLI.

* Updating test-macro to use a glob instead of only a flat directory
2023-01-19 07:34:48 -05:00

166 lines
5.3 KiB
Rust

pub mod disasm;
#[cfg(test)]
mod test {
use super::disasm::disasm;
use anyhow::Context;
use cranelift_codegen::settings;
use serde::{Deserialize, Serialize};
use similar::TextDiff;
use std::str::FromStr;
use target_lexicon::Triple;
use wasmtime_environ::{
wasmparser::{types::Types, Parser as WasmParser, Validator},
DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
};
use winch_codegen::isa::TargetIsa;
use winch_codegen::lookup;
use winch_test_macros::generate_file_tests;
#[derive(Clone, Debug, Serialize, Deserialize)]
struct TestConfig {
target: String,
}
/// A helper function to parse the test configuration from the top of the file.
fn parse_config(wat: &str) -> TestConfig {
let config_lines: Vec<_> = wat
.lines()
.take_while(|l| l.starts_with(";;!"))
.map(|l| &l[3..])
.collect();
let config_text = config_lines.join("\n");
toml::from_str(&config_text)
.context("failed to parse the test configuration")
.unwrap()
}
/// A helper function to parse the expected result from the bottom of the file.
fn parse_expected_result(wat: &str) -> String {
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();
expected_lines.join("\n")
}
/// A helper function to rewrite the expected result in the file.
fn rewrite_expected(wat: &str, actual: &str) -> String {
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');
new_wat
}
#[generate_file_tests]
fn run_test(test_path: &str) {
let binding = std::fs::read_to_string(test_path).unwrap();
let wat = binding.as_str();
let config = parse_config(wat);
let wasm = wat::parse_str(&wat).unwrap();
let triple = Triple::from_str(&config.target).unwrap();
let binding = parse_expected_result(wat);
let expected = binding.as_str();
let shared_flags = settings::Flags::new(settings::builder());
let isa_builder = lookup(triple).unwrap();
let isa = isa_builder.build(shared_flags).unwrap();
let mut validator = Validator::new();
let parser = WasmParser::new(0);
let mut types = Default::default();
let tunables = Tunables::default();
let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
.translate(parser, &wasm)
.context("Failed to translate WebAssembly module")
.unwrap();
let _ = types.finish();
let body_inputs = std::mem::take(&mut translation.function_body_inputs);
let module = &translation.module;
let types = translation.get_types();
let binding = body_inputs
.into_iter()
.flat_map(|func| compile(&*isa, module, types, func))
.collect::<Vec<String>>()
.join("\n");
let actual = binding.as_str();
if std::env::var("WINCH_TEST_BLESS").unwrap_or_default() == "1" {
let new_wat = rewrite_expected(wat, actual);
std::fs::write(test_path, new_wat)
.with_context(|| format!("failed to write file: {}", test_path))
.unwrap();
return;
}
if expected.trim() != actual.trim() {
eprintln!(
"\n{}",
TextDiff::from_lines(expected, actual)
.unified_diff()
.header("expected", "actual")
);
eprintln!(
"note: You can re-run with the `WINCH_TEST_BLESS=1` environment variable set to update test expectations.\n"
);
panic!("Did not get the expected translation");
}
}
fn compile(
isa: &dyn TargetIsa,
module: &Module,
types: &Types,
f: (DefinedFuncIndex, FunctionBodyData<'_>),
) -> Vec<String> {
let index = module.func_index(f.0);
let sig = types
.func_type_at(index.as_u32())
.expect(&format!("function type at index {:?}", index.as_u32()));
let FunctionBodyData { body, validator } = f.1;
let validator = validator.into_validator(Default::default());
let buffer = isa
.compile_function(&sig, &body, validator)
.expect("Couldn't compile function");
disasm(buffer.data(), isa).unwrap()
}
}