use anyhow::{Context, Result};
use libtest_mimic::{Arguments, Trial};
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
use wasmparser::{Validator, WasmFeatures};
use wit_parser::Resolve;
fn main() {
let mut trials = Vec::new();
for (caller, callee, wit) in artifacts::TESTS {
let caller = Path::new(caller);
let callee = Path::new(callee);
let wit = Path::new(wit);
let test_name = wit.file_name().unwrap().to_str().unwrap();
let trial = Trial::test(test_name.to_string(), move || {
let mut tempdir = TempDir::new_in(caller.parent().unwrap()).unwrap();
tempdir.disable_cleanup(true);
run_test(&tempdir, caller, callee, wit)
.with_context(|| {
format!(
"failed test {test_name:?}\nartifacts are in {:?}",
tempdir.path(),
)
})
.inspect(|_| tempdir.disable_cleanup(false))
.map_err(|e| format!("{e:?}").into())
})
.with_ignored_flag(cfg!(target_family = "wasm"));
trials.push(trial);
}
libtest_mimic::run(&Arguments::from_args(), trials).exit();
}
fn run_test(tempdir: &TempDir, caller: &Path, callee: &Path, wit: &Path) -> Result<()> {
let mut resolve = Resolve::default();
let package = resolve.push_file(wit).context("failed to load WIT")?;
let caller_world = resolve.select_world(&[package], Some("caller"))?;
let callee_world = resolve.select_world(&[package], Some("callee"))?;
let composition_file = artifacts::compose(
tempdir,
&resolve,
(caller, caller_world),
(callee, callee_world),
)?;
let mut cmd = Command::new("wasmtime");
cmd.arg("run")
.arg("--invoke=run()")
.arg("-Shttp")
.arg("-Wcomponent-model-async")
.arg("-Wcomponent-model-error-context")
.arg(&composition_file);
let result = cmd.output().context("failed to run wasmtime")?;
if result.status.success() {
return Ok(());
}
let mut error = String::new();
error.push_str(&format!("command: {cmd:?}\n"));
error.push_str(&format!("status: {}\n", result.status));
if !result.stdout.is_empty() {
error.push_str(&format!(
"stdout:\n {}",
String::from_utf8_lossy(&result.stdout).replace("\n", "\n ")
));
}
if !result.stderr.is_empty() {
error.push_str(&format!(
"stderr:\n {}",
String::from_utf8_lossy(&result.stderr).replace("\n", "\n ")
));
}
if uses_async_and_wasmtime_does_not_support_async(&composition_file, &error)? {
return Ok(());
}
anyhow::bail!("{error}")
}
fn uses_async_and_wasmtime_does_not_support_async(wasm: &Path, error: &str) -> Result<bool> {
if !error.contains("invalid leading byte (0x43) for component defined type") {
return Ok(false);
}
let wasm = std::fs::read(wasm)?;
let validates_with_cm_async = Validator::new_with_features(WasmFeatures::all())
.validate_all(&wasm)
.is_ok();
let validates_without_cm_async =
Validator::new_with_features(WasmFeatures::all() ^ WasmFeatures::CM_ASYNC)
.validate_all(&wasm)
.is_ok();
Ok(validates_with_cm_async && !validates_without_cm_async)
}