use super::*;
use std::os::fd::AsRawFd as _;
use rustix::event::{EventfdFlags, eventfd};
fn pending_fence_fd() -> OwnedFd {
eventfd(0, EventfdFlags::CLOEXEC).expect("eventfd")
}
fn signaled_fence_fd() -> OwnedFd {
eventfd(1, EventfdFlags::CLOEXEC).expect("eventfd")
}
#[test]
fn empty_fence_is_invalid_and_unusable() {
let fence = SyncFence::new();
assert!(!fence.is_valid());
assert!(fence.as_fd().is_none());
assert!(matches!(fence.wait(Duration::ZERO), Err(SyncError::Empty)));
let mut fence = SyncFence::new();
assert!(matches!(
fence.merge(SyncFence::new()),
Err(SyncError::Empty)
));
}
#[test]
fn import_dups_so_the_original_can_close() {
let original = pending_fence_fd();
let fence = SyncFence::import(original.as_fd()).expect("import");
assert!(fence.is_valid());
let duped_raw = fence.as_fd().expect("fd").as_raw_fd();
assert_ne!(
duped_raw,
original.as_raw_fd(),
"import must duplicate, not alias"
);
drop(original);
assert!(fence.is_valid());
assert!(matches!(
fence.wait(Duration::from_millis(1)),
Err(SyncError::TimedOut)
));
}
#[test]
fn move_transfers_the_descriptor_intact() {
let source = SyncFence::import(pending_fence_fd().as_fd()).expect("import");
let raw = source.as_fd().expect("fd").as_raw_fd();
let moved = source; assert!(moved.is_valid());
assert_eq!(moved.as_fd().expect("fd").as_raw_fd(), raw);
let owned = moved.into_owned_fd().expect("descriptor released");
assert_eq!(owned.as_raw_fd(), raw);
}
#[test]
fn wait_on_signaled_fence_succeeds() {
let fence = SyncFence::import(signaled_fence_fd().as_fd()).expect("import");
fence
.wait(Duration::from_secs(1))
.expect("a signaled fence must not time out");
}
#[test]
fn wait_times_out_on_pending_fence() {
let fence = SyncFence::import(pending_fence_fd().as_fd()).expect("import");
assert!(matches!(
fence.wait(Duration::from_millis(10)),
Err(SyncError::TimedOut)
));
}
#[test]
fn zero_timeout_polls_without_blocking() {
let signaled = SyncFence::import(signaled_fence_fd().as_fd()).expect("import");
signaled.wait(Duration::ZERO).expect("already readable");
let pending = SyncFence::import(pending_fence_fd().as_fd()).expect("import");
assert!(matches!(
pending.wait(Duration::ZERO),
Err(SyncError::TimedOut)
));
}
#[test]
fn repeated_import_and_drop_does_not_leak() {
let base = pending_fence_fd();
let import_and_drop = |times: usize| {
for _ in 0..times {
let fence = SyncFence::import(base.as_fd()).expect("import");
assert!(fence.is_valid());
drop(fence);
}
};
import_and_drop(16);
let after_few = open_fd_count();
import_and_drop(240);
let after_many = open_fd_count();
let growth = after_many.saturating_sub(after_few);
assert!(
growth < 16,
"240 further import/drop cycles grew the fd table by {growth}; \
a per-import leak would grow it by 240"
);
}
#[test]
fn into_owned_fd_transfers_ownership() {
let fence = SyncFence::import(pending_fence_fd().as_fd()).expect("import");
let raw = fence.as_fd().expect("fd").as_raw_fd();
let owned = fence.into_owned_fd().expect("some");
assert_eq!(owned.as_raw_fd(), raw);
let again = SyncFence::import(owned.as_fd()).expect("still a live descriptor");
assert!(again.is_valid());
}
#[test]
fn from_owned_adopts_without_duplicating() {
let fd = pending_fence_fd();
let raw = fd.as_raw_fd();
let fence = SyncFence::from_owned(fd);
assert_eq!(
fence.as_fd().expect("fd").as_raw_fd(),
raw,
"adopted, not duplicated"
);
}
fn open_fd_count() -> usize {
std::fs::read_dir("/proc/self/fd")
.expect("/proc/self/fd")
.count()
}
#[test]
fn each_error_converts_to_the_references_errno() {
use rustix::io::Errno;
let errno = |error: SyncError| std::io::Error::from(error).raw_os_error();
assert_eq!(errno(SyncError::Empty), Some(Errno::BADF.raw_os_error()));
assert_eq!(
errno(SyncError::MergeReturnedNothing),
Some(Errno::BADF.raw_os_error())
);
assert_eq!(
errno(SyncError::TimedOut),
Some(Errno::TIMEDOUT.raw_os_error())
);
assert_eq!(
errno(SyncError::Io(Errno::MFILE)),
Some(Errno::MFILE.raw_os_error())
);
assert_eq!(
std::io::Error::from(SyncError::TimedOut).kind(),
std::io::ErrorKind::TimedOut,
"and the kind a Rust caller matches on is unchanged"
);
}