#![allow(clippy::expect_used)]
use polyplug::ffi::polyplug_runtime_create;
use polyplug::ffi::polyplug_runtime_destroy;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::HostApi;
use polyplug::runtime::{host_get_error_len, host_get_last_error, host_load_bundle};
#[test]
fn test_runtime_free_null() {
unsafe { polyplug_runtime_destroy(core::ptr::null()) };
}
#[test]
fn test_load_bundle_null_host() {
let path: &[u8] = b"/some/path";
let mut result: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { host_load_bundle(core::ptr::null(), path.as_ptr(), path.len(), &mut result) };
assert_eq!(
result.code,
polyplug_abi::AbiErrorCode::InvalidPointer as u32,
"load_bundle(null host) must return InvalidPointer"
);
}
#[test]
fn test_load_bundle_null_path() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null());
let mut result: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).load_bundle)(host, core::ptr::null(), 0, &mut result) };
assert_eq!(
result.code,
polyplug_abi::AbiErrorCode::InvalidPointer as u32,
"load_bundle(null path) must return InvalidPointer"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn test_find_all_guest_contracts_empty_registry() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null());
let arr: polyplug_abi::Array<GuestContractHandle> =
unsafe { ((*host).find_all_guest_contracts)(host, 0xDEAD_BEEF_u64, 0) };
assert_eq!(
arr.len, 0,
"find_all_guest_contracts on empty registry must return empty array"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn test_resolve_guest_contract_null_handle() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null());
let interface: *const polyplug_abi::GuestContractInterface =
unsafe { ((*host).resolve_guest_contract)(host, GuestContractHandle::null()) };
assert!(
interface.is_null(),
"resolve_guest_contract(null handle) must return null"
);
let len: usize = unsafe { ((*host).get_error_len)(host) };
assert_eq!(len, 0, "error_len must be 0 after null handle resolve");
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn test_get_last_error_null_host() {
let n: usize = unsafe { host_get_last_error(core::ptr::null(), core::ptr::null_mut(), 0) };
assert_eq!(n, 0, "get_last_error(null host) must return 0");
}
#[test]
fn test_get_error_len_null_host() {
let n: usize = unsafe { host_get_error_len(core::ptr::null()) };
assert!(
n > 0,
"get_error_len(null host) must return a non-zero length"
);
}
fn guest_iface_image(
create_bits: usize,
destroy_bits: usize,
dispatch_type: u32,
dispatch_word0: usize,
dispatch_word1: usize,
) -> Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> {
let mut image: Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> =
Box::new(core::mem::MaybeUninit::zeroed());
let base: *mut u8 = image.as_mut_ptr().cast::<u8>();
unsafe {
base.add(core::mem::offset_of!(
polyplug_abi::GuestContractInterface,
dispatch_type
))
.cast::<u32>()
.write(dispatch_type);
base.add(core::mem::offset_of!(
polyplug_abi::GuestContractInterface,
create_instance
))
.cast::<usize>()
.write(create_bits);
base.add(core::mem::offset_of!(
polyplug_abi::GuestContractInterface,
destroy_instance
))
.cast::<usize>()
.write(destroy_bits);
let dispatch_base: *mut u8 = base.add(core::mem::offset_of!(
polyplug_abi::GuestContractInterface,
dispatch
));
dispatch_base.cast::<usize>().write(dispatch_word0);
dispatch_base.add(8).cast::<usize>().write(dispatch_word1);
}
image
}
fn nonnull_fn_bits() -> usize {
test_get_error_len_null_host as fn() as *const core::ffi::c_void as usize
}
fn make_descriptor(name: &'static [u8]) -> polyplug_abi::PluginDescriptor {
polyplug_abi::PluginDescriptor {
name: polyplug_abi::StringView::from_static(name),
contract_name: polyplug_abi::StringView::from_static(name),
version: polyplug_abi::Version {
major: 1,
minor: 0,
patch: 0,
},
}
}
fn register_and_expect_rejection(
image: &core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>,
expected_fragment: &str,
) {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null());
let descriptor: polyplug_abi::PluginDescriptor = make_descriptor(b"null_fn_test");
let mut result: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).register_guest_contract)(host, &descriptor, image.as_ptr(), &mut result) };
assert_eq!(
result.code,
polyplug_abi::AbiErrorCode::InvalidPointer as u32,
"registration must reject with InvalidPointer"
);
let message: &str = unsafe { result.message.try_as_str() }.expect("static UTF-8 message");
assert!(
message.contains(expected_fragment),
"error message must name the offending field; got: {message}"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn register_guest_contract_rejects_null_create_instance() {
let image: Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> =
guest_iface_image(0, nonnull_fn_bits(), 0, 0, 0);
register_and_expect_rejection(&image, "create_instance");
}
#[test]
fn register_guest_contract_rejects_null_destroy_instance() {
let image: Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> =
guest_iface_image(nonnull_fn_bits(), 0, 0, 0, 0);
register_and_expect_rejection(&image, "destroy_instance");
}
#[test]
fn register_guest_contract_rejects_null_vm_call() {
let image: Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> =
guest_iface_image(nonnull_fn_bits(), nonnull_fn_bits(), 1, 0, 0);
register_and_expect_rejection(&image, "dispatch.vm.call");
}
#[test]
fn register_guest_contract_rejects_null_native_function_table() {
let image: Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> =
guest_iface_image(nonnull_fn_bits(), nonnull_fn_bits(), 0, 3, 0);
register_and_expect_rejection(&image, "dispatch.native.functions");
}
#[test]
fn register_guest_contract_rejects_null_native_function_entry() {
let table: [usize; 2] = [nonnull_fn_bits(), 0];
let image: Box<core::mem::MaybeUninit<polyplug_abi::GuestContractInterface>> =
guest_iface_image(
nonnull_fn_bits(),
nonnull_fn_bits(),
0,
2,
table.as_ptr() as usize,
);
register_and_expect_rejection(&image, "null entry");
}
#[test]
fn register_host_contract_rejects_null_create_instance() {
let mut image: Box<core::mem::MaybeUninit<polyplug_abi::HostContractInterface>> =
Box::new(core::mem::MaybeUninit::zeroed());
let base: *mut u8 = image.as_mut_ptr().cast::<u8>();
unsafe {
base.add(core::mem::offset_of!(
polyplug_abi::HostContractInterface,
destroy_instance
))
.cast::<usize>()
.write(nonnull_fn_bits());
}
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null());
let mut result: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).register_host_contract)(host, image.as_ptr(), &mut result) };
assert_eq!(
result.code,
polyplug_abi::AbiErrorCode::InvalidPointer as u32,
"register_host_contract must reject a null create_instance with InvalidPointer"
);
let message: &str = unsafe { result.message.try_as_str() }.expect("static UTF-8 message");
assert!(
message.contains("create_instance"),
"error message must name the offending field; got: {message}"
);
unsafe { polyplug_runtime_destroy(host) };
}