246 lines
6.5 KiB
Rust
246 lines
6.5 KiB
Rust
/* Unit tests for signals module */
|
|
|
|
use redshift_rebooted::signals;
|
|
use serial_test::serial;
|
|
|
|
/* Install handlers once for all tests */
|
|
#[ctor::ctor]
|
|
fn init() {
|
|
let _ = signals::install_handlers();
|
|
}
|
|
|
|
#[test]
|
|
fn test_install_handlers() {
|
|
/* Should successfully install signal handlers */
|
|
let result = signals::install_handlers();
|
|
assert!(result.is_ok(), "Should install handlers without error (can be called multiple times)");
|
|
}
|
|
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_is_exiting_initial_state() {
|
|
/* Clear state first */
|
|
signals::clear_exiting();
|
|
|
|
/* Should not be exiting */
|
|
assert!(!signals::is_exiting(), "Should not be exiting after clear");
|
|
}
|
|
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_check_toggle_initial_state() {
|
|
/* Clear state first */
|
|
signals::check_toggle();
|
|
|
|
/* Should not have toggle requested after clearing */
|
|
assert!(!signals::check_toggle(), "Should not have toggle requested after clearing");
|
|
}
|
|
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_check_toggle_clears_flag() {
|
|
/* check_toggle should return true only once per signal */
|
|
/* Note: This test can't easily set the flag without sending actual signals,
|
|
so this is more of a behavior documentation test */
|
|
|
|
/* Clear state first */
|
|
signals::check_toggle();
|
|
|
|
/* First check - should be false (no signal sent) */
|
|
let first = signals::check_toggle();
|
|
/* Second check - should still be false */
|
|
let second = signals::check_toggle();
|
|
|
|
assert_eq!(first, second, "Multiple checks without signal should return same value");
|
|
}
|
|
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_clear_exiting() {
|
|
/* clear_exiting should reset the exiting flag */
|
|
signals::clear_exiting();
|
|
|
|
/* After clearing, should not be exiting */
|
|
assert!(!signals::is_exiting(), "Should not be exiting after clear");
|
|
}
|
|
|
|
#[test]
|
|
fn test_signal_handlers_thread_safe() {
|
|
/* Signal handlers use Arc<AtomicBool> which is thread-safe */
|
|
use std::thread;
|
|
|
|
signals::install_handlers().expect("Failed to install handlers");
|
|
|
|
/* Spawn multiple threads checking signals */
|
|
let handles: Vec<_> = (0..10)
|
|
.map(|_| {
|
|
thread::spawn(|| {
|
|
for _ in 0..100 {
|
|
let _ = signals::is_exiting();
|
|
let _ = signals::check_toggle();
|
|
}
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
/* All threads should complete without issue */
|
|
for handle in handles {
|
|
handle.join().expect("Thread panicked");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_install_handlers() {
|
|
/* Installing handlers multiple times should work */
|
|
for _ in 0..3 {
|
|
let result = signals::install_handlers();
|
|
assert!(result.is_ok(), "Multiple installs should succeed");
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
#[serial(signals)] /* Use a specific key to ensure proper serialization */
|
|
fn test_actual_sigusr1_signal() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
/* This test is potentially flaky due to signal delivery timing.
|
|
* We retry a few times to reduce false failures. */
|
|
let mut success = false;
|
|
|
|
for attempt in 0..3 {
|
|
/* Clear any previous state */
|
|
signals::clear_toggle();
|
|
|
|
/* Send SIGUSR1 to self */
|
|
unsafe {
|
|
libc::kill(std::process::id() as i32, libc::SIGUSR1);
|
|
}
|
|
|
|
/* Poll for the signal with timeout - peek without clearing */
|
|
let mut detected = false;
|
|
for _ in 0..30 { /* Try for up to 300ms */
|
|
thread::sleep(Duration::from_millis(10));
|
|
if signals::is_toggle_requested() {
|
|
detected = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if detected {
|
|
/* Clear and verify */
|
|
assert!(signals::check_toggle(), "Should still be set");
|
|
assert!(!signals::check_toggle(), "Toggle flag should be cleared after check");
|
|
success = true;
|
|
break;
|
|
}
|
|
|
|
if attempt < 2 {
|
|
eprintln!("Signal delivery attempt {} failed, retrying...", attempt + 1);
|
|
thread::sleep(Duration::from_millis(50));
|
|
}
|
|
}
|
|
|
|
assert!(success, "Should detect SIGUSR1 within 3 attempts");
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_actual_sigint_signal() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
|
|
/* Clear any previous state */
|
|
signals::clear_exiting();
|
|
|
|
/* Send SIGINT to self */
|
|
unsafe {
|
|
libc::kill(std::process::id() as i32, libc::SIGINT);
|
|
}
|
|
|
|
/* Give signal time to be processed */
|
|
thread::sleep(Duration::from_millis(100));
|
|
|
|
/* Should detect exit signal */
|
|
assert!(signals::is_exiting(), "Should detect SIGINT");
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_actual_sigterm_signal() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
|
|
/* Clear any previous state */
|
|
signals::clear_exiting();
|
|
|
|
/* Send SIGTERM to self */
|
|
unsafe {
|
|
libc::kill(std::process::id() as i32, libc::SIGTERM);
|
|
}
|
|
|
|
/* Give signal time to be processed */
|
|
thread::sleep(Duration::from_millis(100));
|
|
|
|
/* Should detect exit signal */
|
|
assert!(signals::is_exiting(), "Should detect SIGTERM");
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_sigint_and_sigterm_both_set_exiting() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
|
|
/* Test SIGINT */
|
|
signals::clear_exiting();
|
|
unsafe {
|
|
libc::kill(std::process::id() as i32, libc::SIGINT);
|
|
}
|
|
thread::sleep(Duration::from_millis(100));
|
|
assert!(signals::is_exiting(), "SIGINT should set exiting");
|
|
|
|
/* Test SIGTERM */
|
|
signals::clear_exiting();
|
|
unsafe {
|
|
libc::kill(std::process::id() as i32, libc::SIGTERM);
|
|
}
|
|
thread::sleep(Duration::from_millis(100));
|
|
assert!(signals::is_exiting(), "SIGTERM should set exiting");
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
#[serial(signals)]
|
|
fn test_multiple_sigusr1_signals() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
|
|
/* Clear state */
|
|
signals::check_toggle();
|
|
|
|
/* Send multiple SIGUSR1 signals */
|
|
for _ in 0..3 {
|
|
unsafe {
|
|
libc::kill(std::process::id() as i32, libc::SIGUSR1);
|
|
}
|
|
thread::sleep(Duration::from_millis(10));
|
|
}
|
|
|
|
thread::sleep(Duration::from_millis(100));
|
|
|
|
/* Should detect at least one toggle (flag might be set/cleared multiple times) */
|
|
let detected = signals::check_toggle();
|
|
/* The behavior here is that the flag is set to true, so even with multiple signals,
|
|
check_toggle will return true once, then false */
|
|
assert!(detected, "Should detect toggle from multiple SIGUSR1 signals");
|
|
}
|