use anyhow::{anyhow, bail, Context, Result};
use owo_colors::OwoColorize;
use pretty_assertions::StrComparison;
use rayon::prelude::*;
use std::{
env,
ffi::OsStr,
fs,
path::{Path, PathBuf},
process::exit,
sync::atomic::{AtomicUsize, Ordering},
};
use support::fmt_err;
use wac_graph::EncodeOptions;
use wac_parser::Document;
use wac_resolver::{packages, FileSystemPackageResolver};
mod support;
fn find_tests() -> Vec<PathBuf> {
let mut tests = Vec::new();
find_tests("tests/encoding", &mut tests);
find_tests("tests/encoding/fail", &mut tests);
tests.sort();
return tests;
fn find_tests(path: impl AsRef<Path>, tests: &mut Vec<PathBuf>) {
for entry in path.as_ref().read_dir().unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
continue;
}
match path.extension().and_then(|s| s.to_str()) {
Some("wac") => {}
_ => continue,
}
tests.push(path);
}
}
}
fn normalize(s: &str, should_fail: bool) -> String {
if should_fail {
return s.replace('\\', "/").replace("\r\n", "\n");
}
s.replace("\r\n", "\n")
}
fn compare_result(test: &Path, result: &str, should_fail: bool) -> Result<()> {
let path = test.with_extension("wac.result");
let result = normalize(result, should_fail);
if env::var_os("BLESS").is_some() {
fs::write(&path, &result).with_context(|| {
format!(
"failed to write result file `{path}`",
path = path.display()
)
})?;
return Ok(());
}
let expected = fs::read_to_string(&path)
.with_context(|| format!("failed to read result file `{path}`", path = path.display()))?
.replace("\r\n", "\n");
if expected != result {
bail!(
"result is not as expected:\n{}",
StrComparison::new(&expected, &result),
);
}
Ok(())
}
fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> {
let should_fail = test.parent().map(|p| p.ends_with("fail")).unwrap_or(false);
let source = std::fs::read_to_string(test)?.replace("\r\n", "\n");
let document = Document::parse(&source).map_err(|e| anyhow!(fmt_err(e, test, &source)))?;
let encode = || {
let resolver = FileSystemPackageResolver::new(
test.parent().unwrap().join(test.file_stem().unwrap()),
Default::default(),
true,
);
let packages = resolver
.resolve(&packages(&document).map_err(|e| fmt_err(e, test, &source))?)
.map_err(|e| fmt_err(e, test, &source))?;
let resolution = document
.resolve(packages)
.map_err(|e| fmt_err(e, test, &source))?;
resolution
.encode(EncodeOptions {
define_components: false,
..Default::default()
})
.map_err(|e| fmt_err(e, test, &source))
};
let result = match encode() {
Ok(bytes) => {
if should_fail {
bail!("the encoding was successful but it was expected to fail");
}
wasmprinter::print_bytes(bytes).with_context(|| {
format!(
"failed to convert binary wasm output to text `{path}`",
path = test.display()
)
})?
}
Err(e) => {
if !should_fail {
return Err(anyhow!(e))
.context("the resolution failed but it was expected to succeed");
}
e
}
};
compare_result(test, &result, should_fail)?;
ntests.fetch_add(1, Ordering::SeqCst);
Ok(())
}
fn main() {
pretty_env_logger::init();
let tests = find_tests();
println!("running {} tests\n", tests.len());
let ntests = AtomicUsize::new(0);
let errors = tests
.par_iter()
.filter_map(|test| {
let test_name = test.file_stem().and_then(OsStr::to_str).unwrap();
match std::panic::catch_unwind(|| {
match run_test(test, &ntests)
.with_context(|| format!("failed to run test `{path}`", path = test.display()))
.err()
{
Some(e) => {
println!("test {test_name} ... {failed}", failed = "failed".red());
Some((test_name, e))
}
None => {
println!("test {test_name} ... {ok}", ok = "ok".green());
None
}
}
}) {
Ok(result) => result,
Err(e) => {
println!(
"test {test_name} ... {panicked}",
panicked = "panicked".red()
);
Some((
test_name,
anyhow!(
"test panicked: {e:?}",
e = e
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| e.downcast_ref::<&str>().copied())
.unwrap_or("no panic message")
),
))
}
}
})
.collect::<Vec<_>>();
if !errors.is_empty() {
eprintln!(
"\n{count} test(s) {failed}:",
count = errors.len(),
failed = "failed".red()
);
for (name, msg) in errors.iter() {
eprintln!("{name}: {msg:?}", msg = msg.red());
}
exit(1);
}
println!(
"\ntest result: ok. {} passed\n",
ntests.load(Ordering::SeqCst)
);
}