//! 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"));
}
