1use crate::semantics::ExitStatus;
45use crate::signal;
46use slab::Slab;
47use std::collections::HashMap;
48use std::iter::FusedIterator;
49use std::ops::Deref;
50use thiserror::Error;
51
52#[cfg(unix)]
53type RawPidDef = libc::pid_t;
54#[cfg(not(unix))]
55type RawPidDef = i32;
56
57pub type RawPid = RawPidDef;
67
68#[repr(transparent)]
87#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
88pub struct Pid(pub RawPid);
89
90impl std::fmt::Display for Pid {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 self.0.fmt(f)
93 }
94}
95
96impl std::ops::Neg for Pid {
97 type Output = Self;
98 fn neg(self) -> Self {
99 Self(-self.0)
100 }
101}
102
103impl Pid {
104 pub const MY_PROCESS_GROUP: Self = Pid(0);
110
111 pub const ALL: Self = Pid(-1);
117}
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum ProcessResult {
130 Stopped(signal::Number),
132 Exited(ExitStatus),
134 Signaled {
136 signal: signal::Number,
137 core_dump: bool,
138 },
139}
140
141impl ProcessResult {
142 #[inline]
144 #[must_use]
145 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
146 Self::Exited(exit_status.into())
147 }
148
149 #[must_use]
151 pub fn is_stopped(&self) -> bool {
152 matches!(self, ProcessResult::Stopped(_))
153 }
154}
155
156impl From<ProcessResult> for ExitStatus {
158 fn from(result: ProcessResult) -> Self {
159 match result {
160 ProcessResult::Exited(exit_status) => exit_status,
161 ProcessResult::Stopped(signal) | ProcessResult::Signaled { signal, .. } => {
162 ExitStatus::from(signal)
163 }
164 }
165 }
166}
167
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub enum ProcessState {
179 Running,
181 Halted(ProcessResult),
183}
184
185impl ProcessState {
186 #[inline]
188 #[must_use]
189 pub fn stopped(signal: signal::Number) -> Self {
190 Self::Halted(ProcessResult::Stopped(signal))
191 }
192
193 #[inline]
195 #[must_use]
196 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
197 Self::Halted(ProcessResult::exited(exit_status))
198 }
199
200 #[must_use]
202 pub fn is_alive(&self) -> bool {
203 match self {
204 ProcessState::Running => true,
205 ProcessState::Halted(result) => result.is_stopped(),
206 }
207 }
208
209 #[must_use]
211 pub fn is_stopped(&self) -> bool {
212 matches!(self, Self::Halted(result) if result.is_stopped())
213 }
214}
215
216impl From<ProcessResult> for ProcessState {
217 #[inline]
218 fn from(result: ProcessResult) -> Self {
219 Self::Halted(result)
220 }
221}
222
223#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
227pub struct RunningProcess;
228
229impl TryFrom<ProcessState> for ExitStatus {
233 type Error = RunningProcess;
234 fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
235 match state {
236 ProcessState::Halted(result) => Ok(result.into()),
237 ProcessState::Running => Err(RunningProcess),
238 }
239 }
240}
241
242#[derive(Clone, Debug, Eq, PartialEq)]
249#[non_exhaustive]
250pub struct Job {
251 pub pid: Pid,
255
256 pub job_controlled: bool,
261
262 pub state: ProcessState,
264
265 pub expected_state: Option<ProcessState>,
269
270 pub state_changed: bool,
275
276 pub is_owned: bool,
282
283 pub name: String,
285}
286
287impl Job {
288 pub fn new(pid: Pid) -> Self {
293 Job {
294 pid,
295 job_controlled: false,
296 state: ProcessState::Running,
297 expected_state: None,
298 state_changed: true,
299 is_owned: true,
300 name: String::new(),
301 }
302 }
303
304 #[must_use]
306 fn is_suspended(&self) -> bool {
307 self.state.is_stopped()
308 }
309}
310
311#[derive(Debug, Eq, PartialEq)]
317pub struct JobRefMut<'a>(&'a mut Job);
318
319impl JobRefMut<'_> {
320 pub fn expect<S>(&mut self, state: S)
331 where
332 S: Into<Option<ProcessState>>,
333 {
334 self.0.expected_state = state.into();
335 }
336
337 pub fn state_reported(&mut self) {
342 self.0.state_changed = false
343 }
344}
345
346impl Deref for JobRefMut<'_> {
347 type Target = Job;
348 fn deref(&self) -> &Job {
349 self.0
350 }
351}
352
353#[derive(Clone, Debug)]
357pub struct Iter<'a>(slab::Iter<'a, Job>);
358
359impl<'a> Iterator for Iter<'a> {
360 type Item = (usize, &'a Job);
361
362 #[inline(always)]
363 fn next(&mut self) -> Option<(usize, &'a Job)> {
364 self.0.next()
365 }
366
367 #[inline(always)]
368 fn size_hint(&self) -> (usize, Option<usize>) {
369 self.0.size_hint()
370 }
371}
372
373impl<'a> DoubleEndedIterator for Iter<'a> {
374 #[inline(always)]
375 fn next_back(&mut self) -> Option<(usize, &'a Job)> {
376 self.0.next_back()
377 }
378}
379
380impl ExactSizeIterator for Iter<'_> {
381 #[inline(always)]
382 fn len(&self) -> usize {
383 self.0.len()
384 }
385}
386
387impl FusedIterator for Iter<'_> {}
388
389#[derive(Debug)]
393pub struct IterMut<'a>(slab::IterMut<'a, Job>);
394
395impl<'a> Iterator for IterMut<'a> {
396 type Item = (usize, JobRefMut<'a>);
397
398 #[inline]
399 fn next(&mut self) -> Option<(usize, JobRefMut<'a>)> {
400 self.0.next().map(|(index, job)| (index, JobRefMut(job)))
401 }
402
403 #[inline(always)]
404 fn size_hint(&self) -> (usize, Option<usize>) {
405 self.0.size_hint()
406 }
407}
408
409impl<'a> DoubleEndedIterator for IterMut<'a> {
410 fn next_back(&mut self) -> Option<(usize, JobRefMut<'a>)> {
411 self.0
412 .next_back()
413 .map(|(index, job)| (index, JobRefMut(job)))
414 }
415}
416
417impl ExactSizeIterator for IterMut<'_> {
418 #[inline(always)]
419 fn len(&self) -> usize {
420 self.0.len()
421 }
422}
423
424impl FusedIterator for IterMut<'_> {}
425
426#[derive(Clone, Debug)]
430pub struct JobList {
431 jobs: Slab<Job>,
433
434 pids_to_indices: HashMap<Pid, usize>,
438
439 current_job_index: usize,
441
442 previous_job_index: usize,
444
445 last_async_pid: Pid,
447}
448
449impl Default for JobList {
450 fn default() -> Self {
451 JobList {
452 jobs: Slab::new(),
453 pids_to_indices: HashMap::new(),
454 current_job_index: usize::default(),
455 previous_job_index: usize::default(),
456 last_async_pid: Pid(0),
457 }
458 }
459}
460
461impl JobList {
462 #[inline]
464 #[must_use]
465 pub fn new() -> Self {
466 Self::default()
467 }
468
469 #[inline]
473 pub fn get(&self, index: usize) -> Option<&Job> {
474 self.jobs.get(index)
475 }
476
477 #[inline]
481 pub fn get_mut(&mut self, index: usize) -> Option<JobRefMut<'_>> {
482 self.jobs.get_mut(index).map(JobRefMut)
483 }
484
485 #[inline]
487 pub fn len(&self) -> usize {
488 self.jobs.len()
489 }
490
491 #[inline]
493 pub fn is_empty(&self) -> bool {
494 self.len() == 0
495 }
496
497 #[inline]
502 pub fn iter(&self) -> Iter<'_> {
503 Iter(self.jobs.iter())
504 }
505
506 #[inline]
514 pub fn iter_mut(&mut self) -> IterMut<'_> {
515 IterMut(self.jobs.iter_mut())
516 }
517
518 pub fn find_by_pid(&self, pid: Pid) -> Option<usize> {
526 self.pids_to_indices.get(&pid).copied()
527 }
528}
529
530impl<'a> IntoIterator for &'a JobList {
531 type Item = (usize, &'a Job);
532 type IntoIter = Iter<'a>;
533 #[inline(always)]
534 fn into_iter(self) -> Iter<'a> {
535 self.iter()
536 }
537}
538
539impl<'a> IntoIterator for &'a mut JobList {
540 type Item = (usize, JobRefMut<'a>);
541 type IntoIter = IterMut<'a>;
542 #[inline(always)]
543 fn into_iter(self) -> IterMut<'a> {
544 self.iter_mut()
545 }
546}
547
548impl std::ops::Index<usize> for JobList {
550 type Output = Job;
551
552 fn index(&self, index: usize) -> &Job {
556 &self.jobs[index]
557 }
558}
559
560#[derive(Debug)]
564pub struct ExtractIf<'a, F>
565where
566 F: FnMut(usize, JobRefMut) -> bool,
567{
568 list: &'a mut JobList,
569 should_remove: F,
570 next_index: usize,
571 len: usize,
572}
573
574impl<F> Iterator for ExtractIf<'_, F>
575where
576 F: FnMut(usize, JobRefMut) -> bool,
577{
578 type Item = (usize, Job);
579
580 fn next(&mut self) -> Option<(usize, Job)> {
581 while self.len > 0 {
582 let index = self.next_index;
583 self.next_index += 1;
584 if let Some(job) = self.list.get_mut(index) {
585 self.len -= 1;
586 if (self.should_remove)(index, job) {
587 let job = self.list.remove(index).unwrap();
588 return Some((index, job));
589 }
590 }
591 }
592 None
593 }
594
595 fn size_hint(&self) -> (usize, Option<usize>) {
596 (0, Some(self.len))
597 }
598}
599
600impl<F> FusedIterator for ExtractIf<'_, F> where F: FnMut(usize, JobRefMut) -> bool {}
601
602impl JobList {
603 pub fn add(&mut self, job: Job) -> usize {
615 let new_job_is_suspended = job.is_suspended();
616 let ex_current_job_is_suspended =
617 self.current_job().map(|index| self[index].is_suspended());
618 let ex_previous_job_is_suspended =
619 self.previous_job().map(|index| self[index].is_suspended());
620
621 use std::collections::hash_map::Entry::*;
623 let index = match self.pids_to_indices.entry(job.pid) {
624 Vacant(entry) => {
625 let index = self.jobs.insert(job);
626 entry.insert(index);
627 index
628 }
629 Occupied(entry) => {
630 let index = *entry.get();
631 self.jobs[index] = job;
632 index
633 }
634 };
635 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
636
637 match ex_current_job_is_suspended {
639 None => self.current_job_index = index,
640 Some(false) if new_job_is_suspended => self.set_current_job(index).unwrap(),
641 Some(_) => match ex_previous_job_is_suspended {
642 None => self.previous_job_index = index,
643 Some(false) if new_job_is_suspended => self.previous_job_index = index,
644 Some(_) => (),
645 },
646 }
647
648 index
649 }
650
651 pub fn remove(&mut self, index: usize) -> Option<Job> {
662 let job = self.jobs.try_remove(index);
663
664 if let Some(job) = &job {
665 self.pids_to_indices.remove(&job.pid);
667
668 if self.jobs.is_empty() {
669 self.jobs.clear();
673 }
674
675 let previous_job_becomes_current_job = index == self.current_job_index;
677 if previous_job_becomes_current_job {
678 self.current_job_index = self.previous_job_index;
679 }
680 if previous_job_becomes_current_job || index == self.previous_job_index {
681 self.previous_job_index = self
682 .any_suspended_job_but_current()
683 .unwrap_or_else(|| self.any_job_but_current().unwrap_or_default());
684 }
685 }
686 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
687
688 job
689 }
690
691 pub fn remove_if<F>(&mut self, should_remove: F)
704 where
705 F: FnMut(usize, JobRefMut) -> bool,
706 {
707 self.extract_if(should_remove).for_each(drop)
708 }
709
710 pub fn extract_if<F>(&mut self, should_remove: F) -> ExtractIf<'_, F>
726 where
727 F: FnMut(usize, JobRefMut) -> bool,
728 {
729 let len = self.len();
730 ExtractIf {
731 list: self,
732 should_remove,
733 next_index: 0,
734 len,
735 }
736 }
737}
738
739impl JobList {
740 pub fn update_status(&mut self, pid: Pid, state: ProcessState) -> Option<usize> {
765 let index = self.find_by_pid(pid)?;
766
767 let job = &mut self.jobs[index];
769 let was_suspended = job.is_suspended();
770 job.state = state;
771 job.state_changed |= job.expected_state != Some(state);
772 job.expected_state = None;
773
774 if !was_suspended && job.is_suspended() {
776 if index != self.current_job_index {
777 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
778 }
779 } else if was_suspended && !job.is_suspended() {
780 if let Some(prev_index) = self.previous_job() {
781 let previous_job_becomes_current_job =
782 index == self.current_job_index && self[prev_index].is_suspended();
783 if previous_job_becomes_current_job {
784 self.current_job_index = prev_index;
785 }
786 if previous_job_becomes_current_job || index == prev_index {
787 self.previous_job_index = self.any_suspended_job_but_current().unwrap_or(index);
788 }
789 }
790 }
791
792 Some(index)
793 }
794
795 pub fn disown_all(&mut self) {
799 for (_, job) in &mut self.jobs {
800 job.is_owned = false;
801 }
802 }
803}
804
805#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
807pub enum SetCurrentJobError {
808 #[error("no such job")]
810 NoSuchJob,
811
812 #[error("the current job must be selected from suspended jobs")]
815 NotSuspended,
816}
817
818impl JobList {
819 pub fn set_current_job(&mut self, index: usize) -> Result<(), SetCurrentJobError> {
831 let job = self.get(index).ok_or(SetCurrentJobError::NoSuchJob)?;
832 if !job.is_suspended() && self.iter().any(|(_, job)| job.is_suspended()) {
833 return Err(SetCurrentJobError::NotSuspended);
834 }
835
836 if index != self.current_job_index {
837 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
838 }
839 Ok(())
840 }
841
842 pub fn current_job(&self) -> Option<usize> {
855 if self.jobs.contains(self.current_job_index) {
856 Some(self.current_job_index)
857 } else {
858 None
859 }
860 }
861
862 pub fn previous_job(&self) -> Option<usize> {
878 if self.previous_job_index != self.current_job_index
879 && self.jobs.contains(self.previous_job_index)
880 {
881 Some(self.previous_job_index)
882 } else {
883 None
884 }
885 }
886
887 fn any_suspended_job_but_current(&self) -> Option<usize> {
889 self.iter()
890 .filter(|&(index, job)| index != self.current_job_index && job.is_suspended())
891 .map(|(index, _)| index)
892 .next()
893 }
894
895 fn any_job_but_current(&self) -> Option<usize> {
897 self.iter()
898 .filter(|&(index, _)| index != self.current_job_index)
899 .map(|(index, _)| index)
900 .next()
901 }
902}
903
904impl JobList {
905 pub fn last_async_pid(&self) -> Pid {
912 self.last_async_pid
913 }
914
915 pub fn set_last_async_pid(&mut self, pid: Pid) {
920 self.last_async_pid = pid;
921 }
922}
923
924pub mod fmt;
925pub mod id;
926
927#[cfg(test)]
928mod tests {
929 use super::*;
930 use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
931
932 #[test]
933 fn job_list_find_by_pid() {
934 let mut list = JobList::default();
935 assert_eq!(list.find_by_pid(Pid(10)), None);
936
937 let i10 = list.add(Job::new(Pid(10)));
938 let i20 = list.add(Job::new(Pid(20)));
939 let i30 = list.add(Job::new(Pid(30)));
940 assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
941 assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
942 assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
943 assert_eq!(list.find_by_pid(Pid(40)), None);
944
945 list.remove(i10);
946 assert_eq!(list.find_by_pid(Pid(10)), None);
947 }
948
949 #[test]
950 fn job_list_add_and_remove() {
951 let mut list = JobList::default();
953
954 assert_eq!(list.add(Job::new(Pid(10))), 0);
955 assert_eq!(list.add(Job::new(Pid(11))), 1);
956 assert_eq!(list.add(Job::new(Pid(12))), 2);
957
958 assert_eq!(list.remove(0).unwrap().pid, Pid(10));
959 assert_eq!(list.remove(1).unwrap().pid, Pid(11));
960
961 assert_eq!(list.add(Job::new(Pid(13))), 1);
963 assert_eq!(list.add(Job::new(Pid(14))), 0);
964
965 assert_eq!(list.remove(0).unwrap().pid, Pid(14));
966 assert_eq!(list.remove(1).unwrap().pid, Pid(13));
967 assert_eq!(list.remove(2).unwrap().pid, Pid(12));
968
969 assert_eq!(list.add(Job::new(Pid(13))), 0);
971 assert_eq!(list.add(Job::new(Pid(14))), 1);
972 }
973
974 #[test]
975 fn job_list_add_same_pid() {
976 let mut list = JobList::default();
977
978 let mut job = Job::new(Pid(10));
979 job.name = "first job".to_string();
980 let i_first = list.add(job);
981
982 let mut job = Job::new(Pid(10));
983 job.name = "second job".to_string();
984 let i_second = list.add(job);
985
986 let job = &list[i_second];
987 assert_eq!(job.pid, Pid(10));
988 assert_eq!(job.name, "second job");
989
990 assert_ne!(
991 list.get(i_first).map(|job| job.name.as_str()),
992 Some("first job")
993 );
994 }
995
996 #[test]
997 fn job_list_extract_if() {
998 let mut list = JobList::default();
999 let i21 = list.add(Job::new(Pid(21)));
1000 let i22 = list.add(Job::new(Pid(22)));
1001 let i23 = list.add(Job::new(Pid(23)));
1002 let i24 = list.add(Job::new(Pid(24)));
1003 let i25 = list.add(Job::new(Pid(25)));
1004 let i26 = list.add(Job::new(Pid(26)));
1005 list.remove(i23).unwrap();
1006
1007 let mut i = list.extract_if(|index, mut job| {
1008 assert_ne!(index, i23);
1009 if index % 2 == 0 {
1010 job.state_reported();
1011 }
1012 index == 0 || job.pid == Pid(26)
1013 });
1014
1015 let mut expected_job_21 = Job::new(Pid(21));
1016 expected_job_21.state_changed = false;
1017 assert_eq!(i.next(), Some((i21, expected_job_21)));
1018 assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1019 assert_eq!(i.next(), None);
1020 assert_eq!(i.next(), None); let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1023 assert_eq!(indices, [i22, i24, i25]);
1024 assert!(list[i22].state_changed);
1025 assert!(list[i24].state_changed);
1026 assert!(!list[i25].state_changed);
1027 }
1028
1029 #[test]
1030 #[allow(clippy::bool_assert_comparison)]
1031 fn updating_job_status_without_expected_state() {
1032 let mut list = JobList::default();
1033 let state = ProcessState::exited(15);
1034 assert_eq!(list.update_status(Pid(20), state), None);
1035
1036 let i10 = list.add(Job::new(Pid(10)));
1037 let i20 = list.add(Job::new(Pid(20)));
1038 let i30 = list.add(Job::new(Pid(30)));
1039 assert_eq!(list[i20].state, ProcessState::Running);
1040
1041 list.get_mut(i20).unwrap().state_reported();
1042 assert_eq!(list[i20].state_changed, false);
1043
1044 assert_eq!(list.update_status(Pid(20), state), Some(i20));
1045 assert_eq!(list[i20].state, ProcessState::exited(15));
1046 assert_eq!(list[i20].state_changed, true);
1047
1048 assert_eq!(list[i10].state, ProcessState::Running);
1049 assert_eq!(list[i30].state, ProcessState::Running);
1050 }
1051
1052 #[test]
1053 #[allow(clippy::bool_assert_comparison)]
1054 fn updating_job_status_with_matching_expected_state() {
1055 let mut list = JobList::default();
1056 let pid = Pid(20);
1057 let mut job = Job::new(pid);
1058 job.expected_state = Some(ProcessState::Running);
1059 job.state_changed = false;
1060 let i20 = list.add(job);
1061
1062 assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1063
1064 let job = &list[i20];
1065 assert_eq!(job.state, ProcessState::Running);
1066 assert_eq!(job.expected_state, None);
1067 assert_eq!(job.state_changed, false);
1068 }
1069
1070 #[test]
1071 #[allow(clippy::bool_assert_comparison)]
1072 fn updating_job_status_with_unmatched_expected_state() {
1073 let mut list = JobList::default();
1074 let pid = Pid(20);
1075 let mut job = Job::new(pid);
1076 job.expected_state = Some(ProcessState::Running);
1077 job.state_changed = false;
1078 let i20 = list.add(job);
1079
1080 let result = list.update_status(pid, ProcessState::exited(0));
1081 assert_eq!(result, Some(i20));
1082
1083 let job = &list[i20];
1084 assert_eq!(job.state, ProcessState::exited(0));
1085 assert_eq!(job.expected_state, None);
1086 assert_eq!(job.state_changed, true);
1087 }
1088
1089 #[test]
1090 #[allow(clippy::bool_assert_comparison)]
1091 fn disowning_jobs() {
1092 let mut list = JobList::default();
1093 let i10 = list.add(Job::new(Pid(10)));
1094 let i20 = list.add(Job::new(Pid(20)));
1095 let i30 = list.add(Job::new(Pid(30)));
1096
1097 list.disown_all();
1098
1099 assert_eq!(list[i10].is_owned, false);
1100 assert_eq!(list[i20].is_owned, false);
1101 assert_eq!(list[i30].is_owned, false);
1102 }
1103
1104 #[test]
1105 fn no_current_and_previous_job_in_empty_job_list() {
1106 let list = JobList::default();
1107 assert_eq!(list.current_job(), None);
1108 assert_eq!(list.previous_job(), None);
1109 }
1110
1111 #[test]
1112 fn current_and_previous_job_in_job_list_with_one_job() {
1113 let mut list = JobList::default();
1114 let i10 = list.add(Job::new(Pid(10)));
1115 assert_eq!(list.current_job(), Some(i10));
1116 assert_eq!(list.previous_job(), None);
1117 }
1118
1119 #[test]
1120 fn current_and_previous_job_in_job_list_with_two_job() {
1121 let mut list = JobList::default();
1124 let mut suspended = Job::new(Pid(10));
1125 suspended.state = ProcessState::stopped(SIGSTOP);
1126 let running = Job::new(Pid(20));
1127 let i10 = list.add(suspended.clone());
1128 let i20 = list.add(running.clone());
1129 assert_eq!(list.current_job(), Some(i10));
1130 assert_eq!(list.previous_job(), Some(i20));
1131
1132 list = JobList::default();
1134 let i20 = list.add(running);
1135 let i10 = list.add(suspended);
1136 assert_eq!(list.current_job(), Some(i10));
1137 assert_eq!(list.previous_job(), Some(i20));
1138 }
1139
1140 #[test]
1141 fn adding_suspended_job_with_running_current_and_previous_job() {
1142 let mut list = JobList::default();
1143 let running_1 = Job::new(Pid(11));
1144 let running_2 = Job::new(Pid(12));
1145 list.add(running_1);
1146 list.add(running_2);
1147 let ex_current_job_index = list.current_job().unwrap();
1148 let ex_previous_job_index = list.previous_job().unwrap();
1149 assert_ne!(ex_current_job_index, ex_previous_job_index);
1150
1151 let mut suspended = Job::new(Pid(20));
1152 suspended.state = ProcessState::stopped(SIGSTOP);
1153 let i20 = list.add(suspended);
1154 let now_current_job_index = list.current_job().unwrap();
1155 let now_previous_job_index = list.previous_job().unwrap();
1156 assert_eq!(now_current_job_index, i20);
1157 assert_eq!(now_previous_job_index, ex_current_job_index);
1158 }
1159
1160 #[test]
1161 fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1162 let mut list = JobList::default();
1163
1164 let running = Job::new(Pid(18));
1165 let i18 = list.add(running);
1166
1167 let mut suspended_1 = Job::new(Pid(19));
1168 suspended_1.state = ProcessState::stopped(SIGSTOP);
1169 let i19 = list.add(suspended_1);
1170
1171 let ex_current_job_index = list.current_job().unwrap();
1172 let ex_previous_job_index = list.previous_job().unwrap();
1173 assert_eq!(ex_current_job_index, i19);
1174 assert_eq!(ex_previous_job_index, i18);
1175
1176 let mut suspended_2 = Job::new(Pid(20));
1177 suspended_2.state = ProcessState::stopped(SIGSTOP);
1178 let i20 = list.add(suspended_2);
1179
1180 let now_current_job_index = list.current_job().unwrap();
1181 let now_previous_job_index = list.previous_job().unwrap();
1182 assert_eq!(now_current_job_index, ex_current_job_index);
1183 assert_eq!(now_previous_job_index, i20);
1184 }
1185
1186 #[test]
1187 fn removing_current_job() {
1188 let mut list = JobList::default();
1189
1190 let running = Job::new(Pid(10));
1191 let i10 = list.add(running);
1192
1193 let mut suspended_1 = Job::new(Pid(11));
1194 let mut suspended_2 = Job::new(Pid(12));
1195 let mut suspended_3 = Job::new(Pid(13));
1196 suspended_1.state = ProcessState::stopped(SIGSTOP);
1197 suspended_2.state = ProcessState::stopped(SIGSTOP);
1198 suspended_3.state = ProcessState::stopped(SIGSTOP);
1199 list.add(suspended_1);
1200 list.add(suspended_2);
1201 list.add(suspended_3);
1202
1203 let current_job_index_1 = list.current_job().unwrap();
1204 let previous_job_index_1 = list.previous_job().unwrap();
1205 assert_ne!(current_job_index_1, i10);
1206 assert_ne!(previous_job_index_1, i10);
1207
1208 list.remove(current_job_index_1);
1209 let current_job_index_2 = list.current_job().unwrap();
1210 let previous_job_index_2 = list.previous_job().unwrap();
1211 assert_eq!(current_job_index_2, previous_job_index_1);
1212 assert_ne!(previous_job_index_2, current_job_index_2);
1213 let previous_job_2 = &list[previous_job_index_2];
1215 assert!(
1216 previous_job_2.is_suspended(),
1217 "previous_job_2 = {previous_job_2:?}"
1218 );
1219
1220 list.remove(current_job_index_2);
1221 let current_job_index_3 = list.current_job().unwrap();
1222 let previous_job_index_3 = list.previous_job().unwrap();
1223 assert_eq!(current_job_index_3, previous_job_index_2);
1224 assert_eq!(previous_job_index_3, i10);
1226
1227 list.remove(current_job_index_3);
1228 let current_job_index_4 = list.current_job().unwrap();
1229 assert_eq!(current_job_index_4, i10);
1230 assert_eq!(list.previous_job(), None);
1232 }
1233
1234 #[test]
1235 fn removing_previous_job_with_suspended_job() {
1236 let mut list = JobList::default();
1237
1238 let running = Job::new(Pid(10));
1239 let i10 = list.add(running);
1240
1241 let mut suspended_1 = Job::new(Pid(11));
1242 let mut suspended_2 = Job::new(Pid(12));
1243 let mut suspended_3 = Job::new(Pid(13));
1244 suspended_1.state = ProcessState::stopped(SIGSTOP);
1245 suspended_2.state = ProcessState::stopped(SIGSTOP);
1246 suspended_3.state = ProcessState::stopped(SIGSTOP);
1247 list.add(suspended_1);
1248 list.add(suspended_2);
1249 list.add(suspended_3);
1250
1251 let ex_current_job_index = list.current_job().unwrap();
1252 let ex_previous_job_index = list.previous_job().unwrap();
1253 assert_ne!(ex_current_job_index, i10);
1254 assert_ne!(ex_previous_job_index, i10);
1255
1256 list.remove(ex_previous_job_index);
1257 let now_current_job_index = list.current_job().unwrap();
1258 let now_previous_job_index = list.previous_job().unwrap();
1259 assert_eq!(now_current_job_index, ex_current_job_index);
1260 assert_ne!(now_previous_job_index, now_current_job_index);
1261 let now_previous_job = &list[now_previous_job_index];
1263 assert!(
1264 now_previous_job.is_suspended(),
1265 "now_previous_job = {now_previous_job:?}"
1266 );
1267 }
1268
1269 #[test]
1270 fn removing_previous_job_with_running_job() {
1271 let mut list = JobList::default();
1272
1273 let running = Job::new(Pid(10));
1274 let i10 = list.add(running);
1275
1276 let mut suspended_1 = Job::new(Pid(11));
1277 let mut suspended_2 = Job::new(Pid(12));
1278 suspended_1.state = ProcessState::stopped(SIGSTOP);
1279 suspended_2.state = ProcessState::stopped(SIGSTOP);
1280 list.add(suspended_1);
1281 list.add(suspended_2);
1282
1283 let ex_current_job_index = list.current_job().unwrap();
1284 let ex_previous_job_index = list.previous_job().unwrap();
1285 assert_ne!(ex_current_job_index, i10);
1286 assert_ne!(ex_previous_job_index, i10);
1287
1288 list.remove(ex_previous_job_index);
1289 let now_current_job_index = list.current_job().unwrap();
1290 let now_previous_job_index = list.previous_job().unwrap();
1291 assert_eq!(now_current_job_index, ex_current_job_index);
1292 assert_eq!(now_previous_job_index, i10);
1295 }
1296
1297 #[test]
1298 fn set_current_job_with_running_jobs_only() {
1299 let mut list = JobList::default();
1300 let i21 = list.add(Job::new(Pid(21)));
1301 let i22 = list.add(Job::new(Pid(22)));
1302
1303 assert_eq!(list.set_current_job(i21), Ok(()));
1304 assert_eq!(list.current_job(), Some(i21));
1305
1306 assert_eq!(list.set_current_job(i22), Ok(()));
1307 assert_eq!(list.current_job(), Some(i22));
1308 }
1309
1310 #[test]
1311 fn set_current_job_to_suspended_job() {
1312 let mut list = JobList::default();
1313 list.add(Job::new(Pid(20)));
1314
1315 let mut suspended_1 = Job::new(Pid(21));
1316 let mut suspended_2 = Job::new(Pid(22));
1317 suspended_1.state = ProcessState::stopped(SIGSTOP);
1318 suspended_2.state = ProcessState::stopped(SIGSTOP);
1319 let i21 = list.add(suspended_1);
1320 let i22 = list.add(suspended_2);
1321
1322 assert_eq!(list.set_current_job(i21), Ok(()));
1323 assert_eq!(list.current_job(), Some(i21));
1324
1325 assert_eq!(list.set_current_job(i22), Ok(()));
1326 assert_eq!(list.current_job(), Some(i22));
1327 }
1328
1329 #[test]
1330 fn set_current_job_no_such_job() {
1331 let mut list = JobList::default();
1332 assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1333 assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1334 assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1335 }
1336
1337 #[test]
1338 fn set_current_job_not_suspended() {
1339 let mut list = JobList::default();
1340 let mut suspended = Job::new(Pid(10));
1341 suspended.state = ProcessState::stopped(SIGTSTP);
1342 let running = Job::new(Pid(20));
1343 let i10 = list.add(suspended);
1344 let i20 = list.add(running);
1345 assert_eq!(
1346 list.set_current_job(i20),
1347 Err(SetCurrentJobError::NotSuspended)
1348 );
1349 assert_eq!(list.current_job(), Some(i10));
1350 }
1351
1352 #[test]
1353 fn set_current_job_no_change() {
1354 let mut list = JobList::default();
1355 list.add(Job::new(Pid(5)));
1356 list.add(Job::new(Pid(6)));
1357 let old_current_job_index = list.current_job().unwrap();
1358 let old_previous_job_index = list.previous_job().unwrap();
1359 list.set_current_job(old_current_job_index).unwrap();
1360 let new_current_job_index = list.current_job().unwrap();
1361 let new_previous_job_index = list.previous_job().unwrap();
1362 assert_eq!(new_current_job_index, old_current_job_index);
1363 assert_eq!(new_previous_job_index, old_previous_job_index);
1364 }
1365
1366 #[test]
1367 fn resuming_current_job_without_other_suspended_jobs() {
1368 let mut list = JobList::default();
1369 let mut suspended = Job::new(Pid(10));
1370 suspended.state = ProcessState::stopped(SIGTSTP);
1371 let running = Job::new(Pid(20));
1372 let i10 = list.add(suspended);
1373 let i20 = list.add(running);
1374 list.update_status(Pid(10), ProcessState::Running);
1375 assert_eq!(list.current_job(), Some(i10));
1376 assert_eq!(list.previous_job(), Some(i20));
1377 }
1378
1379 #[test]
1380 fn resuming_current_job_with_another_suspended_job() {
1381 let mut list = JobList::default();
1382 let mut suspended_1 = Job::new(Pid(10));
1383 let mut suspended_2 = Job::new(Pid(20));
1384 suspended_1.state = ProcessState::stopped(SIGTSTP);
1385 suspended_2.state = ProcessState::stopped(SIGTSTP);
1386 let i10 = list.add(suspended_1);
1387 let i20 = list.add(suspended_2);
1388 list.set_current_job(i10).unwrap();
1389 list.update_status(Pid(10), ProcessState::Running);
1390 assert_eq!(list.current_job(), Some(i20));
1392 assert_eq!(list.previous_job(), Some(i10));
1393 }
1394
1395 #[test]
1396 fn resuming_current_job_with_other_suspended_jobs() {
1397 let mut list = JobList::default();
1398 let mut suspended_1 = Job::new(Pid(10));
1399 let mut suspended_2 = Job::new(Pid(20));
1400 let mut suspended_3 = Job::new(Pid(30));
1401 suspended_1.state = ProcessState::stopped(SIGTSTP);
1402 suspended_2.state = ProcessState::stopped(SIGTSTP);
1403 suspended_3.state = ProcessState::stopped(SIGTSTP);
1404 list.add(suspended_1);
1405 list.add(suspended_2);
1406 list.add(suspended_3);
1407 let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1408 let ex_previous_job_index = list.previous_job().unwrap();
1409
1410 list.update_status(ex_current_job_pid, ProcessState::Running);
1411 let now_current_job_index = list.current_job().unwrap();
1412 let now_previous_job_index = list.previous_job().unwrap();
1413 assert_eq!(now_current_job_index, ex_previous_job_index);
1414 assert_ne!(now_previous_job_index, now_current_job_index);
1415 let now_previous_job = &list[now_previous_job_index];
1417 assert!(
1418 now_previous_job.is_suspended(),
1419 "now_previous_job = {now_previous_job:?}"
1420 );
1421 }
1422
1423 #[test]
1424 fn resuming_previous_job() {
1425 let mut list = JobList::default();
1426 let mut suspended_1 = Job::new(Pid(10));
1427 let mut suspended_2 = Job::new(Pid(20));
1428 let mut suspended_3 = Job::new(Pid(30));
1429 suspended_1.state = ProcessState::stopped(SIGTSTP);
1430 suspended_2.state = ProcessState::stopped(SIGTSTP);
1431 suspended_3.state = ProcessState::stopped(SIGTSTP);
1432 list.add(suspended_1);
1433 list.add(suspended_2);
1434 list.add(suspended_3);
1435 let ex_current_job_index = list.current_job().unwrap();
1436 let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1437
1438 list.update_status(ex_previous_job_pid, ProcessState::Running);
1439 let now_current_job_index = list.current_job().unwrap();
1440 let now_previous_job_index = list.previous_job().unwrap();
1441 assert_eq!(now_current_job_index, ex_current_job_index);
1442 assert_ne!(now_previous_job_index, now_current_job_index);
1443 let now_previous_job = &list[now_previous_job_index];
1445 assert!(
1446 now_previous_job.is_suspended(),
1447 "now_previous_job = {now_previous_job:?}"
1448 );
1449 }
1450
1451 #[test]
1452 fn resuming_other_job() {
1453 let mut list = JobList::default();
1454 let mut suspended_1 = Job::new(Pid(10));
1455 let mut suspended_2 = Job::new(Pid(20));
1456 let mut suspended_3 = Job::new(Pid(30));
1457 suspended_1.state = ProcessState::stopped(SIGTSTP);
1458 suspended_2.state = ProcessState::stopped(SIGTSTP);
1459 suspended_3.state = ProcessState::stopped(SIGTSTP);
1460 let i10 = list.add(suspended_1);
1461 let i20 = list.add(suspended_2);
1462 let _i30 = list.add(suspended_3);
1463 list.set_current_job(i20).unwrap();
1464 list.set_current_job(i10).unwrap();
1465 list.update_status(Pid(30), ProcessState::Running);
1466 assert_eq!(list.current_job(), Some(i10));
1467 assert_eq!(list.previous_job(), Some(i20));
1468 }
1469
1470 #[test]
1471 fn suspending_current_job() {
1472 let mut list = JobList::default();
1473 let i11 = list.add(Job::new(Pid(11)));
1474 let i12 = list.add(Job::new(Pid(12)));
1475 list.set_current_job(i11).unwrap();
1476 list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1477 assert_eq!(list.current_job(), Some(i11));
1478 assert_eq!(list.previous_job(), Some(i12));
1479 }
1480
1481 #[test]
1482 fn suspending_previous_job() {
1483 let mut list = JobList::default();
1484 let i11 = list.add(Job::new(Pid(11)));
1485 let i12 = list.add(Job::new(Pid(12)));
1486 list.set_current_job(i11).unwrap();
1487 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1488 assert_eq!(list.current_job(), Some(i12));
1489 assert_eq!(list.previous_job(), Some(i11));
1490 }
1491
1492 #[test]
1493 fn suspending_job_with_running_current_job() {
1494 let mut list = JobList::default();
1495 let i10 = list.add(Job::new(Pid(10)));
1496 let _i11 = list.add(Job::new(Pid(11)));
1497 let i12 = list.add(Job::new(Pid(12)));
1498 list.set_current_job(i10).unwrap();
1499 list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1500 assert_eq!(list.current_job(), Some(i12));
1501 assert_eq!(list.previous_job(), Some(i10));
1502 }
1503
1504 #[test]
1505 fn suspending_job_with_running_previous_job() {
1506 let mut list = JobList::default();
1507 let i11 = list.add(Job::new(Pid(11)));
1508 let i12 = list.add(Job::new(Pid(12)));
1509 let mut suspended = Job::new(Pid(10));
1510 suspended.state = ProcessState::stopped(SIGTTIN);
1511 let i10 = list.add(suspended);
1512 assert_eq!(list.current_job(), Some(i10));
1513 assert_eq!(list.previous_job(), Some(i11));
1514
1515 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1516 assert_eq!(list.current_job(), Some(i12));
1517 assert_eq!(list.previous_job(), Some(i10));
1518 }
1519}