mod entity_tags;
use ic_memory::ic_stable_structures::{DefaultMemoryImpl, Memory};
use ic_memory::{
MemoryAllocationPool, MemoryAuthority, MemoryManagerConfig, MemoryRequest, MemoryRuntime,
RuntimeMemory, SchemaMetadata, SealedDeclarationSnapshot,
};
use std::sync::OnceLock;
pub(crate) use entity_tags::*;
pub(crate) const RESERVED_INTERNAL_MEMORY_ID: u8 = u8::MAX;
#[must_use]
pub(crate) const fn test_memory_id(id: u8) -> u8 {
assert!(
id != RESERVED_INTERNAL_MEMORY_ID,
"memory id 255 is reserved for stable-structures internals",
);
id
}
pub(crate) fn test_memory(id: u8) -> RuntimeMemory<DefaultMemoryImpl> {
test_memory_with_backing(id, DefaultMemoryImpl::default())
}
pub(crate) fn test_memory_with_backing(
id: u8,
backing: DefaultMemoryImpl,
) -> RuntimeMemory<DefaultMemoryImpl> {
let id = test_memory_id(id);
test_memory_runtime(backing, MemoryManagerConfig::default())
.open_memory(&format!("icydb.core_tests.slot_{id:03}.v1"))
.expect("test memory should open")
}
pub(crate) fn test_memory_runtime<M: Memory>(
backing: M,
config: MemoryManagerConfig,
) -> MemoryRuntime<M> {
static DECLARATIONS: OnceLock<SealedDeclarationSnapshot> = OnceLock::new();
let declarations = DECLARATIONS.get_or_init(|| {
let requests: Vec<_> = (10..=254)
.map(|id| {
MemoryRequest::new(
"icydb.core-tests",
&format!("icydb.core_tests.slot_{id:03}.v1"),
SchemaMetadata::default(),
)
.expect("test request should validate")
})
.collect();
SealedDeclarationSnapshot::new(requests).expect("test requests should seal")
});
let pool = MemoryAllocationPool::new(
vec![MemoryAuthority::new("icydb.core-tests", "icydb.core_tests.").unwrap()],
vec![],
)
.unwrap();
let mut runtime =
MemoryRuntime::new_with_config(backing, config).expect("test runtime should initialize");
runtime
.bootstrap(declarations, &pool, &ic_memory::GenericAllocationPolicy)
.expect("test runtime should bootstrap");
runtime
}
pub(crate) fn run_isolated<T: Send + 'static>(fixture: impl FnOnce() -> T + Send + 'static) -> T {
match std::thread::spawn(fixture).join() {
Ok(result) => result,
Err(payload) => std::panic::resume_unwind(payload),
}
}
#[cfg(test)]
mod isolation_tests {
use crate::testing::run_isolated;
use std::cell::Cell;
thread_local! {
static FIXTURE_VALUE: Cell<u32> = const { Cell::new(0) };
}
#[test]
fn isolated_fixtures_preserve_thread_local_boundaries_and_return_values() {
FIXTURE_VALUE.set(99);
assert_eq!(
run_isolated(|| {
assert_eq!(FIXTURE_VALUE.get(), 0);
FIXTURE_VALUE.set(1);
7
}),
7
);
assert_eq!(FIXTURE_VALUE.get(), 99);
run_isolated(|| assert_eq!(FIXTURE_VALUE.get(), 0));
FIXTURE_VALUE.set(0);
}
#[test]
fn isolated_fixture_preserves_the_original_panic_payload() {
let failure = std::panic::catch_unwind(|| {
run_isolated(|| std::panic::panic_any(17_u32));
})
.unwrap_err();
assert_eq!(failure.downcast_ref::<u32>(), Some(&17));
}
}