use std::io;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use ferranet::dummy::{self, DummyChannel};
fn frame(tag: u8, len: usize) -> Vec<u8> {
vec![tag; len]
}
#[test]
fn send_nothing() {
let mut net = dummy::channel().expect("dummy channel");
let sent = net.tx.send_batch(&[]).expect("send empty batch");
assert_eq!(sent, 0);
assert!(net.drain.try_recv().is_none(), "no frame should have been transmitted");
}
#[test]
fn send_one_frame() {
let mut net = dummy::channel().expect("dummy channel");
let f = frame(9, 20);
net.tx.send(&f).expect("send");
assert_eq!(net.drain.try_recv().expect("one frame transmitted"), f);
assert!(net.drain.try_recv().is_none(), "exactly one frame");
}
#[test]
fn send_multiple_frames_in_order() {
let mut net = dummy::channel().expect("dummy channel");
let frames = [frame(0, 20), frame(1, 20), frame(2, 20)];
let refs: Vec<&[u8]> = frames.iter().map(Vec::as_slice).collect();
assert_eq!(net.tx.send_batch(&refs).expect("send batch"), 3);
for i in 0..3u8 {
assert_eq!(net.drain.try_recv().expect("a frame")[0], i);
}
assert!(net.drain.try_recv().is_none());
}
#[test]
fn recv_one_frame() {
let mut net = dummy::channel().expect("dummy channel");
net.inject.inject(frame(7, 20));
let block = net.rx.recv_block().expect("recv");
assert_eq!(block.len(), 1);
let f = block.frames().next().expect("a frame");
assert_eq!(f.data().len(), 20);
assert_eq!(f.data()[0], 7);
}
#[test]
fn recv_multiple_frames_in_order() {
let mut net = dummy::channel().expect("dummy channel");
for i in 0..3u8 {
net.inject.inject(frame(i, 20));
}
for i in 0..3u8 {
let block = net.rx.recv_block().expect("recv");
assert_eq!(block.frames().next().expect("a frame").data()[0], i);
}
}
#[test]
fn round_trip_send_and_receive() {
let mut net = dummy::channel().expect("dummy channel");
net.inject.inject(frame(0xab, 64));
net.tx.send(&frame(0xcd, 64)).expect("send");
let block = net.rx.recv_block().expect("recv");
assert_eq!(block.frames().next().unwrap().data()[0], 0xab);
assert_eq!(net.drain.try_recv().unwrap()[0], 0xcd);
}
#[test]
fn recv_blocks_when_idle_but_connected() {
let DummyChannel { mut rx, inject, .. } = dummy::channel().expect("dummy channel");
let (done_tx, done_rx) = mpsc::channel();
thread::spawn(move || {
let _ = rx.recv_block(); let _ = done_tx.send(());
});
assert!(
matches!(done_rx.recv_timeout(Duration::from_millis(150)), Err(mpsc::RecvTimeoutError::Timeout)),
"recv_block should not return while the network is idle",
);
drop(inject);
}
#[test]
fn recv_propagates_injected_error() {
let mut net = dummy::channel().expect("dummy channel");
net.inject.inject_error(io::Error::other("simulated rx error"));
let err = net.rx.recv_block().expect_err("error should propagate");
assert!(matches!(err, ferranet::Error::Recv(_)), "got {err:?}");
assert_eq!(err.as_io().expect("carries the OS error").to_string(), "simulated rx error");
}
#[test]
fn recv_returns_eof_when_injector_dropped() {
let DummyChannel { mut rx, inject, .. } = dummy::channel().expect("dummy channel");
drop(inject); let err = rx.recv_block().expect_err("closed network should error");
match err {
ferranet::Error::Recv(e) => assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof),
other => panic!("expected UnexpectedEof, got {other:?}"),
}
}
#[test]
fn drains_queued_frames_before_eof() {
let DummyChannel { mut rx, inject, .. } = dummy::channel().expect("dummy channel");
inject.inject(frame(1, 16));
inject.inject(frame(2, 16));
drop(inject);
assert_eq!(rx.recv_block().unwrap().frames().next().unwrap().data()[0], 1);
assert_eq!(rx.recv_block().unwrap().frames().next().unwrap().data()[0], 2);
assert!(matches!(rx.recv_block(), Err(ferranet::Error::Recv(_))));
}
#[test]
fn dummy_interfaces_listed() {
let ifaces = dummy::interfaces();
assert_eq!(ifaces.len(), 3);
assert_eq!(ifaces[0].name, "dummy0");
assert!(ifaces.iter().all(|i| i.mac.is_some()));
}
#[cfg(feature = "tokio")]
mod async_dummy {
use std::time::Duration;
#[tokio::test]
async fn async_round_trip() {
let mut net = ferranet::dummy::channel_async().expect("async dummy channel");
net.inject.inject(b"incoming".to_vec());
let block = net.rx.recv_block().await.expect("recv injected frame");
assert_eq!(block.len(), 1);
assert_eq!(block.frames().next().expect("one frame").data(), b"incoming");
drop(block);
net.tx.send(b"outgoing").await.expect("send frame");
assert_eq!(net.drain.try_recv().expect("one sent frame"), b"outgoing");
}
#[tokio::test]
async fn async_send_batch_counts() {
let mut net = ferranet::dummy::channel_async().expect("async dummy channel");
let n = net.tx.send_batch(&[b"a".as_slice(), b"b".as_slice()]).await.expect("batch");
assert_eq!(n, 2);
assert_eq!(net.drain.len(), 2);
}
#[tokio::test]
async fn async_recv_eof_when_injector_dropped() {
let mut net = ferranet::dummy::channel_async().expect("async dummy channel");
drop(net.inject);
let err = net.rx.recv_block().await.expect_err("closed network is end-of-stream");
assert!(matches!(
err,
ferranet::Error::Recv(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof
));
}
#[tokio::test]
async fn async_recv_cancellation_is_safe() {
let mut net = ferranet::dummy::channel_async().expect("async dummy channel");
{
let fut = net.rx.recv_block();
tokio::pin!(fut);
tokio::select! {
biased;
_ = &mut fut => panic!("nothing was injected yet"),
_ = tokio::time::sleep(Duration::from_millis(50)) => {}
}
}
net.inject.inject(b"after-cancel".to_vec());
let block = tokio::time::timeout(Duration::from_secs(1), net.rx.recv_block())
.await
.expect("recv after a cancelled future must not hang")
.expect("recv frame");
assert_eq!(block.frames().next().expect("one frame").data(), b"after-cancel");
}
}