//! Explain a wait status.
//!
//! A wait status is returned by one of the `wait` family of system
//! calls. It contains the exit code (given to the `_exit` system
//! call), but also other information.
//!
//! This crate, or its tests, may be Linux specific.

use serde::Serialize;

/// Explained wait status.
#[derive(Debug, Serialize)]
pub struct Explained {
    /// The wait status given to [`Explained::new`].
    pub wait_status: i32,

    /// Did the process exit normally? (If not, it may be running, or
    /// was terminated by a signal.)
    pub exited: bool,

    /// Exit status code, if `exited` is true.
    pub exit_status: Option<i32>,

    /// Was the process terminated by signal?
    pub signaled: bool,

    /// If `signaled` is `true`, what signal?
    pub termsig: Option<i32>,

    /// If `signaled` is `true`, did process produce a core dump?
    pub coredump: bool,

    /// Was the process stopped by a signal?
    pub stopped: bool,

    /// If `stopped` is `true`, what signal?
    pub stopsig: Option<i32>,

    /// Was stopped process resumed by signal **SIGCONT**?
    pub continued: bool,
}

impl Explained {
    pub fn new(wait_status: i32) -> Self {
        let exited = libc::WIFEXITED(wait_status);
        let signaled = libc::WIFSIGNALED(wait_status);
        let stopped = libc::WIFSTOPPED(wait_status);

        Self {
            wait_status,
            exited,
            exit_status: if exited {
                Some(libc::WEXITSTATUS(wait_status))
            } else {
                None
            },
            signaled,
            termsig: if signaled {
                Some(libc::WTERMSIG(wait_status))
            } else {
                None
            },
            coredump: signaled && libc::WCOREDUMP(wait_status),
            stopped,
            stopsig: if stopped {
                Some(libc::WSTOPSIG(wait_status))
            } else {
                None
            },
            continued: libc::WIFCONTINUED(wait_status),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn normal_exits() {
        for i in 0..255 {
            // On Linux, for normal exit codes, wait status is shifted by 8 bits.
            let wait_status = i << 8;
            let ex = Explained::new(wait_status);

            // Print the result to help debug test failures.
            println!("{i} => {ex:#?}");

            assert_eq!(ex.wait_status, wait_status);
            assert!(ex.exited);
            assert_eq!(ex.exit_status, Some(i));
            assert!(!ex.signaled);
            assert_eq!(ex.termsig, None);
            assert!(!ex.coredump);
            assert!(!ex.stopped);
            assert_eq!(ex.stopsig, None);
            assert!(!ex.continued);
        }
    }
}
