#![cfg(all(feature = "signal", unix))]
use std::{io, mem::MaybeUninit, ptr, thread, time::Duration};
#[cfg(feature = "helper")]
use std::{process::Command, sync::mpsc};
use futures::{StreamExt, future::poll_immediate};
use libc::c_int;
use ntest::timeout;
use zruntime::{
Local, LocalRuntime, Mode, Runtime, Shared, SharedRuntime,
signal::{Signal, Signals},
};
#[test]
#[timeout(15000)]
fn a_signal_reaches_its_listener_on_a_local_runtime() {
reaches_its_listener::<Local>(Signal::User1, libc::SIGUSR1);
}
#[test]
#[timeout(15000)]
fn a_signal_reaches_its_listener_on_a_shared_runtime() {
reaches_its_listener::<Shared>(Signal::User2, libc::SIGUSR2);
}
fn reaches_its_listener<M>(signal: Signal, raw: c_int)
where
M: Mode,
{
let runtime = Runtime::<M>::new().unwrap();
let mut signals = Signals::new(&runtime, [signal]).unwrap();
assert_eq!(
format!("{signals:?}"),
format!("Signals {{ signals: {{{signal:?}}}, .. }}")
);
raise(raw);
assert_eq!(runtime.block_on(signals.recv()).unwrap(), signal);
raise(raw);
let next = runtime.block_on(signals.next());
assert_eq!(next.expect("the stream never ends").unwrap(), signal);
assert!(nothing_waits(&runtime, &mut signals));
}
#[test]
#[timeout(15000)]
fn every_listener_of_a_signal_gets_it_and_no_later_one() {
let local = LocalRuntime::new().unwrap();
let mut here = Signals::new(&local, [Signal::Hangup]).unwrap();
let shared = SharedRuntime::new().unwrap();
let mut there = Signals::new(&shared, [Signal::Hangup]).unwrap();
raise(libc::SIGHUP);
let mut later = Signals::new(&local, [Signal::Hangup]).unwrap();
let elsewhere = thread::spawn(move || shared.block_on(there.recv()).unwrap());
assert_eq!(local.block_on(here.recv()).unwrap(), Signal::Hangup);
assert_eq!(elsewhere.join().unwrap(), Signal::Hangup);
assert!(nothing_waits(&local, &mut later));
}
#[test]
#[timeout(15000)]
fn signals_that_arrive_together_are_handed_out_once_each() {
let runtime = LocalRuntime::new().unwrap();
let mut both = Signals::new(&runtime, [Signal::Interrupt, Signal::Terminate]).unwrap();
let mut one = Signals::new(&runtime, [Signal::Terminate]).unwrap();
raise(libc::SIGTERM);
raise(libc::SIGINT);
raise(libc::SIGTERM);
let handed_out = [
runtime.block_on(both.recv()).unwrap(),
runtime.block_on(both.recv()).unwrap(),
];
assert!(handed_out.contains(&Signal::Interrupt));
assert!(handed_out.contains(&Signal::Terminate));
assert!(nothing_waits(&runtime, &mut both));
assert_eq!(runtime.block_on(one.recv()).unwrap(), Signal::Terminate);
assert!(nothing_waits(&runtime, &mut one));
}
#[test]
#[timeout(15000)]
fn a_dropped_wait_loses_no_signal() {
let runtime = LocalRuntime::new().unwrap();
let mut signals = Signals::new(&runtime, [Signal::Quit]).unwrap();
assert!(runtime.block_on(poll_immediate(signals.recv())).is_none());
raise(libc::SIGQUIT);
assert_eq!(runtime.block_on(signals.recv()).unwrap(), Signal::Quit);
}
#[cfg(feature = "helper")]
#[test]
#[timeout(15000)]
fn a_task_on_the_helper_gets_its_signal() {
let runtime = SharedRuntime::current().unwrap();
let mut signals = Signals::new(&runtime, [Signal::Child]).unwrap();
let (report, reported) = mpsc::channel();
runtime
.spawn("a listener", async move {
report.send(signals.recv().await.unwrap()).unwrap();
})
.detach();
let status = Command::new("true").status().unwrap();
assert!(status.success());
assert_eq!(reported.recv().unwrap(), Signal::Child);
}
#[test]
#[timeout(15000)]
fn the_handler_from_before_is_put_back_once_nothing_listens() {
let runtime = LocalRuntime::new().unwrap();
set_handler(libc::SIGWINCH, libc::SIG_DFL);
let first = Signals::new(&runtime, [Signal::WindowChange]).unwrap();
let taken_over = handler(libc::SIGWINCH);
assert_ne!(taken_over, libc::SIG_DFL);
assert_ne!(taken_over, libc::SIG_IGN);
let mut second = Signals::new(&runtime, [Signal::WindowChange]).unwrap();
drop(first);
assert_eq!(handler(libc::SIGWINCH), taken_over);
raise(libc::SIGWINCH);
assert_eq!(
runtime.block_on(second.recv()).unwrap(),
Signal::WindowChange
);
drop(second);
assert_eq!(handler(libc::SIGWINCH), libc::SIG_DFL);
set_handler(libc::SIGWINCH, libc::SIG_IGN);
let listening = Signals::new(&runtime, [Signal::WindowChange]).unwrap();
assert_eq!(handler(libc::SIGWINCH), taken_over);
drop(listening);
assert_eq!(handler(libc::SIGWINCH), libc::SIG_IGN);
set_handler(libc::SIGWINCH, libc::SIG_DFL);
let listening = Signals::new(&runtime, [Signal::WindowChange]).unwrap();
set_handler(libc::SIGWINCH, libc::SIG_IGN);
drop(listening);
assert_eq!(handler(libc::SIGWINCH), libc::SIG_IGN);
set_handler(libc::SIGWINCH, libc::SIG_DFL);
}
fn raise(raw: c_int) {
let raised = unsafe { libc::raise(raw) };
assert_eq!(raised, 0, "{}", io::Error::last_os_error());
}
fn nothing_waits<M>(runtime: &Runtime<M>, signals: &mut Signals<M>) -> bool
where
M: Mode,
{
let waited = runtime.timeout(Duration::from_millis(50), signals.recv());
runtime.block_on(waited).is_err()
}
fn handler(raw: c_int) -> libc::sighandler_t {
let mut action = MaybeUninit::<libc::sigaction>::uninit();
let read = unsafe { libc::sigaction(raw, ptr::null(), action.as_mut_ptr()) };
assert_eq!(read, 0, "{}", io::Error::last_os_error());
unsafe { action.assume_init() }.sa_sigaction
}
fn set_handler(raw: c_int, handler: libc::sighandler_t) {
let previous = unsafe { libc::signal(raw, handler) };
assert_ne!(previous, libc::SIG_ERR, "{}", io::Error::last_os_error());
}