use anyhow::Result;
use extism_pdk::{Memory, ToMemory};
mod harness {
#[link(wasm_import_module = "xtp:test/harness")]
extern "C" {
pub fn call(name: u64, input: u64) -> u64;
pub fn time(name: u64, input: u64) -> u64;
pub fn assert(name: u64, value: u64, message: u64);
pub fn reset();
pub fn group(name: u64);
}
}
pub fn call_memory(func_name: impl AsRef<str>, input: impl ToMemory) -> Result<Memory> {
let func_name = func_name.as_ref();
let func_mem = Memory::from_bytes(func_name)?;
let input_mem = input.to_memory()?;
let output_ptr = unsafe { harness::call(func_mem.offset(), input_mem.offset()) };
func_mem.free();
input_mem.free();
let output = match Memory::find(output_ptr) {
None => anyhow::bail!("Error in call to {func_name}: invalid output offset"),
Some(x) => x,
};
Ok(output)
}
pub fn call<T: extism_pdk::FromBytesOwned>(
func_name: impl AsRef<str>,
input: impl ToMemory,
) -> Result<T> {
let output_mem = call_memory(func_name, input)?;
let output = output_mem.to();
output_mem.free();
output
}
pub fn time_ns(func_name: impl AsRef<str>, input: impl ToMemory) -> Result<u64> {
let func_name = func_name.as_ref();
let func_mem = Memory::from_bytes(func_name)?;
let input_mem = input.to_memory()?;
let ns = unsafe { harness::time(func_mem.offset(), input_mem.offset()) };
func_mem.free();
input_mem.free();
Ok(ns)
}
pub fn time_sec(func_name: impl AsRef<str>, input: impl ToMemory) -> Result<f64> {
time_ns(func_name, input).map(|x| x as f64 / 1e9)
}
pub fn assert(name: impl AsRef<str>, outcome: bool, reason: impl AsRef<str>) {
let name_mem = Memory::from_bytes(name.as_ref()).expect("assert name Extism memory");
let reason_mem = Memory::from_bytes(reason.as_ref()).expect("assert reason Extism memory");
unsafe {
harness::assert(name_mem.offset(), outcome as u64, reason_mem.offset());
}
reason_mem.free();
name_mem.free();
}
pub fn assert_eq<U: std::fmt::Debug, T: std::fmt::Debug + PartialEq<U>>(
msg: impl AsRef<str>,
x: T,
y: U,
) {
assert(msg, x == y, format!("Expected {:?} == {:?}", x, y));
}
pub fn assert_ne<U: std::fmt::Debug, T: std::fmt::Debug + PartialEq<U>>(
msg: impl AsRef<str>,
x: T,
y: U,
) {
assert(msg, x != y, format!("Expected {:?} != {:?}", x, y));
}
pub fn assert_gt<U: std::fmt::Debug, T: std::fmt::Debug + PartialOrd<U>>(
msg: impl AsRef<str>,
x: T,
y: U,
) {
assert(msg, x > y, format!("Expected {:?} > {:?}", x, y));
}
pub fn assert_gte<U: std::fmt::Debug, T: std::fmt::Debug + PartialOrd<U>>(
msg: impl AsRef<str>,
x: T,
y: U,
) {
assert(msg, x >= y, format!("Expected {:?} >= {:?}", x, y));
}
pub fn assert_lt<U: std::fmt::Debug, T: std::fmt::Debug + PartialOrd<U>>(
msg: impl AsRef<str>,
x: T,
y: U,
) {
assert(msg, x < y, format!("Expected {:?} < {:?}", x, y));
}
pub fn assert_lte<U: std::fmt::Debug, T: std::fmt::Debug + PartialOrd<U>>(
msg: impl AsRef<str>,
x: T,
y: U,
) {
assert(msg, x <= y, format!("Expected {:?} <= {:?}", x, y));
}
fn start_group(name: impl AsRef<str>) {
let name_mem = Memory::from_bytes(name.as_ref()).expect("assert message Extism memory");
unsafe {
harness::group(name_mem.offset());
}
name_mem.free();
}
pub fn reset() {
unsafe {
harness::reset();
}
}
pub fn group(name: impl AsRef<str>, f: impl FnOnce() -> Result<()>) -> Result<()> {
reset();
start_group(name);
let res = f();
reset();
res
}
#[macro_export]
macro_rules! assert {
($name:expr, $b:expr) => {
$crate::assert(
$name,
$b,
format!("Assertion failed ({}:{})", file!(), line!()),
);
};
}
#[macro_export]
macro_rules! assert_eq {
($name:expr, $a:expr, $b:expr) => {
$crate::assert(
$name,
$a == $b,
format!("Expected {:?} == {:?} ({}:{})", $a, $b, file!(), line!()),
);
};
}
#[macro_export]
macro_rules! assert_ne {
($name:expr, $a:expr, $b:expr) => {
$crate::assert(
$name,
$a != $b,
format!("Expected {:?} != {:?} ({}:{})", $a, $b, file!(), line!()),
);
};
}
#[macro_export]
macro_rules! assert_lt {
($name:expr, $a:expr, $b:expr) => {
$crate::assert(
$name,
$a < $b,
format!("Expected {:?} < {:?} ({}:{})", $a, $b, file!(), line!()),
);
};
}
#[macro_export]
macro_rules! assert_lte {
($name:expr, $a:expr, $b:expr) => {
$crate::assert(
$name,
$a <= $b,
format!("Expected {:?} <= {:?} ({}:{})", $a, $b, file!(), line!()),
);
};
}
#[macro_export]
macro_rules! assert_gt {
($name:expr, $a:expr, $b:expr) => {
$crate::assert(
$name,
$a > $b,
format!("Expected {:?} > {:?} ({}:{})", $a, $b, file!(), line!()),
);
};
}
#[macro_export]
macro_rules! assert_gte {
($name:expr, $a:expr, $b:expr) => {
$crate::assert(
$name,
$a >= $b,
format!("Expected {:?} >= {:?} ({}:{})", $a, $b, file!(), line!()),
);
};
}