1use crate::Env;
45use crate::io::Fd;
46use crate::semantics::{Divert, ExitStatus};
47use crate::signal;
48use crate::system::{Disposition, Sigaction, Sigmask, SigmaskOp, Signals, TcSetPgrp};
49use slab::Slab;
50use std::collections::HashMap;
51use std::iter::FusedIterator;
52use std::ops::ControlFlow::{Break, Continue};
53use std::ops::Deref;
54use thiserror::Error;
55
56#[cfg(unix)]
57type RawPidDef = libc::pid_t;
58#[cfg(not(unix))]
59type RawPidDef = i32;
60
61pub type RawPid = RawPidDef;
71
72#[repr(transparent)]
91#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
92pub struct Pid(pub RawPid);
93
94impl std::fmt::Display for Pid {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 self.0.fmt(f)
97 }
98}
99
100impl std::ops::Neg for Pid {
101 type Output = Self;
102 fn neg(self) -> Self {
103 Self(-self.0)
104 }
105}
106
107impl Pid {
108 pub const MY_PROCESS_GROUP: Self = Pid(0);
114
115 pub const ALL: Self = Pid(-1);
121}
122
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub enum ProcessResult {
134 Stopped(signal::Number),
136 Exited(ExitStatus),
138 Signaled {
140 signal: signal::Number,
141 core_dump: bool,
142 },
143}
144
145impl ProcessResult {
146 #[inline]
148 #[must_use]
149 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
150 Self::Exited(exit_status.into())
151 }
152
153 #[must_use]
155 pub fn is_stopped(&self) -> bool {
156 matches!(self, ProcessResult::Stopped(_))
157 }
158}
159
160impl From<ProcessResult> for ExitStatus {
162 fn from(result: ProcessResult) -> Self {
163 match result {
164 ProcessResult::Exited(exit_status) => exit_status,
165 ProcessResult::Stopped(signal) | ProcessResult::Signaled { signal, .. } => {
166 ExitStatus::from(signal)
167 }
168 }
169 }
170}
171
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182pub enum ProcessState {
183 Running,
185 Halted(ProcessResult),
187}
188
189impl ProcessState {
190 #[inline]
192 #[must_use]
193 pub fn stopped(signal: signal::Number) -> Self {
194 Self::Halted(ProcessResult::Stopped(signal))
195 }
196
197 #[inline]
199 #[must_use]
200 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
201 Self::Halted(ProcessResult::exited(exit_status))
202 }
203
204 #[must_use]
206 pub fn is_alive(&self) -> bool {
207 match self {
208 ProcessState::Running => true,
209 ProcessState::Halted(result) => result.is_stopped(),
210 }
211 }
212
213 #[must_use]
215 pub fn is_stopped(&self) -> bool {
216 matches!(self, Self::Halted(result) if result.is_stopped())
217 }
218}
219
220impl From<ProcessResult> for ProcessState {
221 #[inline]
222 fn from(result: ProcessResult) -> Self {
223 Self::Halted(result)
224 }
225}
226
227#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
231pub struct RunningProcess;
232
233impl TryFrom<ProcessState> for ExitStatus {
237 type Error = RunningProcess;
238 fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
239 match state {
240 ProcessState::Halted(result) => Ok(result.into()),
241 ProcessState::Running => Err(RunningProcess),
242 }
243 }
244}
245
246#[derive(Clone, Debug, Eq, PartialEq)]
253#[non_exhaustive]
254pub struct Job {
255 pub pid: Pid,
259
260 pub job_controlled: bool,
265
266 pub state: ProcessState,
268
269 pub expected_state: Option<ProcessState>,
273
274 pub state_changed: bool,
279
280 pub is_owned: bool,
286
287 pub name: String,
289}
290
291impl Job {
292 pub fn new(pid: Pid) -> Self {
297 Job {
298 pid,
299 job_controlled: false,
300 state: ProcessState::Running,
301 expected_state: None,
302 state_changed: true,
303 is_owned: true,
304 name: String::new(),
305 }
306 }
307
308 #[must_use]
310 fn is_suspended(&self) -> bool {
311 self.state.is_stopped()
312 }
313}
314
315#[derive(Debug, Eq, PartialEq)]
321pub struct JobRefMut<'a>(&'a mut Job);
322
323impl JobRefMut<'_> {
324 pub fn expect<S>(&mut self, state: S)
335 where
336 S: Into<Option<ProcessState>>,
337 {
338 self.0.expected_state = state.into();
339 }
340
341 pub fn state_reported(&mut self) {
346 self.0.state_changed = false
347 }
348}
349
350impl Deref for JobRefMut<'_> {
351 type Target = Job;
352 fn deref(&self) -> &Job {
353 self.0
354 }
355}
356
357#[derive(Clone, Debug)]
361pub struct Iter<'a>(slab::Iter<'a, Job>);
362
363impl<'a> Iterator for Iter<'a> {
364 type Item = (usize, &'a Job);
365
366 #[inline(always)]
367 fn next(&mut self) -> Option<(usize, &'a Job)> {
368 self.0.next()
369 }
370
371 #[inline(always)]
372 fn size_hint(&self) -> (usize, Option<usize>) {
373 self.0.size_hint()
374 }
375}
376
377impl<'a> DoubleEndedIterator for Iter<'a> {
378 #[inline(always)]
379 fn next_back(&mut self) -> Option<(usize, &'a Job)> {
380 self.0.next_back()
381 }
382}
383
384impl ExactSizeIterator for Iter<'_> {
385 #[inline(always)]
386 fn len(&self) -> usize {
387 self.0.len()
388 }
389}
390
391impl FusedIterator for Iter<'_> {}
392
393#[derive(Debug)]
397pub struct IterMut<'a>(slab::IterMut<'a, Job>);
398
399impl<'a> Iterator for IterMut<'a> {
400 type Item = (usize, JobRefMut<'a>);
401
402 #[inline]
403 fn next(&mut self) -> Option<(usize, JobRefMut<'a>)> {
404 self.0.next().map(|(index, job)| (index, JobRefMut(job)))
405 }
406
407 #[inline(always)]
408 fn size_hint(&self) -> (usize, Option<usize>) {
409 self.0.size_hint()
410 }
411}
412
413impl<'a> DoubleEndedIterator for IterMut<'a> {
414 fn next_back(&mut self) -> Option<(usize, JobRefMut<'a>)> {
415 self.0
416 .next_back()
417 .map(|(index, job)| (index, JobRefMut(job)))
418 }
419}
420
421impl ExactSizeIterator for IterMut<'_> {
422 #[inline(always)]
423 fn len(&self) -> usize {
424 self.0.len()
425 }
426}
427
428impl FusedIterator for IterMut<'_> {}
429
430#[derive(Clone, Debug)]
434pub struct JobList {
435 jobs: Slab<Job>,
437
438 pids_to_indices: HashMap<Pid, usize>,
442
443 current_job_index: usize,
445
446 previous_job_index: usize,
448
449 last_async_pid: Pid,
451}
452
453impl Default for JobList {
454 fn default() -> Self {
455 JobList {
456 jobs: Slab::new(),
457 pids_to_indices: HashMap::new(),
458 current_job_index: usize::default(),
459 previous_job_index: usize::default(),
460 last_async_pid: Pid(0),
461 }
462 }
463}
464
465impl JobList {
466 #[inline]
468 #[must_use]
469 pub fn new() -> Self {
470 Self::default()
471 }
472
473 #[inline]
477 pub fn get(&self, index: usize) -> Option<&Job> {
478 self.jobs.get(index)
479 }
480
481 #[inline]
485 pub fn get_mut(&mut self, index: usize) -> Option<JobRefMut<'_>> {
486 self.jobs.get_mut(index).map(JobRefMut)
487 }
488
489 #[inline]
491 pub fn len(&self) -> usize {
492 self.jobs.len()
493 }
494
495 #[inline]
497 pub fn is_empty(&self) -> bool {
498 self.len() == 0
499 }
500
501 #[inline]
506 pub fn iter(&self) -> Iter<'_> {
507 Iter(self.jobs.iter())
508 }
509
510 #[inline]
518 pub fn iter_mut(&mut self) -> IterMut<'_> {
519 IterMut(self.jobs.iter_mut())
520 }
521
522 pub fn find_by_pid(&self, pid: Pid) -> Option<usize> {
530 self.pids_to_indices.get(&pid).copied()
531 }
532}
533
534impl<'a> IntoIterator for &'a JobList {
535 type Item = (usize, &'a Job);
536 type IntoIter = Iter<'a>;
537 #[inline(always)]
538 fn into_iter(self) -> Iter<'a> {
539 self.iter()
540 }
541}
542
543impl<'a> IntoIterator for &'a mut JobList {
544 type Item = (usize, JobRefMut<'a>);
545 type IntoIter = IterMut<'a>;
546 #[inline(always)]
547 fn into_iter(self) -> IterMut<'a> {
548 self.iter_mut()
549 }
550}
551
552impl std::ops::Index<usize> for JobList {
554 type Output = Job;
555
556 fn index(&self, index: usize) -> &Job {
560 &self.jobs[index]
561 }
562}
563
564#[derive(Debug)]
568pub struct ExtractIf<'a, F>
569where
570 F: FnMut(usize, JobRefMut) -> bool,
571{
572 list: &'a mut JobList,
573 should_remove: F,
574 next_index: usize,
575 len: usize,
576}
577
578impl<F> Iterator for ExtractIf<'_, F>
579where
580 F: FnMut(usize, JobRefMut) -> bool,
581{
582 type Item = (usize, Job);
583
584 fn next(&mut self) -> Option<(usize, Job)> {
585 while self.len > 0 {
586 let index = self.next_index;
587 self.next_index += 1;
588 if let Some(job) = self.list.get_mut(index) {
589 self.len -= 1;
590 if (self.should_remove)(index, job) {
591 let job = self.list.remove(index).unwrap();
592 return Some((index, job));
593 }
594 }
595 }
596 None
597 }
598
599 fn size_hint(&self) -> (usize, Option<usize>) {
600 (0, Some(self.len))
601 }
602}
603
604impl<F> FusedIterator for ExtractIf<'_, F> where F: FnMut(usize, JobRefMut) -> bool {}
605
606impl JobList {
607 pub fn add(&mut self, job: Job) -> usize {
619 let new_job_is_suspended = job.is_suspended();
620 let ex_current_job_is_suspended =
621 self.current_job().map(|index| self[index].is_suspended());
622 let ex_previous_job_is_suspended =
623 self.previous_job().map(|index| self[index].is_suspended());
624
625 use std::collections::hash_map::Entry::*;
627 let index = match self.pids_to_indices.entry(job.pid) {
628 Vacant(entry) => {
629 let index = self.jobs.insert(job);
630 entry.insert(index);
631 index
632 }
633 Occupied(entry) => {
634 let index = *entry.get();
635 self.jobs[index] = job;
636 index
637 }
638 };
639 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
640
641 match ex_current_job_is_suspended {
643 None => self.current_job_index = index,
644 Some(false) if new_job_is_suspended => self.set_current_job(index).unwrap(),
645 Some(_) => match ex_previous_job_is_suspended {
646 None => self.previous_job_index = index,
647 Some(false) if new_job_is_suspended => self.previous_job_index = index,
648 Some(_) => (),
649 },
650 }
651
652 index
653 }
654
655 pub fn remove(&mut self, index: usize) -> Option<Job> {
666 let job = self.jobs.try_remove(index);
667
668 if let Some(job) = &job {
669 self.pids_to_indices.remove(&job.pid);
671
672 if self.jobs.is_empty() {
673 self.jobs.clear();
677 }
678
679 let previous_job_becomes_current_job = index == self.current_job_index;
681 if previous_job_becomes_current_job {
682 self.current_job_index = self.previous_job_index;
683 }
684 if previous_job_becomes_current_job || index == self.previous_job_index {
685 self.previous_job_index = self
686 .any_suspended_job_but_current()
687 .unwrap_or_else(|| self.any_job_but_current().unwrap_or_default());
688 }
689 }
690 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
691
692 job
693 }
694
695 pub fn remove_if<F>(&mut self, should_remove: F)
708 where
709 F: FnMut(usize, JobRefMut) -> bool,
710 {
711 self.extract_if(should_remove).for_each(drop)
712 }
713
714 pub fn extract_if<F>(&mut self, should_remove: F) -> ExtractIf<'_, F>
730 where
731 F: FnMut(usize, JobRefMut) -> bool,
732 {
733 let len = self.len();
734 ExtractIf {
735 list: self,
736 should_remove,
737 next_index: 0,
738 len,
739 }
740 }
741}
742
743impl JobList {
744 pub fn update_status(&mut self, pid: Pid, state: ProcessState) -> Option<usize> {
769 let index = self.find_by_pid(pid)?;
770
771 let job = &mut self.jobs[index];
773 let was_suspended = job.is_suspended();
774 job.state = state;
775 job.state_changed |= job.expected_state != Some(state);
776 job.expected_state = None;
777
778 if !was_suspended && job.is_suspended() {
780 if index != self.current_job_index {
781 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
782 }
783 } else if was_suspended && !job.is_suspended() {
784 if let Some(prev_index) = self.previous_job() {
785 let previous_job_becomes_current_job =
786 index == self.current_job_index && self[prev_index].is_suspended();
787 if previous_job_becomes_current_job {
788 self.current_job_index = prev_index;
789 }
790 if previous_job_becomes_current_job || index == prev_index {
791 self.previous_job_index = self.any_suspended_job_but_current().unwrap_or(index);
792 }
793 }
794 }
795
796 Some(index)
797 }
798
799 pub fn disown_all(&mut self) {
803 for (_, job) in &mut self.jobs {
804 job.is_owned = false;
805 }
806 }
807}
808
809#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
811pub enum SetCurrentJobError {
812 #[error("no such job")]
814 NoSuchJob,
815
816 #[error("the current job must be selected from suspended jobs")]
819 NotSuspended,
820}
821
822impl JobList {
823 pub fn set_current_job(&mut self, index: usize) -> Result<(), SetCurrentJobError> {
835 let job = self.get(index).ok_or(SetCurrentJobError::NoSuchJob)?;
836 if !job.is_suspended() && self.iter().any(|(_, job)| job.is_suspended()) {
837 return Err(SetCurrentJobError::NotSuspended);
838 }
839
840 if index != self.current_job_index {
841 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
842 }
843 Ok(())
844 }
845
846 pub fn current_job(&self) -> Option<usize> {
859 if self.jobs.contains(self.current_job_index) {
860 Some(self.current_job_index)
861 } else {
862 None
863 }
864 }
865
866 pub fn previous_job(&self) -> Option<usize> {
882 if self.previous_job_index != self.current_job_index
883 && self.jobs.contains(self.previous_job_index)
884 {
885 Some(self.previous_job_index)
886 } else {
887 None
888 }
889 }
890
891 fn any_suspended_job_but_current(&self) -> Option<usize> {
893 self.iter()
894 .filter(|&(index, job)| index != self.current_job_index && job.is_suspended())
895 .map(|(index, _)| index)
896 .next()
897 }
898
899 fn any_job_but_current(&self) -> Option<usize> {
901 self.iter()
902 .filter(|&(index, _)| index != self.current_job_index)
903 .map(|(index, _)| index)
904 .next()
905 }
906}
907
908impl JobList {
909 pub fn last_async_pid(&self) -> Pid {
916 self.last_async_pid
917 }
918
919 pub fn set_last_async_pid(&mut self, pid: Pid) {
924 self.last_async_pid = pid;
925 }
926}
927
928pub async fn tcsetpgrp_with_block<S>(system: &S, fd: Fd, pgid: Pid) -> crate::system::Result<()>
940where
941 S: Signals + Sigmask + TcSetPgrp + ?Sized,
942{
943 let mut old_mask = Vec::new();
944
945 system.sigmask(Some((SigmaskOp::Add, &[S::SIGTTOU])), Some(&mut old_mask))?;
946
947 let result = system.tcsetpgrp(fd, pgid).await;
948
949 let result_2 = system.sigmask(Some((SigmaskOp::Set, &old_mask)), None);
950
951 result.and(result_2)
952}
953
954pub async fn tcsetpgrp_without_block<S>(system: &S, fd: Fd, pgid: Pid) -> crate::system::Result<()>
974where
975 S: Signals + Sigaction + Sigmask + TcSetPgrp + ?Sized,
976{
977 match system.sigaction(S::SIGTTOU, Disposition::Default) {
978 Err(e) => Err(e),
979 Ok(old_handling) => {
980 let mut old_mask = Vec::new();
981 let result = match system.sigmask(
982 Some((SigmaskOp::Remove, &[S::SIGTTOU])),
983 Some(&mut old_mask),
984 ) {
985 Err(e) => Err(e),
986 Ok(()) => {
987 let result = system.tcsetpgrp(fd, pgid).await;
988
989 let result_2 = system.sigmask(Some((SigmaskOp::Set, &old_mask)), None);
990
991 result.and(result_2)
992 }
993 };
994
995 let result_2 = system.sigaction(S::SIGTTOU, old_handling).map(drop);
996
997 result.and(result_2)
998 }
999 }
1000}
1001
1002pub fn add_job_if_suspended<S, F>(
1020 env: &mut Env<S>,
1021 pid: Pid,
1022 result: ProcessResult,
1023 name: F,
1024) -> crate::semantics::Result<ExitStatus>
1025where
1026 F: FnOnce() -> String,
1027{
1028 let exit_status = result.into();
1029
1030 if result.is_stopped() {
1031 let mut job = Job::new(pid);
1032 job.job_controlled = true;
1033 job.state = result.into();
1034 job.name = name();
1035 env.jobs.add(job);
1036
1037 if env.is_interactive() {
1038 return Break(Divert::Interrupt(Some(exit_status)));
1039 }
1040 }
1041
1042 Continue(exit_status)
1043}
1044
1045pub mod fmt;
1046pub mod id;
1047
1048#[cfg(test)]
1049mod tests {
1050 use super::*;
1051 use crate::option::Option::Interactive;
1052 use crate::option::State::On;
1053 use crate::signal;
1054 use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
1055 use std::num::NonZero;
1056
1057 #[test]
1058 fn job_list_find_by_pid() {
1059 let mut list = JobList::default();
1060 assert_eq!(list.find_by_pid(Pid(10)), None);
1061
1062 let i10 = list.add(Job::new(Pid(10)));
1063 let i20 = list.add(Job::new(Pid(20)));
1064 let i30 = list.add(Job::new(Pid(30)));
1065 assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
1066 assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
1067 assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
1068 assert_eq!(list.find_by_pid(Pid(40)), None);
1069
1070 list.remove(i10);
1071 assert_eq!(list.find_by_pid(Pid(10)), None);
1072 }
1073
1074 #[test]
1075 fn job_list_add_and_remove() {
1076 let mut list = JobList::default();
1078
1079 assert_eq!(list.add(Job::new(Pid(10))), 0);
1080 assert_eq!(list.add(Job::new(Pid(11))), 1);
1081 assert_eq!(list.add(Job::new(Pid(12))), 2);
1082
1083 assert_eq!(list.remove(0).unwrap().pid, Pid(10));
1084 assert_eq!(list.remove(1).unwrap().pid, Pid(11));
1085
1086 assert_eq!(list.add(Job::new(Pid(13))), 1);
1088 assert_eq!(list.add(Job::new(Pid(14))), 0);
1089
1090 assert_eq!(list.remove(0).unwrap().pid, Pid(14));
1091 assert_eq!(list.remove(1).unwrap().pid, Pid(13));
1092 assert_eq!(list.remove(2).unwrap().pid, Pid(12));
1093
1094 assert_eq!(list.add(Job::new(Pid(13))), 0);
1096 assert_eq!(list.add(Job::new(Pid(14))), 1);
1097 }
1098
1099 #[test]
1100 fn job_list_add_same_pid() {
1101 let mut list = JobList::default();
1102
1103 let mut job = Job::new(Pid(10));
1104 job.name = "first job".to_string();
1105 let i_first = list.add(job);
1106
1107 let mut job = Job::new(Pid(10));
1108 job.name = "second job".to_string();
1109 let i_second = list.add(job);
1110
1111 let job = &list[i_second];
1112 assert_eq!(job.pid, Pid(10));
1113 assert_eq!(job.name, "second job");
1114
1115 assert_ne!(
1116 list.get(i_first).map(|job| job.name.as_str()),
1117 Some("first job")
1118 );
1119 }
1120
1121 #[test]
1122 fn job_list_extract_if() {
1123 let mut list = JobList::default();
1124 let i21 = list.add(Job::new(Pid(21)));
1125 let i22 = list.add(Job::new(Pid(22)));
1126 let i23 = list.add(Job::new(Pid(23)));
1127 let i24 = list.add(Job::new(Pid(24)));
1128 let i25 = list.add(Job::new(Pid(25)));
1129 let i26 = list.add(Job::new(Pid(26)));
1130 list.remove(i23).unwrap();
1131
1132 let mut i = list.extract_if(|index, mut job| {
1133 assert_ne!(index, i23);
1134 if index % 2 == 0 {
1135 job.state_reported();
1136 }
1137 index == 0 || job.pid == Pid(26)
1138 });
1139
1140 let mut expected_job_21 = Job::new(Pid(21));
1141 expected_job_21.state_changed = false;
1142 assert_eq!(i.next(), Some((i21, expected_job_21)));
1143 assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1144 assert_eq!(i.next(), None);
1145 assert_eq!(i.next(), None); let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1148 assert_eq!(indices, [i22, i24, i25]);
1149 assert!(list[i22].state_changed);
1150 assert!(list[i24].state_changed);
1151 assert!(!list[i25].state_changed);
1152 }
1153
1154 #[test]
1155 #[allow(clippy::bool_assert_comparison)]
1156 fn updating_job_status_without_expected_state() {
1157 let mut list = JobList::default();
1158 let state = ProcessState::exited(15);
1159 assert_eq!(list.update_status(Pid(20), state), None);
1160
1161 let i10 = list.add(Job::new(Pid(10)));
1162 let i20 = list.add(Job::new(Pid(20)));
1163 let i30 = list.add(Job::new(Pid(30)));
1164 assert_eq!(list[i20].state, ProcessState::Running);
1165
1166 list.get_mut(i20).unwrap().state_reported();
1167 assert_eq!(list[i20].state_changed, false);
1168
1169 assert_eq!(list.update_status(Pid(20), state), Some(i20));
1170 assert_eq!(list[i20].state, ProcessState::exited(15));
1171 assert_eq!(list[i20].state_changed, true);
1172
1173 assert_eq!(list[i10].state, ProcessState::Running);
1174 assert_eq!(list[i30].state, ProcessState::Running);
1175 }
1176
1177 #[test]
1178 #[allow(clippy::bool_assert_comparison)]
1179 fn updating_job_status_with_matching_expected_state() {
1180 let mut list = JobList::default();
1181 let pid = Pid(20);
1182 let mut job = Job::new(pid);
1183 job.expected_state = Some(ProcessState::Running);
1184 job.state_changed = false;
1185 let i20 = list.add(job);
1186
1187 assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1188
1189 let job = &list[i20];
1190 assert_eq!(job.state, ProcessState::Running);
1191 assert_eq!(job.expected_state, None);
1192 assert_eq!(job.state_changed, false);
1193 }
1194
1195 #[test]
1196 #[allow(clippy::bool_assert_comparison)]
1197 fn updating_job_status_with_unmatched_expected_state() {
1198 let mut list = JobList::default();
1199 let pid = Pid(20);
1200 let mut job = Job::new(pid);
1201 job.expected_state = Some(ProcessState::Running);
1202 job.state_changed = false;
1203 let i20 = list.add(job);
1204
1205 let result = list.update_status(pid, ProcessState::exited(0));
1206 assert_eq!(result, Some(i20));
1207
1208 let job = &list[i20];
1209 assert_eq!(job.state, ProcessState::exited(0));
1210 assert_eq!(job.expected_state, None);
1211 assert_eq!(job.state_changed, true);
1212 }
1213
1214 #[test]
1215 #[allow(clippy::bool_assert_comparison)]
1216 fn disowning_jobs() {
1217 let mut list = JobList::default();
1218 let i10 = list.add(Job::new(Pid(10)));
1219 let i20 = list.add(Job::new(Pid(20)));
1220 let i30 = list.add(Job::new(Pid(30)));
1221
1222 list.disown_all();
1223
1224 assert_eq!(list[i10].is_owned, false);
1225 assert_eq!(list[i20].is_owned, false);
1226 assert_eq!(list[i30].is_owned, false);
1227 }
1228
1229 #[test]
1230 fn no_current_and_previous_job_in_empty_job_list() {
1231 let list = JobList::default();
1232 assert_eq!(list.current_job(), None);
1233 assert_eq!(list.previous_job(), None);
1234 }
1235
1236 #[test]
1237 fn current_and_previous_job_in_job_list_with_one_job() {
1238 let mut list = JobList::default();
1239 let i10 = list.add(Job::new(Pid(10)));
1240 assert_eq!(list.current_job(), Some(i10));
1241 assert_eq!(list.previous_job(), None);
1242 }
1243
1244 #[test]
1245 fn current_and_previous_job_in_job_list_with_two_job() {
1246 let mut list = JobList::default();
1249 let mut suspended = Job::new(Pid(10));
1250 suspended.state = ProcessState::stopped(SIGSTOP);
1251 let running = Job::new(Pid(20));
1252 let i10 = list.add(suspended.clone());
1253 let i20 = list.add(running.clone());
1254 assert_eq!(list.current_job(), Some(i10));
1255 assert_eq!(list.previous_job(), Some(i20));
1256
1257 list = JobList::default();
1259 let i20 = list.add(running);
1260 let i10 = list.add(suspended);
1261 assert_eq!(list.current_job(), Some(i10));
1262 assert_eq!(list.previous_job(), Some(i20));
1263 }
1264
1265 #[test]
1266 fn adding_suspended_job_with_running_current_and_previous_job() {
1267 let mut list = JobList::default();
1268 let running_1 = Job::new(Pid(11));
1269 let running_2 = Job::new(Pid(12));
1270 list.add(running_1);
1271 list.add(running_2);
1272 let ex_current_job_index = list.current_job().unwrap();
1273 let ex_previous_job_index = list.previous_job().unwrap();
1274 assert_ne!(ex_current_job_index, ex_previous_job_index);
1275
1276 let mut suspended = Job::new(Pid(20));
1277 suspended.state = ProcessState::stopped(SIGSTOP);
1278 let i20 = list.add(suspended);
1279 let now_current_job_index = list.current_job().unwrap();
1280 let now_previous_job_index = list.previous_job().unwrap();
1281 assert_eq!(now_current_job_index, i20);
1282 assert_eq!(now_previous_job_index, ex_current_job_index);
1283 }
1284
1285 #[test]
1286 fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1287 let mut list = JobList::default();
1288
1289 let running = Job::new(Pid(18));
1290 let i18 = list.add(running);
1291
1292 let mut suspended_1 = Job::new(Pid(19));
1293 suspended_1.state = ProcessState::stopped(SIGSTOP);
1294 let i19 = list.add(suspended_1);
1295
1296 let ex_current_job_index = list.current_job().unwrap();
1297 let ex_previous_job_index = list.previous_job().unwrap();
1298 assert_eq!(ex_current_job_index, i19);
1299 assert_eq!(ex_previous_job_index, i18);
1300
1301 let mut suspended_2 = Job::new(Pid(20));
1302 suspended_2.state = ProcessState::stopped(SIGSTOP);
1303 let i20 = list.add(suspended_2);
1304
1305 let now_current_job_index = list.current_job().unwrap();
1306 let now_previous_job_index = list.previous_job().unwrap();
1307 assert_eq!(now_current_job_index, ex_current_job_index);
1308 assert_eq!(now_previous_job_index, i20);
1309 }
1310
1311 #[test]
1312 fn removing_current_job() {
1313 let mut list = JobList::default();
1314
1315 let running = Job::new(Pid(10));
1316 let i10 = list.add(running);
1317
1318 let mut suspended_1 = Job::new(Pid(11));
1319 let mut suspended_2 = Job::new(Pid(12));
1320 let mut suspended_3 = Job::new(Pid(13));
1321 suspended_1.state = ProcessState::stopped(SIGSTOP);
1322 suspended_2.state = ProcessState::stopped(SIGSTOP);
1323 suspended_3.state = ProcessState::stopped(SIGSTOP);
1324 list.add(suspended_1);
1325 list.add(suspended_2);
1326 list.add(suspended_3);
1327
1328 let current_job_index_1 = list.current_job().unwrap();
1329 let previous_job_index_1 = list.previous_job().unwrap();
1330 assert_ne!(current_job_index_1, i10);
1331 assert_ne!(previous_job_index_1, i10);
1332
1333 list.remove(current_job_index_1);
1334 let current_job_index_2 = list.current_job().unwrap();
1335 let previous_job_index_2 = list.previous_job().unwrap();
1336 assert_eq!(current_job_index_2, previous_job_index_1);
1337 assert_ne!(previous_job_index_2, current_job_index_2);
1338 let previous_job_2 = &list[previous_job_index_2];
1340 assert!(
1341 previous_job_2.is_suspended(),
1342 "previous_job_2 = {previous_job_2:?}"
1343 );
1344
1345 list.remove(current_job_index_2);
1346 let current_job_index_3 = list.current_job().unwrap();
1347 let previous_job_index_3 = list.previous_job().unwrap();
1348 assert_eq!(current_job_index_3, previous_job_index_2);
1349 assert_eq!(previous_job_index_3, i10);
1351
1352 list.remove(current_job_index_3);
1353 let current_job_index_4 = list.current_job().unwrap();
1354 assert_eq!(current_job_index_4, i10);
1355 assert_eq!(list.previous_job(), None);
1357 }
1358
1359 #[test]
1360 fn removing_previous_job_with_suspended_job() {
1361 let mut list = JobList::default();
1362
1363 let running = Job::new(Pid(10));
1364 let i10 = list.add(running);
1365
1366 let mut suspended_1 = Job::new(Pid(11));
1367 let mut suspended_2 = Job::new(Pid(12));
1368 let mut suspended_3 = Job::new(Pid(13));
1369 suspended_1.state = ProcessState::stopped(SIGSTOP);
1370 suspended_2.state = ProcessState::stopped(SIGSTOP);
1371 suspended_3.state = ProcessState::stopped(SIGSTOP);
1372 list.add(suspended_1);
1373 list.add(suspended_2);
1374 list.add(suspended_3);
1375
1376 let ex_current_job_index = list.current_job().unwrap();
1377 let ex_previous_job_index = list.previous_job().unwrap();
1378 assert_ne!(ex_current_job_index, i10);
1379 assert_ne!(ex_previous_job_index, i10);
1380
1381 list.remove(ex_previous_job_index);
1382 let now_current_job_index = list.current_job().unwrap();
1383 let now_previous_job_index = list.previous_job().unwrap();
1384 assert_eq!(now_current_job_index, ex_current_job_index);
1385 assert_ne!(now_previous_job_index, now_current_job_index);
1386 let now_previous_job = &list[now_previous_job_index];
1388 assert!(
1389 now_previous_job.is_suspended(),
1390 "now_previous_job = {now_previous_job:?}"
1391 );
1392 }
1393
1394 #[test]
1395 fn removing_previous_job_with_running_job() {
1396 let mut list = JobList::default();
1397
1398 let running = Job::new(Pid(10));
1399 let i10 = list.add(running);
1400
1401 let mut suspended_1 = Job::new(Pid(11));
1402 let mut suspended_2 = Job::new(Pid(12));
1403 suspended_1.state = ProcessState::stopped(SIGSTOP);
1404 suspended_2.state = ProcessState::stopped(SIGSTOP);
1405 list.add(suspended_1);
1406 list.add(suspended_2);
1407
1408 let ex_current_job_index = list.current_job().unwrap();
1409 let ex_previous_job_index = list.previous_job().unwrap();
1410 assert_ne!(ex_current_job_index, i10);
1411 assert_ne!(ex_previous_job_index, i10);
1412
1413 list.remove(ex_previous_job_index);
1414 let now_current_job_index = list.current_job().unwrap();
1415 let now_previous_job_index = list.previous_job().unwrap();
1416 assert_eq!(now_current_job_index, ex_current_job_index);
1417 assert_eq!(now_previous_job_index, i10);
1420 }
1421
1422 #[test]
1423 fn set_current_job_with_running_jobs_only() {
1424 let mut list = JobList::default();
1425 let i21 = list.add(Job::new(Pid(21)));
1426 let i22 = list.add(Job::new(Pid(22)));
1427
1428 assert_eq!(list.set_current_job(i21), Ok(()));
1429 assert_eq!(list.current_job(), Some(i21));
1430
1431 assert_eq!(list.set_current_job(i22), Ok(()));
1432 assert_eq!(list.current_job(), Some(i22));
1433 }
1434
1435 #[test]
1436 fn set_current_job_to_suspended_job() {
1437 let mut list = JobList::default();
1438 list.add(Job::new(Pid(20)));
1439
1440 let mut suspended_1 = Job::new(Pid(21));
1441 let mut suspended_2 = Job::new(Pid(22));
1442 suspended_1.state = ProcessState::stopped(SIGSTOP);
1443 suspended_2.state = ProcessState::stopped(SIGSTOP);
1444 let i21 = list.add(suspended_1);
1445 let i22 = list.add(suspended_2);
1446
1447 assert_eq!(list.set_current_job(i21), Ok(()));
1448 assert_eq!(list.current_job(), Some(i21));
1449
1450 assert_eq!(list.set_current_job(i22), Ok(()));
1451 assert_eq!(list.current_job(), Some(i22));
1452 }
1453
1454 #[test]
1455 fn set_current_job_no_such_job() {
1456 let mut list = JobList::default();
1457 assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1458 assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1459 assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1460 }
1461
1462 #[test]
1463 fn set_current_job_not_suspended() {
1464 let mut list = JobList::default();
1465 let mut suspended = Job::new(Pid(10));
1466 suspended.state = ProcessState::stopped(SIGTSTP);
1467 let running = Job::new(Pid(20));
1468 let i10 = list.add(suspended);
1469 let i20 = list.add(running);
1470 assert_eq!(
1471 list.set_current_job(i20),
1472 Err(SetCurrentJobError::NotSuspended)
1473 );
1474 assert_eq!(list.current_job(), Some(i10));
1475 }
1476
1477 #[test]
1478 fn set_current_job_no_change() {
1479 let mut list = JobList::default();
1480 list.add(Job::new(Pid(5)));
1481 list.add(Job::new(Pid(6)));
1482 let old_current_job_index = list.current_job().unwrap();
1483 let old_previous_job_index = list.previous_job().unwrap();
1484 list.set_current_job(old_current_job_index).unwrap();
1485 let new_current_job_index = list.current_job().unwrap();
1486 let new_previous_job_index = list.previous_job().unwrap();
1487 assert_eq!(new_current_job_index, old_current_job_index);
1488 assert_eq!(new_previous_job_index, old_previous_job_index);
1489 }
1490
1491 #[test]
1492 fn resuming_current_job_without_other_suspended_jobs() {
1493 let mut list = JobList::default();
1494 let mut suspended = Job::new(Pid(10));
1495 suspended.state = ProcessState::stopped(SIGTSTP);
1496 let running = Job::new(Pid(20));
1497 let i10 = list.add(suspended);
1498 let i20 = list.add(running);
1499 list.update_status(Pid(10), ProcessState::Running);
1500 assert_eq!(list.current_job(), Some(i10));
1501 assert_eq!(list.previous_job(), Some(i20));
1502 }
1503
1504 #[test]
1505 fn resuming_current_job_with_another_suspended_job() {
1506 let mut list = JobList::default();
1507 let mut suspended_1 = Job::new(Pid(10));
1508 let mut suspended_2 = Job::new(Pid(20));
1509 suspended_1.state = ProcessState::stopped(SIGTSTP);
1510 suspended_2.state = ProcessState::stopped(SIGTSTP);
1511 let i10 = list.add(suspended_1);
1512 let i20 = list.add(suspended_2);
1513 list.set_current_job(i10).unwrap();
1514 list.update_status(Pid(10), ProcessState::Running);
1515 assert_eq!(list.current_job(), Some(i20));
1517 assert_eq!(list.previous_job(), Some(i10));
1518 }
1519
1520 #[test]
1521 fn resuming_current_job_with_other_suspended_jobs() {
1522 let mut list = JobList::default();
1523 let mut suspended_1 = Job::new(Pid(10));
1524 let mut suspended_2 = Job::new(Pid(20));
1525 let mut suspended_3 = Job::new(Pid(30));
1526 suspended_1.state = ProcessState::stopped(SIGTSTP);
1527 suspended_2.state = ProcessState::stopped(SIGTSTP);
1528 suspended_3.state = ProcessState::stopped(SIGTSTP);
1529 list.add(suspended_1);
1530 list.add(suspended_2);
1531 list.add(suspended_3);
1532 let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1533 let ex_previous_job_index = list.previous_job().unwrap();
1534
1535 list.update_status(ex_current_job_pid, ProcessState::Running);
1536 let now_current_job_index = list.current_job().unwrap();
1537 let now_previous_job_index = list.previous_job().unwrap();
1538 assert_eq!(now_current_job_index, ex_previous_job_index);
1539 assert_ne!(now_previous_job_index, now_current_job_index);
1540 let now_previous_job = &list[now_previous_job_index];
1542 assert!(
1543 now_previous_job.is_suspended(),
1544 "now_previous_job = {now_previous_job:?}"
1545 );
1546 }
1547
1548 #[test]
1549 fn resuming_previous_job() {
1550 let mut list = JobList::default();
1551 let mut suspended_1 = Job::new(Pid(10));
1552 let mut suspended_2 = Job::new(Pid(20));
1553 let mut suspended_3 = Job::new(Pid(30));
1554 suspended_1.state = ProcessState::stopped(SIGTSTP);
1555 suspended_2.state = ProcessState::stopped(SIGTSTP);
1556 suspended_3.state = ProcessState::stopped(SIGTSTP);
1557 list.add(suspended_1);
1558 list.add(suspended_2);
1559 list.add(suspended_3);
1560 let ex_current_job_index = list.current_job().unwrap();
1561 let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1562
1563 list.update_status(ex_previous_job_pid, ProcessState::Running);
1564 let now_current_job_index = list.current_job().unwrap();
1565 let now_previous_job_index = list.previous_job().unwrap();
1566 assert_eq!(now_current_job_index, ex_current_job_index);
1567 assert_ne!(now_previous_job_index, now_current_job_index);
1568 let now_previous_job = &list[now_previous_job_index];
1570 assert!(
1571 now_previous_job.is_suspended(),
1572 "now_previous_job = {now_previous_job:?}"
1573 );
1574 }
1575
1576 #[test]
1577 fn resuming_other_job() {
1578 let mut list = JobList::default();
1579 let mut suspended_1 = Job::new(Pid(10));
1580 let mut suspended_2 = Job::new(Pid(20));
1581 let mut suspended_3 = Job::new(Pid(30));
1582 suspended_1.state = ProcessState::stopped(SIGTSTP);
1583 suspended_2.state = ProcessState::stopped(SIGTSTP);
1584 suspended_3.state = ProcessState::stopped(SIGTSTP);
1585 let i10 = list.add(suspended_1);
1586 let i20 = list.add(suspended_2);
1587 let _i30 = list.add(suspended_3);
1588 list.set_current_job(i20).unwrap();
1589 list.set_current_job(i10).unwrap();
1590 list.update_status(Pid(30), ProcessState::Running);
1591 assert_eq!(list.current_job(), Some(i10));
1592 assert_eq!(list.previous_job(), Some(i20));
1593 }
1594
1595 #[test]
1596 fn suspending_current_job() {
1597 let mut list = JobList::default();
1598 let i11 = list.add(Job::new(Pid(11)));
1599 let i12 = list.add(Job::new(Pid(12)));
1600 list.set_current_job(i11).unwrap();
1601 list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1602 assert_eq!(list.current_job(), Some(i11));
1603 assert_eq!(list.previous_job(), Some(i12));
1604 }
1605
1606 #[test]
1607 fn suspending_previous_job() {
1608 let mut list = JobList::default();
1609 let i11 = list.add(Job::new(Pid(11)));
1610 let i12 = list.add(Job::new(Pid(12)));
1611 list.set_current_job(i11).unwrap();
1612 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1613 assert_eq!(list.current_job(), Some(i12));
1614 assert_eq!(list.previous_job(), Some(i11));
1615 }
1616
1617 #[test]
1618 fn suspending_job_with_running_current_job() {
1619 let mut list = JobList::default();
1620 let i10 = list.add(Job::new(Pid(10)));
1621 let _i11 = list.add(Job::new(Pid(11)));
1622 let i12 = list.add(Job::new(Pid(12)));
1623 list.set_current_job(i10).unwrap();
1624 list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1625 assert_eq!(list.current_job(), Some(i12));
1626 assert_eq!(list.previous_job(), Some(i10));
1627 }
1628
1629 #[test]
1630 fn suspending_job_with_running_previous_job() {
1631 let mut list = JobList::default();
1632 let i11 = list.add(Job::new(Pid(11)));
1633 let i12 = list.add(Job::new(Pid(12)));
1634 let mut suspended = Job::new(Pid(10));
1635 suspended.state = ProcessState::stopped(SIGTTIN);
1636 let i10 = list.add(suspended);
1637 assert_eq!(list.current_job(), Some(i10));
1638 assert_eq!(list.previous_job(), Some(i11));
1639
1640 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1641 assert_eq!(list.current_job(), Some(i12));
1642 assert_eq!(list.previous_job(), Some(i10));
1643 }
1644
1645 #[test]
1649 fn do_not_add_job_if_exited() {
1650 let mut env = Env::new_virtual();
1651 let result = add_job_if_suspended(
1652 &mut env,
1653 Pid(123),
1654 ProcessResult::Exited(ExitStatus(42)),
1655 || "foo".to_string(),
1656 );
1657 assert_eq!(result, Continue(ExitStatus(42)));
1658 assert_eq!(env.jobs.len(), 0);
1659 }
1660
1661 #[test]
1662 fn do_not_add_job_if_signaled() {
1663 let mut env = Env::new_virtual();
1664 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1665 let result = add_job_if_suspended(
1666 &mut env,
1667 Pid(123),
1668 ProcessResult::Signaled {
1669 signal,
1670 core_dump: false,
1671 },
1672 || "foo".to_string(),
1673 );
1674 assert_eq!(result, Continue(ExitStatus::from(signal)));
1675 assert_eq!(env.jobs.len(), 0);
1676 }
1677
1678 #[test]
1679 fn add_job_if_stopped() {
1680 let mut env = Env::new_virtual();
1681 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1682 let process_result = ProcessResult::Stopped(signal);
1683 let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1684 assert_eq!(result, Continue(ExitStatus::from(signal)));
1685 assert_eq!(env.jobs.len(), 1);
1686 let job = env.jobs.get(0).unwrap();
1687 assert_eq!(job.pid, Pid(123));
1688 assert!(job.job_controlled);
1689 assert_eq!(job.state, ProcessState::Halted(process_result));
1690 assert_eq!(job.name, "foo");
1691 }
1692
1693 #[test]
1694 fn break_if_stopped_and_interactive() {
1695 let mut env = Env::new_virtual();
1696 env.options.set(Interactive, On);
1697 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1698 let process_result = ProcessResult::Stopped(signal);
1699 let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1700 assert_eq!(
1701 result,
1702 Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1703 );
1704 assert_eq!(env.jobs.len(), 1);
1705 }
1706}