implement fuzzing for component types (#4537)
This addresses #4307. For the static API we generate 100 arbitrary test cases at build time, each of which includes 0-5 parameter types, a result type, and a WAT fragment containing an imported function and an exported function. The exported function calls the imported function, which is implemented by the host. At runtime, the fuzz test selects a test case at random and feeds it zero or more sets of arbitrary parameters and results, checking that values which flow host-to-guest and guest-to-host make the transition unchanged. The fuzz test for the dynamic API follows a similar pattern, the only difference being that test cases are generated at runtime. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
This commit is contained in:
13
crates/misc/component-test-util/Cargo.toml
Normal file
13
crates/misc/component-test-util/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "component-test-util"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.9.0"
|
||||
anyhow = "1.0.19"
|
||||
arbitrary = { version = "1.1.0", features = ["derive"] }
|
||||
wasmtime = { path = "../../wasmtime", features = ["component-model"] }
|
||||
112
crates/misc/component-test-util/src/lib.rs
Normal file
112
crates/misc/component-test-util/src/lib.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use anyhow::Result;
|
||||
use arbitrary::Arbitrary;
|
||||
use std::mem::MaybeUninit;
|
||||
use wasmtime::component::__internal::{
|
||||
ComponentTypes, InterfaceType, Memory, MemoryMut, Options, StoreOpaque,
|
||||
};
|
||||
use wasmtime::component::{ComponentParams, ComponentType, Func, Lift, Lower, TypedFunc, Val};
|
||||
use wasmtime::{AsContextMut, Config, Engine, StoreContextMut};
|
||||
|
||||
pub trait TypedFuncExt<P, R> {
|
||||
fn call_and_post_return(&self, store: impl AsContextMut, params: P) -> Result<R>;
|
||||
}
|
||||
|
||||
impl<P, R> TypedFuncExt<P, R> for TypedFunc<P, R>
|
||||
where
|
||||
P: ComponentParams + Lower,
|
||||
R: Lift,
|
||||
{
|
||||
fn call_and_post_return(&self, mut store: impl AsContextMut, params: P) -> Result<R> {
|
||||
let result = self.call(&mut store, params)?;
|
||||
self.post_return(&mut store)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FuncExt {
|
||||
fn call_and_post_return(&self, store: impl AsContextMut, args: &[Val]) -> Result<Val>;
|
||||
}
|
||||
|
||||
impl FuncExt for Func {
|
||||
fn call_and_post_return(&self, mut store: impl AsContextMut, args: &[Val]) -> Result<Val> {
|
||||
let result = self.call(&mut store, args)?;
|
||||
self.post_return(&mut store)?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn engine() -> Engine {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
let mut config = Config::new();
|
||||
config.wasm_component_model(true);
|
||||
|
||||
// When `WASMTIME_TEST_NO_HOG_MEMORY` is set it means we're in qemu. The
|
||||
// component model tests create a disproportionate number of instances so
|
||||
// try to cut down on virtual memory usage by avoiding 4G reservations.
|
||||
if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() {
|
||||
config.static_memory_maximum_size(0);
|
||||
config.dynamic_memory_guard_size(0);
|
||||
}
|
||||
Engine::new(&config).unwrap()
|
||||
}
|
||||
|
||||
/// Newtype wrapper for `f32` whose `PartialEq` impl considers NaNs equal to each other.
|
||||
#[derive(Copy, Clone, Debug, Arbitrary)]
|
||||
pub struct Float32(pub f32);
|
||||
|
||||
/// Newtype wrapper for `f64` whose `PartialEq` impl considers NaNs equal to each other.
|
||||
#[derive(Copy, Clone, Debug, Arbitrary)]
|
||||
pub struct Float64(pub f64);
|
||||
|
||||
macro_rules! forward_impls {
|
||||
($($a:ty => $b:ty,)*) => ($(
|
||||
unsafe impl ComponentType for $a {
|
||||
type Lower = <$b as ComponentType>::Lower;
|
||||
|
||||
const SIZE32: usize = <$b as ComponentType>::SIZE32;
|
||||
const ALIGN32: u32 = <$b as ComponentType>::ALIGN32;
|
||||
|
||||
#[inline]
|
||||
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> {
|
||||
<$b as ComponentType>::typecheck(ty, types)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Lower for $a {
|
||||
fn lower<U>(
|
||||
&self,
|
||||
store: &mut StoreContextMut<U>,
|
||||
options: &Options,
|
||||
dst: &mut MaybeUninit<Self::Lower>,
|
||||
) -> Result<()> {
|
||||
<$b as Lower>::lower(&self.0, store, options, dst)
|
||||
}
|
||||
|
||||
fn store<U>(&self, memory: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
|
||||
<$b as Lower>::store(&self.0, memory, offset)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Lift for $a {
|
||||
fn lift(store: &StoreOpaque, options: &Options, src: &Self::Lower) -> Result<Self> {
|
||||
Ok(Self(<$b as Lift>::lift(store, options, src)?))
|
||||
}
|
||||
|
||||
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
|
||||
Ok(Self(<$b as Lift>::load(memory, bytes)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for $a {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0 || (self.0.is_nan() && other.0.is_nan())
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
forward_impls! {
|
||||
Float32 => f32,
|
||||
Float64 => f64,
|
||||
}
|
||||
Reference in New Issue
Block a user