//! Byte-ring guarantees (behavior invariants, not implementation shape). //! //! Written before the implementation: every test here is failing until //! `byte_ring` lands. Each test names the property it defends from the //! protocol list in `src/byte_ring.rs`. #![cfg(target_os = "linux")] use data_plane::arena::{ArenaConfig, ArenaManager, NodeId}; use data_plane::byte_ring::{ self, ByteRingSpec, FlowError, HeaderError, RecordKind, Role, attach, install, }; fn arena() -> ArenaManager { ArenaManager::boot(ArenaConfig { node_id: NodeId(10), reservation_ceiling: 1 << 20, base_alignment: 64, }) .expect("test arena must boot") } fn spec(capacity: u64, generation: u64) -> ByteRingSpec { ByteRingSpec { capacity, generation, alignment: 64, request_id: 1, } } fn installed(capacity: u64, generation: u64) -> (ArenaManager, byte_ring::RingHandle) { let mut arena = arena(); let handle = install(&mut arena, spec(capacity, generation)).expect("ring must install"); (arena, handle) } fn scribble_u64(arena: &ArenaManager, handle: &byte_ring::RingHandle, off: u64, value: u64) { arena .write_arena(handle.offset + off, &value.to_le_bytes()) .expect("scribble"); } /// Endpoints must be thread-safe enough to hand one side to another thread /// (property P1's concurrency test depends on this). #[test] fn endpoints_are_send() { fn assert_send() {} assert_send::(); } // ─── install ───────────────────────────────────────────────────────────────── /// B10-style: after `install` returns, the header is fully written. #[test] fn install_writes_valid_header_and_zeroed_data() { use data_plane::byte_ring::{ OFF_CAPACITY, OFF_COMMIT, OFF_CONSUME, OFF_GENERATION, OFF_MAGIC, OFF_VERSION, RING_MAGIC, RING_VERSION, }; let (arena, handle) = installed(4096, 7); assert_eq!(handle.capacity, 4096); assert_eq!(handle.generation, 7); let page = arena.read_arena(handle.offset, 128).expect("read header"); let u32_at = |off: usize| u32::from_le_bytes(page[off..off + 4].try_into().unwrap()); let u16_at = |off: usize| u16::from_le_bytes(page[off..off + 2].try_into().unwrap()); let u64_at = |off: usize| u64::from_le_bytes(page[off..off + 8].try_into().unwrap()); assert_eq!(u32_at(OFF_MAGIC as usize), RING_MAGIC); assert_eq!(u16_at(OFF_VERSION as usize), RING_VERSION); assert_eq!(u64_at(OFF_CAPACITY as usize), 4096); assert_eq!(u64_at(OFF_GENERATION as usize), 7); assert_eq!(u64_at(OFF_COMMIT as usize), 0); assert_eq!(u64_at(OFF_CONSUME as usize), 0); assert!(page[40..128].iter().all(|&b| b == 0), "reserved bytes"); let data = arena .read_arena(handle.offset + 128, 64) .expect("read data"); assert!(data.iter().all(|&b| b == 0), "fresh data region is zero"); } #[test] fn install_rejects_bad_specs() { let mut arena = arena(); assert!(matches!( install(&mut arena, spec(0, 1)).unwrap_err(), byte_ring::InstallError::ZeroCapacity )); assert!(matches!( install(&mut arena, spec(64, 0)).unwrap_err(), byte_ring::InstallError::ZeroGeneration )); assert!(matches!( install(&mut arena, spec(1 << 21, 1)).unwrap_err(), byte_ring::InstallError::LeaseRejected(_) )); } // ─── attach: untrusted-header containment (P5) ────────────────────────────── type CorruptHeaderCase = (Option<(u64, u64)>, (u64, u64), HeaderError); #[test] fn attach_rejects_corrupt_headers_without_trusting_them() { let cases: Vec = vec![ // (optional pre-scribble, (field, value), expected) ( None, (byte_ring::OFF_MAGIC, 0xDEAD_BEEF), HeaderError::BadMagic { found: 0xDEAD_BEEF }, ), ( None, (byte_ring::OFF_VERSION, 2), HeaderError::UnsupportedVersion { found: 2, supported: 1, }, ), ( None, (byte_ring::OFF_CAPACITY, 999), HeaderError::CapacityMismatch { header: 999, handle: 512, }, ), ( None, (byte_ring::OFF_GENERATION, 4), HeaderError::GenerationMismatch { header: 4, handle: 3, }, ), ( Some((byte_ring::OFF_CONSUME, 10)), (byte_ring::OFF_COMMIT, 5), HeaderError::CommitBelowConsume { commit: 5, consume: 10, }, ), ( None, (byte_ring::OFF_COMMIT, 600), HeaderError::ReadableExceedsCapacity { commit: 600, consume: 0, capacity: 512, }, ), ]; for (pre, (field, value), expected) in cases { let (fresh_arena, fresh_handle) = installed(512, 3); if let Some((pre_field, pre_value)) = pre { scribble_u64(&fresh_arena, &fresh_handle, pre_field, pre_value); } scribble_u64(&fresh_arena, &fresh_handle, field, value); let error = attach(&fresh_arena, fresh_handle, Role::Producer) .expect_err("corrupt header must fail attach"); assert_eq!( error, byte_ring::AttachError::Header(expected), "field {field} = {value}" ); } } // ─── movement: exactly-once, in order (P1, P2) ─────────────────────────────── #[test] fn basic_round_trip_preserves_bytes() { let (arena, handle) = installed(4096, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); producer .send_record(RecordKind::Data, b"weights-bytes") .expect("send"); let received = consumer.recv_record().expect("recv"); assert_eq!( received, Some((RecordKind::Data, b"weights-bytes".to_vec())) ); assert_eq!(consumer.recv_record().expect("recv empty"), None); } #[test] fn wraparound_preserves_stream_exactly() { // Capacity 128 forces many wraps; move 16 KiB through in odd chunks. let (arena, handle) = installed(128, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); let expected: Vec = (0..16_u32 * 1024).map(|i| (i % 251) as u8).collect(); let mut sent = 0; let mut received = Vec::new(); // Chunk sizes chosen to straddle the capacity and force partial state; // every chunk must satisfy 5-byte record header + chunk <= 128. let chunks = [3usize, 123, 1, 100, 65, 64, 122, 17]; let mut chunk_index = 0; while received.len() < expected.len() { let chunk = chunks[chunk_index % chunks.len()]; chunk_index += 1; let end = (sent + chunk).min(expected.len()); // Producer sends what fits, drains when the ring is full. while producer .send_record(RecordKind::Data, &expected[sent..end]) .is_err() { match consumer.recv_record().expect("recv") { Some((RecordKind::Data, bytes)) => received.extend_from_slice(&bytes), Some((kind, _)) => panic!("unexpected record kind {kind:?}"), None => panic!("deadlock: producer blocked, consumer empty"), } } sent = end; while let Some((RecordKind::Data, bytes)) = consumer.recv_record().expect("recv") { received.extend_from_slice(&bytes); } } assert_eq!(received, expected, "byte stream must survive wraparound"); } #[test] fn concurrent_producer_consumer_delivers_exactly_once() { use std::thread::scope; let (arena, handle) = installed(1024, 1); let expected: Vec<(RecordKind, Vec)> = { let mut rng = 0x5357_4752_1111_u64; // xorshift, deterministic let mut records = Vec::new(); let mut total = 0; while total < 8 * 1024 { let len = ((rng & 0xFF) as usize).max(1); let bytes: Vec = (0..len).map(|i| ((total + i) % 251) as u8).collect(); total += len; rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; records.push((RecordKind::Data, bytes)); } records.push((RecordKind::Eof, Vec::new())); records }; let producer_handle = handle; let consumer_handle = handle; let producer_records = &expected; let received: parking_lot::Mutex)>> = parking_lot::Mutex::new(Vec::new()); let mut producer = attach(&arena, producer_handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, consumer_handle, Role::Consumer).expect("consumer"); scope(|s| { let producer_thread = s.spawn(move || { for (kind, bytes) in producer_records { // Retry until space frees up: the consumer runs concurrently. while producer.send_record(*kind, bytes).is_err() {} } }); let consumer_records = &received; let consumer_thread = s.spawn(move || { loop { match consumer.recv_record().expect("recv") { Some((RecordKind::Eof, _)) => break, Some(record) => consumer_records.lock().push(record), None => continue, // spin: sleep discipline is the binding slice's concern } } }); producer_thread.join().expect("producer thread"); consumer_thread.join().expect("consumer thread"); }); let got = received.into_inner(); assert_eq!( got, expected[..expected.len() - 1], "records must arrive exactly once, in order, under concurrency" ); } // ─── backpressure (P7) ─────────────────────────────────────────────────────── #[test] fn reserve_reports_exact_free_space_and_recovers() { let (arena, handle) = installed(128, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); let first = producer.reserve(100).expect("reserve fits"); producer.write(&first, &[7; 100]).expect("write"); producer.commit(first).expect("commit"); assert_eq!( producer.reserve(50).unwrap_err(), FlowError::InsufficientSpace { requested: 50, free: 28 } ); // Bytes are readable, intact, and consuming frees the space again. assert_eq!(consumer.readable().expect("readable"), 100); let bytes = consumer.read(100).expect("read"); assert!(bytes.iter().all(|&b| b == 7)); consumer.consume(100).expect("consume"); assert_eq!(producer.reserve(50).expect("reserve recovers").len, 50); } // ─── completion semantics (P9) ─────────────────────────────────────────────── #[test] fn records_delimit_completion_distinctly() { let (arena, handle) = installed(4096, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); producer.send_record(RecordKind::Data, b"a").expect("send"); producer.send_record(RecordKind::Data, b"b").expect("send"); producer.send_record(RecordKind::Eof, b"").expect("send"); assert_eq!( consumer.recv_record().expect("recv"), Some((RecordKind::Data, b"a".to_vec())) ); assert_eq!( consumer.recv_record().expect("recv"), Some((RecordKind::Data, b"b".to_vec())) ); assert_eq!( consumer.recv_record().expect("recv"), Some((RecordKind::Eof, Vec::new())) ); assert_eq!(consumer.recv_record().expect("post-eof"), None); // Fault is a distinct terminal, not a second EOF. producer .send_record(RecordKind::Fault, b"reason") .expect("send"); assert_eq!( consumer.recv_record().expect("recv fault"), Some((RecordKind::Fault, b"reason".to_vec())) ); } #[test] fn torn_records_stay_invisible_until_committed() { let (arena, handle) = installed(512, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); // Frame the record manually so only the commit is withheld. let mut framed = Vec::with_capacity(5 + 64); framed.push(1_u8); // RecordKind::Data framed.extend_from_slice(&64_u32.to_le_bytes()); framed.extend_from_slice(&[9; 64]); let reservation = producer.reserve(69).expect("reserve"); producer.write(&reservation, &framed).expect("write"); // Deliberately do NOT commit. assert_eq!(consumer.readable().expect("readable"), 0); assert_eq!(consumer.recv_record().expect("torn invisible"), None); producer.commit(reservation).expect("commit"); assert_eq!(consumer.readable().expect("readable"), 69); let (kind, bytes) = consumer.recv_record().expect("recv").unwrap(); assert_eq!(kind, RecordKind::Data); assert!(bytes.iter().all(|&b| b == 9)); } // ─── generation fence (P10) ────────────────────────────────────────────────── #[test] fn stale_reservations_are_rejected_after_generation_change() { let (arena, handle) = installed(512, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let reservation = producer.reserve(32).expect("reserve"); // The ring is replaced under us: generation moves on. scribble_u64(&arena, &handle, byte_ring::OFF_GENERATION, 2); assert_eq!( producer.commit(reservation).unwrap_err(), FlowError::StaleReservation { reservation: 1, ring: 2 } ); } // ─── role enforcement (P3) ─────────────────────────────────────────────────── #[test] fn wrong_role_operations_are_rejected() { let (arena, handle) = installed(512, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); assert_eq!( consumer.reserve(8).unwrap_err(), FlowError::RoleViolation { operation: "reserve", role: Role::Consumer } ); assert_eq!( consumer .commit(byte_ring::Reservation { start: 0, len: 0, generation: 1, }) .unwrap_err(), FlowError::RoleViolation { operation: "commit", role: Role::Consumer } ); assert_eq!( producer.consume(8).unwrap_err(), FlowError::RoleViolation { operation: "consume", role: Role::Producer } ); assert_eq!( producer.readable().unwrap_err(), FlowError::RoleViolation { operation: "readable", role: Role::Producer } ); } // ─── mid-protocol revalidation (P5) ────────────────────────────────────────── #[test] fn operations_revalidate_cursors_and_never_panic() { let (arena, handle) = installed(512, 1); let producer = attach(&arena, handle, Role::Producer).expect("producer"); let consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); // Corrupt the cursors mid-protocol (consume beyond commit). scribble_u64(&arena, &handle, byte_ring::OFF_CONSUME, 5); assert_eq!( producer.reserve(8).unwrap_err(), FlowError::Corrupt(HeaderError::CommitBelowConsume { commit: 0, consume: 5 }) ); assert_eq!( consumer.readable().unwrap_err(), FlowError::Corrupt(HeaderError::CommitBelowConsume { commit: 0, consume: 5 }) ); // An impossible readable span is rejected at attach, not crashed on. let (arena2, handle2) = installed(512, 1); scribble_u64(&arena2, &handle2, byte_ring::OFF_COMMIT, 600); assert_eq!( attach(&arena2, handle2, Role::Consumer).unwrap_err(), byte_ring::AttachError::Header(HeaderError::ReadableExceedsCapacity { commit: 600, consume: 0, capacity: 512 }) ); } #[test] fn pinned_record_blocks_capacity_until_release() { let (arena, handle) = installed(32, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); producer .send_record(RecordKind::Data, &[7; 20]) .expect("send"); let view = consumer .peek_record() .expect("peek") .expect("record must be visible"); assert_eq!(view.kind(), RecordKind::Data); assert_eq!(view.len(), 20); assert!(view.spans().1.is_empty()); assert_eq!( producer.reserve(8).unwrap_err(), FlowError::InsufficientSpace { requested: 8, free: 7, } ); drop(view); assert_eq!(producer.reserve(8).expect("capacity released").len, 8); } #[test] fn pinned_record_exposes_wrapped_payload_as_two_spans() { let (arena, handle) = installed(32, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); producer .send_record(RecordKind::Data, &[1; 18]) .expect("first"); assert_eq!( consumer.recv_record().expect("consume first"), Some((RecordKind::Data, vec![1; 18])) ); let expected: Vec = (0..15).collect(); producer .send_record(RecordKind::Data, &expected) .expect("wrapped record"); let view = consumer .peek_record() .expect("peek") .expect("wrapped record visible"); let (first, second) = view.spans(); assert!(!first.is_empty()); assert!(!second.is_empty()); let observed: Vec = first.iter().chain(second).copied().collect(); assert_eq!(observed, expected); } #[test] fn writable_record_is_invisible_until_commit() { let (arena, handle) = installed(64, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); let mut reservation = producer .reserve_record(RecordKind::Data, 17) .expect("reserve record"); let (first, second) = reservation.spans_mut(); for (index, byte) in first.iter_mut().chain(second).enumerate() { *byte = index as u8; } assert!(consumer.peek_record().expect("peek uncommitted").is_none()); reservation.commit().expect("commit"); let view = consumer .peek_record() .expect("peek") .expect("committed record"); let observed: Vec = view .spans() .0 .iter() .chain(view.spans().1) .copied() .collect(); assert_eq!(observed, (0..17).collect::>()); } #[test] fn partial_record_cursor_keeps_capacity_pinned_until_full_release() { let (arena, handle) = installed(32, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); producer .send_record(RecordKind::Data, b"abcdefgh") .expect("record"); let cursor = consumer .record_cursor() .expect("cursor") .expect("record visible"); let mut first = [0xa5; 5]; assert_eq!( consumer .copy_record_range(cursor, 0, &mut first[..3]) .expect("partial prefix"), 3 ); assert_eq!(&first, b"abc\xa5\xa5"); assert!(matches!( producer.reserve(20), Err(FlowError::InsufficientSpace { .. }) )); let mut rest = [0_u8; 8]; assert_eq!( consumer .copy_record_range(cursor, 3, &mut rest) .expect("partial suffix"), 5 ); assert_eq!(&rest[..5], b"defgh"); consumer .release_record_cursor(cursor) .expect("release complete record"); assert!(producer.reserve(20).is_ok()); } #[test] fn partial_record_cursor_hides_payload_wraparound() { let (arena, handle) = installed(32, 1); let mut producer = attach(&arena, handle, Role::Producer).expect("producer"); let mut consumer = attach(&arena, handle, Role::Consumer).expect("consumer"); producer .send_record(RecordKind::Data, &[1; 18]) .expect("advance cursor"); consumer.recv_record().expect("consume advance"); producer .send_record(RecordKind::Data, b"0123456789abcde") .expect("wrapped record"); let cursor = consumer .record_cursor() .expect("cursor") .expect("wrapped record visible"); let mut observed = [0_u8; 15]; assert_eq!( consumer .copy_record_range(cursor, 0, &mut observed) .expect("copy wrapped payload"), observed.len() ); assert_eq!(&observed, b"0123456789abcde"); consumer .release_record_cursor(cursor) .expect("release wrapped record"); }