Skip to main content

ic_host_process/status/
mod.rs

1//! Explicit shell-style projection of observed child status; callers own CLI fallback policy.
2
3use std::process::ExitStatus;
4
5/// Return the numeric exit code, or `128 + signal` for Unix signal termination.
6///
7/// An unclassified status returns `None`. Callers choose its fallback and the
8/// conversion to their CLI's supported exit-code range. This does not wait,
9/// signal, reap or otherwise manage a process, and does not imply success.
10#[must_use]
11pub fn shell_status_code(status: ExitStatus) -> Option<i32> {
12    if let Some(code) = status.code() {
13        return Some(code);
14    }
15    #[cfg(unix)]
16    {
17        use std::os::unix::process::ExitStatusExt as _;
18        status.signal().map(|signal| 128 + signal)
19    }
20    #[cfg(not(unix))]
21    {
22        None
23    }
24}
25
26#[cfg(all(test, unix))]
27mod tests {
28    use super::*;
29    use std::{os::unix::process::ExitStatusExt as _, process::Command};
30
31    #[test]
32    fn observed_exits_and_signals_keep_their_numeric_identity() {
33        for code in [0, 37, 255] {
34            let status = Command::new("/bin/sh")
35                .args(["-c", &format!("exit {code}")])
36                .status()
37                .unwrap();
38            assert_eq!(shell_status_code(status), Some(code));
39        }
40        let status = Command::new("/bin/sh")
41            .args(["-c", "kill -TERM $$"])
42            .status()
43            .unwrap();
44        assert_eq!(shell_status_code(status), Some(143));
45    }
46
47    #[test]
48    fn unclassified_status_leaves_the_fallback_to_each_caller() {
49        let stopped = ExitStatus::from_raw((19 << 8) | 0x7f);
50        assert_eq!(shell_status_code(stopped), None);
51        assert_eq!(shell_status_code(stopped).unwrap_or(1), 1);
52        assert_eq!(shell_status_code(stopped).unwrap_or(129), 129);
53    }
54}