Files
interchange/tests/loom.rs
2024-06-14 09:22:18 +02:00

124 lines
3.6 KiB
Rust

#[cfg(loom)]
use loom::thread;
#[cfg(not(loom))]
use std::thread;
use std::mem::drop;
use interchange::{Channel, Requester, Responder};
#[cfg(loom)]
use std::sync::atomic::Ordering::Acquire;
use std::sync::atomic::{AtomicBool, Ordering::Release};
static BRANCHES_USED: [AtomicBool; 14] = {
#[allow(clippy::declare_interior_mutable_const)]
const ATOMIC_BOOL_INIT: AtomicBool = AtomicBool::new(false);
[ATOMIC_BOOL_INIT; 14]
};
#[cfg(loom)]
#[test]
fn loom_interchange() {
loom::model(test_function);
// Verify that the model explored the expected branches
for b in &BRANCHES_USED {
assert!(b.load(Acquire));
}
}
// This is tested even with the standard library to ensure that the Send/Sync traits are implemented as necessary
// Loom's thread::spawn doesn't require the function to be `Send`
#[cfg_attr(not(loom), test)]
fn test_function() {
// thread closures must be 'static
let channel = Box::leak(Box::new(Channel::new()));
let dropper = unsafe { Box::from_raw(channel as _) };
let (rq, rp) = channel.split().unwrap();
let handle1 = thread::spawn(move || requester_thread(rq));
let handle2 = thread::spawn(move || responder_thread(rp));
let res1 = handle1.join();
let res2 = handle2.join();
// Avoid memory leak
drop(dropper);
res1.unwrap();
res2.unwrap();
}
fn requester_thread(mut requester: Requester<'static, u64, u64>) -> Option<()> {
requester.request(53).unwrap();
match requester.cancel() {
Ok(Some(53) | None) => {
BRANCHES_USED[0].store(true, Release);
return None;
}
Ok(_) => panic!("Invalid state"),
Err(_) => {
BRANCHES_USED[1].store(true, Release);
}
}
requester
.with_response(|r| {
BRANCHES_USED[2].store(true, Release);
assert_eq!(*r, 63)
})
.ok()
.or_else(|| {
BRANCHES_USED[3].store(true, Release);
None
})?;
requester.with_response(|r| assert_eq!(*r, 63)).unwrap();
requester.take_response().unwrap();
requester.with_request_mut(|r| *r = 51).unwrap();
requester.send_request().unwrap();
thread::yield_now();
match requester.cancel() {
Ok(Some(51) | None) => BRANCHES_USED[4].store(true, Release),
Ok(_) => panic!("Invalid state"),
Err(_) => {
BRANCHES_USED[5].store(true, Release);
match requester.take_response() {
Some(i) => {
assert_eq!(i, 79);
BRANCHES_USED[6].store(true, Release);
}
None => BRANCHES_USED[7].store(true, Release),
}
}
}
BRANCHES_USED[8].store(true, Release);
None
}
fn responder_thread(mut responder: Responder<'static, u64, u64>) -> Option<()> {
let req = responder.take_request().or_else(|| {
BRANCHES_USED[9].store(true, Release);
None
})?;
assert_eq!(req, 53);
responder.respond(req + 10).ok().or_else(|| {
BRANCHES_USED[10].store(true, Release);
None
})?;
thread::yield_now();
responder
.with_request(|r| {
BRANCHES_USED[11].store(true, Release);
assert_eq!(*r, 51)
})
.map(|_| assert!(responder.with_request(|_| {}).is_err()))
.or_else(|_| {
BRANCHES_USED[12].store(true, Release);
responder.acknowledge_cancel()
})
.ok()?;
responder.with_response_mut(|r| *r = 79).ok();
responder.send_response().ok();
BRANCHES_USED[13].store(true, Release);
None
}