#![allow(clippy::expect_used)]
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use polyplug::error::LoaderError;
use polyplug::loader::BundleLoader;
use polyplug::loader::ManifestData;
use polyplug::runtime::Runtime;
use polyplug::runtime_builder::RuntimeBuilder;
use polyplug_abi::AbiError;
use polyplug_abi::AbiErrorCode;
use polyplug_abi::GuestContractHandle;
use polyplug_abi::GuestContractInstance;
use polyplug_abi::dispatch::DispatchType;
use polyplug_python::PythonConfig;
use polyplug_python::PythonLoader;
use polyplug_utils::BundleId;
use polyplug_utils::GuestContractId;
use polyplug_utils::bundle_id;
use pyo3::Python;
use pyo3::types::PyAnyMethods;
use pyo3::types::PyDict;
use pyo3::types::PyDictMethods;
use pyo3::types::PyModule;
fn write_bundle(name: &str, content: &str) -> (TempDir, PathBuf) {
let dir: TempDir = TempDir::new().expect("tempdir");
let path: PathBuf = dir.path().join("bundle.py");
fs::write(&path, content).expect("write bundle.py");
let bundle_id: u64 = bundle_id(name);
let manifest: String = format!(
r#"id = {}
name = "{}"
loader = "python"
file = "bundle.py"
"#,
bundle_id, name
);
fs::write(dir.path().join("manifest.toml"), &manifest).expect("write manifest.toml");
(dir, path)
}
fn make_runtime() -> Arc<Runtime> {
RuntimeBuilder::new()
.loader(PythonLoader::default())
.build()
.expect("runtime build must succeed")
}
fn make_manifest(path: &Path, name: &str) -> ManifestData {
ManifestData {
id: bundle_id(name),
name: name.to_owned(),
loader: "python".to_owned(),
file: path
.file_name()
.expect("bundle path must have a file name")
.to_string_lossy()
.into_owned(),
path: path
.parent()
.expect("bundle path must have a parent directory")
.to_path_buf(),
version: String::new(),
provides: Vec::new(),
function_count: HashMap::new(),
dependencies: Vec::new(),
needs_reinit_on_dep_reload: false,
bundle_dependencies: Vec::new(),
}
}
unsafe fn dispatch(
runtime: &Runtime,
contract_id: u64,
fn_id: u32,
args: *const (),
out: *mut (),
) -> AbiError {
let cid: GuestContractId = GuestContractId::from_u64(contract_id);
let handle: GuestContractHandle = runtime
.registry()
.find(cid, 0)
.expect("contract must be registered");
let interface_ptr: *const polyplug_abi::GuestContractInterface = runtime
.registry()
.resolve_guest_contract(handle)
.expect("contract must resolve to an interface");
let interface: &polyplug_abi::GuestContractInterface = unsafe { &*interface_ptr };
assert_eq!(
interface.dispatch_type,
DispatchType::VirtualMachine,
"Python contracts must register VM dispatch"
);
let vm: polyplug_abi::dispatch::vm_dispatch::VmDispatch = unsafe { interface.dispatch.vm };
let mut err: AbiError = AbiError::ok();
unsafe {
(vm.call)(
vm.loader_data,
GuestContractInstance::null(),
fn_id,
args,
out,
core::ptr::null_mut(),
&mut err as *mut AbiError,
);
}
err
}
unsafe fn dispatch_with_arena(
runtime: &Runtime,
contract_id: u64,
fn_id: u32,
args: *const (),
out: *mut (),
arena: *mut polyplug_abi::CallArena,
) -> AbiError {
let cid: GuestContractId = GuestContractId::from_u64(contract_id);
let handle: GuestContractHandle = runtime
.registry()
.find(cid, 0)
.expect("contract must be registered");
let interface_ptr: *const polyplug_abi::GuestContractInterface = runtime
.registry()
.resolve_guest_contract(handle)
.expect("contract must resolve to an interface");
let interface: &polyplug_abi::GuestContractInterface = unsafe { &*interface_ptr };
let vm: polyplug_abi::dispatch::vm_dispatch::VmDispatch = unsafe { interface.dispatch.vm };
let mut err: AbiError = AbiError::ok();
unsafe {
(vm.call)(
vm.loader_data,
GuestContractInstance::null(),
fn_id,
args,
out,
arena,
&mut err as *mut AbiError,
);
}
err
}
const WRITE_OUT_PLUGIN_SRC: &str = r#"
import ctypes
_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] = 0x2A
def polyplug_init(host_interface: int, ctx: int):
return [
{
"contract": "writeout@1",
"plugin_name": "writeout_plugin",
"factory": lambda host_ptr: None,
"functions": [_fn0],
},
], _ABI_OK
"#;
fn writeout_contract_id() -> u64 {
GuestContractId::new("writeout", 1).id()
}
const RAISING_FN_PLUGIN_SRC: &str = r#"
_ABI_OK = type("AbiError", (), {"code": 0})()
def _fn0(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
raise ValueError("dispatch boom")
def polyplug_init(host_interface: int, ctx: int):
return [
{"contract": "raiser@1", "factory": lambda host_ptr: None, "functions": [_fn0]},
], _ABI_OK
"#;
fn raiser_contract_id() -> u64 {
GuestContractId::new("raiser", 1).id()
}
const ARENA_FORWARD_PLUGIN_SRC: &str = r#"
import ctypes
_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_int64))[0] = arena_ptr
def polyplug_init(host_interface: int, ctx: int):
return [
{"contract": "arenafwd@1", "factory": lambda host_ptr: None, "functions": [_fn0]},
], _ABI_OK
"#;
fn arenafwd_contract_id() -> u64 {
GuestContractId::new("arenafwd", 1).id()
}
const RAISING_INIT_PLUGIN_SRC: &str = r#"
def polyplug_init(_host_interface: int, _ctx: int) -> None:
raise RuntimeError("intentional test error")
"#;
const MISSING_INIT_PLUGIN_SRC: &str = r#"
def not_polyplug_init():
pass
"#;
const NO_REGISTRATIONS_PLUGIN_SRC: &str = r#"
def polyplug_init(_host_interface: int, _ctx: int):
return None
"#;
const EMPTY_REGISTRATIONS_PLUGIN_SRC: &str = r#"
_ABI_OK = type("AbiError", (), {"code": 0})()
def polyplug_init(_host_interface: int, _ctx: int):
return [], _ABI_OK
"#;
const SYNTAX_ERROR_PLUGIN_SRC: &str = r#"
def polyplug_init(:
pass
"#;
const IMPORT_ERROR_PLUGIN_SRC: &str = r#"
import _polyplug_nonexistent_module_xyz_123456
def polyplug_init(_host_interface: int, _ctx: int) -> None:
pass
"#;
#[test]
fn test_interpreter_initializes_without_panic() {
let (_dir, path) = write_bundle("noop_init", WRITE_OUT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "noop_init");
let result: Result<(), LoaderError> = loader.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
);
assert!(result.is_ok(), "unexpected error: {result:?}");
}
#[test]
fn test_default_config_version_check_passes() {
let (_dir, path) = write_bundle("ver_check", WRITE_OUT_PLUGIN_SRC);
let runtime: Arc<Runtime> = RuntimeBuilder::new()
.loader(PythonLoader::new(PythonConfig::default()))
.build()
.expect("runtime build must succeed");
let manifest: ManifestData = make_manifest(&path, "ver_check");
let result: Result<(), LoaderError> = PythonLoader::new(PythonConfig::default()).load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
);
assert!(result.is_ok(), "version check failed: {result:?}");
}
#[test]
fn test_version_too_old_returns_version_mismatch() {
let (_dir, path) = write_bundle("ver_mismatch", WRITE_OUT_PLUGIN_SRC);
let config: PythonConfig = PythonConfig {
min_version: (99, 0),
};
let runtime: Arc<Runtime> = RuntimeBuilder::new()
.loader(PythonLoader::new(config.clone()))
.build()
.expect("runtime build must succeed");
let manifest: ManifestData = make_manifest(&path, "ver_mismatch");
let err: LoaderError = PythonLoader::new(config)
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected version mismatch");
match err {
LoaderError::InitFailed { bundle, error } => {
assert_eq!(bundle, "python");
assert!(
error.contains("version"),
"error should mention version: {error}"
);
assert!(
error.contains("99"),
"error should mention required version: {error}"
);
}
other => panic!("expected InitFailed for version mismatch, got: {other:?}"),
}
}
#[test]
fn test_valid_plugin_registers_vm_contract() {
let (_dir, path) = write_bundle("valid_plugin", WRITE_OUT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "valid_plugin");
let result: Result<(), LoaderError> = loader.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
);
assert!(result.is_ok(), "load failed: {result:?}");
let cid: GuestContractId = GuestContractId::from_u64(writeout_contract_id());
let handle: GuestContractHandle = runtime
.registry()
.find(cid, 0)
.expect("contract must be registered");
let interface_ptr: *const polyplug_abi::GuestContractInterface = runtime
.registry()
.resolve_guest_contract(handle)
.expect("contract must resolve");
let interface: &polyplug_abi::GuestContractInterface = unsafe { &*interface_ptr };
assert_eq!(
interface.dispatch_type,
DispatchType::VirtualMachine,
"Python must register VM dispatch"
);
}
#[test]
fn test_vm_dispatch_writes_out_and_returns_ok() {
let (_dir, path) = write_bundle("writeout_disp", WRITE_OUT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "writeout_disp");
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let mut out_buf: i32 = 0;
let err: AbiError = unsafe {
dispatch(
&runtime,
writeout_contract_id(),
0,
core::ptr::null(),
&mut out_buf as *mut i32 as *mut (),
)
};
assert!(
err.is_ok(),
"dispatch should return Ok, got code {}",
err.code
);
assert_eq!(out_buf, 0x2A, "callable must have written 0x2A into out");
}
#[test]
fn test_vm_dispatch_exception_maps_to_generic() {
let (_dir, path) = write_bundle("raiser_disp", RAISING_FN_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "raiser_disp");
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let err: AbiError = unsafe {
dispatch(
&runtime,
raiser_contract_id(),
0,
core::ptr::null(),
core::ptr::null_mut(),
)
};
assert_eq!(
err.code,
AbiErrorCode::Generic as u32,
"a guest exception must map to Generic"
);
}
#[test]
fn test_vm_dispatch_fn_id_out_of_range() {
let (_dir, path) = write_bundle("range_disp", WRITE_OUT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "range_disp");
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let err: AbiError = unsafe {
dispatch(
&runtime,
writeout_contract_id(),
5,
core::ptr::null(),
core::ptr::null_mut(),
)
};
assert_eq!(
err.code,
AbiErrorCode::FunctionNotAvailable as u32,
"out-of-range fn_id must map to FunctionNotAvailable"
);
}
#[test]
fn test_vm_dispatch_arena_forwarded_zero_when_null() {
let (_dir, path) = write_bundle("arena_disp", ARENA_FORWARD_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "arena_disp");
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let mut out_buf: i64 = -1;
let err: AbiError = unsafe {
dispatch(
&runtime,
arenafwd_contract_id(),
0,
core::ptr::null(),
&mut out_buf as *mut i64 as *mut (),
)
};
assert!(
err.is_ok(),
"dispatch should return Ok, got code {}",
err.code
);
assert_eq!(out_buf, 0, "null arena must be forwarded as integer 0");
}
#[test]
fn test_syntax_error_returns_init_failed() {
let (_dir, path) = write_bundle("syntax_err", SYNTAX_ERROR_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "syntax_err");
let err: LoaderError = loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected failure for syntax error plugin");
assert!(matches!(err, LoaderError::InitFailed { .. }));
}
#[test]
fn test_import_error_returns_init_failed() {
let (_dir, path) = write_bundle("import_err", IMPORT_ERROR_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "import_err");
let err: LoaderError = loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected failure for import-error plugin");
assert!(matches!(err, LoaderError::InitFailed { .. }));
}
#[test]
fn test_missing_init_returns_init_symbol_missing() {
let (_dir, path) = write_bundle("no_init", MISSING_INIT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "no_init");
let err: LoaderError = loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected failure");
assert!(matches!(err, LoaderError::InitSymbolMissing { .. }));
}
#[test]
fn test_raising_init_returns_init_failed() {
let (_dir, path) = write_bundle("raising_init", RAISING_INIT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "raising_init");
let err: LoaderError = loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected failure");
match err {
LoaderError::InitFailed { error, .. } => {
assert!(
error.contains("intentional test error"),
"error should contain the Python exception text; got: {error}"
);
}
other => panic!("expected InitFailed, got: {other:?}"),
}
}
#[test]
fn test_missing_registrations_attr_fails() {
let (_dir, path) = write_bundle("no_regs", NO_REGISTRATIONS_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "no_regs");
let err: LoaderError = loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected failure for non-tuple polyplug_init return");
match err {
LoaderError::InitFailed { error, .. } => {
assert!(
error.contains("polyplug_init must return"),
"error should mention the bad return; got: {error}"
);
}
other => panic!("expected InitFailed, got: {other:?}"),
}
}
#[test]
fn test_empty_registrations_fails() {
let (_dir, path) = write_bundle("empty_regs", EMPTY_REGISTRATIONS_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "empty_regs");
let err: LoaderError = loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect_err("expected failure for empty registrations");
assert!(matches!(err, LoaderError::InitFailed { .. }));
}
#[test]
fn test_loader_name() {
let loader: PythonLoader = PythonLoader::default();
assert_eq!(loader.loader_name(), "python");
}
#[test]
fn test_loader_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<PythonLoader>();
}
#[test]
fn test_many_sequential_loads() {
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
for i in 0u32..8u32 {
let name: String = format!("seq_{i}");
let (_dir, path) = write_bundle(&name, WRITE_OUT_PLUGIN_SRC);
let manifest: ManifestData = make_manifest(&path, &name);
let result: Result<(), LoaderError> = loader.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
);
assert!(
result.is_ok() || matches!(result, Err(LoaderError::InitFailed { .. })),
"sequential load {i} produced an unexpected error: {result:?}"
);
}
}
#[test]
fn test_valid_load_after_failed_load_succeeds() {
let (_dir1, bad_path) = write_bundle("bad_recover", SYNTAX_ERROR_PLUGIN_SRC);
let (_dir2, good_path) = write_bundle("good_after_bad", WRITE_OUT_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let bad_manifest: ManifestData = make_manifest(&bad_path, "bad_recover");
let good_manifest: ManifestData = make_manifest(&good_path, "good_after_bad");
assert!(
loader
.load(
&bad_manifest,
&polyplug::loader::BundleSource::Path(bad_manifest.path.clone()),
&runtime
)
.is_err(),
"bad load should fail"
);
let result: Result<(), LoaderError> = loader.load(
&good_manifest,
&polyplug::loader::BundleSource::Path(good_manifest.path.clone()),
&runtime,
);
assert!(result.is_ok(), "recovery load failed: {result:?}");
}
#[test]
fn test_plugin_context_bundle_path_accessible() {
let plugin_src: &str = r#"
import ctypes
_ABI_OK = type("AbiError", (), {"code": 0})()
class _StringView(ctypes.Structure):
_fields_ = [("ptr", ctypes.c_void_p), ("len", ctypes.c_size_t)]
class _BundleInitContext(ctypes.Structure):
_fields_ = [("bundle_id", ctypes.c_uint64), ("bundle_path", _StringView)]
def _fn0(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
pass
def polyplug_init(_host_interface: int, ctx_addr: int):
ctx = _BundleInitContext.from_address(ctx_addr)
assert ctx.bundle_path.ptr is not None and ctx.bundle_path.ptr != 0
assert ctx.bundle_path.len > 0
return [{"contract": "ctxcheck@1", "factory": lambda host_ptr: None, "functions": [_fn0]}], _ABI_OK
"#;
let (_dir, path) = write_bundle("ctx_check", plugin_src);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "ctx_check");
let result: Result<(), LoaderError> = loader.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
);
assert!(result.is_ok(), "context check plugin failed: {result:?}");
}
#[test]
fn test_split_module_registrations_via_init_globals() {
let helper_src: &str = r#"
import ctypes
_ABI_OK = type("AbiError", (), {"code": 0})()
def _fn0(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
buf = arena_alloc(16, arena_ptr)
if buf == 0:
raise RuntimeError("arena_alloc returned 0")
ctypes.cast(out_ptr, ctypes.POINTER(ctypes.c_int32))[0] = 0x2A
def polyplug_init(host_interface: int, ctx: int):
return [
{
"contract": "splitmod@1",
"plugin_name": "splitmod_plugin",
"factory": lambda host_ptr: None,
"functions": [_fn0],
},
], _ABI_OK
"#;
let entry_src: &str = r#"
from _splitmod_helper import polyplug_init
"#;
let dir: TempDir = TempDir::new().expect("tempdir");
fs::write(dir.path().join("_splitmod_helper.py"), helper_src).expect("write helper module");
let entry_path: PathBuf = dir.path().join("bundle.py");
fs::write(&entry_path, entry_src).expect("write entry module");
let manifest: ManifestData = make_manifest(&entry_path, "splitmod");
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let result: Result<(), LoaderError> = loader.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
);
assert!(result.is_ok(), "split-module load failed: {result:?}");
let mut out_buf: i32 = 0;
let err: AbiError = unsafe {
dispatch(
&runtime,
GuestContractId::new("splitmod", 1).id(),
0,
core::ptr::null(),
&mut out_buf as *mut i32 as *mut (),
)
};
assert_eq!(err.code, AbiErrorCode::Ok as u32, "dispatch should succeed");
assert_eq!(out_buf, 0x2A, "callable should write 0x2A into out");
}
fn count_bundle_modules(helper_substr: &str) -> usize {
Python::attach(|py: Python<'_>| -> usize {
let sys_mod: pyo3::Bound<'_, PyModule> = PyModule::import(py, "sys").expect("sys import");
let modules: pyo3::Bound<'_, pyo3::PyAny> =
sys_mod.getattr("modules").expect("sys.modules");
let dict: pyo3::Bound<'_, PyDict> =
modules.cast_into::<PyDict>().expect("sys.modules dict");
let mut count: usize = 0;
for (key, _value) in dict.iter() {
let key_str: String = match key.extract::<String>() {
Ok(s) => s,
Err(_) => continue,
};
if key_str.starts_with("__polyplug_bundle_") && key_str.contains(helper_substr) {
count += 1;
}
}
count
})
}
fn write_split_bundle(name: &str, helper_module: &str, contract: &str) -> (TempDir, PathBuf) {
let helper_src: String = format!(
r#"
_ABI_OK = type("AbiError", (), {{"code": 0}})()
def _fn0(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
pass
def polyplug_init(host_interface: int, ctx: int):
return [
{{
"contract": "{contract}",
"plugin_name": "{name}_plugin",
"factory": lambda host_ptr: None,
"functions": [_fn0],
}},
], _ABI_OK
"#
);
let entry_src: String = format!("from {helper_module} import polyplug_init\n");
let dir: TempDir = TempDir::new().expect("tempdir");
fs::write(dir.path().join(format!("{helper_module}.py")), helper_src).expect("write helper");
let entry_path: PathBuf = dir.path().join("bundle.py");
fs::write(&entry_path, entry_src).expect("write entry");
(dir, entry_path)
}
#[test]
fn unload_purges_bundle_modules_from_sys_modules() {
let bundle_name: &str = "reclaim_purge";
let helper_module: &str = "_reclaim_purge_helper";
let (_dir, path) = write_split_bundle(bundle_name, helper_module, "reclaimpurge@1");
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, bundle_name);
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let before: usize = count_bundle_modules(helper_module);
assert!(
before > 0,
"bundle's helper module must be re-keyed into sys.modules after load"
);
loader
.unload(BundleId::from_u64(bundle_id(bundle_name)), &runtime)
.expect("unload must succeed");
let after: usize = count_bundle_modules(helper_module);
assert_eq!(
after, 0,
"unload must purge the bundle's re-keyed sys.modules entries"
);
}
const NESTED_ARENA_PLUGIN_SRC: &str = r#"
import ctypes
_ABI_OK = type("AbiError", (), {"code": 0})()
def _inner(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
arena_alloc(16, arena_ptr)
def _outer(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
# Real nested dispatch into fn_id 1 via the test-injected Rust trampoline.
__polyplug_test_nested_dispatch()
# After the nested call returned, allocate from the OUTER arena_ptr and report.
addr = arena_alloc(64, arena_ptr)
ctypes.cast(out_ptr, ctypes.POINTER(ctypes.c_int64))[0] = addr
def polyplug_init(host_interface: int, ctx: int):
return [
{"contract": "nestedarena@1", "factory": lambda host_ptr: None, "functions": [_outer, _inner]},
], _ABI_OK
"#;
fn nestedarena_contract_id() -> u64 {
GuestContractId::new("nestedarena", 1).id()
}
#[test]
fn test_nested_dispatch_preserves_outer_arena() {
let contract_id: u64 = nestedarena_contract_id();
let (_dir, path) = write_bundle("nested_arena", NESTED_ARENA_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "nested_arena");
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let runtime_addr: usize = Arc::as_ptr(&runtime) as usize;
Python::attach(|py: Python<'_>| {
let trampoline = pyo3::types::PyCFunction::new_closure(
py,
None,
None,
move |_args: &pyo3::Bound<'_, pyo3::types::PyTuple>,
_kwargs: Option<&pyo3::Bound<'_, PyDict>>|
-> i64 {
let rt: &Runtime = unsafe { &*(runtime_addr as *const Runtime) };
let err: AbiError = unsafe {
dispatch_with_arena(
rt,
contract_id,
1,
core::ptr::null(),
core::ptr::null_mut(),
core::ptr::null_mut(),
)
};
err.code as i64
},
)
.expect("trampoline creation must succeed");
let builtins: pyo3::Bound<'_, PyModule> =
PyModule::import(py, "builtins").expect("builtins import");
builtins
.setattr("__polyplug_test_nested_dispatch", trampoline)
.expect("inject trampoline builtin");
});
let mut buf: Vec<u8> = vec![0_u8; 4096];
let lo: usize = buf.as_ptr() as usize;
let hi: usize = lo + buf.len();
let mut arena: polyplug_abi::CallArena =
polyplug_abi::CallArena::new(&mut buf, core::ptr::null());
let mut out: i64 = 0;
let err: AbiError = unsafe {
dispatch_with_arena(
&runtime,
contract_id,
0,
core::ptr::null(),
&mut out as *mut i64 as *mut (),
&mut arena as *mut polyplug_abi::CallArena,
)
};
assert!(
err.is_ok(),
"outer dispatch must succeed: code={}",
err.code
);
let p: usize = out as usize;
assert!(
p >= lo && p < hi,
"outer allocation {p:#x} must land inside the OUTER arena buffer [{lo:#x}, {hi:#x}); a value outside means the nested call's clear wiped the outer arena (shared-cell bug)"
);
}
const ARENA_ECHO_PLUGIN_SRC: &str = r#"
import ctypes
_ABI_OK = type("AbiError", (), {"code": 0})()
def _fn0(impl, args_ptr, out_ptr, arena_ptr, arena_alloc):
addr = arena_alloc(64, arena_ptr)
ctypes.cast(out_ptr, ctypes.POINTER(ctypes.c_int64))[0] = addr
def polyplug_init(host_interface: int, ctx: int):
return [
{"contract": "arenaecho@1", "factory": lambda host_ptr: None, "functions": [_fn0]},
], _ABI_OK
"#;
fn arenaecho_contract_id() -> u64 {
GuestContractId::new("arenaecho", 1).id()
}
#[test]
fn test_concurrent_dispatch_distinct_arenas_isolated() {
let contract_id: u64 = arenaecho_contract_id();
let (_dir, path) = write_bundle("arena_echo", ARENA_ECHO_PLUGIN_SRC);
let loader: PythonLoader = PythonLoader::default();
let runtime: Arc<Runtime> = make_runtime();
let manifest: ManifestData = make_manifest(&path, "arena_echo");
loader
.load(
&manifest,
&polyplug::loader::BundleSource::Path(manifest.path.clone()),
&runtime,
)
.expect("load must succeed");
let buf_a: &'static mut [u8] = Box::leak(vec![0_u8; 4096].into_boxed_slice());
let buf_b: &'static mut [u8] = Box::leak(vec![0_u8; 4096].into_boxed_slice());
let a_lo: usize = buf_a.as_ptr() as usize;
let a_hi: usize = a_lo + buf_a.len();
let b_lo: usize = buf_b.as_ptr() as usize;
let b_hi: usize = b_lo + buf_b.len();
let arena_a: &'static mut polyplug_abi::CallArena = Box::leak(Box::new(
polyplug_abi::CallArena::new(buf_a, core::ptr::null()),
));
let arena_b: &'static mut polyplug_abi::CallArena = Box::leak(Box::new(
polyplug_abi::CallArena::new(buf_b, core::ptr::null()),
));
let arena_a_addr: usize = arena_a as *mut polyplug_abi::CallArena as usize;
let arena_b_addr: usize = arena_b as *mut polyplug_abi::CallArena as usize;
const ITERS: usize = 500;
let runtime_a: Arc<Runtime> = Arc::clone(&runtime);
let runtime_b: Arc<Runtime> = Arc::clone(&runtime);
let handle_a: std::thread::JoinHandle<Result<(), String>> = std::thread::spawn(move || {
for i in 0..ITERS {
let arena: &mut polyplug_abi::CallArena =
unsafe { &mut *(arena_a_addr as *mut polyplug_abi::CallArena) };
arena.reset();
let mut out: i64 = 0;
let err: AbiError = unsafe {
dispatch_with_arena(
&runtime_a,
contract_id,
0,
core::ptr::null(),
&mut out as *mut i64 as *mut (),
arena_a_addr as *mut polyplug_abi::CallArena,
)
};
if !err.is_ok() {
return Err(format!("A iter {i}: dispatch failed code={}", err.code));
}
let p: usize = out as usize;
if !(p >= a_lo && p < a_hi) {
return Err(format!(
"A iter {i}: allocation {p:#x} escaped arena A buffer [{a_lo:#x}, {a_hi:#x})"
));
}
}
Ok(())
});
let handle_b: std::thread::JoinHandle<Result<(), String>> = std::thread::spawn(move || {
for i in 0..ITERS {
let arena: &mut polyplug_abi::CallArena =
unsafe { &mut *(arena_b_addr as *mut polyplug_abi::CallArena) };
arena.reset();
let mut out: i64 = 0;
let err: AbiError = unsafe {
dispatch_with_arena(
&runtime_b,
contract_id,
0,
core::ptr::null(),
&mut out as *mut i64 as *mut (),
arena_b_addr as *mut polyplug_abi::CallArena,
)
};
if !err.is_ok() {
return Err(format!("B iter {i}: dispatch failed code={}", err.code));
}
let p: usize = out as usize;
if !(p >= b_lo && p < b_hi) {
return Err(format!(
"B iter {i}: allocation {p:#x} escaped arena B buffer [{b_lo:#x}, {b_hi:#x})"
));
}
}
Ok(())
});
let a_outcome: Result<(), String> = handle_a.join().expect("thread A must not panic");
let b_outcome: Result<(), String> = handle_b.join().expect("thread B must not panic");
if let Err(e) = a_outcome {
panic!("{e}");
}
if let Err(e) = b_outcome {
panic!("{e}");
}
}
const SHARED_NAME_ENTRY_SRC: &str = r#"
import ctypes
from shared_helper import VALUE
_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] = VALUE
def polyplug_init(host_interface: int, ctx: int):
return [
{"contract": "{contract}", "factory": lambda host_ptr: None, "functions": [_fn0]},
], _ABI_OK
"#;
fn write_shared_name_bundle(contract_name: &str, value: i32) -> (TempDir, PathBuf) {
let dir: TempDir = TempDir::new().expect("tempdir");
let entry: PathBuf = dir.path().join("bundle.py");
let entry_src: String =
SHARED_NAME_ENTRY_SRC.replace("{contract}", &format!("{contract_name}@1"));
fs::write(&entry, entry_src).expect("write bundle.py");
fs::write(
dir.path().join("shared_helper.py"),
format!("VALUE = {value}\n"),
)
.expect("write shared_helper.py");
let name: &str = "shared_name";
let manifest: String = format!(
r#"id = {}
name = "{}"
loader = "python"
file = "bundle.py"
"#,
bundle_id(name),
name
);
fs::write(dir.path().join("manifest.toml"), &manifest).expect("write manifest.toml");
(dir, entry)
}
#[test]
fn two_runtimes_same_named_bundle_do_not_collide_in_sys_modules() {
let name: &str = "shared_name";
let (_dir_a, path_a) = write_shared_name_bundle("alpha_contract", 0x11);
let (_dir_b, path_b) = write_shared_name_bundle("beta_contract", 0x22);
let loader_a: PythonLoader = PythonLoader::default();
let loader_b: PythonLoader = PythonLoader::default();
let runtime_a: Arc<Runtime> = make_runtime();
let runtime_b: Arc<Runtime> = make_runtime();
let manifest_a: ManifestData = make_manifest(&path_a, name);
let manifest_b: ManifestData = make_manifest(&path_b, name);
loader_a
.load(
&manifest_a,
&polyplug::loader::BundleSource::Path(manifest_a.path.clone()),
&runtime_a,
)
.expect("runtime A load must succeed");
loader_b
.load(
&manifest_b,
&polyplug::loader::BundleSource::Path(manifest_b.path.clone()),
&runtime_b,
)
.expect("runtime B load must succeed");
let alpha_cid: u64 = GuestContractId::new("alpha_contract", 1).id();
let beta_cid: u64 = GuestContractId::new("beta_contract", 1).id();
let mut out_a: i32 = 0;
let err_a: AbiError = unsafe {
dispatch(
&runtime_a,
alpha_cid,
0,
core::ptr::null(),
&mut out_a as *mut i32 as *mut (),
)
};
assert!(err_a.is_ok(), "A dispatch failed code={}", err_a.code);
let mut out_b: i32 = 0;
let err_b: AbiError = unsafe {
dispatch(
&runtime_b,
beta_cid,
0,
core::ptr::null(),
&mut out_b as *mut i32 as *mut (),
)
};
assert!(err_b.is_ok(), "B dispatch failed code={}", err_b.code);
assert_eq!(
out_a, 0x11,
"runtime A must read its OWN shared_helper.VALUE (0x11)"
);
assert_eq!(
out_b, 0x22,
"runtime B must read its OWN shared_helper.VALUE (0x22), not A's cached module"
);
}