pub type JobId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JobStatus {
Running,
Stopped(i32), Done(i32), Terminated(i32), }
impl std::fmt::Display for JobStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
JobStatus::Running => write!(f, "Running"),
JobStatus::Stopped(sig) => {
let name = crate::signal::signal_number_to_name(*sig).unwrap_or("UNKNOWN");
write!(f, "Stopped(SIG{})", name)
}
JobStatus::Done(0) => write!(f, "Done"),
JobStatus::Done(code) => write!(f, "Done({})", code),
JobStatus::Terminated(sig) => {
let name = crate::signal::signal_number_to_name(*sig).unwrap_or("UNKNOWN");
write!(f, "Terminated(SIG{})", name)
}
}
}
}
impl JobStatus {
pub fn is_terminal(self) -> bool {
matches!(self, JobStatus::Done(_) | JobStatus::Terminated(_))
}
}
#[derive(Debug, Clone)]
pub struct Job {
pub id: JobId,
pub pgid: nix::unistd::Pid,
pub pids: Vec<nix::unistd::Pid>,
pub command: String,
pub status: JobStatus,
pub notified: bool,
pub foreground: bool,
pub(super) saved_tmodes: Option<nix::sys::termios::Termios>,
}
impl Job {
pub fn saved_tmodes(&self) -> Option<&nix::sys::termios::Termios> {
self.saved_tmodes.as_ref()
}
pub fn set_saved_tmodes(&mut self, t: Option<nix::sys::termios::Termios>) {
self.saved_tmodes = t;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env::jobs::JobTable;
use nix::unistd::Pid;
fn pid(n: i32) -> Pid {
Pid::from_raw(n)
}
#[test]
fn test_job_status_equality() {
assert_eq!(JobStatus::Running, JobStatus::Running);
assert_eq!(JobStatus::Done(0), JobStatus::Done(0));
assert_ne!(JobStatus::Done(0), JobStatus::Done(1));
assert_eq!(JobStatus::Stopped(20), JobStatus::Stopped(20));
assert_eq!(JobStatus::Terminated(9), JobStatus::Terminated(9));
}
#[test]
fn test_job_saved_tmodes_defaults_none() {
let mut table = JobTable::default();
let id = table.add_job(pid(42), vec![pid(42)], "cmd", false);
let job = table.get(id).expect("job should exist");
assert!(
job.saved_tmodes().is_none(),
"saved_tmodes() should default to None on new job"
);
}
#[test]
fn test_job_set_saved_tmodes_overwrites_with_none() {
let mut table = JobTable::default();
let id = table.add_job(pid(42), vec![pid(42)], "cmd", false);
let zeroed: libc::termios = unsafe { std::mem::zeroed() };
let t: nix::sys::termios::Termios = zeroed.into();
table
.get_mut(id)
.expect("job should exist")
.set_saved_tmodes(Some(t));
assert!(
table.get(id).unwrap().saved_tmodes().is_some(),
"saved_tmodes() should return Some after set_saved_tmodes(Some(_))"
);
table
.get_mut(id)
.expect("job should exist")
.set_saved_tmodes(None);
assert!(
table.get(id).unwrap().saved_tmodes().is_none(),
"saved_tmodes() should return None after set_saved_tmodes(None)"
);
}
#[test]
fn test_display_running() {
assert_eq!(JobStatus::Running.to_string(), "Running");
}
#[test]
fn test_display_done_success() {
assert_eq!(JobStatus::Done(0).to_string(), "Done");
}
#[test]
fn test_display_done_nonzero() {
assert_eq!(JobStatus::Done(2).to_string(), "Done(2)");
assert_eq!(JobStatus::Done(127).to_string(), "Done(127)");
}
#[test]
fn test_display_stopped_known_signal() {
let s = JobStatus::Stopped(20).to_string();
assert!(s.starts_with("Stopped(SIG"), "got: {}", s);
}
#[test]
fn test_display_stopped_unknown_signal() {
assert_eq!(JobStatus::Stopped(99).to_string(), "Stopped(SIGUNKNOWN)");
}
#[test]
fn test_display_terminated_known_signal() {
let s = JobStatus::Terminated(9).to_string();
assert!(s.starts_with("Terminated(SIG"), "got: {}", s);
}
#[test]
fn test_display_terminated_unknown_signal() {
assert_eq!(
JobStatus::Terminated(99).to_string(),
"Terminated(SIGUNKNOWN)"
);
}
#[test]
fn test_is_terminal_running_false() {
assert!(!JobStatus::Running.is_terminal());
}
#[test]
fn test_is_terminal_stopped_false() {
assert!(!JobStatus::Stopped(20).is_terminal());
}
#[test]
fn test_is_terminal_done_true() {
assert!(JobStatus::Done(0).is_terminal());
assert!(JobStatus::Done(127).is_terminal());
}
#[test]
fn test_is_terminal_terminated_true() {
assert!(JobStatus::Terminated(9).is_terminal());
assert!(JobStatus::Terminated(15).is_terminal());
}
}