use arbitrary::{Result, Unstructured};
use indexmap::IndexMap;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
use wit_parser::decoding::DecodedWasm;
use wit_parser::{
Function, FunctionKind, Handle, Interface, Package, PackageName, Param, Resolve, Type, TypeDef,
TypeDefKind, TypeOwner, World, WorldItem, WorldKey,
};
#[test]
fn run() {
let _ = env_logger::try_init();
if cfg!(target_family = "wasm") {
return;
}
arbtest::arbtest(run_one)
.run();
}
fn run_one(u: &mut Unstructured<'_>) -> Result<()> {
println!("iter...");
let seed = u.arbitrary::<u64>()?;
let iters = 200;
let mut config = u.arbitrary::<wit_smith::Config>()?;
config.error_context = false;
config.fixed_length_lists = false;
config.futures = false; config.streams = false; config.async_ = false;
let wasm = wit_smith::smith(&config, u)?;
let (mut resolve, _pkg) = match wit_parser::decoding::decode(&wasm).unwrap() {
DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg),
DecodedWasm::Component(..) => unreachable!(),
};
update_resources(&mut resolve);
let interfaces = resolve
.packages
.iter()
.flat_map(|(_, pkg)| pkg.interfaces.values().cloned())
.collect::<Vec<_>>();
let world_items = interfaces
.iter()
.map(|id| {
(
WorldKey::Interface(*id),
WorldItem::Interface {
id: *id,
stability: Default::default(),
span: Default::default(),
},
)
})
.collect::<IndexMap<_, _>>();
let package = resolve.packages.alloc(Package {
name: PackageName {
namespace: "wit-dylib".to_string(),
name: "roundtrip-test".to_string(),
version: None,
},
interfaces: Default::default(),
worlds: Default::default(),
docs: Default::default(),
});
let alloc = resolve.interfaces.alloc(Interface {
name: Some("alloc".to_string()),
stability: Default::default(),
package: Some(package),
docs: Default::default(),
types: Default::default(),
functions: {
let mut funcs = IndexMap::new();
funcs.insert(
"allocated-bytes".to_string(),
Function {
name: "allocated-bytes".to_string(),
kind: FunctionKind::Freestanding,
params: Vec::new(),
result: Some(Type::U32),
stability: Default::default(),
docs: Default::default(),
span: Default::default(),
},
);
funcs.insert(
"set-seed".to_string(),
Function {
name: "set-seed".to_string(),
kind: FunctionKind::Freestanding,
params: vec![Param {
name: "seed".to_string(),
ty: Type::U64,
span: Default::default(),
}],
result: None,
stability: Default::default(),
docs: Default::default(),
span: Default::default(),
},
);
funcs.insert(
"checkpoint".to_string(),
Function {
name: "checkpoint".to_string(),
kind: FunctionKind::Freestanding,
params: Vec::new(),
result: Some(Type::U32),
stability: Default::default(),
docs: Default::default(),
span: Default::default(),
},
);
funcs
},
span: Default::default(),
clone_of: None,
});
let callee = resolve.worlds.alloc(World {
name: "callee".to_string(),
stability: Default::default(),
package: Some(package),
exports: world_items.clone(),
imports: Default::default(),
includes: Default::default(),
docs: Default::default(),
span: Default::default(),
});
let caller = resolve.worlds.alloc(World {
name: "caller".to_string(),
stability: Default::default(),
package: Some(package),
imports: world_items,
exports: Default::default(),
includes: Default::default(),
docs: Default::default(),
span: Default::default(),
});
resolve.worlds[callee].exports.insert(
WorldKey::Interface(alloc),
WorldItem::Interface {
id: alloc,
stability: Default::default(),
span: Default::default(),
},
);
resolve.worlds[caller].imports.insert(
WorldKey::Interface(alloc),
WorldItem::Interface {
id: alloc,
stability: Default::default(),
span: Default::default(),
},
);
resolve.worlds[caller].exports.insert(
WorldKey::Name("run".to_string()),
WorldItem::Function(Function {
name: "run".to_string(),
kind: FunctionKind::Freestanding,
params: vec![
Param {
name: "iters".to_string(),
ty: Type::U32,
span: Default::default(),
},
Param {
name: "seed".to_string(),
ty: Type::U64,
span: Default::default(),
},
],
result: None,
stability: Default::default(),
docs: Default::default(),
span: Default::default(),
}),
);
resolve.packages[package]
.interfaces
.insert("alloc".to_string(), alloc);
resolve.packages[package]
.worlds
.insert("callee".to_string(), callee);
resolve.packages[package]
.worlds
.insert("caller".to_string(), caller);
if false {
let mut printer = wit_component::WitPrinter::default();
printer
.print(
&resolve,
package,
&resolve
.packages
.iter()
.map(|(id, _)| id)
.filter(|i| *i != package)
.collect::<Vec<_>>(),
)
.unwrap();
println!("{}", printer.output);
}
let mut tempdir =
TempDir::new_in(Path::new(artifacts::ROUNDTRIP_CALLER).parent().unwrap()).unwrap();
tempdir.disable_cleanup(true);
let composition = artifacts::compose(
&tempdir,
&resolve,
(artifacts::ROUNDTRIP_CALLER.as_ref(), caller),
(artifacts::ROUNDTRIP_CALLEE.as_ref(), callee),
)
.expect("failed to compose");
let mut cmd = Command::new("wasmtime");
cmd.arg("run")
.arg(format!("--invoke=run({iters}, {seed})"))
.arg("-Shttp")
.arg("-Wcomponent-model-async")
.arg("-Wcomponent-model-error-context")
.arg(&composition);
let result = cmd.output().expect("failed to run wasmtime");
if result.status.success() {
tempdir.disable_cleanup(false);
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 {}\n",
String::from_utf8_lossy(&result.stdout).replace("\n", "\n ")
));
}
if !result.stderr.is_empty() {
error.push_str(&format!(
"stderr:\n {}\n",
String::from_utf8_lossy(&result.stderr).replace("\n", "\n ")
));
}
panic!("{error}")
}
fn update_resources(resolve: &mut Resolve) {
let interface_resources = resolve
.interfaces
.iter()
.flat_map(|(id, iface)| {
iface
.types
.iter()
.filter(|(_name, id)| {
let ty = &resolve.types[**id];
matches!(ty.kind, TypeDefKind::Resource)
})
.map(move |(name, resource_id)| (id, *resource_id, name.clone()))
})
.collect::<Vec<_>>();
for (interface_id, resource_id, resource_name) in interface_resources {
let own = resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Handle(Handle::Own(resource_id)),
owner: TypeOwner::None,
docs: Default::default(),
stability: Default::default(),
span: Default::default(),
});
let borrow = resolve.types.alloc(TypeDef {
name: None,
kind: TypeDefKind::Handle(Handle::Borrow(resource_id)),
owner: TypeOwner::None,
docs: Default::default(),
stability: Default::default(),
span: Default::default(),
});
let iface = &mut resolve.interfaces[interface_id];
let ctor = format!("[constructor]{resource_name}");
let rep = format!("[method]{resource_name}.rep");
iface.functions.swap_remove(&ctor);
iface.functions.swap_remove(&rep);
iface.functions.insert(
ctor.clone(),
Function {
name: ctor,
kind: FunctionKind::Constructor(resource_id),
params: vec![Param {
name: "rep".to_string(),
ty: Type::U32,
span: Default::default(),
}],
result: Some(Type::Id(own)),
stability: Default::default(),
docs: Default::default(),
span: Default::default(),
},
);
iface.functions.insert(
rep.clone(),
Function {
name: rep,
kind: FunctionKind::Method(resource_id),
params: vec![Param {
name: "self".to_string(),
ty: Type::Id(borrow),
span: Default::default(),
}],
result: Some(Type::U32),
stability: Default::default(),
docs: Default::default(),
span: Default::default(),
},
);
}
}