#![cfg(loom)]
use bytes::Bytes;
use loom::{future::block_on, thread};
use moq_net::{Error, Timestamp, broadcast, cache};
const PREEMPTION_BOUND: usize = 3;
fn model<F: Fn() + Sync + Send + 'static>(f: F) {
let mut builder = loom::model::Builder::new();
builder.preemption_bound.get_or_insert(PREEMPTION_BOUND);
builder.check(f);
}
#[test]
fn frame_reaches_a_parked_subscriber() {
model(|| {
let broadcast = broadcast::Info::new().produce();
let consumer = broadcast.consume();
let track = broadcast.create_track("video", None).expect("create track");
let track_consumer = consumer.track("video").expect("track");
let publisher = thread::spawn(move || {
let mut group = track.append_group().expect("append group");
group
.write_frame(Timestamp::ZERO, Bytes::from_static(b"frame"))
.expect("write frame");
group.finish().expect("finish group");
track.finish().expect("finish track");
});
let mut subscriber = block_on(track_consumer.subscribe(None)).expect("subscribe");
let mut group = block_on(subscriber.recv_group())
.expect("recv group")
.expect("a group was published");
let frame = block_on(group.read_frame()).expect("read frame").expect("a frame");
assert_eq!(frame.payload, Bytes::from_static(b"frame"));
publisher.join().unwrap();
});
}
#[test]
fn back_to_back_groups_arrive_in_order() {
model(|| {
let broadcast = broadcast::Info::new().produce();
let consumer = broadcast.consume();
let track = broadcast.create_track("video", None).expect("create track");
let track_consumer = consumer.track("video").expect("track");
let publisher = thread::spawn(move || {
for _ in 0..2 {
let group = track.append_group().expect("append group");
group.finish().expect("finish group");
}
track.finish().expect("finish track");
});
let mut subscriber = block_on(track_consumer.subscribe(None)).expect("subscribe");
let first = block_on(subscriber.recv_group()).expect("recv").expect("first group");
let second = block_on(subscriber.recv_group()).expect("recv").expect("second group");
assert!(second.sequence > first.sequence, "groups arrived out of order");
publisher.join().unwrap();
});
}
#[test]
fn group_abort_flag_never_leads_the_group_state() {
model(|| {
let broadcast = broadcast::Info::new().produce();
let track = broadcast.create_track("video", None).expect("create track");
let group = track.append_group().expect("append group");
let mut before = track.subscribe(None);
let mut racing = track.subscribe(None);
let mut after = track.subscribe(None);
track.finish().expect("finish track");
assert!(matches!(
before.poll_recv_group(&kio::Waiter::noop()),
std::task::Poll::Ready(Ok(Some(_)))
));
let mut observer = group.consume();
let aborter = thread::spawn(move || group.abort(Error::Cancel).expect("abort group"));
match racing.poll_recv_group(&kio::Waiter::noop()) {
std::task::Poll::Ready(Ok(None)) => assert!(
matches!(
observer.poll_next_frame(&kio::Waiter::noop()),
std::task::Poll::Ready(Err(_))
),
"a scan skipped a group whose own state is not aborted"
),
std::task::Poll::Ready(Ok(Some(_))) => {}
_ => panic!("a finalized track cannot park during the abort race"),
}
aborter.join().unwrap();
assert!(matches!(
after.poll_recv_group(&kio::Waiter::noop()),
std::task::Poll::Ready(Ok(None))
));
});
}
#[test]
fn subscriber_wakes_parked_demand() {
model(|| {
let broadcast = broadcast::Info::new().produce();
let consumer = broadcast.consume();
let track = broadcast.create_track("video", None).expect("create track");
let demand = track.demand();
let subscriber = thread::spawn(move || {
let track_consumer = consumer.track("video").expect("track");
block_on(track_consumer.subscribe(None)).expect("subscribe")
});
block_on(demand.used()).expect("the new subscriber was missed");
drop(subscriber.join().unwrap());
drop(track);
});
}
#[test]
fn concurrent_tracks_drain_a_shared_pool() {
model(|| {
let config = cache::Config::default()
.with_capacity(512)
.with_expiry(cache::DEFAULT_EXPIRY);
let pool = cache::Pool::new(config);
let mut info = broadcast::Info::new();
info.pool = pool.clone();
let broadcast = info.produce();
let handles: Vec<_> = ["video", "audio"]
.into_iter()
.map(|name| {
let track = broadcast.create_track(name, None).expect("create track");
thread::spawn(move || {
let mut group = track.append_group().expect("append group");
group
.write_frame(Timestamp::ZERO, Bytes::from_static(b"0123456789"))
.expect("write frame");
group.finish().expect("finish group");
track.finish().expect("finish track");
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
broadcast.finish();
drop(broadcast);
assert_eq!(pool.used(), 0, "the pool kept a charge after every group was dropped");
});
}
#[test]
fn an_idle_teardown_never_cancels_a_returning_viewer() {
model(|| {
let broadcast = broadcast::Info::new().produce();
let consumer = broadcast.consume();
let track = broadcast.create_track("video", None).expect("create track");
let viewer = thread::spawn(move || consumer.track("video"));
let teardown = track.abort_unused(Error::Cancel);
let looked_up = viewer.join().unwrap();
match looked_up {
Ok(consumer) => {
let subscribing = consumer.subscribe(None).into_inner();
if let std::task::Poll::Ready(res) = subscribing.poll_ok(&kio::Waiter::noop()) {
let mut subscriber = res.expect("subscribing to a track just handed out");
if let std::task::Poll::Ready(Err(err)) = subscriber.poll_recv_group(&kio::Waiter::noop()) {
panic!("the teardown cancelled a track the broadcast had handed out: {err}");
}
}
}
Err(err) => {
assert!(teardown.is_ok(), "a declined teardown refused a viewer");
assert!(matches!(err, Error::NotFound), "unexpected lookup error: {err}");
}
}
});
}
#[test]
fn publisher_drop_resolves_a_parked_subscriber() {
model(|| {
let broadcast = broadcast::Info::new().produce();
let consumer = broadcast.consume();
let track = broadcast.create_track("video", None).expect("create track");
let track_consumer = consumer.track("video").expect("track");
let publisher = thread::spawn(move || drop(track));
let mut subscriber = block_on(track_consumer.subscribe(None)).expect("subscribe");
let _ = block_on(subscriber.recv_group());
publisher.join().unwrap();
});
}