#![allow(clippy::expect_used)]
use polyplug_abi::{
DispatchMechanisms, DispatchType, GuestContractInstance, GuestContractInterface, HostApi,
NativeDispatch, PluginDescriptor, StringView, Version,
};
use polyplug_utils::GuestContractId;
unsafe extern "C" fn null_create_instance(
_loader_data: polyplug_abi::dispatch::VmLoaderData,
_host: *const HostApi,
_args: *const (),
out_instance: *mut GuestContractInstance,
) {
if !out_instance.is_null() {
unsafe { out_instance.write(GuestContractInstance::null()) };
}
}
unsafe extern "C" fn null_destroy_instance(
_loader_data: polyplug_abi::dispatch::VmLoaderData,
_host: *const HostApi,
_instance: GuestContractInstance,
) {
}
fn make_static_interface(cid: GuestContractId) -> &'static GuestContractInterface {
make_static_interface_minor(cid, 0)
}
fn make_static_interface_minor(
cid: GuestContractId,
minor: u32,
) -> &'static GuestContractInterface {
Box::leak(Box::new(GuestContractInterface {
contract_id: cid,
contract_version: Version {
major: 1,
minor,
patch: 0,
},
dispatch_type: DispatchType::Native,
create_instance: null_create_instance,
destroy_instance: null_destroy_instance,
dispatch: DispatchMechanisms {
native: NativeDispatch {
function_count: 0,
functions: core::ptr::null(),
},
},
}))
}
fn make_desc(plugin_name: &'static str, contract_name: &'static str) -> PluginDescriptor {
PluginDescriptor {
name: StringView::from_static(plugin_name.as_bytes()),
contract_name: StringView::from_static(contract_name.as_bytes()),
version: Version {
major: 1,
minor: 0,
patch: 0,
},
}
}
#[cfg(test)]
mod tests {
use super::make_desc;
use super::make_static_interface;
use super::make_static_interface_minor;
use polyplug::error::RegistryError;
use polyplug::runtime_store::RuntimeStore;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::GuestContractInterface;
use polyplug_abi::PluginDescriptor;
use polyplug_utils::{BundleId, GuestContractId};
#[test]
fn find_by_contract_single_plugin() {
let registry: RuntimeStore = RuntimeStore::new();
let cid: GuestContractId = GuestContractId::new("audio.Decoder", 0);
let bid: BundleId = BundleId::new("audio-engine");
let interface: &'static GuestContractInterface = make_static_interface(cid);
let desc: PluginDescriptor = make_desc("decoder", "audio.Decoder");
unsafe {
registry.register_guest_contract(desc, interface, "audio.Decoder".to_owned(), bid)
}
.expect("register should succeed");
let handle: GuestContractHandle = registry
.find_guest_contract(cid, 0)
.expect("find_guest_contract should return Ok");
assert!(!handle.is_null(), "returned handle must not be null");
}
#[test]
fn find_all_returns_two_impls() {
let registry: RuntimeStore = RuntimeStore::new();
let cid: GuestContractId = GuestContractId::new("audio.Decoder", 0);
let interface_a: &'static GuestContractInterface = make_static_interface(cid);
let interface_b: &'static GuestContractInterface = make_static_interface(cid);
unsafe {
registry
.register_guest_contract(
make_desc("decoder-a", "audio.Decoder"),
interface_a,
"audio.Decoder".to_owned(),
BundleId::new("bundle-a"),
)
.expect("register bundle-a")
};
unsafe {
registry
.register_guest_contract(
make_desc("decoder-b", "audio.Decoder"),
interface_b,
"audio.Decoder".to_owned(),
BundleId::new("bundle-b"),
)
.expect("register bundle-b")
};
let mut handles: [GuestContractHandle; 4] = [GuestContractHandle::null(); 4];
let count: usize = registry.find_all_guest_contracts(cid, 0, &mut handles);
assert_eq!(count, 2, "must find exactly 2 providers");
}
#[test]
fn find_by_bundle_specificity() {
let registry: RuntimeStore = RuntimeStore::new();
let cid: GuestContractId = GuestContractId::new("audio.Decoder", 0);
let bid_a: BundleId = BundleId::new("bundle-a");
let bid_b: BundleId = BundleId::new("bundle-b");
let interface_a: &'static GuestContractInterface = make_static_interface_minor(cid, 7);
let interface_b: &'static GuestContractInterface = make_static_interface_minor(cid, 9);
unsafe {
registry
.register_guest_contract(
make_desc("decoder-a", "audio.Decoder"),
interface_a,
"audio.Decoder".to_owned(),
bid_a,
)
.expect("register bundle-a")
};
unsafe {
registry
.register_guest_contract(
make_desc("decoder-b", "audio.Decoder"),
interface_b,
"audio.Decoder".to_owned(),
bid_b,
)
.expect("register bundle-b")
};
let found: GuestContractHandle = registry
.find_guest_contract_by_bundle(bid_b, cid, 0)
.expect("find_by_bundle(bundle-b) should succeed");
let resolved_ptr: *const GuestContractInterface = registry
.resolve_guest_contract(found)
.expect("resolve must succeed for a freshly registered handle");
let resolved_minor: u32 = unsafe { (*resolved_ptr).contract_version.minor };
assert_eq!(
resolved_minor, interface_b.contract_version.minor,
"resolved interface must be bundle-b's interface (minor=9), not bundle-a's (minor=7)"
);
assert_ne!(
resolved_minor, interface_a.contract_version.minor,
"resolved interface must not be bundle-a's interface"
);
}
#[test]
fn invalid_handle_rejected() {
let registry: RuntimeStore = RuntimeStore::new();
let invalid: GuestContractHandle = GuestContractHandle {
index: 999,
generation: 0,
};
let result = registry.resolve_guest_contract(invalid);
assert!(
matches!(result, Err(RegistryError::InvalidHandle { .. })),
"invalid handle must return Err(InvalidHandle)"
);
}
}