test: treat non-wake readiness as a failure in wake assertions

The old assertions required poll to report nothing, but the shared
helpers only looked for readable input, so a hangup or a bad descriptor
could satisfy a test that expects no wake. Reject any other readiness in
both helpers and pin that with a hung-up pipe.
This commit is contained in:
devmobasa committed 2026-10-30 22:54:45 +01:00
1 parent b7bfbd6df6
commit 5966f2d7b8
2 files changed
+29 -3

No files matched your search

+19 -1
View File
@@ -142,6 +142,8 @@ impl RuntimeWakeSource {
} }
/// Reports whether a wake arrives within `timeout`, without draining it. /// Reports whether a wake arrives within `timeout`, without draining it.
/// Any other readiness is an error, so a broken descriptor cannot pass for
/// silence in a test that expects no wake.
#[cfg(test)] #[cfg(test)]
pub(crate) fn is_readable_within(&self, timeout: Duration) -> io::Result<bool> { pub(crate) fn is_readable_within(&self, timeout: Duration) -> io::Result<bool> {
let readable = poll_with_retry(Some(timeout), |timeout_ms| { let readable = poll_with_retry(Some(timeout), |timeout_ms| {
@@ -149,8 +151,14 @@ impl RuntimeWakeSource {
if poll_for_ms(&mut pollfds, timeout_ms)? == 0 { if poll_for_ms(&mut pollfds, timeout_ms)? == 0 {
return Ok(None); return Ok(None);
} }
Ok(Some(pollfds[0].revents().contains(PollFlags::IN))) validate_poll_readiness(
pollfds[0].revents(),
"runtime wake",
TerminalReadinessPolicy::Reject,
)
.map(Some)
})?; })?;
Ok(readable == Some(true)) Ok(readable == Some(true))
} }
} }
@@ -194,6 +202,7 @@ mod tests {
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::{fs, thread}; use std::{fs, thread};
use rustix::pipe;
use rustix::process::Pid; use rustix::process::Pid;
use rustix::thread as rustix_thread; use rustix::thread as rustix_thread;
@@ -274,6 +283,15 @@ mod tests {
assert!(!source.drain().unwrap()); assert!(!source.drain().unwrap());
} }
#[test]
fn readiness_without_a_wake_is_an_error_rather_than_silence() {
let (read, write) = pipe::pipe().unwrap();
drop(write);
let source = RuntimeWakeSource { fd: Arc::new(read) };
assert!(source.is_readable_within(Duration::ZERO).is_err());
}
#[test] #[test]
fn wait_readable_times_out_without_a_wake() { fn wait_readable_times_out_without_a_wake() {
let source = RuntimeWakeSource::try_new().unwrap(); let source = RuntimeWakeSource::try_new().unwrap();
+10 -2
View File
@@ -10,11 +10,19 @@ use crate::{env_vars, test_env, test_temp};
use super::*; use super::*;
/// Polls for input. Any other readiness fails the test, so a broken descriptor
/// cannot pass for silence.
fn is_readable(fd: BorrowedFd<'_>, timeout: Duration) -> bool { fn is_readable(fd: BorrowedFd<'_>, timeout: Duration) -> bool {
let mut pollfds = [PollFd::from_borrowed_fd(fd, PollFlags::IN)]; let mut pollfds = [PollFd::from_borrowed_fd(fd, PollFlags::IN)];
let timeout = Timespec::try_from(timeout).unwrap(); let timeout = Timespec::try_from(timeout).unwrap();
event::poll(&mut pollfds, Some(&timeout)).unwrap() == 1 let ready = event::poll(&mut pollfds, Some(&timeout)).unwrap();
&& pollfds[0].revents().contains(PollFlags::IN) let revents = pollfds[0].revents();
assert!(
ready == 0 || revents == PollFlags::IN,
"unexpected readiness {:#x}",
revents.bits()
);
ready == 1
} }
#[test] #[test]