//! Harte Frist auch für blockierende Bibliotheksaufrufe im Testprozess. use std::sync::mpsc::{self, Sender}; use std::time::Duration; pub struct Prozessfrist(Sender<()>); impl Prozessfrist { pub fn neu(name: &str, dauer: Duration) -> Self { let (tx, rx) = mpsc::channel(); let name = name.to_string(); std::thread::spawn(move || { if matches!(rx.recv_timeout(dauer), Err(mpsc::RecvTimeoutError::Timeout)) { eprintln!("Prozessfrist überschritten: {name}"); // Auch ein in redb blockierter Test lässt sich so sicher beenden. std::process::exit(124); } }); Self(tx) } } impl Drop for Prozessfrist { fn drop(&mut self) { let _ = self.0.send(()); } } #[test] fn frist_beendet_blockierten_testprozess() { use std::process::{Command, Stdio}; use std::time::Instant; const MARKER: &str = "TB_TEST_FRIST_KIND"; if std::env::var_os(MARKER).is_some() { let _frist = Prozessfrist::neu("absichtlich blockiert", Duration::from_millis(100)); loop { std::thread::park(); } } let mut child = Command::new(std::env::current_exe().unwrap()) .arg("frist_beendet_blockierten_testprozess") .arg("--nocapture") .current_dir(std::env::temp_dir()) .env(MARKER, "1") .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); let start = Instant::now(); loop { if child.try_wait().unwrap().is_some() { break; } if start.elapsed() > Duration::from_secs(5) { let _ = child.kill(); let _ = child.wait(); panic!("Prozessfrist hat den blockierten Test nicht beendet"); } std::thread::sleep(Duration::from_millis(10)); } let out = child.wait_with_output().unwrap(); assert_eq!(out.status.code(), Some(124), "{out:?}"); assert!(String::from_utf8_lossy(&out.stderr).contains("absichtlich blockiert")); }