1use crate::Env;
45use crate::semantics::{Divert, ExitStatus};
46use crate::signal;
47use slab::Slab;
48use std::collections::HashMap;
49use std::iter::FusedIterator;
50use std::ops::ControlFlow::{Break, Continue};
51use std::ops::Deref;
52use thiserror::Error;
53
54#[cfg(unix)]
55type RawPidDef = libc::pid_t;
56#[cfg(not(unix))]
57type RawPidDef = i32;
58
59pub type RawPid = RawPidDef;
69
70#[repr(transparent)]
89#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
90pub struct Pid(pub RawPid);
91
92impl std::fmt::Display for Pid {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 self.0.fmt(f)
95 }
96}
97
98impl std::ops::Neg for Pid {
99 type Output = Self;
100 fn neg(self) -> Self {
101 Self(-self.0)
102 }
103}
104
105impl Pid {
106 pub const MY_PROCESS_GROUP: Self = Pid(0);
112
113 pub const ALL: Self = Pid(-1);
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
131pub enum ProcessResult {
132 Stopped(signal::Number),
134 Exited(ExitStatus),
136 Signaled {
138 signal: signal::Number,
139 core_dump: bool,
140 },
141}
142
143impl ProcessResult {
144 #[inline]
146 #[must_use]
147 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
148 Self::Exited(exit_status.into())
149 }
150
151 #[must_use]
153 pub fn is_stopped(&self) -> bool {
154 matches!(self, ProcessResult::Stopped(_))
155 }
156}
157
158impl From<ProcessResult> for ExitStatus {
160 fn from(result: ProcessResult) -> Self {
161 match result {
162 ProcessResult::Exited(exit_status) => exit_status,
163 ProcessResult::Stopped(signal) | ProcessResult::Signaled { signal, .. } => {
164 ExitStatus::from(signal)
165 }
166 }
167 }
168}
169
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub enum ProcessState {
181 Running,
183 Halted(ProcessResult),
185}
186
187impl ProcessState {
188 #[inline]
190 #[must_use]
191 pub fn stopped(signal: signal::Number) -> Self {
192 Self::Halted(ProcessResult::Stopped(signal))
193 }
194
195 #[inline]
197 #[must_use]
198 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
199 Self::Halted(ProcessResult::exited(exit_status))
200 }
201
202 #[must_use]
204 pub fn is_alive(&self) -> bool {
205 match self {
206 ProcessState::Running => true,
207 ProcessState::Halted(result) => result.is_stopped(),
208 }
209 }
210
211 #[must_use]
213 pub fn is_stopped(&self) -> bool {
214 matches!(self, Self::Halted(result) if result.is_stopped())
215 }
216}
217
218impl From<ProcessResult> for ProcessState {
219 #[inline]
220 fn from(result: ProcessResult) -> Self {
221 Self::Halted(result)
222 }
223}
224
225#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
229pub struct RunningProcess;
230
231impl TryFrom<ProcessState> for ExitStatus {
235 type Error = RunningProcess;
236 fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
237 match state {
238 ProcessState::Halted(result) => Ok(result.into()),
239 ProcessState::Running => Err(RunningProcess),
240 }
241 }
242}
243
244#[derive(Clone, Debug, Eq, PartialEq)]
251#[non_exhaustive]
252pub struct Job {
253 pub pid: Pid,
257
258 pub job_controlled: bool,
263
264 pub state: ProcessState,
266
267 pub expected_state: Option<ProcessState>,
271
272 pub state_changed: bool,
277
278 pub is_owned: bool,
284
285 pub name: String,
287}
288
289impl Job {
290 pub fn new(pid: Pid) -> Self {
295 Job {
296 pid,
297 job_controlled: false,
298 state: ProcessState::Running,
299 expected_state: None,
300 state_changed: true,
301 is_owned: true,
302 name: String::new(),
303 }
304 }
305
306 #[must_use]
308 fn is_suspended(&self) -> bool {
309 self.state.is_stopped()
310 }
311}
312
313#[derive(Debug, Eq, PartialEq)]
319pub struct JobRefMut<'a>(&'a mut Job);
320
321impl JobRefMut<'_> {
322 pub fn expect<S>(&mut self, state: S)
333 where
334 S: Into<Option<ProcessState>>,
335 {
336 self.0.expected_state = state.into();
337 }
338
339 pub fn state_reported(&mut self) {
344 self.0.state_changed = false
345 }
346}
347
348impl Deref for JobRefMut<'_> {
349 type Target = Job;
350 fn deref(&self) -> &Job {
351 self.0
352 }
353}
354
355#[derive(Clone, Debug)]
359pub struct Iter<'a>(slab::Iter<'a, Job>);
360
361impl<'a> Iterator for Iter<'a> {
362 type Item = (usize, &'a Job);
363
364 #[inline(always)]
365 fn next(&mut self) -> Option<(usize, &'a Job)> {
366 self.0.next()
367 }
368
369 #[inline(always)]
370 fn size_hint(&self) -> (usize, Option<usize>) {
371 self.0.size_hint()
372 }
373}
374
375impl<'a> DoubleEndedIterator for Iter<'a> {
376 #[inline(always)]
377 fn next_back(&mut self) -> Option<(usize, &'a Job)> {
378 self.0.next_back()
379 }
380}
381
382impl ExactSizeIterator for Iter<'_> {
383 #[inline(always)]
384 fn len(&self) -> usize {
385 self.0.len()
386 }
387}
388
389impl FusedIterator for Iter<'_> {}
390
391#[derive(Debug)]
395pub struct IterMut<'a>(slab::IterMut<'a, Job>);
396
397impl<'a> Iterator for IterMut<'a> {
398 type Item = (usize, JobRefMut<'a>);
399
400 #[inline]
401 fn next(&mut self) -> Option<(usize, JobRefMut<'a>)> {
402 self.0.next().map(|(index, job)| (index, JobRefMut(job)))
403 }
404
405 #[inline(always)]
406 fn size_hint(&self) -> (usize, Option<usize>) {
407 self.0.size_hint()
408 }
409}
410
411impl<'a> DoubleEndedIterator for IterMut<'a> {
412 fn next_back(&mut self) -> Option<(usize, JobRefMut<'a>)> {
413 self.0
414 .next_back()
415 .map(|(index, job)| (index, JobRefMut(job)))
416 }
417}
418
419impl ExactSizeIterator for IterMut<'_> {
420 #[inline(always)]
421 fn len(&self) -> usize {
422 self.0.len()
423 }
424}
425
426impl FusedIterator for IterMut<'_> {}
427
428#[derive(Clone, Debug)]
432pub struct JobList {
433 jobs: Slab<Job>,
435
436 pids_to_indices: HashMap<Pid, usize>,
440
441 current_job_index: usize,
443
444 previous_job_index: usize,
446
447 last_async_pid: Pid,
449}
450
451impl Default for JobList {
452 fn default() -> Self {
453 JobList {
454 jobs: Slab::new(),
455 pids_to_indices: HashMap::new(),
456 current_job_index: usize::default(),
457 previous_job_index: usize::default(),
458 last_async_pid: Pid(0),
459 }
460 }
461}
462
463impl JobList {
464 #[inline]
466 #[must_use]
467 pub fn new() -> Self {
468 Self::default()
469 }
470
471 #[inline]
475 pub fn get(&self, index: usize) -> Option<&Job> {
476 self.jobs.get(index)
477 }
478
479 #[inline]
483 pub fn get_mut(&mut self, index: usize) -> Option<JobRefMut<'_>> {
484 self.jobs.get_mut(index).map(JobRefMut)
485 }
486
487 #[inline]
489 pub fn len(&self) -> usize {
490 self.jobs.len()
491 }
492
493 #[inline]
495 pub fn is_empty(&self) -> bool {
496 self.len() == 0
497 }
498
499 #[inline]
504 pub fn iter(&self) -> Iter<'_> {
505 Iter(self.jobs.iter())
506 }
507
508 #[inline]
516 pub fn iter_mut(&mut self) -> IterMut<'_> {
517 IterMut(self.jobs.iter_mut())
518 }
519
520 pub fn find_by_pid(&self, pid: Pid) -> Option<usize> {
528 self.pids_to_indices.get(&pid).copied()
529 }
530}
531
532impl<'a> IntoIterator for &'a JobList {
533 type Item = (usize, &'a Job);
534 type IntoIter = Iter<'a>;
535 #[inline(always)]
536 fn into_iter(self) -> Iter<'a> {
537 self.iter()
538 }
539}
540
541impl<'a> IntoIterator for &'a mut JobList {
542 type Item = (usize, JobRefMut<'a>);
543 type IntoIter = IterMut<'a>;
544 #[inline(always)]
545 fn into_iter(self) -> IterMut<'a> {
546 self.iter_mut()
547 }
548}
549
550impl std::ops::Index<usize> for JobList {
552 type Output = Job;
553
554 fn index(&self, index: usize) -> &Job {
558 &self.jobs[index]
559 }
560}
561
562#[derive(Debug)]
566pub struct ExtractIf<'a, F>
567where
568 F: FnMut(usize, JobRefMut) -> bool,
569{
570 list: &'a mut JobList,
571 should_remove: F,
572 next_index: usize,
573 len: usize,
574}
575
576impl<F> Iterator for ExtractIf<'_, F>
577where
578 F: FnMut(usize, JobRefMut) -> bool,
579{
580 type Item = (usize, Job);
581
582 fn next(&mut self) -> Option<(usize, Job)> {
583 while self.len > 0 {
584 let index = self.next_index;
585 self.next_index += 1;
586 if let Some(job) = self.list.get_mut(index) {
587 self.len -= 1;
588 if (self.should_remove)(index, job) {
589 let job = self.list.remove(index).unwrap();
590 return Some((index, job));
591 }
592 }
593 }
594 None
595 }
596
597 fn size_hint(&self) -> (usize, Option<usize>) {
598 (0, Some(self.len))
599 }
600}
601
602impl<F> FusedIterator for ExtractIf<'_, F> where F: FnMut(usize, JobRefMut) -> bool {}
603
604impl JobList {
605 pub fn add(&mut self, job: Job) -> usize {
617 let new_job_is_suspended = job.is_suspended();
618 let ex_current_job_is_suspended =
619 self.current_job().map(|index| self[index].is_suspended());
620 let ex_previous_job_is_suspended =
621 self.previous_job().map(|index| self[index].is_suspended());
622
623 use std::collections::hash_map::Entry::*;
625 let index = match self.pids_to_indices.entry(job.pid) {
626 Vacant(entry) => {
627 let index = self.jobs.insert(job);
628 entry.insert(index);
629 index
630 }
631 Occupied(entry) => {
632 let index = *entry.get();
633 self.jobs[index] = job;
634 index
635 }
636 };
637 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
638
639 match ex_current_job_is_suspended {
641 None => self.current_job_index = index,
642 Some(false) if new_job_is_suspended => self.set_current_job(index).unwrap(),
643 Some(_) => match ex_previous_job_is_suspended {
644 None => self.previous_job_index = index,
645 Some(false) if new_job_is_suspended => self.previous_job_index = index,
646 Some(_) => (),
647 },
648 }
649
650 index
651 }
652
653 pub fn remove(&mut self, index: usize) -> Option<Job> {
664 let job = self.jobs.try_remove(index);
665
666 if let Some(job) = &job {
667 self.pids_to_indices.remove(&job.pid);
669
670 if self.jobs.is_empty() {
671 self.jobs.clear();
675 }
676
677 let previous_job_becomes_current_job = index == self.current_job_index;
679 if previous_job_becomes_current_job {
680 self.current_job_index = self.previous_job_index;
681 }
682 if previous_job_becomes_current_job || index == self.previous_job_index {
683 self.previous_job_index = self
684 .any_suspended_job_but_current()
685 .unwrap_or_else(|| self.any_job_but_current().unwrap_or_default());
686 }
687 }
688 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
689
690 job
691 }
692
693 pub fn remove_if<F>(&mut self, should_remove: F)
706 where
707 F: FnMut(usize, JobRefMut) -> bool,
708 {
709 self.extract_if(should_remove).for_each(drop)
710 }
711
712 pub fn extract_if<F>(&mut self, should_remove: F) -> ExtractIf<'_, F>
728 where
729 F: FnMut(usize, JobRefMut) -> bool,
730 {
731 let len = self.len();
732 ExtractIf {
733 list: self,
734 should_remove,
735 next_index: 0,
736 len,
737 }
738 }
739}
740
741impl JobList {
742 pub fn update_status(&mut self, pid: Pid, state: ProcessState) -> Option<usize> {
767 let index = self.find_by_pid(pid)?;
768
769 let job = &mut self.jobs[index];
771 let was_suspended = job.is_suspended();
772 job.state = state;
773 job.state_changed |= job.expected_state != Some(state);
774 job.expected_state = None;
775
776 if !was_suspended && job.is_suspended() {
778 if index != self.current_job_index {
779 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
780 }
781 } else if was_suspended && !job.is_suspended() {
782 if let Some(prev_index) = self.previous_job() {
783 let previous_job_becomes_current_job =
784 index == self.current_job_index && self[prev_index].is_suspended();
785 if previous_job_becomes_current_job {
786 self.current_job_index = prev_index;
787 }
788 if previous_job_becomes_current_job || index == prev_index {
789 self.previous_job_index = self.any_suspended_job_but_current().unwrap_or(index);
790 }
791 }
792 }
793
794 Some(index)
795 }
796
797 pub fn disown_all(&mut self) {
801 for (_, job) in &mut self.jobs {
802 job.is_owned = false;
803 }
804 }
805}
806
807#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
809pub enum SetCurrentJobError {
810 #[error("no such job")]
812 NoSuchJob,
813
814 #[error("the current job must be selected from suspended jobs")]
817 NotSuspended,
818}
819
820impl JobList {
821 pub fn set_current_job(&mut self, index: usize) -> Result<(), SetCurrentJobError> {
833 let job = self.get(index).ok_or(SetCurrentJobError::NoSuchJob)?;
834 if !job.is_suspended() && self.iter().any(|(_, job)| job.is_suspended()) {
835 return Err(SetCurrentJobError::NotSuspended);
836 }
837
838 if index != self.current_job_index {
839 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
840 }
841 Ok(())
842 }
843
844 pub fn current_job(&self) -> Option<usize> {
857 if self.jobs.contains(self.current_job_index) {
858 Some(self.current_job_index)
859 } else {
860 None
861 }
862 }
863
864 pub fn previous_job(&self) -> Option<usize> {
880 if self.previous_job_index != self.current_job_index
881 && self.jobs.contains(self.previous_job_index)
882 {
883 Some(self.previous_job_index)
884 } else {
885 None
886 }
887 }
888
889 fn any_suspended_job_but_current(&self) -> Option<usize> {
891 self.iter()
892 .filter(|&(index, job)| index != self.current_job_index && job.is_suspended())
893 .map(|(index, _)| index)
894 .next()
895 }
896
897 fn any_job_but_current(&self) -> Option<usize> {
899 self.iter()
900 .filter(|&(index, _)| index != self.current_job_index)
901 .map(|(index, _)| index)
902 .next()
903 }
904}
905
906impl JobList {
907 pub fn last_async_pid(&self) -> Pid {
914 self.last_async_pid
915 }
916
917 pub fn set_last_async_pid(&mut self, pid: Pid) {
922 self.last_async_pid = pid;
923 }
924}
925
926pub fn add_job_if_suspended<F>(
944 env: &mut Env,
945 pid: Pid,
946 result: ProcessResult,
947 name: F,
948) -> crate::semantics::Result<ExitStatus>
949where
950 F: FnOnce() -> String,
951{
952 let exit_status = result.into();
953
954 if result.is_stopped() {
955 let mut job = Job::new(pid);
956 job.job_controlled = true;
957 job.state = result.into();
958 job.name = name();
959 env.jobs.add(job);
960
961 if env.is_interactive() {
962 return Break(Divert::Interrupt(Some(exit_status)));
963 }
964 }
965
966 Continue(exit_status)
967}
968
969pub mod fmt;
970pub mod id;
971
972#[cfg(test)]
973mod tests {
974 use super::*;
975 use crate::option::Option::Interactive;
976 use crate::option::State::On;
977 use crate::signal;
978 use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
979 use std::num::NonZero;
980
981 #[test]
982 fn job_list_find_by_pid() {
983 let mut list = JobList::default();
984 assert_eq!(list.find_by_pid(Pid(10)), None);
985
986 let i10 = list.add(Job::new(Pid(10)));
987 let i20 = list.add(Job::new(Pid(20)));
988 let i30 = list.add(Job::new(Pid(30)));
989 assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
990 assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
991 assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
992 assert_eq!(list.find_by_pid(Pid(40)), None);
993
994 list.remove(i10);
995 assert_eq!(list.find_by_pid(Pid(10)), None);
996 }
997
998 #[test]
999 fn job_list_add_and_remove() {
1000 let mut list = JobList::default();
1002
1003 assert_eq!(list.add(Job::new(Pid(10))), 0);
1004 assert_eq!(list.add(Job::new(Pid(11))), 1);
1005 assert_eq!(list.add(Job::new(Pid(12))), 2);
1006
1007 assert_eq!(list.remove(0).unwrap().pid, Pid(10));
1008 assert_eq!(list.remove(1).unwrap().pid, Pid(11));
1009
1010 assert_eq!(list.add(Job::new(Pid(13))), 1);
1012 assert_eq!(list.add(Job::new(Pid(14))), 0);
1013
1014 assert_eq!(list.remove(0).unwrap().pid, Pid(14));
1015 assert_eq!(list.remove(1).unwrap().pid, Pid(13));
1016 assert_eq!(list.remove(2).unwrap().pid, Pid(12));
1017
1018 assert_eq!(list.add(Job::new(Pid(13))), 0);
1020 assert_eq!(list.add(Job::new(Pid(14))), 1);
1021 }
1022
1023 #[test]
1024 fn job_list_add_same_pid() {
1025 let mut list = JobList::default();
1026
1027 let mut job = Job::new(Pid(10));
1028 job.name = "first job".to_string();
1029 let i_first = list.add(job);
1030
1031 let mut job = Job::new(Pid(10));
1032 job.name = "second job".to_string();
1033 let i_second = list.add(job);
1034
1035 let job = &list[i_second];
1036 assert_eq!(job.pid, Pid(10));
1037 assert_eq!(job.name, "second job");
1038
1039 assert_ne!(
1040 list.get(i_first).map(|job| job.name.as_str()),
1041 Some("first job")
1042 );
1043 }
1044
1045 #[test]
1046 fn job_list_extract_if() {
1047 let mut list = JobList::default();
1048 let i21 = list.add(Job::new(Pid(21)));
1049 let i22 = list.add(Job::new(Pid(22)));
1050 let i23 = list.add(Job::new(Pid(23)));
1051 let i24 = list.add(Job::new(Pid(24)));
1052 let i25 = list.add(Job::new(Pid(25)));
1053 let i26 = list.add(Job::new(Pid(26)));
1054 list.remove(i23).unwrap();
1055
1056 let mut i = list.extract_if(|index, mut job| {
1057 assert_ne!(index, i23);
1058 if index % 2 == 0 {
1059 job.state_reported();
1060 }
1061 index == 0 || job.pid == Pid(26)
1062 });
1063
1064 let mut expected_job_21 = Job::new(Pid(21));
1065 expected_job_21.state_changed = false;
1066 assert_eq!(i.next(), Some((i21, expected_job_21)));
1067 assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1068 assert_eq!(i.next(), None);
1069 assert_eq!(i.next(), None); let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1072 assert_eq!(indices, [i22, i24, i25]);
1073 assert!(list[i22].state_changed);
1074 assert!(list[i24].state_changed);
1075 assert!(!list[i25].state_changed);
1076 }
1077
1078 #[test]
1079 #[allow(clippy::bool_assert_comparison)]
1080 fn updating_job_status_without_expected_state() {
1081 let mut list = JobList::default();
1082 let state = ProcessState::exited(15);
1083 assert_eq!(list.update_status(Pid(20), state), None);
1084
1085 let i10 = list.add(Job::new(Pid(10)));
1086 let i20 = list.add(Job::new(Pid(20)));
1087 let i30 = list.add(Job::new(Pid(30)));
1088 assert_eq!(list[i20].state, ProcessState::Running);
1089
1090 list.get_mut(i20).unwrap().state_reported();
1091 assert_eq!(list[i20].state_changed, false);
1092
1093 assert_eq!(list.update_status(Pid(20), state), Some(i20));
1094 assert_eq!(list[i20].state, ProcessState::exited(15));
1095 assert_eq!(list[i20].state_changed, true);
1096
1097 assert_eq!(list[i10].state, ProcessState::Running);
1098 assert_eq!(list[i30].state, ProcessState::Running);
1099 }
1100
1101 #[test]
1102 #[allow(clippy::bool_assert_comparison)]
1103 fn updating_job_status_with_matching_expected_state() {
1104 let mut list = JobList::default();
1105 let pid = Pid(20);
1106 let mut job = Job::new(pid);
1107 job.expected_state = Some(ProcessState::Running);
1108 job.state_changed = false;
1109 let i20 = list.add(job);
1110
1111 assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1112
1113 let job = &list[i20];
1114 assert_eq!(job.state, ProcessState::Running);
1115 assert_eq!(job.expected_state, None);
1116 assert_eq!(job.state_changed, false);
1117 }
1118
1119 #[test]
1120 #[allow(clippy::bool_assert_comparison)]
1121 fn updating_job_status_with_unmatched_expected_state() {
1122 let mut list = JobList::default();
1123 let pid = Pid(20);
1124 let mut job = Job::new(pid);
1125 job.expected_state = Some(ProcessState::Running);
1126 job.state_changed = false;
1127 let i20 = list.add(job);
1128
1129 let result = list.update_status(pid, ProcessState::exited(0));
1130 assert_eq!(result, Some(i20));
1131
1132 let job = &list[i20];
1133 assert_eq!(job.state, ProcessState::exited(0));
1134 assert_eq!(job.expected_state, None);
1135 assert_eq!(job.state_changed, true);
1136 }
1137
1138 #[test]
1139 #[allow(clippy::bool_assert_comparison)]
1140 fn disowning_jobs() {
1141 let mut list = JobList::default();
1142 let i10 = list.add(Job::new(Pid(10)));
1143 let i20 = list.add(Job::new(Pid(20)));
1144 let i30 = list.add(Job::new(Pid(30)));
1145
1146 list.disown_all();
1147
1148 assert_eq!(list[i10].is_owned, false);
1149 assert_eq!(list[i20].is_owned, false);
1150 assert_eq!(list[i30].is_owned, false);
1151 }
1152
1153 #[test]
1154 fn no_current_and_previous_job_in_empty_job_list() {
1155 let list = JobList::default();
1156 assert_eq!(list.current_job(), None);
1157 assert_eq!(list.previous_job(), None);
1158 }
1159
1160 #[test]
1161 fn current_and_previous_job_in_job_list_with_one_job() {
1162 let mut list = JobList::default();
1163 let i10 = list.add(Job::new(Pid(10)));
1164 assert_eq!(list.current_job(), Some(i10));
1165 assert_eq!(list.previous_job(), None);
1166 }
1167
1168 #[test]
1169 fn current_and_previous_job_in_job_list_with_two_job() {
1170 let mut list = JobList::default();
1173 let mut suspended = Job::new(Pid(10));
1174 suspended.state = ProcessState::stopped(SIGSTOP);
1175 let running = Job::new(Pid(20));
1176 let i10 = list.add(suspended.clone());
1177 let i20 = list.add(running.clone());
1178 assert_eq!(list.current_job(), Some(i10));
1179 assert_eq!(list.previous_job(), Some(i20));
1180
1181 list = JobList::default();
1183 let i20 = list.add(running);
1184 let i10 = list.add(suspended);
1185 assert_eq!(list.current_job(), Some(i10));
1186 assert_eq!(list.previous_job(), Some(i20));
1187 }
1188
1189 #[test]
1190 fn adding_suspended_job_with_running_current_and_previous_job() {
1191 let mut list = JobList::default();
1192 let running_1 = Job::new(Pid(11));
1193 let running_2 = Job::new(Pid(12));
1194 list.add(running_1);
1195 list.add(running_2);
1196 let ex_current_job_index = list.current_job().unwrap();
1197 let ex_previous_job_index = list.previous_job().unwrap();
1198 assert_ne!(ex_current_job_index, ex_previous_job_index);
1199
1200 let mut suspended = Job::new(Pid(20));
1201 suspended.state = ProcessState::stopped(SIGSTOP);
1202 let i20 = list.add(suspended);
1203 let now_current_job_index = list.current_job().unwrap();
1204 let now_previous_job_index = list.previous_job().unwrap();
1205 assert_eq!(now_current_job_index, i20);
1206 assert_eq!(now_previous_job_index, ex_current_job_index);
1207 }
1208
1209 #[test]
1210 fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1211 let mut list = JobList::default();
1212
1213 let running = Job::new(Pid(18));
1214 let i18 = list.add(running);
1215
1216 let mut suspended_1 = Job::new(Pid(19));
1217 suspended_1.state = ProcessState::stopped(SIGSTOP);
1218 let i19 = list.add(suspended_1);
1219
1220 let ex_current_job_index = list.current_job().unwrap();
1221 let ex_previous_job_index = list.previous_job().unwrap();
1222 assert_eq!(ex_current_job_index, i19);
1223 assert_eq!(ex_previous_job_index, i18);
1224
1225 let mut suspended_2 = Job::new(Pid(20));
1226 suspended_2.state = ProcessState::stopped(SIGSTOP);
1227 let i20 = list.add(suspended_2);
1228
1229 let now_current_job_index = list.current_job().unwrap();
1230 let now_previous_job_index = list.previous_job().unwrap();
1231 assert_eq!(now_current_job_index, ex_current_job_index);
1232 assert_eq!(now_previous_job_index, i20);
1233 }
1234
1235 #[test]
1236 fn removing_current_job() {
1237 let mut list = JobList::default();
1238
1239 let running = Job::new(Pid(10));
1240 let i10 = list.add(running);
1241
1242 let mut suspended_1 = Job::new(Pid(11));
1243 let mut suspended_2 = Job::new(Pid(12));
1244 let mut suspended_3 = Job::new(Pid(13));
1245 suspended_1.state = ProcessState::stopped(SIGSTOP);
1246 suspended_2.state = ProcessState::stopped(SIGSTOP);
1247 suspended_3.state = ProcessState::stopped(SIGSTOP);
1248 list.add(suspended_1);
1249 list.add(suspended_2);
1250 list.add(suspended_3);
1251
1252 let current_job_index_1 = list.current_job().unwrap();
1253 let previous_job_index_1 = list.previous_job().unwrap();
1254 assert_ne!(current_job_index_1, i10);
1255 assert_ne!(previous_job_index_1, i10);
1256
1257 list.remove(current_job_index_1);
1258 let current_job_index_2 = list.current_job().unwrap();
1259 let previous_job_index_2 = list.previous_job().unwrap();
1260 assert_eq!(current_job_index_2, previous_job_index_1);
1261 assert_ne!(previous_job_index_2, current_job_index_2);
1262 let previous_job_2 = &list[previous_job_index_2];
1264 assert!(
1265 previous_job_2.is_suspended(),
1266 "previous_job_2 = {previous_job_2:?}"
1267 );
1268
1269 list.remove(current_job_index_2);
1270 let current_job_index_3 = list.current_job().unwrap();
1271 let previous_job_index_3 = list.previous_job().unwrap();
1272 assert_eq!(current_job_index_3, previous_job_index_2);
1273 assert_eq!(previous_job_index_3, i10);
1275
1276 list.remove(current_job_index_3);
1277 let current_job_index_4 = list.current_job().unwrap();
1278 assert_eq!(current_job_index_4, i10);
1279 assert_eq!(list.previous_job(), None);
1281 }
1282
1283 #[test]
1284 fn removing_previous_job_with_suspended_job() {
1285 let mut list = JobList::default();
1286
1287 let running = Job::new(Pid(10));
1288 let i10 = list.add(running);
1289
1290 let mut suspended_1 = Job::new(Pid(11));
1291 let mut suspended_2 = Job::new(Pid(12));
1292 let mut suspended_3 = Job::new(Pid(13));
1293 suspended_1.state = ProcessState::stopped(SIGSTOP);
1294 suspended_2.state = ProcessState::stopped(SIGSTOP);
1295 suspended_3.state = ProcessState::stopped(SIGSTOP);
1296 list.add(suspended_1);
1297 list.add(suspended_2);
1298 list.add(suspended_3);
1299
1300 let ex_current_job_index = list.current_job().unwrap();
1301 let ex_previous_job_index = list.previous_job().unwrap();
1302 assert_ne!(ex_current_job_index, i10);
1303 assert_ne!(ex_previous_job_index, i10);
1304
1305 list.remove(ex_previous_job_index);
1306 let now_current_job_index = list.current_job().unwrap();
1307 let now_previous_job_index = list.previous_job().unwrap();
1308 assert_eq!(now_current_job_index, ex_current_job_index);
1309 assert_ne!(now_previous_job_index, now_current_job_index);
1310 let now_previous_job = &list[now_previous_job_index];
1312 assert!(
1313 now_previous_job.is_suspended(),
1314 "now_previous_job = {now_previous_job:?}"
1315 );
1316 }
1317
1318 #[test]
1319 fn removing_previous_job_with_running_job() {
1320 let mut list = JobList::default();
1321
1322 let running = Job::new(Pid(10));
1323 let i10 = list.add(running);
1324
1325 let mut suspended_1 = Job::new(Pid(11));
1326 let mut suspended_2 = Job::new(Pid(12));
1327 suspended_1.state = ProcessState::stopped(SIGSTOP);
1328 suspended_2.state = ProcessState::stopped(SIGSTOP);
1329 list.add(suspended_1);
1330 list.add(suspended_2);
1331
1332 let ex_current_job_index = list.current_job().unwrap();
1333 let ex_previous_job_index = list.previous_job().unwrap();
1334 assert_ne!(ex_current_job_index, i10);
1335 assert_ne!(ex_previous_job_index, i10);
1336
1337 list.remove(ex_previous_job_index);
1338 let now_current_job_index = list.current_job().unwrap();
1339 let now_previous_job_index = list.previous_job().unwrap();
1340 assert_eq!(now_current_job_index, ex_current_job_index);
1341 assert_eq!(now_previous_job_index, i10);
1344 }
1345
1346 #[test]
1347 fn set_current_job_with_running_jobs_only() {
1348 let mut list = JobList::default();
1349 let i21 = list.add(Job::new(Pid(21)));
1350 let i22 = list.add(Job::new(Pid(22)));
1351
1352 assert_eq!(list.set_current_job(i21), Ok(()));
1353 assert_eq!(list.current_job(), Some(i21));
1354
1355 assert_eq!(list.set_current_job(i22), Ok(()));
1356 assert_eq!(list.current_job(), Some(i22));
1357 }
1358
1359 #[test]
1360 fn set_current_job_to_suspended_job() {
1361 let mut list = JobList::default();
1362 list.add(Job::new(Pid(20)));
1363
1364 let mut suspended_1 = Job::new(Pid(21));
1365 let mut suspended_2 = Job::new(Pid(22));
1366 suspended_1.state = ProcessState::stopped(SIGSTOP);
1367 suspended_2.state = ProcessState::stopped(SIGSTOP);
1368 let i21 = list.add(suspended_1);
1369 let i22 = list.add(suspended_2);
1370
1371 assert_eq!(list.set_current_job(i21), Ok(()));
1372 assert_eq!(list.current_job(), Some(i21));
1373
1374 assert_eq!(list.set_current_job(i22), Ok(()));
1375 assert_eq!(list.current_job(), Some(i22));
1376 }
1377
1378 #[test]
1379 fn set_current_job_no_such_job() {
1380 let mut list = JobList::default();
1381 assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1382 assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1383 assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1384 }
1385
1386 #[test]
1387 fn set_current_job_not_suspended() {
1388 let mut list = JobList::default();
1389 let mut suspended = Job::new(Pid(10));
1390 suspended.state = ProcessState::stopped(SIGTSTP);
1391 let running = Job::new(Pid(20));
1392 let i10 = list.add(suspended);
1393 let i20 = list.add(running);
1394 assert_eq!(
1395 list.set_current_job(i20),
1396 Err(SetCurrentJobError::NotSuspended)
1397 );
1398 assert_eq!(list.current_job(), Some(i10));
1399 }
1400
1401 #[test]
1402 fn set_current_job_no_change() {
1403 let mut list = JobList::default();
1404 list.add(Job::new(Pid(5)));
1405 list.add(Job::new(Pid(6)));
1406 let old_current_job_index = list.current_job().unwrap();
1407 let old_previous_job_index = list.previous_job().unwrap();
1408 list.set_current_job(old_current_job_index).unwrap();
1409 let new_current_job_index = list.current_job().unwrap();
1410 let new_previous_job_index = list.previous_job().unwrap();
1411 assert_eq!(new_current_job_index, old_current_job_index);
1412 assert_eq!(new_previous_job_index, old_previous_job_index);
1413 }
1414
1415 #[test]
1416 fn resuming_current_job_without_other_suspended_jobs() {
1417 let mut list = JobList::default();
1418 let mut suspended = Job::new(Pid(10));
1419 suspended.state = ProcessState::stopped(SIGTSTP);
1420 let running = Job::new(Pid(20));
1421 let i10 = list.add(suspended);
1422 let i20 = list.add(running);
1423 list.update_status(Pid(10), ProcessState::Running);
1424 assert_eq!(list.current_job(), Some(i10));
1425 assert_eq!(list.previous_job(), Some(i20));
1426 }
1427
1428 #[test]
1429 fn resuming_current_job_with_another_suspended_job() {
1430 let mut list = JobList::default();
1431 let mut suspended_1 = Job::new(Pid(10));
1432 let mut suspended_2 = Job::new(Pid(20));
1433 suspended_1.state = ProcessState::stopped(SIGTSTP);
1434 suspended_2.state = ProcessState::stopped(SIGTSTP);
1435 let i10 = list.add(suspended_1);
1436 let i20 = list.add(suspended_2);
1437 list.set_current_job(i10).unwrap();
1438 list.update_status(Pid(10), ProcessState::Running);
1439 assert_eq!(list.current_job(), Some(i20));
1441 assert_eq!(list.previous_job(), Some(i10));
1442 }
1443
1444 #[test]
1445 fn resuming_current_job_with_other_suspended_jobs() {
1446 let mut list = JobList::default();
1447 let mut suspended_1 = Job::new(Pid(10));
1448 let mut suspended_2 = Job::new(Pid(20));
1449 let mut suspended_3 = Job::new(Pid(30));
1450 suspended_1.state = ProcessState::stopped(SIGTSTP);
1451 suspended_2.state = ProcessState::stopped(SIGTSTP);
1452 suspended_3.state = ProcessState::stopped(SIGTSTP);
1453 list.add(suspended_1);
1454 list.add(suspended_2);
1455 list.add(suspended_3);
1456 let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1457 let ex_previous_job_index = list.previous_job().unwrap();
1458
1459 list.update_status(ex_current_job_pid, ProcessState::Running);
1460 let now_current_job_index = list.current_job().unwrap();
1461 let now_previous_job_index = list.previous_job().unwrap();
1462 assert_eq!(now_current_job_index, ex_previous_job_index);
1463 assert_ne!(now_previous_job_index, now_current_job_index);
1464 let now_previous_job = &list[now_previous_job_index];
1466 assert!(
1467 now_previous_job.is_suspended(),
1468 "now_previous_job = {now_previous_job:?}"
1469 );
1470 }
1471
1472 #[test]
1473 fn resuming_previous_job() {
1474 let mut list = JobList::default();
1475 let mut suspended_1 = Job::new(Pid(10));
1476 let mut suspended_2 = Job::new(Pid(20));
1477 let mut suspended_3 = Job::new(Pid(30));
1478 suspended_1.state = ProcessState::stopped(SIGTSTP);
1479 suspended_2.state = ProcessState::stopped(SIGTSTP);
1480 suspended_3.state = ProcessState::stopped(SIGTSTP);
1481 list.add(suspended_1);
1482 list.add(suspended_2);
1483 list.add(suspended_3);
1484 let ex_current_job_index = list.current_job().unwrap();
1485 let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1486
1487 list.update_status(ex_previous_job_pid, ProcessState::Running);
1488 let now_current_job_index = list.current_job().unwrap();
1489 let now_previous_job_index = list.previous_job().unwrap();
1490 assert_eq!(now_current_job_index, ex_current_job_index);
1491 assert_ne!(now_previous_job_index, now_current_job_index);
1492 let now_previous_job = &list[now_previous_job_index];
1494 assert!(
1495 now_previous_job.is_suspended(),
1496 "now_previous_job = {now_previous_job:?}"
1497 );
1498 }
1499
1500 #[test]
1501 fn resuming_other_job() {
1502 let mut list = JobList::default();
1503 let mut suspended_1 = Job::new(Pid(10));
1504 let mut suspended_2 = Job::new(Pid(20));
1505 let mut suspended_3 = Job::new(Pid(30));
1506 suspended_1.state = ProcessState::stopped(SIGTSTP);
1507 suspended_2.state = ProcessState::stopped(SIGTSTP);
1508 suspended_3.state = ProcessState::stopped(SIGTSTP);
1509 let i10 = list.add(suspended_1);
1510 let i20 = list.add(suspended_2);
1511 let _i30 = list.add(suspended_3);
1512 list.set_current_job(i20).unwrap();
1513 list.set_current_job(i10).unwrap();
1514 list.update_status(Pid(30), ProcessState::Running);
1515 assert_eq!(list.current_job(), Some(i10));
1516 assert_eq!(list.previous_job(), Some(i20));
1517 }
1518
1519 #[test]
1520 fn suspending_current_job() {
1521 let mut list = JobList::default();
1522 let i11 = list.add(Job::new(Pid(11)));
1523 let i12 = list.add(Job::new(Pid(12)));
1524 list.set_current_job(i11).unwrap();
1525 list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1526 assert_eq!(list.current_job(), Some(i11));
1527 assert_eq!(list.previous_job(), Some(i12));
1528 }
1529
1530 #[test]
1531 fn suspending_previous_job() {
1532 let mut list = JobList::default();
1533 let i11 = list.add(Job::new(Pid(11)));
1534 let i12 = list.add(Job::new(Pid(12)));
1535 list.set_current_job(i11).unwrap();
1536 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1537 assert_eq!(list.current_job(), Some(i12));
1538 assert_eq!(list.previous_job(), Some(i11));
1539 }
1540
1541 #[test]
1542 fn suspending_job_with_running_current_job() {
1543 let mut list = JobList::default();
1544 let i10 = list.add(Job::new(Pid(10)));
1545 let _i11 = list.add(Job::new(Pid(11)));
1546 let i12 = list.add(Job::new(Pid(12)));
1547 list.set_current_job(i10).unwrap();
1548 list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1549 assert_eq!(list.current_job(), Some(i12));
1550 assert_eq!(list.previous_job(), Some(i10));
1551 }
1552
1553 #[test]
1554 fn suspending_job_with_running_previous_job() {
1555 let mut list = JobList::default();
1556 let i11 = list.add(Job::new(Pid(11)));
1557 let i12 = list.add(Job::new(Pid(12)));
1558 let mut suspended = Job::new(Pid(10));
1559 suspended.state = ProcessState::stopped(SIGTTIN);
1560 let i10 = list.add(suspended);
1561 assert_eq!(list.current_job(), Some(i10));
1562 assert_eq!(list.previous_job(), Some(i11));
1563
1564 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1565 assert_eq!(list.current_job(), Some(i12));
1566 assert_eq!(list.previous_job(), Some(i10));
1567 }
1568
1569 #[test]
1570 fn do_not_add_job_if_exited() {
1571 let mut env = Env::new_virtual();
1572 let result = add_job_if_suspended(
1573 &mut env,
1574 Pid(123),
1575 ProcessResult::Exited(ExitStatus(42)),
1576 || "foo".to_string(),
1577 );
1578 assert_eq!(result, Continue(ExitStatus(42)));
1579 assert_eq!(env.jobs.len(), 0);
1580 }
1581
1582 #[test]
1583 fn do_not_add_job_if_signaled() {
1584 let mut env = Env::new_virtual();
1585 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1586 let result = add_job_if_suspended(
1587 &mut env,
1588 Pid(123),
1589 ProcessResult::Signaled {
1590 signal,
1591 core_dump: false,
1592 },
1593 || "foo".to_string(),
1594 );
1595 assert_eq!(result, Continue(ExitStatus::from(signal)));
1596 assert_eq!(env.jobs.len(), 0);
1597 }
1598
1599 #[test]
1600 fn add_job_if_stopped() {
1601 let mut env = Env::new_virtual();
1602 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1603 let process_result = ProcessResult::Stopped(signal);
1604 let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1605 assert_eq!(result, Continue(ExitStatus::from(signal)));
1606 assert_eq!(env.jobs.len(), 1);
1607 let job = env.jobs.get(0).unwrap();
1608 assert_eq!(job.pid, Pid(123));
1609 assert!(job.job_controlled);
1610 assert_eq!(job.state, ProcessState::Halted(process_result));
1611 assert_eq!(job.name, "foo");
1612 }
1613
1614 #[test]
1615 fn break_if_stopped_and_interactive() {
1616 let mut env = Env::new_virtual();
1617 env.options.set(Interactive, On);
1618 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1619 let process_result = ProcessResult::Stopped(signal);
1620 let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1621 assert_eq!(
1622 result,
1623 Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1624 );
1625 assert_eq!(env.jobs.len(), 1);
1626 }
1627}