#![expect(clippy::unwrap_used, reason = "Fail fast in tests")]
use super::{
DIRTY_MASK, INSERTING_BIT, ISLEAF_BIT, LOCK_BIT, LockGuard, NodeVersion, SPLITTING_BIT,
VINSERT_LOWBIT, VSPLIT_LOWBIT,
};
#[test]
fn test_new_leaf() {
let v = NodeVersion::new(true);
assert!(v.is_leaf());
assert!(!v.is_root());
assert!(!v.is_deleted());
assert!(!v.is_locked());
assert!(!v.is_dirty());
}
#[test]
fn test_new_internode() {
let v = NodeVersion::new(false);
assert!(!v.is_leaf());
assert!(!v.is_root());
assert!(!v.is_locked());
}
#[test]
fn test_lock_unlock_roundtrip() {
let v = NodeVersion::new(true);
let stable_before: u32 = v.stable();
{
let guard: LockGuard<'_> = v.lock();
assert!(v.is_locked());
assert_eq!(guard.locked_value() & LOCK_BIT, LOCK_BIT);
assert_eq!(guard.locked_value() & INSERTING_BIT, 0);
}
assert!(!v.is_locked());
assert!(!v.has_changed(stable_before));
}
#[test]
fn test_try_lock() {
let v = NodeVersion::new(true);
let guard: Option<LockGuard<'_>> = v.try_lock();
assert!(guard.is_some());
assert!(v.is_locked());
let second: Option<LockGuard<'_>> = v.try_lock();
assert!(second.is_none());
drop(guard);
assert!(!v.is_locked());
}
#[test]
fn test_version_increment_on_insert() {
let v: NodeVersion = NodeVersion::new(true);
let stable_before: u32 = v.stable();
{
let mut guard: LockGuard<'_> = v.lock();
guard.mark_insert();
assert!(v.is_inserting());
}
assert!(v.has_changed(stable_before));
assert!(!v.has_split(stable_before));
}
#[test]
fn test_version_increment_on_split() {
let v: NodeVersion = NodeVersion::new(true);
let stable_before: u32 = v.stable();
{
let mut guard: LockGuard<'_> = v.lock();
guard.mark_split();
assert!(v.is_splitting());
}
assert!(v.has_changed(stable_before));
assert!(v.has_split(stable_before));
}
#[test]
fn test_version_does_not_increment_without_mark_insert() {
let v: NodeVersion = NodeVersion::new(true);
let stable_before: u32 = v.stable();
{
let _guard: LockGuard<'_> = v.lock();
}
assert!(!v.has_changed(stable_before));
}
#[test]
fn test_mark_root() {
let v = NodeVersion::new(true);
assert!(!v.is_root());
v.mark_root();
assert!(v.is_root());
}
#[test]
fn test_mark_deleted() {
let v = NodeVersion::new(true);
{
let mut guard: LockGuard<'_> = v.lock();
guard.mark_deleted();
assert!(v.is_deleted());
assert!(v.is_splitting()); }
assert!(v.is_deleted()); }
#[test]
fn test_mark_nonroot() {
let v: NodeVersion = NodeVersion::new(true);
v.mark_root();
assert!(v.is_root());
{
let mut guard: LockGuard<'_> = v.lock();
guard.mark_nonroot();
assert!(!v.is_root());
}
}
#[test]
fn test_has_changed_ignores_lock_bit() {
let v = NodeVersion::new(true);
let stable: u32 = v.stable();
{
let _guard: LockGuard<'_> = v.lock();
assert!(
!v.has_changed(stable),
"lock bit alone should not trigger has_changed"
);
}
}
#[test]
fn test_version_counter_wraparound() {
let near_max: u32 = ISLEAF_BIT | ((VSPLIT_LOWBIT - VINSERT_LOWBIT) - VINSERT_LOWBIT);
let v = NodeVersion::from_value(near_max);
let stable_before: u32 = v.stable();
{
let mut guard: LockGuard<'_> = v.lock();
guard.mark_insert();
}
assert!(v.has_changed(stable_before));
}
#[test]
fn test_stable_returns_clean_version() {
let v = NodeVersion::new(true);
let stable: u32 = v.stable();
assert_eq!(stable & DIRTY_MASK, 0);
assert_eq!(stable & LOCK_BIT, 0);
}
#[test]
fn test_flag_combinations() {
let v = NodeVersion::new(true);
v.mark_root();
{
let mut guard: LockGuard<'_> = v.lock();
guard.mark_deleted();
assert!(v.is_leaf());
assert!(v.is_root()); assert!(v.is_deleted());
assert!(v.is_locked());
assert!(v.is_splitting()); }
}
#[test]
fn test_guard_unlocks_on_drop() {
let v = NodeVersion::new(true);
let guard: LockGuard<'_> = v.lock();
assert!(v.is_locked());
drop(guard);
assert!(!v.is_locked());
}
#[test]
fn test_guard_locked_value() {
let v = NodeVersion::new(true);
let initial: u32 = v.value();
let guard: LockGuard<'_> = v.lock();
assert_eq!(guard.locked_value(), initial | LOCK_BIT);
}
#[test]
fn test_guard_mark_insert_sets_bit() {
let v: NodeVersion = NodeVersion::new(true);
let mut guard: LockGuard<'_> = v.lock();
let initial_locked: u32 = guard.locked_value();
assert_eq!(initial_locked & INSERTING_BIT, 0);
guard.mark_insert();
assert_ne!(guard.locked_value() & INSERTING_BIT, 0);
assert!(v.is_inserting());
}
#[test]
fn test_guard_mark_insert_is_idempotent() {
let v: NodeVersion = NodeVersion::new(true);
let mut guard: LockGuard<'_> = v.lock();
guard.mark_insert();
let after_first: u32 = guard.locked_value();
guard.mark_insert();
let after_second: u32 = guard.locked_value();
assert_eq!(after_first, after_second);
}
#[test]
#[cfg_attr(miri, ignore)] fn test_insert_counter_wraparound_stress() {
let v = NodeVersion::new(true);
v.mark_root();
for i in 0..100 {
let stable_before = v.stable();
{
let mut guard = v.lock();
guard.mark_insert();
}
assert!(
v.has_changed(stable_before),
"Version should change after unlock (iteration {i})"
);
assert!(v.is_leaf(), "is_leaf should persist through wraparound");
assert!(v.is_root(), "is_root should persist through wraparound");
assert!(!v.is_deleted(), "is_deleted should stay false");
}
}
#[test]
#[cfg_attr(miri, ignore)] fn test_split_counter_wraparound() {
let v = NodeVersion::new(true);
let mut last_value = v.stable();
for _ in 0..10 {
{
let mut guard = v.lock();
guard.mark_split();
}
let new_value = v.stable();
assert!(
v.has_split(last_value),
"has_split should detect split counter change"
);
last_value = new_value;
}
}
#[test]
fn test_has_split_no_compiler_fence() {
let v = NodeVersion::new(true);
let before = v.stable();
{
let mut guard = v.lock();
guard.mark_split();
}
assert!(v.has_split_no_compiler_fence(before));
assert_eq!(v.has_split(before), v.has_split_no_compiler_fence(before));
}
#[test]
fn test_new_for_split() {
let source = NodeVersion::new(true);
let _guard = source.lock();
let split_version = NodeVersion::new_for_split(&source);
assert!(split_version.is_split_locked());
assert!(split_version.is_leaf());
assert!(!split_version.is_root());
let value = split_version.value();
assert!((value & LOCK_BIT) != 0, "LOCK_BIT should be set");
assert!((value & SPLITTING_BIT) != 0, "SPLITTING_BIT should be set");
assert!((value & ISLEAF_BIT) != 0, "ISLEAF_BIT should be preserved");
}
#[test]
fn test_unlock_for_split() {
let source = NodeVersion::new(true);
let _guard = source.lock();
let split_version = NodeVersion::new_for_split(&source);
assert!(split_version.is_split_locked());
split_version.unlock_for_split();
assert!(!split_version.is_locked());
assert!(!split_version.is_splitting());
assert!(!split_version.is_split_locked());
let v = split_version.stable();
assert!((v & DIRTY_MASK) == 0);
}
#[test]
fn test_split_version_blocks_stable() {
let split_version = NodeVersion::from_value(ISLEAF_BIT | LOCK_BIT | SPLITTING_BIT);
assert!(split_version.is_split_locked());
assert!(split_version.is_dirty());
let value = split_version.value();
assert!(
(value & DIRTY_MASK) != 0,
"Split-locked version should have dirty bits set"
);
split_version.unlock_for_split();
let stable = split_version.stable();
assert!((stable & DIRTY_MASK) == 0);
}
#[test]
fn test_new_for_split_preserves_isleaf() {
let leaf_source = NodeVersion::new(true);
let guard1 = leaf_source.lock();
let split_leaf = NodeVersion::new_for_split(&leaf_source);
assert!(split_leaf.is_leaf());
drop(guard1);
let inode_source = NodeVersion::new(false);
let _guard2 = inode_source.lock();
let split_inode = NodeVersion::new_for_split(&inode_source);
assert!(!split_inode.is_leaf());
}
#[test]
fn test_unlock_for_split_increments_split_counter() {
let source = NodeVersion::new(true);
let _guard = source.lock();
let split_version = NodeVersion::new_for_split(&source);
let before = split_version.value();
split_version.unlock_for_split();
let after = split_version.value();
assert!(
after != before,
"Version should change after unlock_for_split"
);
assert!(
(after & DIRTY_MASK) == 0,
"Dirty bits should be cleared after unlock"
);
assert!(
(after & LOCK_BIT) == 0,
"Lock bit should be cleared after unlock"
);
}
#[test]
fn test_try_stable_clean_node() {
let v = NodeVersion::new(true);
let result = v.try_stable();
assert!(result.is_some());
assert_eq!(result.unwrap() & DIRTY_MASK, 0);
}
#[test]
fn test_try_stable_equals_stable_on_clean() {
let v = NodeVersion::new(true);
let try_result = v.try_stable().unwrap();
let stable_result = v.stable();
assert_eq!(try_result, stable_result);
}
#[test]
fn test_acquire_raw_clean_node() {
let v = NodeVersion::new(true);
let raw = v.acquire_raw();
assert!(!NodeVersion::is_dirty_value(raw));
}
#[test]
fn test_is_dirty_value_static() {
assert!(!NodeVersion::is_dirty_value(0));
assert!(!NodeVersion::is_dirty_value(ISLEAF_BIT));
assert!(!NodeVersion::is_dirty_value(LOCK_BIT)); assert!(NodeVersion::is_dirty_value(INSERTING_BIT));
assert!(NodeVersion::is_dirty_value(SPLITTING_BIT));
assert!(NodeVersion::is_dirty_value(INSERTING_BIT | SPLITTING_BIT));
assert!(NodeVersion::is_dirty_value(LOCK_BIT | INSERTING_BIT)); }
#[test]
fn test_stable_yield_clean_node() {
let v = NodeVersion::new(true);
let result = v.stable_yield();
assert_eq!(result & DIRTY_MASK, 0);
assert_eq!(result, v.stable());
}
#[test]
fn test_acquire_raw_vs_stable_equivalence_when_clean() {
let v = NodeVersion::new(true);
v.mark_root();
let raw = v.acquire_raw();
let stable = v.stable();
assert!(!NodeVersion::is_dirty_value(raw));
assert_eq!(raw & DIRTY_MASK, 0);
assert_eq!(raw, stable);
}