#![allow(clippy::expect_used)]
use std::path::Path;
use std::sync::Arc;
use std::sync::Barrier;
use std::thread;
use polyplug::runtime::Runtime;
use polyplug_utils::GuestContractId;
use crate::common::TestNativeLoader;
use crate::fixtures::{RELOAD_V1_DIR, make_hot_reload_runtime, resolve_version_fn};
const RUNTIMES: usize = 8_usize;
#[test]
fn two_runtimes_do_not_share_registry_or_host_api() {
let contract_id: u64 = GuestContractId::new("reload.test", 1).id();
let rt_loaded: Arc<Runtime> = make_hot_reload_runtime();
let rt_empty: Arc<Runtime> = make_hot_reload_runtime();
rt_loaded
.load_bundle(Path::new(RELOAD_V1_DIR))
.expect("load into rt_loaded must succeed");
assert!(
resolve_version_fn(&rt_loaded, contract_id).is_some(),
"rt_loaded must resolve the contract it loaded"
);
assert!(
rt_empty.find_guest_contract(contract_id, 0).is_err(),
"rt_empty must NOT see a contract loaded into a different runtime (Rule 12 isolation)"
);
assert!(
!core::ptr::eq(rt_loaded.host_abi(), rt_empty.host_abi()),
"each runtime must own a distinct HostApi — no shared global table"
);
}
#[test]
fn parallel_runtimes_have_isolated_registries() {
let contract_id: u64 = GuestContractId::new("reload.test", 1).id();
let barrier: Arc<Barrier> = Arc::new(Barrier::new(RUNTIMES));
let handles: Vec<thread::JoinHandle<()>> = (0_usize..RUNTIMES)
.map(|idx| {
let barrier_clone: Arc<Barrier> = Arc::clone(&barrier);
thread::spawn(move || {
let rt: Arc<Runtime> = make_hot_reload_runtime();
let loads_bundle: bool = idx % 2_usize == 0_usize;
barrier_clone.wait();
if loads_bundle {
rt.load_bundle(Path::new(RELOAD_V1_DIR))
.unwrap_or_else(|e| panic!("thread {idx}: load must succeed: {e}"));
let version_fn: extern "C" fn() -> u32 = resolve_version_fn(&rt, contract_id)
.unwrap_or_else(|| panic!("thread {idx}: loaded contract must resolve"));
assert_eq!(
version_fn(),
100_u32,
"thread {idx}: own bundle must resolve to v1 (100)"
);
} else {
assert!(
rt.find_guest_contract(contract_id, 0).is_err(),
"thread {idx}: a runtime that loaded nothing must not observe another \
runtime's concurrently-loaded contract"
);
}
})
})
.collect();
for handle in handles {
handle.join().expect("runtime thread must not panic");
}
}
#[test]
fn parallel_runtime_build_use_destroy_churn() {
const THREADS: usize = 6_usize;
const CYCLES_PER_THREAD: usize = 8_usize;
let contract_id: u64 = GuestContractId::new("reload.test", 1).id();
let barrier: Arc<Barrier> = Arc::new(Barrier::new(THREADS));
let handles: Vec<thread::JoinHandle<()>> = (0_usize..THREADS)
.map(|t| {
let barrier_clone: Arc<Barrier> = Arc::clone(&barrier);
thread::spawn(move || {
barrier_clone.wait();
for cycle in 0_usize..CYCLES_PER_THREAD {
let rt: Arc<Runtime> = Runtime::builder()
.config(crate::fixtures::hot_reload_config())
.loader(TestNativeLoader::new())
.build()
.unwrap_or_else(|e| panic!("thread {t} cycle {cycle}: build: {e}"));
rt.load_bundle(Path::new(RELOAD_V1_DIR))
.unwrap_or_else(|e| panic!("thread {t} cycle {cycle}: load: {e}"));
let version_fn: extern "C" fn() -> u32 = resolve_version_fn(&rt, contract_id)
.unwrap_or_else(|| {
panic!("thread {t} cycle {cycle}: contract must resolve")
});
assert_eq!(
version_fn(),
100_u32,
"thread {t} cycle {cycle}: dispatch must return v1 (100)"
);
}
})
})
.collect();
for handle in handles {
handle.join().expect("churn thread must not panic");
}
}