robotrt-buffer-core 0.1.0-beta.1

RobotRT modular robotics runtime and middleware components.
Documentation
use buffer_core::{
    Buffer, BufferKind, BufferLease, BufferLeaseProvider, SharedBufferDescriptor, SimpleBufferPool,
};
use core_types::BufferId;

// ─── Buffer tests ─────────────────────────────────────────────────────────────

#[test]
fn buffer_from_vec_stores_data() {
    let id = BufferId::new(1);
    let buf = Buffer::from_vec(id, vec![10, 20, 30]);
    assert_eq!(buf.id, id);
    assert_eq!(buf.kind, BufferKind::Owned);
    assert_eq!(buf.as_slice(), &[10, 20, 30]);
    assert_eq!(buf.len(), 3);
}

#[test]
fn buffer_clone_shares_arc() {
    let id = BufferId::new(2);
    let buf = Buffer::from_vec(id, vec![1, 2, 3, 4]);
    let clone = buf.clone();
    assert_eq!(buf.as_slice(), clone.as_slice());
    assert_eq!(buf.id, clone.id);
}

// ─── BufferLease tests ────────────────────────────────────────────────────────

#[test]
fn full_lease_covers_entire_buffer() {
    let buf = Buffer::from_vec(BufferId::new(10), vec![0u8, 1, 2, 3, 4]);
    let lease = BufferLease::full(buf.clone());
    assert_eq!(lease.len(), 5);
    assert_eq!(lease.as_slice(), &[0, 1, 2, 3, 4]);
    assert_eq!(lease.buffer_id(), BufferId::new(10));
}

#[test]
fn slice_returns_sub_lease() {
    let buf = Buffer::from_vec(BufferId::new(11), b"hello world".to_vec());
    let lease = BufferLease::full(buf);
    let sub = lease.slice(0, 5).expect("slice must succeed");
    assert_eq!(sub.as_slice(), b"hello");
    assert_eq!(sub.len(), 5);
}

#[test]
fn slice_of_slice_is_relative() {
    let buf = Buffer::from_vec(BufferId::new(12), b"abcdefgh".to_vec());
    let lease = BufferLease::full(buf);
    // slice [2..6] = "cdef"
    let mid = lease.slice(2, 6).expect("slice must succeed");
    assert_eq!(mid.as_slice(), b"cdef");
    // sub-slice [1..3] of "cdef" = "de"
    let sub = mid.slice(1, 3).expect("sub-slice must succeed");
    assert_eq!(sub.as_slice(), b"de");
}

#[test]
fn slice_out_of_bounds_returns_none() {
    let buf = Buffer::from_vec(BufferId::new(13), vec![1, 2, 3]);
    let lease = BufferLease::full(buf);
    assert!(lease.slice(0, 10).is_none(), "end > len must fail");
    assert!(lease.slice(5, 2).is_none(), "start > end must fail");
}

#[test]
fn slice_empty_range_is_allowed() {
    let buf = Buffer::from_vec(BufferId::new(14), vec![1, 2, 3]);
    let lease = BufferLease::full(buf);
    let empty = lease.slice(1, 1).expect("empty slice must succeed");
    assert_eq!(empty.len(), 0);
    assert_eq!(empty.as_slice(), &[] as &[u8]);
}

// ─── PacketView tests ─────────────────────────────────────────────────────────

#[test]
fn packet_view_borrows_slice_correctly() {
    let buf = Buffer::from_vec(BufferId::new(20), b"viewme".to_vec());
    let lease = BufferLease::full(buf);
    let view = lease.view();
    assert_eq!(view.bytes, b"viewme");
    assert_eq!(view.buffer_id, BufferId::new(20));
}

#[test]
fn packet_view_of_sub_lease() {
    let buf = Buffer::from_vec(BufferId::new(21), b"0123456789".to_vec());
    let lease = BufferLease::full(buf);
    let sub = lease.slice(3, 7).unwrap(); // "3456"
    let view = sub.view();
    assert_eq!(view.bytes, b"3456");
}

// ─── SharedBufferDescriptor tests ─────────────────────────────────────────────

#[test]
fn shared_buffer_descriptor_round_trip() {
    let desc = SharedBufferDescriptor {
        buffer_id: BufferId::new(99),
        len: 1024,
    };
    assert_eq!(desc.buffer_id, BufferId::new(99));
    assert_eq!(desc.len, 1024);
    // Clone and equality
    let desc2 = desc;
    assert_eq!(desc, desc2);
}

#[test]
fn shared_descriptor_from_allocate() {
    let mut pool = SimpleBufferPool::default();
    let data = vec![0xABu8; 512];
    let lease = pool.allocate(data.clone());
    let desc = SharedBufferDescriptor {
        buffer_id: lease.buffer_id(),
        len: lease.len(),
    };
    // Retrieve by descriptor
    let retrieved = pool
        .get(desc.buffer_id)
        .expect("must find allocated buffer");
    assert_eq!(retrieved.as_slice(), &data[..]);
}

// ─── SimpleBufferPool tests ───────────────────────────────────────────────────

#[test]
fn pool_allocate_gives_unique_ids() {
    let mut pool = SimpleBufferPool::default();
    let l1 = pool.allocate(vec![1]);
    let l2 = pool.allocate(vec![2]);
    assert_ne!(l1.buffer_id(), l2.buffer_id());
}

