const STATUS_MASK: i32 = 0o177;
const STOPPED: i32 = 0o177;
const CODE_SHIFT: i32 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExitStatus {
raw: i32,
}
impl ExitStatus {
pub(crate) fn from_raw(raw: i32) -> Self {
Self { raw }
}
pub fn success(&self) -> bool {
self.code() == Some(0)
}
pub fn code(&self) -> Option<i32> {
if self.raw & STATUS_MASK != 0 {
return None;
}
Some((self.raw >> CODE_SHIFT) & 0xff)
}
pub fn signal(&self) -> Option<i32> {
let status = self.raw & STATUS_MASK;
if status == 0 || status == STOPPED {
return None;
}
Some(status)
}
pub fn raw(&self) -> i32 {
self.raw
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProcessOutput {
stdout: Vec<u8>,
stderr: Vec<u8>,
status: ExitStatus,
}
impl ProcessOutput {
pub(crate) fn new(stdout: Vec<u8>, stderr: Vec<u8>, status: ExitStatus) -> Self {
Self {
stdout,
stderr,
status,
}
}
pub fn status(&self) -> ExitStatus {
self.status
}
pub fn stdout(&self) -> &[u8] {
&self.stdout
}
pub fn stderr(&self) -> &[u8] {
&self.stderr
}
pub fn into_parts(self) -> (Vec<u8>, Vec<u8>, ExitStatus) {
(self.stdout, self.stderr, self.status)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_ordinary_exit_has_a_code_and_no_signal() {
let ok = ExitStatus::from_raw(0);
assert!(ok.success(), "a zero status must be a success");
assert_eq!(ok.code(), Some(0), "a zero status must read as code 0");
assert_eq!(ok.signal(), None, "a clean exit must have no signal");
let failed = ExitStatus::from_raw(1 << 8);
assert!(!failed.success(), "a non zero code must not be a success");
assert_eq!(
failed.code(),
Some(1),
"the code must come out of the high byte"
);
assert_eq!(failed.signal(), None, "a clean exit must have no signal");
}
#[test]
fn a_killed_child_has_a_signal_and_no_code() {
let killed = ExitStatus::from_raw(libc::SIGKILL);
assert!(!killed.success(), "a killed child must not be a success");
assert_eq!(killed.code(), None, "a killed child has no code of its own");
assert_eq!(
killed.signal(),
Some(libc::SIGKILL),
"the signal must come out of the low bits"
);
}
#[test]
fn a_stopped_child_is_neither_a_code_nor_a_signal() {
let stopped = ExitStatus::from_raw((libc::SIGSTOP << 8) | 0o177);
assert_eq!(stopped.code(), None, "a stop is not an exit");
assert_eq!(stopped.signal(), None, "a stop is not a kill");
assert!(!stopped.success(), "a stop is certainly not a success");
}
}