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<S, F>(
944 env: &mut Env<S>,
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;
971mod tcsetpgrp;
972
973pub use self::tcsetpgrp::*;
974
975#[cfg(test)]
976mod tests {
977 use super::*;
978 use crate::option::Option::Interactive;
979 use crate::option::State::On;
980 use crate::signal;
981 use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
982 use std::num::NonZero;
983
984 #[test]
985 fn job_list_find_by_pid() {
986 let mut list = JobList::default();
987 assert_eq!(list.find_by_pid(Pid(10)), None);
988
989 let i10 = list.add(Job::new(Pid(10)));
990 let i20 = list.add(Job::new(Pid(20)));
991 let i30 = list.add(Job::new(Pid(30)));
992 assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
993 assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
994 assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
995 assert_eq!(list.find_by_pid(Pid(40)), None);
996
997 list.remove(i10);
998 assert_eq!(list.find_by_pid(Pid(10)), None);
999 }
1000
1001 #[test]
1002 fn job_list_add_and_remove() {
1003 let mut list = JobList::default();
1005
1006 assert_eq!(list.add(Job::new(Pid(10))), 0);
1007 assert_eq!(list.add(Job::new(Pid(11))), 1);
1008 assert_eq!(list.add(Job::new(Pid(12))), 2);
1009
1010 assert_eq!(list.remove(0).unwrap().pid, Pid(10));
1011 assert_eq!(list.remove(1).unwrap().pid, Pid(11));
1012
1013 assert_eq!(list.add(Job::new(Pid(13))), 1);
1015 assert_eq!(list.add(Job::new(Pid(14))), 0);
1016
1017 assert_eq!(list.remove(0).unwrap().pid, Pid(14));
1018 assert_eq!(list.remove(1).unwrap().pid, Pid(13));
1019 assert_eq!(list.remove(2).unwrap().pid, Pid(12));
1020
1021 assert_eq!(list.add(Job::new(Pid(13))), 0);
1023 assert_eq!(list.add(Job::new(Pid(14))), 1);
1024 }
1025
1026 #[test]
1027 fn job_list_add_same_pid() {
1028 let mut list = JobList::default();
1029
1030 let mut job = Job::new(Pid(10));
1031 job.name = "first job".to_string();
1032 let i_first = list.add(job);
1033
1034 let mut job = Job::new(Pid(10));
1035 job.name = "second job".to_string();
1036 let i_second = list.add(job);
1037
1038 let job = &list[i_second];
1039 assert_eq!(job.pid, Pid(10));
1040 assert_eq!(job.name, "second job");
1041
1042 assert_ne!(
1043 list.get(i_first).map(|job| job.name.as_str()),
1044 Some("first job")
1045 );
1046 }
1047
1048 #[test]
1049 fn job_list_extract_if() {
1050 let mut list = JobList::default();
1051 let i21 = list.add(Job::new(Pid(21)));
1052 let i22 = list.add(Job::new(Pid(22)));
1053 let i23 = list.add(Job::new(Pid(23)));
1054 let i24 = list.add(Job::new(Pid(24)));
1055 let i25 = list.add(Job::new(Pid(25)));
1056 let i26 = list.add(Job::new(Pid(26)));
1057 list.remove(i23).unwrap();
1058
1059 let mut i = list.extract_if(|index, mut job| {
1060 assert_ne!(index, i23);
1061 if index % 2 == 0 {
1062 job.state_reported();
1063 }
1064 index == 0 || job.pid == Pid(26)
1065 });
1066
1067 let mut expected_job_21 = Job::new(Pid(21));
1068 expected_job_21.state_changed = false;
1069 assert_eq!(i.next(), Some((i21, expected_job_21)));
1070 assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1071 assert_eq!(i.next(), None);
1072 assert_eq!(i.next(), None); let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1075 assert_eq!(indices, [i22, i24, i25]);
1076 assert!(list[i22].state_changed);
1077 assert!(list[i24].state_changed);
1078 assert!(!list[i25].state_changed);
1079 }
1080
1081 #[test]
1082 #[allow(
1083 clippy::bool_assert_comparison,
1084 reason = "to make the expected values clearer"
1085 )]
1086 fn updating_job_status_without_expected_state() {
1087 let mut list = JobList::default();
1088 let state = ProcessState::exited(15);
1089 assert_eq!(list.update_status(Pid(20), state), None);
1090
1091 let i10 = list.add(Job::new(Pid(10)));
1092 let i20 = list.add(Job::new(Pid(20)));
1093 let i30 = list.add(Job::new(Pid(30)));
1094 assert_eq!(list[i20].state, ProcessState::Running);
1095
1096 list.get_mut(i20).unwrap().state_reported();
1097 assert_eq!(list[i20].state_changed, false);
1098
1099 assert_eq!(list.update_status(Pid(20), state), Some(i20));
1100 assert_eq!(list[i20].state, ProcessState::exited(15));
1101 assert_eq!(list[i20].state_changed, true);
1102
1103 assert_eq!(list[i10].state, ProcessState::Running);
1104 assert_eq!(list[i30].state, ProcessState::Running);
1105 }
1106
1107 #[test]
1108 #[allow(
1109 clippy::bool_assert_comparison,
1110 reason = "to make the expected values clearer"
1111 )]
1112 fn updating_job_status_with_matching_expected_state() {
1113 let mut list = JobList::default();
1114 let pid = Pid(20);
1115 let mut job = Job::new(pid);
1116 job.expected_state = Some(ProcessState::Running);
1117 job.state_changed = false;
1118 let i20 = list.add(job);
1119
1120 assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1121
1122 let job = &list[i20];
1123 assert_eq!(job.state, ProcessState::Running);
1124 assert_eq!(job.expected_state, None);
1125 assert_eq!(job.state_changed, false);
1126 }
1127
1128 #[test]
1129 #[allow(
1130 clippy::bool_assert_comparison,
1131 reason = "to make the expected values clearer"
1132 )]
1133 fn updating_job_status_with_unmatched_expected_state() {
1134 let mut list = JobList::default();
1135 let pid = Pid(20);
1136 let mut job = Job::new(pid);
1137 job.expected_state = Some(ProcessState::Running);
1138 job.state_changed = false;
1139 let i20 = list.add(job);
1140
1141 let result = list.update_status(pid, ProcessState::exited(0));
1142 assert_eq!(result, Some(i20));
1143
1144 let job = &list[i20];
1145 assert_eq!(job.state, ProcessState::exited(0));
1146 assert_eq!(job.expected_state, None);
1147 assert_eq!(job.state_changed, true);
1148 }
1149
1150 #[test]
1151 #[allow(
1152 clippy::bool_assert_comparison,
1153 reason = "to make the expected values clearer"
1154 )]
1155 fn disowning_jobs() {
1156 let mut list = JobList::default();
1157 let i10 = list.add(Job::new(Pid(10)));
1158 let i20 = list.add(Job::new(Pid(20)));
1159 let i30 = list.add(Job::new(Pid(30)));
1160
1161 list.disown_all();
1162
1163 assert_eq!(list[i10].is_owned, false);
1164 assert_eq!(list[i20].is_owned, false);
1165 assert_eq!(list[i30].is_owned, false);
1166 }
1167
1168 #[test]
1169 fn no_current_and_previous_job_in_empty_job_list() {
1170 let list = JobList::default();
1171 assert_eq!(list.current_job(), None);
1172 assert_eq!(list.previous_job(), None);
1173 }
1174
1175 #[test]
1176 fn current_and_previous_job_in_job_list_with_one_job() {
1177 let mut list = JobList::default();
1178 let i10 = list.add(Job::new(Pid(10)));
1179 assert_eq!(list.current_job(), Some(i10));
1180 assert_eq!(list.previous_job(), None);
1181 }
1182
1183 #[test]
1184 fn current_and_previous_job_in_job_list_with_two_job() {
1185 let mut list = JobList::default();
1188 let mut suspended = Job::new(Pid(10));
1189 suspended.state = ProcessState::stopped(SIGSTOP);
1190 let running = Job::new(Pid(20));
1191 let i10 = list.add(suspended.clone());
1192 let i20 = list.add(running.clone());
1193 assert_eq!(list.current_job(), Some(i10));
1194 assert_eq!(list.previous_job(), Some(i20));
1195
1196 list = JobList::default();
1198 let i20 = list.add(running);
1199 let i10 = list.add(suspended);
1200 assert_eq!(list.current_job(), Some(i10));
1201 assert_eq!(list.previous_job(), Some(i20));
1202 }
1203
1204 #[test]
1205 fn adding_suspended_job_with_running_current_and_previous_job() {
1206 let mut list = JobList::default();
1207 let running_1 = Job::new(Pid(11));
1208 let running_2 = Job::new(Pid(12));
1209 list.add(running_1);
1210 list.add(running_2);
1211 let ex_current_job_index = list.current_job().unwrap();
1212 let ex_previous_job_index = list.previous_job().unwrap();
1213 assert_ne!(ex_current_job_index, ex_previous_job_index);
1214
1215 let mut suspended = Job::new(Pid(20));
1216 suspended.state = ProcessState::stopped(SIGSTOP);
1217 let i20 = list.add(suspended);
1218 let now_current_job_index = list.current_job().unwrap();
1219 let now_previous_job_index = list.previous_job().unwrap();
1220 assert_eq!(now_current_job_index, i20);
1221 assert_eq!(now_previous_job_index, ex_current_job_index);
1222 }
1223
1224 #[test]
1225 fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1226 let mut list = JobList::default();
1227
1228 let running = Job::new(Pid(18));
1229 let i18 = list.add(running);
1230
1231 let mut suspended_1 = Job::new(Pid(19));
1232 suspended_1.state = ProcessState::stopped(SIGSTOP);
1233 let i19 = list.add(suspended_1);
1234
1235 let ex_current_job_index = list.current_job().unwrap();
1236 let ex_previous_job_index = list.previous_job().unwrap();
1237 assert_eq!(ex_current_job_index, i19);
1238 assert_eq!(ex_previous_job_index, i18);
1239
1240 let mut suspended_2 = Job::new(Pid(20));
1241 suspended_2.state = ProcessState::stopped(SIGSTOP);
1242 let i20 = list.add(suspended_2);
1243
1244 let now_current_job_index = list.current_job().unwrap();
1245 let now_previous_job_index = list.previous_job().unwrap();
1246 assert_eq!(now_current_job_index, ex_current_job_index);
1247 assert_eq!(now_previous_job_index, i20);
1248 }
1249
1250 #[test]
1251 fn removing_current_job() {
1252 let mut list = JobList::default();
1253
1254 let running = Job::new(Pid(10));
1255 let i10 = list.add(running);
1256
1257 let mut suspended_1 = Job::new(Pid(11));
1258 let mut suspended_2 = Job::new(Pid(12));
1259 let mut suspended_3 = Job::new(Pid(13));
1260 suspended_1.state = ProcessState::stopped(SIGSTOP);
1261 suspended_2.state = ProcessState::stopped(SIGSTOP);
1262 suspended_3.state = ProcessState::stopped(SIGSTOP);
1263 list.add(suspended_1);
1264 list.add(suspended_2);
1265 list.add(suspended_3);
1266
1267 let current_job_index_1 = list.current_job().unwrap();
1268 let previous_job_index_1 = list.previous_job().unwrap();
1269 assert_ne!(current_job_index_1, i10);
1270 assert_ne!(previous_job_index_1, i10);
1271
1272 list.remove(current_job_index_1);
1273 let current_job_index_2 = list.current_job().unwrap();
1274 let previous_job_index_2 = list.previous_job().unwrap();
1275 assert_eq!(current_job_index_2, previous_job_index_1);
1276 assert_ne!(previous_job_index_2, current_job_index_2);
1277 let previous_job_2 = &list[previous_job_index_2];
1279 assert!(
1280 previous_job_2.is_suspended(),
1281 "previous_job_2 = {previous_job_2:?}"
1282 );
1283
1284 list.remove(current_job_index_2);
1285 let current_job_index_3 = list.current_job().unwrap();
1286 let previous_job_index_3 = list.previous_job().unwrap();
1287 assert_eq!(current_job_index_3, previous_job_index_2);
1288 assert_eq!(previous_job_index_3, i10);
1290
1291 list.remove(current_job_index_3);
1292 let current_job_index_4 = list.current_job().unwrap();
1293 assert_eq!(current_job_index_4, i10);
1294 assert_eq!(list.previous_job(), None);
1296 }
1297
1298 #[test]
1299 fn removing_previous_job_with_suspended_job() {
1300 let mut list = JobList::default();
1301
1302 let running = Job::new(Pid(10));
1303 let i10 = list.add(running);
1304
1305 let mut suspended_1 = Job::new(Pid(11));
1306 let mut suspended_2 = Job::new(Pid(12));
1307 let mut suspended_3 = Job::new(Pid(13));
1308 suspended_1.state = ProcessState::stopped(SIGSTOP);
1309 suspended_2.state = ProcessState::stopped(SIGSTOP);
1310 suspended_3.state = ProcessState::stopped(SIGSTOP);
1311 list.add(suspended_1);
1312 list.add(suspended_2);
1313 list.add(suspended_3);
1314
1315 let ex_current_job_index = list.current_job().unwrap();
1316 let ex_previous_job_index = list.previous_job().unwrap();
1317 assert_ne!(ex_current_job_index, i10);
1318 assert_ne!(ex_previous_job_index, i10);
1319
1320 list.remove(ex_previous_job_index);
1321 let now_current_job_index = list.current_job().unwrap();
1322 let now_previous_job_index = list.previous_job().unwrap();
1323 assert_eq!(now_current_job_index, ex_current_job_index);
1324 assert_ne!(now_previous_job_index, now_current_job_index);
1325 let now_previous_job = &list[now_previous_job_index];
1327 assert!(
1328 now_previous_job.is_suspended(),
1329 "now_previous_job = {now_previous_job:?}"
1330 );
1331 }
1332
1333 #[test]
1334 fn removing_previous_job_with_running_job() {
1335 let mut list = JobList::default();
1336
1337 let running = Job::new(Pid(10));
1338 let i10 = list.add(running);
1339
1340 let mut suspended_1 = Job::new(Pid(11));
1341 let mut suspended_2 = Job::new(Pid(12));
1342 suspended_1.state = ProcessState::stopped(SIGSTOP);
1343 suspended_2.state = ProcessState::stopped(SIGSTOP);
1344 list.add(suspended_1);
1345 list.add(suspended_2);
1346
1347 let ex_current_job_index = list.current_job().unwrap();
1348 let ex_previous_job_index = list.previous_job().unwrap();
1349 assert_ne!(ex_current_job_index, i10);
1350 assert_ne!(ex_previous_job_index, i10);
1351
1352 list.remove(ex_previous_job_index);
1353 let now_current_job_index = list.current_job().unwrap();
1354 let now_previous_job_index = list.previous_job().unwrap();
1355 assert_eq!(now_current_job_index, ex_current_job_index);
1356 assert_eq!(now_previous_job_index, i10);
1359 }
1360
1361 #[test]
1362 fn set_current_job_with_running_jobs_only() {
1363 let mut list = JobList::default();
1364 let i21 = list.add(Job::new(Pid(21)));
1365 let i22 = list.add(Job::new(Pid(22)));
1366
1367 assert_eq!(list.set_current_job(i21), Ok(()));
1368 assert_eq!(list.current_job(), Some(i21));
1369
1370 assert_eq!(list.set_current_job(i22), Ok(()));
1371 assert_eq!(list.current_job(), Some(i22));
1372 }
1373
1374 #[test]
1375 fn set_current_job_to_suspended_job() {
1376 let mut list = JobList::default();
1377 list.add(Job::new(Pid(20)));
1378
1379 let mut suspended_1 = Job::new(Pid(21));
1380 let mut suspended_2 = Job::new(Pid(22));
1381 suspended_1.state = ProcessState::stopped(SIGSTOP);
1382 suspended_2.state = ProcessState::stopped(SIGSTOP);
1383 let i21 = list.add(suspended_1);
1384 let i22 = list.add(suspended_2);
1385
1386 assert_eq!(list.set_current_job(i21), Ok(()));
1387 assert_eq!(list.current_job(), Some(i21));
1388
1389 assert_eq!(list.set_current_job(i22), Ok(()));
1390 assert_eq!(list.current_job(), Some(i22));
1391 }
1392
1393 #[test]
1394 fn set_current_job_no_such_job() {
1395 let mut list = JobList::default();
1396 assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1397 assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1398 assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1399 }
1400
1401 #[test]
1402 fn set_current_job_not_suspended() {
1403 let mut list = JobList::default();
1404 let mut suspended = Job::new(Pid(10));
1405 suspended.state = ProcessState::stopped(SIGTSTP);
1406 let running = Job::new(Pid(20));
1407 let i10 = list.add(suspended);
1408 let i20 = list.add(running);
1409 assert_eq!(
1410 list.set_current_job(i20),
1411 Err(SetCurrentJobError::NotSuspended)
1412 );
1413 assert_eq!(list.current_job(), Some(i10));
1414 }
1415
1416 #[test]
1417 fn set_current_job_no_change() {
1418 let mut list = JobList::default();
1419 list.add(Job::new(Pid(5)));
1420 list.add(Job::new(Pid(6)));
1421 let old_current_job_index = list.current_job().unwrap();
1422 let old_previous_job_index = list.previous_job().unwrap();
1423 list.set_current_job(old_current_job_index).unwrap();
1424 let new_current_job_index = list.current_job().unwrap();
1425 let new_previous_job_index = list.previous_job().unwrap();
1426 assert_eq!(new_current_job_index, old_current_job_index);
1427 assert_eq!(new_previous_job_index, old_previous_job_index);
1428 }
1429
1430 #[test]
1431 fn resuming_current_job_without_other_suspended_jobs() {
1432 let mut list = JobList::default();
1433 let mut suspended = Job::new(Pid(10));
1434 suspended.state = ProcessState::stopped(SIGTSTP);
1435 let running = Job::new(Pid(20));
1436 let i10 = list.add(suspended);
1437 let i20 = list.add(running);
1438 list.update_status(Pid(10), ProcessState::Running);
1439 assert_eq!(list.current_job(), Some(i10));
1440 assert_eq!(list.previous_job(), Some(i20));
1441 }
1442
1443 #[test]
1444 fn resuming_current_job_with_another_suspended_job() {
1445 let mut list = JobList::default();
1446 let mut suspended_1 = Job::new(Pid(10));
1447 let mut suspended_2 = Job::new(Pid(20));
1448 suspended_1.state = ProcessState::stopped(SIGTSTP);
1449 suspended_2.state = ProcessState::stopped(SIGTSTP);
1450 let i10 = list.add(suspended_1);
1451 let i20 = list.add(suspended_2);
1452 list.set_current_job(i10).unwrap();
1453 list.update_status(Pid(10), ProcessState::Running);
1454 assert_eq!(list.current_job(), Some(i20));
1456 assert_eq!(list.previous_job(), Some(i10));
1457 }
1458
1459 #[test]
1460 fn resuming_current_job_with_other_suspended_jobs() {
1461 let mut list = JobList::default();
1462 let mut suspended_1 = Job::new(Pid(10));
1463 let mut suspended_2 = Job::new(Pid(20));
1464 let mut suspended_3 = Job::new(Pid(30));
1465 suspended_1.state = ProcessState::stopped(SIGTSTP);
1466 suspended_2.state = ProcessState::stopped(SIGTSTP);
1467 suspended_3.state = ProcessState::stopped(SIGTSTP);
1468 list.add(suspended_1);
1469 list.add(suspended_2);
1470 list.add(suspended_3);
1471 let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1472 let ex_previous_job_index = list.previous_job().unwrap();
1473
1474 list.update_status(ex_current_job_pid, ProcessState::Running);
1475 let now_current_job_index = list.current_job().unwrap();
1476 let now_previous_job_index = list.previous_job().unwrap();
1477 assert_eq!(now_current_job_index, ex_previous_job_index);
1478 assert_ne!(now_previous_job_index, now_current_job_index);
1479 let now_previous_job = &list[now_previous_job_index];
1481 assert!(
1482 now_previous_job.is_suspended(),
1483 "now_previous_job = {now_previous_job:?}"
1484 );
1485 }
1486
1487 #[test]
1488 fn resuming_previous_job() {
1489 let mut list = JobList::default();
1490 let mut suspended_1 = Job::new(Pid(10));
1491 let mut suspended_2 = Job::new(Pid(20));
1492 let mut suspended_3 = Job::new(Pid(30));
1493 suspended_1.state = ProcessState::stopped(SIGTSTP);
1494 suspended_2.state = ProcessState::stopped(SIGTSTP);
1495 suspended_3.state = ProcessState::stopped(SIGTSTP);
1496 list.add(suspended_1);
1497 list.add(suspended_2);
1498 list.add(suspended_3);
1499 let ex_current_job_index = list.current_job().unwrap();
1500 let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1501
1502 list.update_status(ex_previous_job_pid, ProcessState::Running);
1503 let now_current_job_index = list.current_job().unwrap();
1504 let now_previous_job_index = list.previous_job().unwrap();
1505 assert_eq!(now_current_job_index, ex_current_job_index);
1506 assert_ne!(now_previous_job_index, now_current_job_index);
1507 let now_previous_job = &list[now_previous_job_index];
1509 assert!(
1510 now_previous_job.is_suspended(),
1511 "now_previous_job = {now_previous_job:?}"
1512 );
1513 }
1514
1515 #[test]
1516 fn resuming_other_job() {
1517 let mut list = JobList::default();
1518 let mut suspended_1 = Job::new(Pid(10));
1519 let mut suspended_2 = Job::new(Pid(20));
1520 let mut suspended_3 = Job::new(Pid(30));
1521 suspended_1.state = ProcessState::stopped(SIGTSTP);
1522 suspended_2.state = ProcessState::stopped(SIGTSTP);
1523 suspended_3.state = ProcessState::stopped(SIGTSTP);
1524 let i10 = list.add(suspended_1);
1525 let i20 = list.add(suspended_2);
1526 let _i30 = list.add(suspended_3);
1527 list.set_current_job(i20).unwrap();
1528 list.set_current_job(i10).unwrap();
1529 list.update_status(Pid(30), ProcessState::Running);
1530 assert_eq!(list.current_job(), Some(i10));
1531 assert_eq!(list.previous_job(), Some(i20));
1532 }
1533
1534 #[test]
1535 fn suspending_current_job() {
1536 let mut list = JobList::default();
1537 let i11 = list.add(Job::new(Pid(11)));
1538 let i12 = list.add(Job::new(Pid(12)));
1539 list.set_current_job(i11).unwrap();
1540 list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1541 assert_eq!(list.current_job(), Some(i11));
1542 assert_eq!(list.previous_job(), Some(i12));
1543 }
1544
1545 #[test]
1546 fn suspending_previous_job() {
1547 let mut list = JobList::default();
1548 let i11 = list.add(Job::new(Pid(11)));
1549 let i12 = list.add(Job::new(Pid(12)));
1550 list.set_current_job(i11).unwrap();
1551 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1552 assert_eq!(list.current_job(), Some(i12));
1553 assert_eq!(list.previous_job(), Some(i11));
1554 }
1555
1556 #[test]
1557 fn suspending_job_with_running_current_job() {
1558 let mut list = JobList::default();
1559 let i10 = list.add(Job::new(Pid(10)));
1560 let _i11 = list.add(Job::new(Pid(11)));
1561 let i12 = list.add(Job::new(Pid(12)));
1562 list.set_current_job(i10).unwrap();
1563 list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1564 assert_eq!(list.current_job(), Some(i12));
1565 assert_eq!(list.previous_job(), Some(i10));
1566 }
1567
1568 #[test]
1569 fn suspending_job_with_running_previous_job() {
1570 let mut list = JobList::default();
1571 let i11 = list.add(Job::new(Pid(11)));
1572 let i12 = list.add(Job::new(Pid(12)));
1573 let mut suspended = Job::new(Pid(10));
1574 suspended.state = ProcessState::stopped(SIGTTIN);
1575 let i10 = list.add(suspended);
1576 assert_eq!(list.current_job(), Some(i10));
1577 assert_eq!(list.previous_job(), Some(i11));
1578
1579 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1580 assert_eq!(list.current_job(), Some(i12));
1581 assert_eq!(list.previous_job(), Some(i10));
1582 }
1583
1584 #[test]
1585 fn do_not_add_job_if_exited() {
1586 let mut env = Env::new_virtual();
1587 let result = add_job_if_suspended(
1588 &mut env,
1589 Pid(123),
1590 ProcessResult::Exited(ExitStatus(42)),
1591 || "foo".to_string(),
1592 );
1593 assert_eq!(result, Continue(ExitStatus(42)));
1594 assert_eq!(env.jobs.len(), 0);
1595 }
1596
1597 #[test]
1598 fn do_not_add_job_if_signaled() {
1599 let mut env = Env::new_virtual();
1600 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1601 let result = add_job_if_suspended(
1602 &mut env,
1603 Pid(123),
1604 ProcessResult::Signaled {
1605 signal,
1606 core_dump: false,
1607 },
1608 || "foo".to_string(),
1609 );
1610 assert_eq!(result, Continue(ExitStatus::from(signal)));
1611 assert_eq!(env.jobs.len(), 0);
1612 }
1613
1614 #[test]
1615 fn add_job_if_stopped() {
1616 let mut env = Env::new_virtual();
1617 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1618 let process_result = ProcessResult::Stopped(signal);
1619 let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1620 assert_eq!(result, Continue(ExitStatus::from(signal)));
1621 assert_eq!(env.jobs.len(), 1);
1622 let job = env.jobs.get(0).unwrap();
1623 assert_eq!(job.pid, Pid(123));
1624 assert!(job.job_controlled);
1625 assert_eq!(job.state, ProcessState::Halted(process_result));
1626 assert_eq!(job.name, "foo");
1627 }
1628
1629 #[test]
1630 fn break_if_stopped_and_interactive() {
1631 let mut env = Env::new_virtual();
1632 env.options.set(Interactive, On);
1633 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1634 let process_result = ProcessResult::Stopped(signal);
1635 let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1636 assert_eq!(
1637 result,
1638 Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1639 );
1640 assert_eq!(env.jobs.len(), 1);
1641 }
1642}