#[test]
fn pool_get_returns_correct_data() {
    let mut pool = SimpleBufferPool::default();
    let data = b"payload".to_vec();
    let lease = pool.allocate(data.clone());
    let id = lease.buffer_id();

    let retrieved = pool.get(id).expect("buffer must exist");
    assert_eq!(retrieved.as_slice(), &data[..]);
}

#[test]
fn pool_release_removes_buffer() {
    let mut pool = SimpleBufferPool::default();
    let lease = pool.allocate(vec![9, 8, 7]);
    let id = lease.buffer_id();
    assert!(pool.get(id).is_some());

    pool.release(id);
    assert!(pool.get(id).is_none(), "buffer must be gone after release");
}

#[test]
fn pool_get_after_release_returns_none() {
    let mut pool = SimpleBufferPool::default();
    let l = pool.allocate(vec![0]);
    let id = l.buffer_id();
    pool.release(id);
    assert!(pool.get(id).is_none());
}

// ─── Zero-copy path verification ─────────────────────────────────────────────

/// Verifies that cloning a BufferLease does NOT copy the underlying bytes
/// (both leases share the same Arc<[u8]> backing store).
#[test]
fn buffer_clone_is_arc_shared_no_copy() {
    let payload = vec![42u8; 65536]; // 64 KiB
    let buf = Buffer::from_vec(BufferId::new(50), payload.clone());
    let lease1 = BufferLease::full(buf);
    let lease2 = lease1.clone();

    // Same backing pointer — compare raw pointer of first byte
    assert_eq!(
        lease1.as_slice().as_ptr(),
        lease2.as_slice().as_ptr(),
        "cloned lease must point to the same memory (zero-copy)"
    );
}

/// Verifies that a sub-slice also shares the same allocation.
#[test]
fn sub_slice_shares_backing_allocation() {
    let payload = b"ABCDEFGHIJKLMNOP".to_vec();
    let buf = Buffer::from_vec(BufferId::new(51), payload.clone());
    let full = BufferLease::full(buf);
    let sub = full.slice(4, 12).unwrap(); // "EFGHIJKL"

    // The sub-slice pointer should be full.as_ptr() + 4
    let expected_ptr = unsafe { full.as_slice().as_ptr().add(4) };
    assert_eq!(sub.as_slice().as_ptr(), expected_ptr);
}

// ─── M4.C: BufferLease lifecycle / lease-count audit ─────────────────────────

#[test]
fn pool_outstanding_leases_starts_at_zero() {
    let pool = SimpleBufferPool::new();
    assert_eq!(pool.outstanding_leases(), 0);
}

#[test]
fn pool_outstanding_leases_increments_on_allocate() {
    let mut pool = SimpleBufferPool::new();
    pool.allocate(vec![1]);
    assert_eq!(pool.outstanding_leases(), 1);
    pool.allocate(vec![2]);
    assert_eq!(pool.outstanding_leases(), 2);
}

#[test]
fn pool_outstanding_leases_decrements_on_release() {
    let mut pool = SimpleBufferPool::new();
    let l1 = pool.allocate(vec![1]);
    let l2 = pool.allocate(vec![2]);
    assert_eq!(pool.outstanding_leases(), 2);

    pool.release(l1.buffer_id());
    assert_eq!(pool.outstanding_leases(), 1);

    pool.release(l2.buffer_id());
    assert_eq!(pool.outstanding_leases(), 0);
}

#[test]
fn pool_assert_no_leaks_passes_when_all_released() {
    let mut pool = SimpleBufferPool::new();
    let l = pool.allocate(vec![0xDE, 0xAD, 0xBE, 0xEF]);
    pool.release(l.buffer_id());
    pool.assert_no_leaks(); // must not panic
}

#[test]
#[should_panic(expected = "buffer leak detected")]
fn pool_assert_no_leaks_panics_on_unreleased_buffer() {
    let mut pool = SimpleBufferPool::new();
    let _l = pool.allocate(vec![1, 2, 3]);
    pool.assert_no_leaks(); // must panic: 1 outstanding lease
}

#[test]
fn pool_release_unknown_id_does_not_underflow() {
    let mut pool = SimpleBufferPool::new();
    // Release a buffer id that was never allocated — must not underflow counter
    pool.release(BufferId::new(9999));
    assert_eq!(pool.outstanding_leases(), 0);
}

#[test]
fn pool_get_does_not_affect_lease_count() {
    let mut pool = SimpleBufferPool::new();
    let l = pool.allocate(vec![42]);
    let id = l.buffer_id();
    assert_eq!(pool.outstanding_leases(), 1);

    // Repeated get() calls must not inflate the counter
    let _ = pool.get(id);
    let _ = pool.get(id);
    assert_eq!(pool.outstanding_leases(), 1);

    pool.release(id);
    assert_eq!(pool.outstanding_leases(), 0);
}

#[test]
fn full_allocate_use_release_cycle() {
    let mut pool = SimpleBufferPool::new();

    // Allocate several buffers, verify data, then release all
    let payloads: Vec<Vec<u8>> = (0..5).map(|i| vec![i; 16]).collect();
    let leases: Vec<_> = payloads.iter().map(|p| pool.allocate(p.clone())).collect();

    assert_eq!(pool.outstanding_leases(), 5);

    for (i, lease) in leases.iter().enumerate() {
        assert_eq!(lease.as_slice(), &payloads[i][..]);
    }

    for lease in &leases {
        pool.release(lease.buffer_id());
    }

    pool.assert_no_leaks(); // zero leaks after full cycle
}