use std::sync::Arc;
use futures::future::BoxFuture;
use velo::streaming::{AnchorManager, FrameTransport};
use velo_ext::{TransportKey, WorkerAddress, WorkerId};
pub struct MockFrameTransport;
impl MockFrameTransport {
pub fn new() -> Self {
Self
}
}
impl FrameTransport for MockFrameTransport {
fn key(&self) -> TransportKey {
TransportKey::new("mock-stream")
}
fn address(&self) -> WorkerAddress {
WorkerAddress::empty()
}
fn bind(
&self,
_anchor_id: u64,
_session_id: u64,
) -> BoxFuture<'_, anyhow::Result<flume::Receiver<Vec<u8>>>> {
Box::pin(async move {
let (_tx, rx) = flume::bounded::<Vec<u8>>(256);
Ok(rx)
})
}
fn connect(
&self,
_peer: WorkerId,
_anchor_id: u64,
_session_id: u64,
) -> BoxFuture<'_, anyhow::Result<flume::Sender<Vec<u8>>>> {
Box::pin(async move {
let (tx, _rx) = flume::bounded::<Vec<u8>>(1);
Ok(tx)
})
}
}
#[allow(dead_code)]
pub async fn make_mock_manager() -> Arc<AnchorManager> {
Arc::new(AnchorManager::new(
WorkerId::from_u64(1),
Arc::new(MockFrameTransport::new()),
))
}
#[macro_export]
macro_rules! run_transport_tests {
($mod_name:ident, $make_manager:expr) => {
mod $mod_name {
use super::*;
use futures::StreamExt;
use std::sync::Arc;
use velo::streaming::{AnchorManager, AttachError, StreamAnchorHandle, StreamFrame};
use velo_ext::WorkerId;
async fn manager() -> Arc<AnchorManager> {
$make_manager
}
#[tokio::test(flavor = "multi_thread")]
async fn test_01_local_round_trip() {
let mgr = manager().await;
assert_eq!(mgr.active_anchor_count(), 0);
let mut anchor = mgr.create_anchor::<u32>();
assert_eq!(mgr.active_anchor_count(), 1);
let handle = anchor.handle();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach must succeed");
for i in 0u32..10 {
sender.send(i).await.expect("send");
}
sender.finalize().expect("finalize");
let mut items = Vec::new();
while let Some(frame) = anchor.next().await {
match frame {
Ok(StreamFrame::Item(v)) => items.push(v),
Ok(StreamFrame::Finalized) => break,
other => panic!("unexpected frame: {:?}", other),
}
}
assert_eq!(items, (0u32..10).collect::<Vec<_>>());
assert!(anchor.next().await.is_none());
assert_eq!(
mgr.active_anchor_count(),
0,
"registry must be empty after finalize drain"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_04_detach_reattach() {
let mgr = manager().await;
let mut anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let sender1 = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("first attach");
for i in 0u32..3 {
sender1.send(i).await.expect("send first batch");
}
let returned_handle = sender1.detach().expect("detach must succeed");
let sender2 = mgr
.attach_stream_anchor::<u32>(returned_handle)
.await
.expect("second attach");
for i in 3u32..6 {
sender2.send(i).await.expect("send second batch");
}
sender2.finalize().expect("finalize");
let mut items = Vec::new();
while let Some(frame) = anchor.next().await {
match frame {
Ok(StreamFrame::Item(v)) => items.push(v),
Ok(StreamFrame::Detached) => { }
Ok(StreamFrame::Finalized) => break,
other => panic!("unexpected frame: {:?}", other),
}
}
assert_eq!(items, vec![0u32, 1, 2, 3, 4, 5]);
assert!(anchor.next().await.is_none());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_05_finalize_closes_stream() {
let mgr = manager().await;
let mut anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach");
for i in 0u32..5 {
sender.send(i).await.expect("send");
}
sender.finalize().expect("finalize");
let mut items = Vec::new();
while let Some(frame) = anchor.next().await {
match frame {
Ok(StreamFrame::Item(v)) => items.push(v),
Ok(StreamFrame::Finalized) => break,
other => panic!("unexpected: {:?}", other),
}
}
assert_eq!(items.len(), 5);
assert!(anchor.next().await.is_none());
assert_eq!(mgr.active_anchor_count(), 0);
let second_attach = mgr.attach_stream_anchor::<u32>(handle).await;
assert!(
matches!(
second_attach,
Err(velo::streaming::AttachError::AnchorNotFound { .. })
),
"anchor must be absent from registry after finalize"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_06_cancel_prevents_attach() {
let mgr = manager().await;
let anchor = mgr.create_anchor::<u32>();
assert_eq!(mgr.active_anchor_count(), 1);
let handle = anchor.handle();
anchor.cancel();
tokio::task::yield_now().await;
assert_eq!(
mgr.active_anchor_count(),
0,
"cancel must remove the anchor from the registry"
);
let result = mgr.attach_stream_anchor::<u32>(handle).await;
assert!(
matches!(
result,
Err(velo::streaming::AttachError::AnchorNotFound { .. })
),
"attach after cancel must return AnchorNotFound"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_08_drop_safety() {
let mgr = manager().await;
let mut anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach");
sender.send(42u32).await.expect("send one item");
drop(sender);
let frame1 = anchor.next().await;
assert!(
matches!(frame1, Some(Ok(StreamFrame::Item(42u32)))),
"first frame must be Item(42), got {:?}",
frame1
);
let frame2 = anchor.next().await;
assert!(
matches!(
frame2,
Some(Err(velo::streaming::StreamError::SenderDropped))
),
"second frame must be Err(SenderDropped) from Dropped sentinel, got {:?}",
frame2
);
assert!(
anchor.next().await.is_none(),
"stream must be exhausted after Dropped"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_12_sentinel_ordering() {
let mgr = manager().await;
let mut anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach");
let send_task = tokio::spawn(async move {
for i in 0u32..1000 {
sender.send(i).await.expect("send");
}
});
let mut items: Vec<u32> = Vec::new();
let mut saw_dropped = false;
let mut saw_item_after_dropped = false;
while let Some(frame) = anchor.next().await {
match frame {
Ok(StreamFrame::Item(v)) => {
if saw_dropped {
saw_item_after_dropped = true;
}
items.push(v);
}
Err(velo::streaming::StreamError::SenderDropped) => {
saw_dropped = true;
break; }
other => panic!("unexpected frame: {:?}", other),
}
}
send_task.await.expect("send task must not panic");
assert_eq!(items.len(), 1000, "must receive exactly 1000 items");
assert!(saw_dropped, "Dropped sentinel must be present");
assert!(
!saw_item_after_dropped,
"no Item frame may follow the Dropped sentinel"
);
assert_eq!(
items,
(0u32..1000).collect::<Vec<_>>(),
"items must be in order"
);
assert!(
anchor.next().await.is_none(),
"stream exhausted after Dropped"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_11_mock_transport_full_cycle() {
let mgr = manager().await;
let mut anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach must succeed");
for i in 0u32..5 {
sender.send(i).await.expect("send");
}
sender.finalize().expect("finalize");
let mut items = Vec::new();
while let Some(frame) = anchor.next().await {
match frame {
Ok(StreamFrame::Item(v)) => items.push(v),
Ok(StreamFrame::Finalized) => break,
other => panic!("unexpected frame: {:?}", other),
}
}
assert_eq!(items, vec![0u32, 1, 2, 3, 4]);
assert!(
anchor.next().await.is_none(),
"stream must yield None after Finalized"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_13_unit_coverage() {
let wid = WorkerId::from_u64(42);
let handle = StreamAnchorHandle::pack(wid, 99);
let (got_wid, got_lid) = handle.unpack();
assert_eq!(got_wid, wid);
assert_eq!(got_lid, 99u64);
let mgr = manager().await;
let a1 = mgr.create_anchor::<u32>();
let a2 = mgr.create_anchor::<u32>();
let (_, lid1) = a1.handle().unpack();
let (_, lid2) = a2.handle().unpack();
assert!(lid2 > lid1, "local IDs must be monotonically increasing");
let anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let _sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("first attach must succeed");
let result = mgr.attach_stream_anchor::<u32>(handle).await;
assert!(
matches!(result, Err(AttachError::AlreadyAttached { .. })),
"second concurrent attach must return AlreadyAttached"
);
}
}
};
}