use super::core::wait_for_any_job_or_trap;
use super::core::Error;
use std::ops::ControlFlow;
use yash_env::job::JobList;
use yash_env::job::ProcessResult;
use yash_env::job::ProcessState;
use yash_env::option::State;
use yash_env::semantics::ExitStatus;
use yash_env::Env;
pub async fn wait_while_running(
env: &mut Env,
job_status: &mut dyn FnMut(&mut JobList) -> ControlFlow<ExitStatus>,
) -> Result<ExitStatus, Error> {
loop {
if let ControlFlow::Break(exit_status) = job_status(&mut env.jobs) {
return Ok(exit_status);
}
wait_for_any_job_or_trap(env).await?;
}
}
pub fn job_status(
index: usize,
job_control: State,
) -> impl FnMut(&mut JobList) -> ControlFlow<ExitStatus> {
move |jobs| {
let Some(job) = jobs.get(index) else {
return ControlFlow::Break(ExitStatus::NOT_FOUND);
};
if !job.is_owned {
jobs.remove(index);
return ControlFlow::Break(ExitStatus::NOT_FOUND);
}
if let ProcessState::Halted(result) = job.state {
match result {
ProcessResult::Exited(_) | ProcessResult::Signaled { .. } => {
jobs.remove(index);
return ControlFlow::Break(result.into());
}
ProcessResult::Stopped(_) => {
if job_control.into() {
return ControlFlow::Break(result.into());
}
}
}
}
ControlFlow::Continue(())
}
}
pub fn any_job_is_running(
job_control: State,
) -> impl FnMut(&mut JobList) -> ControlFlow<ExitStatus> {
move |jobs| {
let Some((max_index, _)) = jobs.iter().next_back() else {
return ControlFlow::Break(ExitStatus::SUCCESS);
};
if (0..=max_index).all(|index| job_status(index, job_control)(jobs).is_break()) {
ControlFlow::Break(ExitStatus::SUCCESS)
} else {
ControlFlow::Continue(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use yash_env::job::{Job, Pid};
use yash_env::option::State::{Off, On};
use yash_env::system::r#virtual::{SIGABRT, SIGHUP, SIGSTOP, SIGTSTP};
#[test]
fn status_of_unknown_job() {
let mut jobs = JobList::new();
assert_eq!(
job_status(0, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
);
assert_eq!(
job_status(1, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
);
assert_eq!(
job_status(0, On)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
);
assert_eq!(jobs.len(), 0);
}
#[test]
fn status_of_exited_job() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.state = ProcessState::exited(0);
let index = jobs.add(job);
assert_eq!(
job_status(index, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
);
assert_eq!(jobs.get(index), None);
let mut job = Job::new(Pid(456));
job.state = ProcessState::exited(42);
let index = jobs.add(job);
assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus(42)),
);
assert_eq!(jobs.get(index), None);
}
#[test]
fn status_of_signaled_job() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.state = ProcessState::Halted(ProcessResult::Signaled {
signal: SIGHUP,
core_dump: false,
});
let index = jobs.add(job);
assert_eq!(
job_status(index, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::from(SIGHUP)),
);
assert_eq!(jobs.get(index), None);
let mut job = Job::new(Pid(456));
job.state = ProcessState::Halted(ProcessResult::Signaled {
signal: SIGABRT,
core_dump: true,
});
let index = jobs.add(job);
assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus::from(SIGABRT)),
);
assert_eq!(jobs.get(index), None);
}
#[test]
fn status_of_stopped_job_without_job_control() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.state = ProcessState::stopped(SIGTSTP);
let index = jobs.add(job);
assert_eq!(job_status(index, Off)(&mut jobs), ControlFlow::Continue(()),);
assert_eq!(jobs[index].pid, Pid(123));
let mut job = Job::new(Pid(456));
job.state = ProcessState::stopped(SIGSTOP);
let index = jobs.add(job);
assert_eq!(job_status(index, Off)(&mut jobs), ControlFlow::Continue(()),);
assert_eq!(jobs[index].pid, Pid(456));
}
#[test]
fn status_of_stopped_job_with_job_control() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.state = ProcessState::stopped(SIGTSTP);
let index = jobs.add(job);
assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus::from(SIGTSTP)),
);
assert_eq!(jobs[index].pid, Pid(123));
let mut job = Job::new(Pid(456));
job.state = ProcessState::stopped(SIGSTOP);
let index = jobs.add(job);
assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus::from(SIGSTOP)),
);
assert_eq!(jobs[index].pid, Pid(456));
}
#[test]
fn status_of_continued_job() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.state = ProcessState::Running;
let index = jobs.add(job);
assert_eq!(job_status(index, Off)(&mut jobs), ControlFlow::Continue(()));
assert_eq!(jobs[index].pid, Pid(123));
let mut job = Job::new(Pid(456));
job.state = ProcessState::Running;
let index = jobs.add(job);
assert_eq!(job_status(index, On)(&mut jobs), ControlFlow::Continue(()));
assert_eq!(jobs[index].pid, Pid(456));
}
#[test]
fn status_of_disowned_job() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.is_owned = false;
let index = jobs.add(job);
assert_eq!(
job_status(index, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
);
assert_eq!(jobs.get(index), None);
}
#[test]
fn any_job_is_running_with_no_job() {
let mut jobs = JobList::new();
assert_eq!(
any_job_is_running(Off)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
);
assert_eq!(
any_job_is_running(On)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
);
}
#[test]
fn any_job_is_running_with_exited_jobs() {
let mut jobs = JobList::new();
let mut job = Job::new(Pid(123));
job.state = ProcessState::exited(0);
jobs.add(job);
assert_eq!(
any_job_is_running(Off)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
);
let mut job = Job::new(Pid(456));
job.state = ProcessState::exited(42);
jobs.add(job);
assert_eq!(
any_job_is_running(On)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
);
}
#[test]
fn any_job_is_running_with_running_job() {
let mut jobs = JobList::new();
jobs.add(Job::new(Pid(123)));
assert_eq!(
any_job_is_running(Off)(&mut jobs),
ControlFlow::Continue(()),
);
jobs.add(Job::new(Pid(456)));
assert_eq!(any_job_is_running(On)(&mut jobs), ControlFlow::Continue(()));
let mut job = Job::new(Pid(789));
job.state = ProcessState::exited(0);
jobs.add(job);
assert_eq!(any_job_is_running(On)(&mut jobs), ControlFlow::Continue(()));
}
}