#![allow(clippy::expect_used)]
use polyplug_abi::AbiError;
use polyplug_abi::AbiErrorCode;
use polyplug_abi::BundleInitContext;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::GuestContractInterface;
use polyplug_abi::HostApi;
use polyplug_abi::PluginDescriptor;
use polyplug_abi::StringView;
use polyplug_utils::GuestContractId;
const TEST_PLUGIN_SO: &str = env!("TEST_PLUGIN_SO");
unsafe extern "C" fn capture_register(
_this: *const HostApi,
descriptor: *const PluginDescriptor,
interface: *const GuestContractInterface,
out_err: *mut AbiError,
) {
if descriptor.is_null() || interface.is_null() {
if !out_err.is_null() {
unsafe {
out_err.write(AbiError {
code: polyplug_abi::AbiErrorCode::Generic as u32,
message: polyplug_abi::StringView::null(),
})
};
}
return;
}
let contract_id: u64 = unsafe { (*interface).contract_id.id() };
let function_count: u32 = unsafe { (*interface).dispatch.native.function_count };
CAPTURED_CONTRACT_ID.with(|cell| {
*cell.borrow_mut() = Some(contract_id);
});
CAPTURED_FUNCTION_COUNT.with(|cell| {
*cell.borrow_mut() = Some(function_count);
});
if !out_err.is_null() {
unsafe {
out_err.write(AbiError {
code: AbiErrorCode::Ok as u32,
message: polyplug_abi::StringView::null(),
})
};
}
}
unsafe extern "C" fn noop_alloc(this: *const HostApi, size: usize, align: usize) -> *mut u8 {
let _ = this;
polyplug_abi::ffi::polyplug_host_alloc(size, align)
}
unsafe extern "C" fn noop_free(this: *const HostApi, ptr: *mut u8, size: usize, align: usize) {
let _ = this;
unsafe { polyplug_abi::ffi::polyplug_host_free(ptr, size, align) }
}
unsafe extern "C" fn noop_find_guest_contract(
this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> GuestContractHandle {
let _ = this;
GuestContractHandle::null()
}
unsafe extern "C" fn noop_find_all_guest_contracts(
this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> polyplug_abi::Array<GuestContractHandle> {
let _ = this;
polyplug_abi::Array::empty()
}
unsafe extern "C" fn noop_resolve_guest_contract(
this: *const HostApi,
_handle: GuestContractHandle,
) -> *const GuestContractInterface {
let _ = this;
core::ptr::null()
}
unsafe extern "C" fn noop_get_host_contract(
this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> polyplug_abi::HostContractInstance {
let _ = this;
polyplug_abi::HostContractInstance::null()
}
unsafe extern "C" fn noop_list_bundles(
this: *const HostApi,
) -> polyplug_abi::Array<polyplug_utils::BundleId> {
let _ = this;
polyplug_abi::Array::empty()
}
unsafe extern "C" fn noop_get_dependencies(
this: *const HostApi,
) -> polyplug_abi::Array<polyplug_abi::DependencyInfo> {
let _ = this;
polyplug_abi::Array::empty()
}
unsafe extern "C" fn noop_resolve_host_contract_interface(
_this: *const HostApi,
_contract_id: u64,
_min_version: u32,
) -> *const polyplug_abi::HostContractInterface {
core::ptr::null()
}
unsafe extern "C" fn noop_load_bundle(
_this: *const HostApi,
_path: *const u8,
_path_len: usize,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn noop_reload_bundle(
_this: *const HostApi,
_path: *const u8,
_path_len: usize,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn noop_register_host_contract(
_this: *const HostApi,
_interface: *const polyplug_abi::HostContractInterface,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn noop_register_loader(
_this: *const HostApi,
_loader_ptr: *mut core::ffi::c_void,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
unsafe extern "C" fn noop_get_last_error(
_this: *const HostApi,
_buf: *mut u8,
_buf_len: usize,
) -> usize {
0
}
unsafe extern "C" fn noop_get_error_len(_this: *const HostApi) -> usize {
0
}
unsafe extern "C" fn noop_unload_bundle(
_this: *const HostApi,
_bundle_id: polyplug_utils::BundleId,
out_err: *mut AbiError,
) {
if !out_err.is_null() {
unsafe { out_err.write(AbiError::ok()) };
}
}
std::thread_local! {
static CAPTURED_CONTRACT_ID: core::cell::RefCell<Option<u64>> =
const { core::cell::RefCell::new(None) };
static CAPTURED_FUNCTION_COUNT: core::cell::RefCell<Option<u32>> =
const { core::cell::RefCell::new(None) };
}
#[test]
fn test_load_and_abi_version() {
let library: libloading::Library = unsafe {
libloading::Library::new(TEST_PLUGIN_SO).expect("failed to load test_plugin shared library")
};
let abi_version_fn: libloading::Symbol<'_, unsafe extern "C" fn() -> u32> = unsafe {
library
.get(b"polyplug_abi_version\0")
.expect("polyplug_abi_version symbol not found")
};
let version: u32 = unsafe { abi_version_fn() };
assert_eq!(version, 1, "polyplug_abi_version() must return 1");
core::mem::forget(library);
}
#[test]
fn test_init_registers_interface() {
let library: libloading::Library = unsafe {
libloading::Library::new(TEST_PLUGIN_SO).expect("failed to load test_plugin shared library")
};
let init_fn: libloading::Symbol<
'_,
unsafe extern "C" fn(*const HostApi, *const BundleInitContext) -> AbiError,
> = unsafe {
library
.get(b"polyplug_init\0")
.expect("polyplug_init symbol not found")
};
let host_interface: HostApi = HostApi {
runtime: core::ptr::null_mut(),
register_guest_contract: capture_register,
alloc: noop_alloc,
free: noop_free,
find_guest_contract: noop_find_guest_contract,
find_all_guest_contracts: noop_find_all_guest_contracts,
resolve_guest_contract: noop_resolve_guest_contract,
get_host_contract: noop_get_host_contract,
resolve_host_contract_interface: noop_resolve_host_contract_interface,
list_bundles: noop_list_bundles,
get_dependencies: noop_get_dependencies,
load_bundle: noop_load_bundle,
reload_bundle: noop_reload_bundle,
register_host_contract: noop_register_host_contract,
register_loader: noop_register_loader,
get_last_error: noop_get_last_error,
get_error_len: noop_get_error_len,
unload_bundle: noop_unload_bundle,
log: stub_host_log,
create_guest_instance: stub_create_guest_instance,
destroy_guest_instance: stub_destroy_guest_instance,
revision_counter: stub_revision_counter,
reserved: core::ptr::null(),
};
CAPTURED_CONTRACT_ID.with(|cell| *cell.borrow_mut() = None);
CAPTURED_FUNCTION_COUNT.with(|cell| *cell.borrow_mut() = None);
let ctx: BundleInitContext = BundleInitContext {
bundle_id: 0,
bundle_path: StringView::null(),
};
let result: AbiError = unsafe {
init_fn(
&host_interface as *const HostApi,
&ctx as *const BundleInitContext,
)
};
assert!(
result.code == AbiErrorCode::Ok as u32,
"polyplug_init must return Ok"
);
let captured_id: u64 = CAPTURED_CONTRACT_ID
.with(|cell| *cell.borrow())
.expect("interface was not registered during init");
let captured_count: u32 = CAPTURED_FUNCTION_COUNT
.with(|cell| *cell.borrow())
.expect("function_count was not captured");
let expected_contract_id: u64 = GuestContractId::new("test.add", 1).id();
assert_eq!(
captured_id, expected_contract_id,
"contract_id must match FNV-1a(\"test.add@1\")"
);
assert_eq!(
captured_count, 1,
"test.add interface must have function_count = 1"
);
core::mem::forget(library);
}
#[test]
fn test_missing_symbol_returns_error() {
let library: libloading::Library = unsafe {
libloading::Library::new(TEST_PLUGIN_SO).expect("failed to load test_plugin shared library")
};
let result: Result<libloading::Symbol<'_, unsafe extern "C" fn()>, _> =
unsafe { library.get(b"nonexistent_symbol_xyz\0") };
assert!(result.is_err(), "non-existent symbol must return Err");
core::mem::forget(library);
}
unsafe extern "C" fn stub_host_log(
_this: *const polyplug_abi::HostApi,
_level: u32,
_scope: polyplug_abi::StringView,
_message: polyplug_abi::StringView,
) {
}
unsafe extern "C" fn stub_create_guest_instance(
_this: *const polyplug_abi::HostApi,
_interface: *const polyplug_abi::GuestContractInterface,
_args: *const core::ffi::c_void,
out_instance: *mut polyplug_abi::GuestContractInstance,
) {
if !out_instance.is_null() {
unsafe { out_instance.write(polyplug_abi::GuestContractInstance::null()) };
}
}
unsafe extern "C" fn stub_destroy_guest_instance(
_this: *const polyplug_abi::HostApi,
_interface: *const polyplug_abi::GuestContractInterface,
_instance: polyplug_abi::GuestContractInstance,
) {
}
unsafe extern "C" fn stub_revision_counter(_this: *const polyplug_abi::HostApi) -> *const u64 {
core::ptr::null()
}