#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::undocumented_unsafe_blocks)]
use std::path::PathBuf;
use polyplug::ffi::polyplug_runtime_create;
use polyplug::ffi::polyplug_runtime_destroy;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::HostApi;
use polyplug_utils::bundle_id;
use polyplug_utils::guest_contract_id;
mod common;
use common::register_native_loader;
const TEST_PLUGIN_DIR: &str = env!("TEST_PLUGIN_DIR");
const RELOAD_PLUGIN_V1_DIR: &str = env!("RELOAD_PLUGIN_V1_DIR");
const TEST_PLUGIN_CPP_SO: &str = env!("TEST_PLUGIN_CPP_SO");
#[test]
fn test_resolve_plugin_null_host() {
let handle: GuestContractHandle = GuestContractHandle::null();
let interface: *const polyplug_abi::GuestContractInterface =
unsafe { polyplug::runtime::host_resolve_guest_contract(core::ptr::null(), handle) };
assert!(
interface.is_null(),
"resolve_guest_contract(null host) must return null"
);
}
#[test]
fn test_resolve_plugin_null_handle() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
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"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn test_resolve_plugin_stale_handle() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
unsafe { register_native_loader(host) };
let path_bytes: &[u8] = TEST_PLUGIN_DIR.as_bytes();
let mut rc: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).load_bundle)(host, path_bytes.as_ptr(), path_bytes.len(), &mut rc) };
assert_eq!(
rc.code,
polyplug_abi::AbiErrorCode::Ok as u32,
"plugin load must succeed"
);
let contract_id: u64 = guest_contract_id("test.add", 1);
let handle: GuestContractHandle =
unsafe { ((*host).find_guest_contract)(host, contract_id, 0) };
assert!(!handle.is_null(), "plugin must be found");
let invalid_handle: GuestContractHandle = GuestContractHandle {
index: 999_999_999_u32,
generation: 0,
};
let interface: *const polyplug_abi::GuestContractInterface =
unsafe { ((*host).resolve_guest_contract)(host, invalid_handle) };
assert!(
interface.is_null(),
"resolve_guest_contract(invalid handle) must return null"
);
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(), "runtime creation must succeed");
let arr: polyplug_abi::Array<GuestContractHandle> =
unsafe { ((*host).find_all_guest_contracts)(host, 0xDEAD_BEEF_u64, 0) };
assert_eq!(
arr.len, 0,
"find_all on empty registry must return empty array"
);
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn test_find_all_guest_contracts_single_plugin() {
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
unsafe { register_native_loader(host) };
let v1_path_bytes: &[u8] = RELOAD_PLUGIN_V1_DIR.as_bytes();
let mut rc: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe { ((*host).load_bundle)(host, v1_path_bytes.as_ptr(), v1_path_bytes.len(), &mut rc) };
assert_eq!(
rc.code,
polyplug_abi::AbiErrorCode::Ok as u32,
"reload_plugin_v1 load must succeed"
);
let contract_id: u64 = guest_contract_id("reload.test", 1);
let arr: polyplug_abi::Array<GuestContractHandle> =
unsafe { ((*host).find_all_guest_contracts)(host, contract_id, 0) };
assert_eq!(arr.len, 1, "find_all must return exactly 1 result");
assert!(!arr.items.is_null(), "array items must not be null");
let handles: &[GuestContractHandle] =
unsafe { core::slice::from_raw_parts(arr.items, arr.len) };
assert!(!handles[0].is_null(), "handle must not be null");
unsafe {
((*host).free)(
host,
arr.items as *mut u8,
arr.len * core::mem::size_of::<GuestContractHandle>(),
core::mem::align_of::<GuestContractHandle>(),
);
}
unsafe { polyplug_runtime_destroy(host) };
}
#[test]
fn test_find_all_guest_contracts_multiple_plugins() {
if TEST_PLUGIN_CPP_SO.is_empty() {
eprintln!(
"Skipping test_find_all_guest_contracts_multiple_plugins: TEST_PLUGIN_CPP_SO not set"
);
return;
}
let host: *const HostApi = unsafe { polyplug_runtime_create(core::ptr::null()) };
assert!(!host.is_null(), "runtime creation must succeed");
unsafe { register_native_loader(host) };
let rust_path_bytes: &[u8] = TEST_PLUGIN_DIR.as_bytes();
let mut rc_rust: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe {
((*host).load_bundle)(
host,
rust_path_bytes.as_ptr(),
rust_path_bytes.len(),
&mut rc_rust,
)
};
assert_eq!(
rc_rust.code,
polyplug_abi::AbiErrorCode::Ok as u32,
"test_plugin load must succeed"
);
let temp_dir: tempfile::TempDir = tempfile::TempDir::new().expect("failed to create temp dir");
let cpp_bundle_dir: PathBuf = temp_dir.path().join("cpp_test_plugin");
std::fs::create_dir_all(&cpp_bundle_dir).expect("failed to create cpp bundle dir");
let cpp_so_path: PathBuf = PathBuf::from(TEST_PLUGIN_CPP_SO);
let cpp_so_filename: &str = cpp_so_path
.file_name()
.expect("cpp so has filename")
.to_str()
.unwrap();
std::fs::copy(&cpp_so_path, cpp_bundle_dir.join(cpp_so_filename))
.expect("failed to copy cpp so");
let manifest_toml: String = format!(
"id = {}\nname = \"cpp_test_adder\"\nloader = \"native\"\nfile = \"{}\"\nversion = \"1.0\"\nprovides = [\"test.add\"]\nfunction_count = {{ \"test.add@1\" = 1 }}\n",
bundle_id("cpp_test_adder"),
cpp_so_filename
);
std::fs::write(cpp_bundle_dir.join("manifest.toml"), manifest_toml)
.expect("failed to write manifest");
let cpp_path_str: String = cpp_bundle_dir.to_string_lossy().into_owned();
let cpp_path_bytes: &[u8] = cpp_path_str.as_bytes();
let mut rc_cpp: polyplug_abi::AbiError = polyplug_abi::AbiError::ok();
unsafe {
((*host).load_bundle)(
host,
cpp_path_bytes.as_ptr(),
cpp_path_bytes.len(),
&mut rc_cpp,
)
};
if rc_cpp.code != polyplug_abi::AbiErrorCode::Ok as u32 {
let mut err_buf: [u8; 512] = [0_u8; 512];
let err_len: usize =
unsafe { ((*host).get_last_error)(host, err_buf.as_mut_ptr(), err_buf.len()) };
let err_msg: &str = core::str::from_utf8(&err_buf[..err_len]).unwrap_or("invalid UTF-8");
panic!(
"cpp_test_plugin load failed: {} (path: {})",
err_msg, cpp_path_str
);
}
let arr: polyplug_abi::Array<GuestContractHandle> =
unsafe { ((*host).find_all_guest_contracts)(host, guest_contract_id("test.add", 1), 0) };
assert_eq!(arr.len, 2, "find_all must find both plugins");
unsafe {
((*host).free)(
host,
arr.items as *mut u8,
arr.len * core::mem::size_of::<GuestContractHandle>(),
core::mem::align_of::<GuestContractHandle>(),
);
}
unsafe { polyplug_runtime_destroy(host) };
}