use super::*;
use std::cell::RefCell;
use std::collections::HashMap;

pub(super) const LOCK_ERROR: i32 = 0x1234;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Call {
    Number,
    Dictionary,
    Surface,
    Extent,
    Lock,
    Base,
    Id,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Event {
    Call(Call),
    Acquire(usize),
    Release(usize),
    Locked,
    Unlocked,
}

#[derive(Clone, Copy)]
enum Fault {
    None,
    Fail(Call, usize),
    Extent(usize),
    NullBase,
    MisalignedBase,
    WrappingBase,
}

struct Trace {
    fault: Fault,
    events: Vec<Event>,
}

thread_local! {
    static TRACE: RefCell<Option<Trace>> = const { RefCell::new(None) };
}

pub(super) fn record(event: Event) {
    TRACE.with_borrow_mut(|trace| {
        if let Some(trace) = trace {
            trace.events.push(event);
        }
    });
}

pub(super) fn fail(call: Call) -> bool {
    record(Event::Call(call));
    TRACE.with_borrow(|trace| match trace {
        Some(Trace {
            fault: Fault::Fail(target, occurrence),
            events,
        }) => {
            *target == call
                && events.iter().filter(|e| **e == Event::Call(call)).count() == *occurrence
        }
        _ => false,
    })
}

pub(super) fn extent(actual: usize) -> usize {
    record(Event::Call(Call::Extent));
    TRACE.with_borrow(|trace| match trace {
        Some(Trace {
            fault: Fault::Extent(value),
            ..
        }) => *value,
        _ => actual,
    })
}

pub(super) fn base(actual: *mut u8) -> *mut u8 {
    record(Event::Call(Call::Base));
    TRACE.with_borrow(|trace| match trace.as_ref().map(|trace| trace.fault) {
        Some(Fault::NullBase) => std::ptr::null_mut(),
        Some(Fault::MisalignedBase) => actual.wrapping_add(1),
        Some(Fault::WrappingBase) => std::ptr::without_provenance_mut(usize::MAX & !3),
        _ => actual,
    })
}

fn traced(fault: Fault, f: impl FnOnce()) -> Vec<Event> {
    struct Reset;
    impl Drop for Reset {
        fn drop(&mut self) {
            TRACE.with_borrow_mut(|trace| *trace = None);
        }
    }
    TRACE.with_borrow_mut(|trace| {
        assert!(trace.is_none());
        *trace = Some(Trace {
            fault,
            events: Vec::new(),
        });
    });
    let _reset = Reset;
    f();
    TRACE.with_borrow_mut(|trace| trace.take().unwrap().events)
}

fn assert_balanced(events: &[Event], acquisitions: usize) {
    let mut references = HashMap::<usize, usize>::new();
    let mut total = 0;
    for event in events {
        match *event {
            Event::Acquire(raw) => {
                *references.entry(raw).or_default() += 1;
                total += 1;
            }
            Event::Release(raw) => {
                let count = references
                    .get_mut(&raw)
                    .expect("release requires an acquired owner");
                assert!(*count > 0, "duplicate release: {events:?}");
                *count -= 1;
            }
            _ => {}
        }
    }
    assert_eq!(total, acquisitions);
    assert!(
        references.values().all(|count| *count == 0),
        "leaked reference: {events:?}"
    );
}

fn calls(events: &[Event]) -> Vec<Call> {
    events
        .iter()
        .filter_map(|event| match event {
            Event::Call(call) => Some(*call),
            _ => None,
        })
        .collect()
}

#[test]
fn checked_numeric_boundaries_without_native_allocation() {
    assert!(matches!(
        BlockPlan::checked(0, 1, 1),
        Err(MemError::ZeroSize)
    ));
    for value in [isize::MAX as usize + 1, usize::MAX] {
        assert!(matches!(
            BlockPlan::checked(value, 1, 1),
            Err(MemError::SizeOverflow)
        ));
    }
    let max = isize::MAX as usize;
    assert_eq!(
        BlockPlan::checked(max, 1, 1).unwrap().allocation_size(),
        max
    );
    let last = max / 128 * 128;
    assert_eq!(
        BlockPlan::checked(last, 128, 1).unwrap().allocation_size(),
        last
    );
    assert!(matches!(
        BlockPlan::checked(last + 1, 128, 1),
        Err(MemError::SizeOverflow)
    ));
    assert!(BlockPlan::checked(last, 1, 128).is_ok());
    assert!(matches!(
        BlockPlan::checked(last + 1, 1, 128),
        Err(MemError::SizeOverflow)
    ));
    assert!(matches!(
        aligned_size(usize::MAX - 1, 4),
        Err(MemError::SizeOverflow)
    ));
    for (row, allocation) in [(0, 1), (1, 0)] {
        assert!(matches!(
            BlockPlan::checked(1, row, allocation),
            Err(MemError::BlockAlignmentInvalid)
        ));
    }
}

#[test]
fn checked_rounding_and_native_disagreement() {
    for (request, row_align, alloc_align, row, allocation) in [
        (1, 3, 5, 3, 5),
        (3, 3, 5, 3, 5),
        (4, 3, 5, 6, 10),
        (128, 128, 256, 128, 256),
        (256, 128, 256, 256, 256),
    ] {
        let plan = BlockPlan::checked(request, row_align, alloc_align).unwrap();
        assert_eq!(plan.requested_size(), request);
        assert_eq!(plan.row_bytes(), row);
        assert_eq!(plan.allocation_size(), allocation);
        assert!(plan.check_native_alignment(row, allocation).is_ok());
        for pair in [(row + 1, allocation), (row, allocation + 1)] {
            assert!(matches!(
                plan.check_native_alignment(pair.0, pair.1),
                Err(MemError::BlockAlignmentInvalid)
            ));
        }
    }
}

#[test]
fn rejected_sizes_and_planning_create_no_native_owners() {
    let events = traced(Fault::None, || {
        assert!(matches!(Block::open(0), Err(MemError::ZeroSize)));
        assert!(matches!(
            Block::open(usize::MAX),
            Err(MemError::SizeOverflow)
        ));
        let _ = Block::plan(1).unwrap();
    });
    assert!(events.is_empty());
}

#[test]
fn every_number_null_releases_only_preceding_create_references() {
    let plan = Block::plan(13).unwrap();
    for index in 1..=6 {
        let events = traced(Fault::Fail(Call::Number, index), || {
            assert!(matches!(plan.open(), Err(MemError::BlockPropertiesFailed)));
        });
        assert_balanced(&events, index - 1);
        assert_eq!(calls(&events), vec![Call::Number; index]);
    }
}

#[test]
fn dictionary_and_surface_null_release_properties() {
    let plan = Block::plan(13).unwrap();
    for (stage, count) in [(Call::Dictionary, 6), (Call::Surface, 7)] {
        let events = traced(Fault::Fail(stage, 1), || {
            let error = plan.open().err().unwrap();
            match stage {
                Call::Dictionary => assert!(matches!(error, MemError::BlockPropertiesFailed)),
                Call::Surface => assert!(matches!(error, MemError::BlockCreateFailed)),
                _ => unreachable!(),
            }
        });
        assert_balanced(&events, count);
        let mut expected = vec![Call::Number; 6];
        expected.push(Call::Dictionary);
        if stage == Call::Surface {
            expected.push(Call::Surface);
        }
        assert_eq!(calls(&events), expected);
        assert!(!events.contains(&Event::Unlocked));
    }
}

#[test]
fn extent_mismatch_is_fatal_before_lock() {
    let plan = Block::plan(13).unwrap();
    for actual in [
        0,
        plan.allocation_size() - 1,
        plan.allocation_size() + 1,
        usize::MAX,
    ] {
        let events = traced(Fault::Extent(actual), || match plan.open().err().unwrap() {
            MemError::BlockExtentMismatch {
                expected,
                actual: observed,
            } => {
                assert_eq!(expected, plan.allocation_size());
                assert_eq!(observed, actual);
            }
            other => panic!("unexpected error: {other:?}"),
        });
        assert_balanced(&events, 8);
        assert_eq!(calls(&events).last(), Some(&Call::Extent));
        assert!(!events.contains(&Event::Locked));
        assert!(!events.contains(&Event::Unlocked));
    }
}

#[test]
fn failed_lock_releases_without_unlock() {
    let plan = Block::plan(13).unwrap();
    let events = traced(Fault::Fail(Call::Lock, 1), || {
        assert!(matches!(
            plan.open(),
            Err(MemError::BlockLockFailed(LOCK_ERROR))
        ));
    });
    assert_balanced(&events, 8);
    assert_eq!(calls(&events).last(), Some(&Call::Lock));
    assert!(!events.contains(&Event::Locked));
    assert!(!events.contains(&Event::Unlocked));
}

fn assert_unlock_before_final_release(events: &[Event]) {
    assert_eq!(events.iter().filter(|e| **e == Event::Locked).count(), 1);
    assert_eq!(events.iter().filter(|e| **e == Event::Unlocked).count(), 1);
    assert_eq!(events[events.len() - 2], Event::Unlocked);
    assert!(matches!(events.last(), Some(Event::Release(_))));
}

#[test]
fn mapping_rejections_unlock_then_release() {
    let plan = Block::plan(13).unwrap();
    for fault in [Fault::NullBase, Fault::MisalignedBase, Fault::WrappingBase] {
        let events = traced(fault, || {
            assert!(matches!(plan.open(), Err(MemError::BlockAddressInvalid)));
        });
        assert_balanced(&events, 8);
        assert_eq!(calls(&events).last(), Some(&Call::Base));
        assert_unlock_before_final_release(&events);
    }
}

#[test]
fn success_transfers_one_locked_owner_to_block_drop() {
    let plan = Block::plan(13).unwrap();
    let events = traced(Fault::None, || {
        let block = plan.open().unwrap();
        TRACE.with_borrow(|trace| {
            assert!(!trace.as_ref().unwrap().events.contains(&Event::Unlocked));
        });
        assert_eq!(block.size(), plan.allocation_size());
        drop(block);
    });
    assert_balanced(&events, 8);
    assert_eq!(calls(&events).last(), Some(&Call::Id));
    assert_unlock_before_final_release(&events);
}

Homonyms

warriors/trisha/rs/lower/tests.rs
cyb/honeycrisp/aruminium/src/tests.rs
soft3/radio/iroh-blobs/src/tests.rs
neural/trident/src/neural/tests.rs
soft3/lens/cli/src/tests.rs
neural/trident/src/deploy/tests.rs
neural/trident/src/package/registry/tests.rs
neural/trident/src/lsp/semantic/tests.rs
neural/trident/src/verify/sym/tests.rs
cyb/wysm/crates/wasmi/src/tests.rs
soft3/radio/cyber-bao/src/tree/tests.rs
neural/trident/src/verify/report/tests.rs
neural/trident/src/config/resolve/tests.rs
neural/trident/src/verify/solve/tests.rs
neural/trident/src/syntax/grammar/tests.rs
neural/trident/src/package/hash/tests.rs
neural/trident/src/lsp/util/tests.rs
neural/trident/src/config/scaffold/tests.rs
neural/trident/src/verify/synthesize/tests.rs
neural/trident/src/package/manifest/tests.rs
neural/trident/src/verify/equiv/tests.rs
neural/trident/src/verify/smt/tests.rs
soft3/radio/quinn/quinn-udp/tests/tests.rs
cyb/honeycrisp/aruminium/src/render/tests.rs
warriors/trisha/rs/lower/legalize/tests.rs
soft3/radio/quinn/quinn/src/tests.rs
cyb/wysm/crates/collections/src/tests.rs
cyb/wysm/crates/cli/src/tests.rs
neural/trident/src/package/store/tests.rs
warriors/trisha/rs/cost/stack_verifier/tests.rs
soft3/radio/cyber-bao/src/group/tests.rs
neural/trident/src/syntax/lexer/tests.rs
neural/trident/src/syntax/format/tests.rs
cyb/wysm/crates/core/src/table/tests.rs
soft3/radio/quinn/quinn-proto/src/range_set/tests.rs
neural/trident/src/neural/data/tir_graph/tests.rs
cyb/wysm/crates/wasmi/src/instance/tests.rs
warriors/erga/rs/mine-bench/src/texture_ffi/tests.rs
warriors/erga/rs/blake-bench/src/batch/tests.rs
neural/trident/src/ir/tir/optimize/tests.rs
soft3/zheng/rs/src/ccs/universal/tests.rs
neural/trident/src/ir/tir/stack/tests.rs
warriors/erga/rs/rtable-bench/src/table/tests.rs
cyb/evy/forks/bevy_ecs/src/bundle/tests.rs
warriors/erga/rs/rtable-bench/src/probe/tests.rs
warriors/erga/rs/mine-bench/src/owner/tests.rs
cyb/wysm/crates/core/src/memory/tests.rs
cyb/wysm/crates/wasmi/src/module/instantiate/tests.rs
cyb/wysm/crates/wasmi/src/engine/limits/tests.rs
cyb/evy/forks/naga/src/front/wgsl/tests.rs
warriors/erga/rs/mine-bench/src/cli/timed/tests.rs

Graph