#![allow(clippy::expect_used)]
use std::collections::HashMap;
use polyplug::error::LoaderError;
use polyplug::error::RuntimeError;
use polyplug::loader::BundleSource;
use polyplug::loader::ManifestData;
use polyplug::runtime::Runtime;
use polyplug::runtime_builder::RuntimeBuilder;
use polyplug_abi::AbiError;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::GuestContractInstance;
use polyplug_abi::dispatch::DispatchType;
use polyplug_python::PythonLoader;
use polyplug_utils::GuestContractId;
use polyplug_utils::bundle_id;
const CODE_PLUGIN_SRC: &str = r#"
import ctypes
# Minimal stand-in for the SDK AbiError: the loader reads only `.code` (Ok == 0).
_ABI_OK = type("AbiError", (), {"code": 0})()
def _fn0(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
ctypes.cast(out_ptr, ctypes.POINTER(ctypes.c_int32))[0] = 0x7B
def polyplug_init(host_interface: int, _ctx: int):
# polyplug_init RETURNS (registrations, AbiError); nothing is deposited.
return [
{
"contract": "code.contract@1",
"plugin_name": "code_plugin",
"factory": lambda host_ptr: None,
"functions": [_fn0],
},
], _ABI_OK
"#;
fn code_contract_id() -> u64 {
GuestContractId::new("code.contract", 1).id()
}
fn inline_manifest(name: &str) -> ManifestData {
ManifestData {
id: bundle_id(name),
name: name.to_owned(),
loader: "python".to_owned(),
file: "<inline>".to_owned(),
path: std::path::PathBuf::new(),
version: String::new(),
provides: Vec::new(),
function_count: HashMap::new(),
dependencies: Vec::new(),
needs_reinit_on_dep_reload: false,
bundle_dependencies: Vec::new(),
}
}
#[test]
fn code_source_loads_resolves_and_dispatches() {
let runtime: std::sync::Arc<Runtime> = RuntimeBuilder::new()
.loader(PythonLoader::default())
.build()
.expect("runtime build");
let manifest: ManifestData = inline_manifest("code_plugin");
let result: Result<(), RuntimeError> =
runtime.load_bundle_from_source(manifest, BundleSource::Code(CODE_PLUGIN_SRC.to_owned()));
assert!(result.is_ok(), "inline Code load failed: {result:?}");
let contract_id: GuestContractId = GuestContractId::from_u64(code_contract_id());
let handle: GuestContractHandle = runtime
.registry()
.find(contract_id, 0)
.expect("contract must be registered after inline Code load");
let interface_ptr: *const polyplug_abi::GuestContractInterface = runtime
.registry()
.resolve_guest_contract(handle)
.expect("registered contract must resolve to an interface");
assert!(
!interface_ptr.is_null(),
"resolved interface must be non-null"
);
let interface: &polyplug_abi::GuestContractInterface = unsafe { &*interface_ptr };
assert_eq!(
interface.contract_id.id(),
code_contract_id(),
"resolved contract id must match the registered one"
);
assert_eq!(
interface.dispatch_type,
DispatchType::VirtualMachine,
"inline plugin registered a VM dispatch contract"
);
let vm: polyplug_abi::dispatch::vm_dispatch::VmDispatch = unsafe { interface.dispatch.vm };
let mut out_buf: i32 = 0;
let mut err: AbiError = AbiError::ok();
unsafe {
(vm.call)(
vm.loader_data,
GuestContractInstance::null(),
0,
core::ptr::null(),
&mut out_buf as *mut i32 as *mut (),
core::ptr::null_mut(),
&mut err as *mut AbiError,
);
}
assert!(
err.is_ok(),
"vm dispatch should return Ok, got code {}",
err.code
);
assert_eq!(out_buf, 0x7B, "callable must have written 0x7B into out");
}
#[test]
fn bytes_source_valid_utf8_loads() {
let runtime: std::sync::Arc<Runtime> = RuntimeBuilder::new()
.loader(PythonLoader::default())
.build()
.expect("runtime build");
let manifest: ManifestData = inline_manifest("bytes_plugin");
let bytes: Vec<u8> = CODE_PLUGIN_SRC.as_bytes().to_vec();
let result: Result<(), RuntimeError> =
runtime.load_bundle_from_source(manifest, BundleSource::Bytes(bytes));
assert!(result.is_ok(), "inline Bytes load failed: {result:?}");
let contract_id: GuestContractId = GuestContractId::from_u64(code_contract_id());
assert!(
runtime.registry().find(contract_id, 0).is_ok(),
"contract must be registered after inline Bytes load"
);
}
#[test]
fn bytes_source_invalid_utf8_returns_structured_error() {
let runtime: std::sync::Arc<Runtime> = RuntimeBuilder::new()
.loader(PythonLoader::default())
.build()
.expect("runtime build");
let manifest: ManifestData = inline_manifest("bad_utf8_plugin");
let bytes: Vec<u8> = vec![0x70, 0x79, 0xFF, 0xFE, 0x00];
let err: RuntimeError = runtime
.load_bundle_from_source(manifest, BundleSource::Bytes(bytes))
.expect_err("invalid UTF-8 bytes must be rejected");
match err {
RuntimeError::Loader(LoaderError::InvalidSourceEncoding {
loader,
source_kind,
bundle,
}) => {
assert_eq!(loader, "python");
assert_eq!(source_kind, "bytes");
assert_eq!(bundle, "bad_utf8_plugin");
}
other => panic!("expected LoaderError::InvalidSourceEncoding, got: {other:?}"),
}
}