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>
201 lines
5.1 KiB
Rust
201 lines
5.1 KiB
Rust
use anyhow::Result;
|
|
use component_test_util::{engine, TypedFuncExt};
|
|
use std::fmt::Write;
|
|
use std::iter;
|
|
use wasmtime::component::Component;
|
|
use wasmtime_component_util::REALLOC_AND_FREE;
|
|
|
|
mod dynamic;
|
|
mod func;
|
|
mod import;
|
|
mod instance;
|
|
mod macros;
|
|
mod nested;
|
|
mod post_return;
|
|
|
|
#[test]
|
|
fn components_importing_modules() -> Result<()> {
|
|
let engine = engine();
|
|
|
|
// FIXME: these components should actually get instantiated in `*.wast`
|
|
// tests once supplying imports has actually been implemented.
|
|
|
|
Component::new(
|
|
&engine,
|
|
r#"
|
|
(component
|
|
(import "" (core module))
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
Component::new(
|
|
&engine,
|
|
r#"
|
|
(component
|
|
(import "" (core module $m1
|
|
(import "" "" (func))
|
|
(import "" "x" (global i32))
|
|
|
|
(export "a" (table 1 funcref))
|
|
(export "b" (memory 1))
|
|
(export "c" (func (result f32)))
|
|
(export "d" (global i64))
|
|
))
|
|
|
|
(core module $m2
|
|
(func (export ""))
|
|
(global (export "x") i32 i32.const 0)
|
|
)
|
|
(core instance $i2 (instantiate (module $m2)))
|
|
(core instance $i1 (instantiate (module $m1) (with "" (instance $i2))))
|
|
|
|
(core module $m3
|
|
(import "mod" "1" (memory 1))
|
|
(import "mod" "2" (table 1 funcref))
|
|
(import "mod" "3" (global i64))
|
|
(import "mod" "4" (func (result f32)))
|
|
)
|
|
|
|
(core instance $i3 (instantiate (module $m3)
|
|
(with "mod" (instance
|
|
(export "1" (memory $i1 "b"))
|
|
(export "2" (table $i1 "a"))
|
|
(export "3" (global $i1 "d"))
|
|
(export "4" (func $i1 "c"))
|
|
))
|
|
))
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
enum Type {
|
|
S8,
|
|
U8,
|
|
S16,
|
|
U16,
|
|
I32,
|
|
I64,
|
|
F32,
|
|
F64,
|
|
}
|
|
|
|
impl Type {
|
|
fn store(&self) -> &'static str {
|
|
match self {
|
|
Self::S8 | Self::U8 => "store8",
|
|
Self::S16 | Self::U16 => "store16",
|
|
Self::I32 | Self::F32 | Self::I64 | Self::F64 => "store",
|
|
}
|
|
}
|
|
|
|
fn primitive(&self) -> &'static str {
|
|
match self {
|
|
Self::S8 | Self::U8 | Self::S16 | Self::U16 | Self::I32 => "i32",
|
|
Self::I64 => "i64",
|
|
Self::F32 => "f32",
|
|
Self::F64 => "f64",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
struct Param(Type, Option<usize>);
|
|
|
|
fn make_echo_component(type_definition: &str, type_size: u32) -> String {
|
|
let mut offset = 0;
|
|
make_echo_component_with_params(
|
|
type_definition,
|
|
&iter::repeat(Type::I32)
|
|
.map(|ty| {
|
|
let param = Param(ty, Some(offset));
|
|
offset += 4;
|
|
param
|
|
})
|
|
.take(usize::try_from(type_size).unwrap() / 4)
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
}
|
|
|
|
fn make_echo_component_with_params(type_definition: &str, params: &[Param]) -> String {
|
|
let func = if params.len() == 0 {
|
|
format!("(func (export \"echo\"))")
|
|
} else if params.len() == 1 || params.len() > 16 {
|
|
let primitive = if params.len() == 1 {
|
|
params[0].0.primitive()
|
|
} else {
|
|
"i32"
|
|
};
|
|
|
|
format!(
|
|
r#"
|
|
(func (export "echo") (param {primitive}) (result {primitive})
|
|
local.get 0
|
|
)"#,
|
|
)
|
|
} else {
|
|
let mut param_string = String::new();
|
|
let mut store = String::new();
|
|
let mut size = 8;
|
|
|
|
for (index, Param(ty, offset)) in params.iter().enumerate() {
|
|
let primitive = ty.primitive();
|
|
|
|
write!(&mut param_string, " {primitive}").unwrap();
|
|
if let Some(offset) = offset {
|
|
write!(
|
|
&mut store,
|
|
"({primitive}.{} offset={offset} (local.get $base) (local.get {index}))",
|
|
ty.store(),
|
|
)
|
|
.unwrap();
|
|
|
|
size = size.max(offset + 8);
|
|
}
|
|
}
|
|
|
|
format!(
|
|
r#"
|
|
(func (export "echo") (param{param_string}) (result i32)
|
|
(local $base i32)
|
|
(local.set $base
|
|
(call $realloc
|
|
(i32.const 0)
|
|
(i32.const 0)
|
|
(i32.const 4)
|
|
(i32.const {size})))
|
|
{store}
|
|
local.get $base
|
|
)"#
|
|
)
|
|
};
|
|
|
|
format!(
|
|
r#"
|
|
(component
|
|
(core module $m
|
|
{func}
|
|
|
|
(memory (export "memory") 1)
|
|
{REALLOC_AND_FREE}
|
|
)
|
|
|
|
(core instance $i (instantiate $m))
|
|
|
|
(type $Foo {type_definition})
|
|
|
|
(func (export "echo") (param $Foo) (result $Foo)
|
|
(canon lift
|
|
(core func $i "echo")
|
|
(memory $i "memory")
|
|
(realloc (func $i "realloc"))
|
|
)
|
|
)
|
|
)"#
|
|
)
|
|
}
|