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 = nix::libc::pid_t;
54#[cfg(not(unix))]
55type RawPidDef = i32;
56
57pub type RawPid = RawPidDef;
69
70#[repr(transparent)]
90#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
91pub struct Pid(pub RawPid);
92
93impl std::fmt::Display for Pid {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 self.0.fmt(f)
96 }
97}
98
99impl std::ops::Neg for Pid {
100 type Output = Self;
101 fn neg(self) -> Self {
102 Self(-self.0)
103 }
104}
105
106impl Pid {
107 pub const MY_PROCESS_GROUP: Self = Pid(0);
113
114 pub const ALL: Self = Pid(-1);
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum ProcessResult {
133 Stopped(signal::Number),
135 Exited(ExitStatus),
137 Signaled {
139 signal: signal::Number,
140 core_dump: bool,
141 },
142}
143
144impl ProcessResult {
145 #[inline]
147 #[must_use]
148 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
149 Self::Exited(exit_status.into())
150 }
151
152 #[must_use]
154 pub fn is_stopped(&self) -> bool {
155 matches!(self, ProcessResult::Stopped(_))
156 }
157}
158
159impl From<ProcessResult> for ExitStatus {
161 fn from(result: ProcessResult) -> Self {
162 match result {
163 ProcessResult::Exited(exit_status) => exit_status,
164 ProcessResult::Stopped(signal) | ProcessResult::Signaled { signal, .. } => {
165 ExitStatus::from(signal)
166 }
167 }
168 }
169}
170
171#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub enum ProcessState {
182 Running,
184 Halted(ProcessResult),
186}
187
188impl ProcessState {
189 #[inline]
191 #[must_use]
192 pub fn stopped(signal: signal::Number) -> Self {
193 Self::Halted(ProcessResult::Stopped(signal))
194 }
195
196 #[inline]
198 #[must_use]
199 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
200 Self::Halted(ProcessResult::exited(exit_status))
201 }
202
203 #[must_use]
205 pub fn is_alive(&self) -> bool {
206 match self {
207 ProcessState::Running => true,
208 ProcessState::Halted(result) => result.is_stopped(),
209 }
210 }
211
212 #[must_use]
214 pub fn is_stopped(&self) -> bool {
215 matches!(self, Self::Halted(result) if result.is_stopped())
216 }
217}
218
219impl From<ProcessResult> for ProcessState {
220 #[inline]
221 fn from(result: ProcessResult) -> Self {
222 Self::Halted(result)
223 }
224}
225
226#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
230pub struct RunningProcess;
231
232impl TryFrom<ProcessState> for ExitStatus {
236 type Error = RunningProcess;
237 fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
238 match state {
239 ProcessState::Halted(result) => Ok(result.into()),
240 ProcessState::Running => Err(RunningProcess),
241 }
242 }
243}
244
245#[derive(Clone, Debug, Eq, PartialEq)]
252#[non_exhaustive]
253pub struct Job {
254 pub pid: Pid,
258
259 pub job_controlled: bool,
264
265 pub state: ProcessState,
267
268 pub expected_state: Option<ProcessState>,
272
273 pub state_changed: bool,
278
279 pub is_owned: bool,
285
286 pub name: String,
288}
289
290impl Job {
291 pub fn new(pid: Pid) -> Self {
296 Job {
297 pid,
298 job_controlled: false,
299 state: ProcessState::Running,
300 expected_state: None,
301 state_changed: true,
302 is_owned: true,
303 name: String::new(),
304 }
305 }
306
307 #[must_use]
309 fn is_suspended(&self) -> bool {
310 self.state.is_stopped()
311 }
312}
313
314#[derive(Debug, Eq, PartialEq)]
320pub struct JobRefMut<'a>(&'a mut Job);
321
322impl JobRefMut<'_> {
323 pub fn expect<S>(&mut self, state: S)
334 where
335 S: Into<Option<ProcessState>>,
336 {
337 self.0.expected_state = state.into();
338 }
339
340 pub fn state_reported(&mut self) {
345 self.0.state_changed = false
346 }
347}
348
349impl Deref for JobRefMut<'_> {
350 type Target = Job;
351 fn deref(&self) -> &Job {
352 self.0
353 }
354}
355
356#[derive(Clone, Debug)]
360pub struct Iter<'a>(slab::Iter<'a, Job>);
361
362impl<'a> Iterator for Iter<'a> {
363 type Item = (usize, &'a Job);
364
365 #[inline(always)]
366 fn next(&mut self) -> Option<(usize, &'a Job)> {
367 self.0.next()
368 }
369
370 #[inline(always)]
371 fn size_hint(&self) -> (usize, Option<usize>) {
372 self.0.size_hint()
373 }
374}
375
376impl<'a> DoubleEndedIterator for Iter<'a> {
377 #[inline(always)]
378 fn next_back(&mut self) -> Option<(usize, &'a Job)> {
379 self.0.next_back()
380 }
381}
382
383impl ExactSizeIterator for Iter<'_> {
384 #[inline(always)]
385 fn len(&self) -> usize {
386 self.0.len()
387 }
388}
389
390impl FusedIterator for Iter<'_> {}
391
392#[derive(Debug)]
396pub struct IterMut<'a>(slab::IterMut<'a, Job>);
397
398impl<'a> Iterator for IterMut<'a> {
399 type Item = (usize, JobRefMut<'a>);
400
401 #[inline]
402 fn next(&mut self) -> Option<(usize, JobRefMut<'a>)> {
403 self.0.next().map(|(index, job)| (index, JobRefMut(job)))
404 }
405
406 #[inline(always)]
407 fn size_hint(&self) -> (usize, Option<usize>) {
408 self.0.size_hint()
409 }
410}
411
412impl<'a> DoubleEndedIterator for IterMut<'a> {
413 fn next_back(&mut self) -> Option<(usize, JobRefMut<'a>)> {
414 self.0
415 .next_back()
416 .map(|(index, job)| (index, JobRefMut(job)))
417 }
418}
419
420impl ExactSizeIterator for IterMut<'_> {
421 #[inline(always)]
422 fn len(&self) -> usize {
423 self.0.len()
424 }
425}
426
427impl FusedIterator for IterMut<'_> {}
428
429#[derive(Clone, Debug)]
433pub struct JobList {
434 jobs: Slab<Job>,
436
437 pids_to_indices: HashMap<Pid, usize>,
441
442 current_job_index: usize,
444
445 previous_job_index: usize,
447
448 last_async_pid: Pid,
450}
451
452impl Default for JobList {
453 fn default() -> Self {
454 JobList {
455 jobs: Slab::new(),
456 pids_to_indices: HashMap::new(),
457 current_job_index: usize::default(),
458 previous_job_index: usize::default(),
459 last_async_pid: Pid(0),
460 }
461 }
462}
463
464impl JobList {
465 #[inline]
467 #[must_use]
468 pub fn new() -> Self {
469 Self::default()
470 }
471
472 #[inline]
476 pub fn get(&self, index: usize) -> Option<&Job> {
477 self.jobs.get(index)
478 }
479
480 #[inline]
484 pub fn get_mut(&mut self, index: usize) -> Option<JobRefMut> {
485 self.jobs.get_mut(index).map(JobRefMut)
486 }
487
488 #[inline]
490 pub fn len(&self) -> usize {
491 self.jobs.len()
492 }
493
494 #[inline]
496 pub fn is_empty(&self) -> bool {
497 self.len() == 0
498 }
499
500 #[inline]
505 pub fn iter(&self) -> Iter {
506 Iter(self.jobs.iter())
507 }
508
509 #[inline]
517 pub fn iter_mut(&mut self) -> IterMut {
518 IterMut(self.jobs.iter_mut())
519 }
520
521 pub fn find_by_pid(&self, pid: Pid) -> Option<usize> {
529 self.pids_to_indices.get(&pid).copied()
530 }
531}
532
533impl<'a> IntoIterator for &'a JobList {
534 type Item = (usize, &'a Job);
535 type IntoIter = Iter<'a>;
536 #[inline(always)]
537 fn into_iter(self) -> Iter<'a> {
538 self.iter()
539 }
540}
541
542impl<'a> IntoIterator for &'a mut JobList {
543 type Item = (usize, JobRefMut<'a>);
544 type IntoIter = IterMut<'a>;
545 #[inline(always)]
546 fn into_iter(self) -> IterMut<'a> {
547 self.iter_mut()
548 }
549}
550
551impl std::ops::Index<usize> for JobList {
553 type Output = Job;
554
555 fn index(&self, index: usize) -> &Job {
559 &self.jobs[index]
560 }
561}
562
563#[derive(Debug)]
567pub struct ExtractIf<'a, F>
568where
569 F: FnMut(usize, JobRefMut) -> bool,
570{
571 list: &'a mut JobList,
572 should_remove: F,
573 next_index: usize,
574 len: usize,
575}
576
577impl<F> Iterator for ExtractIf<'_, F>
578where
579 F: FnMut(usize, JobRefMut) -> bool,
580{
581 type Item = (usize, Job);
582
583 fn next(&mut self) -> Option<(usize, Job)> {
584 while self.len > 0 {
585 let index = self.next_index;
586 self.next_index += 1;
587 if let Some(job) = self.list.get_mut(index) {
588 self.len -= 1;
589 if (self.should_remove)(index, job) {
590 let job = self.list.remove(index).unwrap();
591 return Some((index, job));
592 }
593 }
594 }
595 None
596 }
597
598 fn size_hint(&self) -> (usize, Option<usize>) {
599 (0, Some(self.len))
600 }
601}
602
603impl<F> FusedIterator for ExtractIf<'_, F> where F: FnMut(usize, JobRefMut) -> bool {}
604
605impl JobList {
606 pub fn add(&mut self, job: Job) -> usize {
618 let new_job_is_suspended = job.is_suspended();
619 let ex_current_job_is_suspended =
620 self.current_job().map(|index| self[index].is_suspended());
621 let ex_previous_job_is_suspended =
622 self.previous_job().map(|index| self[index].is_suspended());
623
624 use std::collections::hash_map::Entry::*;
626 let index = match self.pids_to_indices.entry(job.pid) {
627 Vacant(entry) => {
628 let index = self.jobs.insert(job);
629 entry.insert(index);
630 index
631 }
632 Occupied(entry) => {
633 let index = *entry.get();
634 self.jobs[index] = job;
635 index
636 }
637 };
638 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
639
640 match ex_current_job_is_suspended {
642 None => self.current_job_index = index,
643 Some(false) if new_job_is_suspended => self.set_current_job(index).unwrap(),
644 Some(_) => match ex_previous_job_is_suspended {
645 None => self.previous_job_index = index,
646 Some(false) if new_job_is_suspended => self.previous_job_index = index,
647 Some(_) => (),
648 },
649 }
650
651 index
652 }
653
654 pub fn remove(&mut self, index: usize) -> Option<Job> {
665 let job = self.jobs.try_remove(index);
666
667 if let Some(job) = &job {
668 self.pids_to_indices.remove(&job.pid);
670
671 if self.jobs.is_empty() {
672 self.jobs.clear();
676 }
677
678 let previous_job_becomes_current_job = index == self.current_job_index;
680 if previous_job_becomes_current_job {
681 self.current_job_index = self.previous_job_index;
682 }
683 if previous_job_becomes_current_job || index == self.previous_job_index {
684 self.previous_job_index = self
685 .any_suspended_job_but_current()
686 .unwrap_or_else(|| self.any_job_but_current().unwrap_or_default());
687 }
688 }
689 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
690
691 job
692 }
693
694 pub fn remove_if<F>(&mut self, should_remove: F)
707 where
708 F: FnMut(usize, JobRefMut) -> bool,
709 {
710 self.extract_if(should_remove).for_each(drop)
711 }
712
713 pub fn extract_if<F>(&mut self, should_remove: F) -> ExtractIf<'_, F>
729 where
730 F: FnMut(usize, JobRefMut) -> bool,
731 {
732 let len = self.len();
733 ExtractIf {
734 list: self,
735 should_remove,
736 next_index: 0,
737 len,
738 }
739 }
740}
741
742impl JobList {
743 pub fn update_status(&mut self, pid: Pid, state: ProcessState) -> Option<usize> {
768 let index = self.find_by_pid(pid)?;
769
770 let job = &mut self.jobs[index];
772 let was_suspended = job.is_suspended();
773 job.state = state;
774 job.state_changed |= job.expected_state != Some(state);
775 job.expected_state = None;
776
777 if !was_suspended && job.is_suspended() {
779 if index != self.current_job_index {
780 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
781 }
782 } else if was_suspended && !job.is_suspended() {
783 if let Some(prev_index) = self.previous_job() {
784 let previous_job_becomes_current_job =
785 index == self.current_job_index && self[prev_index].is_suspended();
786 if previous_job_becomes_current_job {
787 self.current_job_index = prev_index;
788 }
789 if previous_job_becomes_current_job || index == prev_index {
790 self.previous_job_index = self.any_suspended_job_but_current().unwrap_or(index);
791 }
792 }
793 }
794
795 Some(index)
796 }
797
798 pub fn disown_all(&mut self) {
802 for (_, job) in &mut self.jobs {
803 job.is_owned = false;
804 }
805 }
806}
807
808#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
810pub enum SetCurrentJobError {
811 #[error("no such job")]
813 NoSuchJob,
814
815 #[error("the current job must be selected from suspended jobs")]
818 NotSuspended,
819}
820
821impl JobList {
822 pub fn set_current_job(&mut self, index: usize) -> Result<(), SetCurrentJobError> {
834 let job = self.get(index).ok_or(SetCurrentJobError::NoSuchJob)?;
835 if !job.is_suspended() && self.iter().any(|(_, job)| job.is_suspended()) {
836 return Err(SetCurrentJobError::NotSuspended);
837 }
838
839 if index != self.current_job_index {
840 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
841 }
842 Ok(())
843 }
844
845 pub fn current_job(&self) -> Option<usize> {
858 if self.jobs.contains(self.current_job_index) {
859 Some(self.current_job_index)
860 } else {
861 None
862 }
863 }
864
865 pub fn previous_job(&self) -> Option<usize> {
881 if self.previous_job_index != self.current_job_index
882 && self.jobs.contains(self.previous_job_index)
883 {
884 Some(self.previous_job_index)
885 } else {
886 None
887 }
888 }
889
890 fn any_suspended_job_but_current(&self) -> Option<usize> {
892 self.iter()
893 .filter(|&(index, job)| index != self.current_job_index && job.is_suspended())
894 .map(|(index, _)| index)
895 .next()
896 }
897
898 fn any_job_but_current(&self) -> Option<usize> {
900 self.iter()
901 .filter(|&(index, _)| index != self.current_job_index)
902 .map(|(index, _)| index)
903 .next()
904 }
905}
906
907impl JobList {
908 pub fn last_async_pid(&self) -> Pid {
915 self.last_async_pid
916 }
917
918 pub fn set_last_async_pid(&mut self, pid: Pid) {
923 self.last_async_pid = pid;
924 }
925}
926
927pub mod fmt;
928pub mod id;
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933 use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
934
935 #[test]
936 fn job_list_find_by_pid() {
937 let mut list = JobList::default();
938 assert_eq!(list.find_by_pid(Pid(10)), None);
939
940 let i10 = list.add(Job::new(Pid(10)));
941 let i20 = list.add(Job::new(Pid(20)));
942 let i30 = list.add(Job::new(Pid(30)));
943 assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
944 assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
945 assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
946 assert_eq!(list.find_by_pid(Pid(40)), None);
947
948 list.remove(i10);
949 assert_eq!(list.find_by_pid(Pid(10)), None);
950 }
951
952 #[test]
953 fn job_list_add_and_remove() {
954 let mut list = JobList::default();
956
957 assert_eq!(list.add(Job::new(Pid(10))), 0);
958 assert_eq!(list.add(Job::new(Pid(11))), 1);
959 assert_eq!(list.add(Job::new(Pid(12))), 2);
960
961 assert_eq!(list.remove(0).unwrap().pid, Pid(10));
962 assert_eq!(list.remove(1).unwrap().pid, Pid(11));
963
964 assert_eq!(list.add(Job::new(Pid(13))), 1);
966 assert_eq!(list.add(Job::new(Pid(14))), 0);
967
968 assert_eq!(list.remove(0).unwrap().pid, Pid(14));
969 assert_eq!(list.remove(1).unwrap().pid, Pid(13));
970 assert_eq!(list.remove(2).unwrap().pid, Pid(12));
971
972 assert_eq!(list.add(Job::new(Pid(13))), 0);
974 assert_eq!(list.add(Job::new(Pid(14))), 1);
975 }
976
977 #[test]
978 fn job_list_add_same_pid() {
979 let mut list = JobList::default();
980
981 let mut job = Job::new(Pid(10));
982 job.name = "first job".to_string();
983 let i_first = list.add(job);
984
985 let mut job = Job::new(Pid(10));
986 job.name = "second job".to_string();
987 let i_second = list.add(job);
988
989 let job = &list[i_second];
990 assert_eq!(job.pid, Pid(10));
991 assert_eq!(job.name, "second job");
992
993 assert_ne!(
994 list.get(i_first).map(|job| job.name.as_str()),
995 Some("first job")
996 );
997 }
998
999 #[test]
1000 fn job_list_extract_if() {
1001 let mut list = JobList::default();
1002 let i21 = list.add(Job::new(Pid(21)));
1003 let i22 = list.add(Job::new(Pid(22)));
1004 let i23 = list.add(Job::new(Pid(23)));
1005 let i24 = list.add(Job::new(Pid(24)));
1006 let i25 = list.add(Job::new(Pid(25)));
1007 let i26 = list.add(Job::new(Pid(26)));
1008 list.remove(i23).unwrap();
1009
1010 let mut i = list.extract_if(|index, mut job| {
1011 assert_ne!(index, i23);
1012 if index % 2 == 0 {
1013 job.state_reported();
1014 }
1015 index == 0 || job.pid == Pid(26)
1016 });
1017
1018 let mut expected_job_21 = Job::new(Pid(21));
1019 expected_job_21.state_changed = false;
1020 assert_eq!(i.next(), Some((i21, expected_job_21)));
1021 assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1022 assert_eq!(i.next(), None);
1023 assert_eq!(i.next(), None); let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1026 assert_eq!(indices, [i22, i24, i25]);
1027 assert!(list[i22].state_changed);
1028 assert!(list[i24].state_changed);
1029 assert!(!list[i25].state_changed);
1030 }
1031
1032 #[test]
1033 #[allow(clippy::bool_assert_comparison)]
1034 fn updating_job_status_without_expected_state() {
1035 let mut list = JobList::default();
1036 let state = ProcessState::exited(15);
1037 assert_eq!(list.update_status(Pid(20), state), None);
1038
1039 let i10 = list.add(Job::new(Pid(10)));
1040 let i20 = list.add(Job::new(Pid(20)));
1041 let i30 = list.add(Job::new(Pid(30)));
1042 assert_eq!(list[i20].state, ProcessState::Running);
1043
1044 list.get_mut(i20).unwrap().state_reported();
1045 assert_eq!(list[i20].state_changed, false);
1046
1047 assert_eq!(list.update_status(Pid(20), state), Some(i20));
1048 assert_eq!(list[i20].state, ProcessState::exited(15));
1049 assert_eq!(list[i20].state_changed, true);
1050
1051 assert_eq!(list[i10].state, ProcessState::Running);
1052 assert_eq!(list[i30].state, ProcessState::Running);
1053 }
1054
1055 #[test]
1056 #[allow(clippy::bool_assert_comparison)]
1057 fn updating_job_status_with_matching_expected_state() {
1058 let mut list = JobList::default();
1059 let pid = Pid(20);
1060 let mut job = Job::new(pid);
1061 job.expected_state = Some(ProcessState::Running);
1062 job.state_changed = false;
1063 let i20 = list.add(job);
1064
1065 assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1066
1067 let job = &list[i20];
1068 assert_eq!(job.state, ProcessState::Running);
1069 assert_eq!(job.expected_state, None);
1070 assert_eq!(job.state_changed, false);
1071 }
1072
1073 #[test]
1074 #[allow(clippy::bool_assert_comparison)]
1075 fn updating_job_status_with_unmatched_expected_state() {
1076 let mut list = JobList::default();
1077 let pid = Pid(20);
1078 let mut job = Job::new(pid);
1079 job.expected_state = Some(ProcessState::Running);
1080 job.state_changed = false;
1081 let i20 = list.add(job);
1082
1083 let result = list.update_status(pid, ProcessState::exited(0));
1084 assert_eq!(result, Some(i20));
1085
1086 let job = &list[i20];
1087 assert_eq!(job.state, ProcessState::exited(0));
1088 assert_eq!(job.expected_state, None);
1089 assert_eq!(job.state_changed, true);
1090 }
1091
1092 #[test]
1093 #[allow(clippy::bool_assert_comparison)]
1094 fn disowning_jobs() {
1095 let mut list = JobList::default();
1096 let i10 = list.add(Job::new(Pid(10)));
1097 let i20 = list.add(Job::new(Pid(20)));
1098 let i30 = list.add(Job::new(Pid(30)));
1099
1100 list.disown_all();
1101
1102 assert_eq!(list[i10].is_owned, false);
1103 assert_eq!(list[i20].is_owned, false);
1104 assert_eq!(list[i30].is_owned, false);
1105 }
1106
1107 #[test]
1108 fn no_current_and_previous_job_in_empty_job_list() {
1109 let list = JobList::default();
1110 assert_eq!(list.current_job(), None);
1111 assert_eq!(list.previous_job(), None);
1112 }
1113
1114 #[test]
1115 fn current_and_previous_job_in_job_list_with_one_job() {
1116 let mut list = JobList::default();
1117 let i10 = list.add(Job::new(Pid(10)));
1118 assert_eq!(list.current_job(), Some(i10));
1119 assert_eq!(list.previous_job(), None);
1120 }
1121
1122 #[test]
1123 fn current_and_previous_job_in_job_list_with_two_job() {
1124 let mut list = JobList::default();
1127 let mut suspended = Job::new(Pid(10));
1128 suspended.state = ProcessState::stopped(SIGSTOP);
1129 let running = Job::new(Pid(20));
1130 let i10 = list.add(suspended.clone());
1131 let i20 = list.add(running.clone());
1132 assert_eq!(list.current_job(), Some(i10));
1133 assert_eq!(list.previous_job(), Some(i20));
1134
1135 list = JobList::default();
1137 let i20 = list.add(running);
1138 let i10 = list.add(suspended);
1139 assert_eq!(list.current_job(), Some(i10));
1140 assert_eq!(list.previous_job(), Some(i20));
1141 }
1142
1143 #[test]
1144 fn adding_suspended_job_with_running_current_and_previous_job() {
1145 let mut list = JobList::default();
1146 let running_1 = Job::new(Pid(11));
1147 let running_2 = Job::new(Pid(12));
1148 list.add(running_1);
1149 list.add(running_2);
1150 let ex_current_job_index = list.current_job().unwrap();
1151 let ex_previous_job_index = list.previous_job().unwrap();
1152 assert_ne!(ex_current_job_index, ex_previous_job_index);
1153
1154 let mut suspended = Job::new(Pid(20));
1155 suspended.state = ProcessState::stopped(SIGSTOP);
1156 let i20 = list.add(suspended);
1157 let now_current_job_index = list.current_job().unwrap();
1158 let now_previous_job_index = list.previous_job().unwrap();
1159 assert_eq!(now_current_job_index, i20);
1160 assert_eq!(now_previous_job_index, ex_current_job_index);
1161 }
1162
1163 #[test]
1164 fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1165 let mut list = JobList::default();
1166
1167 let running = Job::new(Pid(18));
1168 let i18 = list.add(running);
1169
1170 let mut suspended_1 = Job::new(Pid(19));
1171 suspended_1.state = ProcessState::stopped(SIGSTOP);
1172 let i19 = list.add(suspended_1);
1173
1174 let ex_current_job_index = list.current_job().unwrap();
1175 let ex_previous_job_index = list.previous_job().unwrap();
1176 assert_eq!(ex_current_job_index, i19);
1177 assert_eq!(ex_previous_job_index, i18);
1178
1179 let mut suspended_2 = Job::new(Pid(20));
1180 suspended_2.state = ProcessState::stopped(SIGSTOP);
1181 let i20 = list.add(suspended_2);
1182
1183 let now_current_job_index = list.current_job().unwrap();
1184 let now_previous_job_index = list.previous_job().unwrap();
1185 assert_eq!(now_current_job_index, ex_current_job_index);
1186 assert_eq!(now_previous_job_index, i20);
1187 }
1188
1189 #[test]
1190 fn removing_current_job() {
1191 let mut list = JobList::default();
1192
1193 let running = Job::new(Pid(10));
1194 let i10 = list.add(running);
1195
1196 let mut suspended_1 = Job::new(Pid(11));
1197 let mut suspended_2 = Job::new(Pid(12));
1198 let mut suspended_3 = Job::new(Pid(13));
1199 suspended_1.state = ProcessState::stopped(SIGSTOP);
1200 suspended_2.state = ProcessState::stopped(SIGSTOP);
1201 suspended_3.state = ProcessState::stopped(SIGSTOP);
1202 list.add(suspended_1);
1203 list.add(suspended_2);
1204 list.add(suspended_3);
1205
1206 let current_job_index_1 = list.current_job().unwrap();
1207 let previous_job_index_1 = list.previous_job().unwrap();
1208 assert_ne!(current_job_index_1, i10);
1209 assert_ne!(previous_job_index_1, i10);
1210
1211 list.remove(current_job_index_1);
1212 let current_job_index_2 = list.current_job().unwrap();
1213 let previous_job_index_2 = list.previous_job().unwrap();
1214 assert_eq!(current_job_index_2, previous_job_index_1);
1215 assert_ne!(previous_job_index_2, current_job_index_2);
1216 let previous_job_2 = &list[previous_job_index_2];
1218 assert!(
1219 previous_job_2.is_suspended(),
1220 "previous_job_2 = {previous_job_2:?}"
1221 );
1222
1223 list.remove(current_job_index_2);
1224 let current_job_index_3 = list.current_job().unwrap();
1225 let previous_job_index_3 = list.previous_job().unwrap();
1226 assert_eq!(current_job_index_3, previous_job_index_2);
1227 assert_eq!(previous_job_index_3, i10);
1229
1230 list.remove(current_job_index_3);
1231 let current_job_index_4 = list.current_job().unwrap();
1232 assert_eq!(current_job_index_4, i10);
1233 assert_eq!(list.previous_job(), None);
1235 }
1236
1237 #[test]
1238 fn removing_previous_job_with_suspended_job() {
1239 let mut list = JobList::default();
1240
1241 let running = Job::new(Pid(10));
1242 let i10 = list.add(running);
1243
1244 let mut suspended_1 = Job::new(Pid(11));
1245 let mut suspended_2 = Job::new(Pid(12));
1246 let mut suspended_3 = Job::new(Pid(13));
1247 suspended_1.state = ProcessState::stopped(SIGSTOP);
1248 suspended_2.state = ProcessState::stopped(SIGSTOP);
1249 suspended_3.state = ProcessState::stopped(SIGSTOP);
1250 list.add(suspended_1);
1251 list.add(suspended_2);
1252 list.add(suspended_3);
1253
1254 let ex_current_job_index = list.current_job().unwrap();
1255 let ex_previous_job_index = list.previous_job().unwrap();
1256 assert_ne!(ex_current_job_index, i10);
1257 assert_ne!(ex_previous_job_index, i10);
1258
1259 list.remove(ex_previous_job_index);
1260 let now_current_job_index = list.current_job().unwrap();
1261 let now_previous_job_index = list.previous_job().unwrap();
1262 assert_eq!(now_current_job_index, ex_current_job_index);
1263 assert_ne!(now_previous_job_index, now_current_job_index);
1264 let now_previous_job = &list[now_previous_job_index];
1266 assert!(
1267 now_previous_job.is_suspended(),
1268 "now_previous_job = {now_previous_job:?}"
1269 );
1270 }
1271
1272 #[test]
1273 fn removing_previous_job_with_running_job() {
1274 let mut list = JobList::default();
1275
1276 let running = Job::new(Pid(10));
1277 let i10 = list.add(running);
1278
1279 let mut suspended_1 = Job::new(Pid(11));
1280 let mut suspended_2 = Job::new(Pid(12));
1281 suspended_1.state = ProcessState::stopped(SIGSTOP);
1282 suspended_2.state = ProcessState::stopped(SIGSTOP);
1283 list.add(suspended_1);
1284 list.add(suspended_2);
1285
1286 let ex_current_job_index = list.current_job().unwrap();
1287 let ex_previous_job_index = list.previous_job().unwrap();
1288 assert_ne!(ex_current_job_index, i10);
1289 assert_ne!(ex_previous_job_index, i10);
1290
1291 list.remove(ex_previous_job_index);
1292 let now_current_job_index = list.current_job().unwrap();
1293 let now_previous_job_index = list.previous_job().unwrap();
1294 assert_eq!(now_current_job_index, ex_current_job_index);
1295 assert_eq!(now_previous_job_index, i10);
1298 }
1299
1300 #[test]
1301 fn set_current_job_with_running_jobs_only() {
1302 let mut list = JobList::default();
1303 let i21 = list.add(Job::new(Pid(21)));
1304 let i22 = list.add(Job::new(Pid(22)));
1305
1306 assert_eq!(list.set_current_job(i21), Ok(()));
1307 assert_eq!(list.current_job(), Some(i21));
1308
1309 assert_eq!(list.set_current_job(i22), Ok(()));
1310 assert_eq!(list.current_job(), Some(i22));
1311 }
1312
1313 #[test]
1314 fn set_current_job_to_suspended_job() {
1315 let mut list = JobList::default();
1316 list.add(Job::new(Pid(20)));
1317
1318 let mut suspended_1 = Job::new(Pid(21));
1319 let mut suspended_2 = Job::new(Pid(22));
1320 suspended_1.state = ProcessState::stopped(SIGSTOP);
1321 suspended_2.state = ProcessState::stopped(SIGSTOP);
1322 let i21 = list.add(suspended_1);
1323 let i22 = list.add(suspended_2);
1324
1325 assert_eq!(list.set_current_job(i21), Ok(()));
1326 assert_eq!(list.current_job(), Some(i21));
1327
1328 assert_eq!(list.set_current_job(i22), Ok(()));
1329 assert_eq!(list.current_job(), Some(i22));
1330 }
1331
1332 #[test]
1333 fn set_current_job_no_such_job() {
1334 let mut list = JobList::default();
1335 assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1336 assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1337 assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1338 }
1339
1340 #[test]
1341 fn set_current_job_not_suspended() {
1342 let mut list = JobList::default();
1343 let mut suspended = Job::new(Pid(10));
1344 suspended.state = ProcessState::stopped(SIGTSTP);
1345 let running = Job::new(Pid(20));
1346 let i10 = list.add(suspended);
1347 let i20 = list.add(running);
1348 assert_eq!(
1349 list.set_current_job(i20),
1350 Err(SetCurrentJobError::NotSuspended)
1351 );
1352 assert_eq!(list.current_job(), Some(i10));
1353 }
1354
1355 #[test]
1356 fn set_current_job_no_change() {
1357 let mut list = JobList::default();
1358 list.add(Job::new(Pid(5)));
1359 list.add(Job::new(Pid(6)));
1360 let old_current_job_index = list.current_job().unwrap();
1361 let old_previous_job_index = list.previous_job().unwrap();
1362 list.set_current_job(old_current_job_index).unwrap();
1363 let new_current_job_index = list.current_job().unwrap();
1364 let new_previous_job_index = list.previous_job().unwrap();
1365 assert_eq!(new_current_job_index, old_current_job_index);
1366 assert_eq!(new_previous_job_index, old_previous_job_index);
1367 }
1368
1369 #[test]
1370 fn resuming_current_job_without_other_suspended_jobs() {
1371 let mut list = JobList::default();
1372 let mut suspended = Job::new(Pid(10));
1373 suspended.state = ProcessState::stopped(SIGTSTP);
1374 let running = Job::new(Pid(20));
1375 let i10 = list.add(suspended);
1376 let i20 = list.add(running);
1377 list.update_status(Pid(10), ProcessState::Running);
1378 assert_eq!(list.current_job(), Some(i10));
1379 assert_eq!(list.previous_job(), Some(i20));
1380 }
1381
1382 #[test]
1383 fn resuming_current_job_with_another_suspended_job() {
1384 let mut list = JobList::default();
1385 let mut suspended_1 = Job::new(Pid(10));
1386 let mut suspended_2 = Job::new(Pid(20));
1387 suspended_1.state = ProcessState::stopped(SIGTSTP);
1388 suspended_2.state = ProcessState::stopped(SIGTSTP);
1389 let i10 = list.add(suspended_1);
1390 let i20 = list.add(suspended_2);
1391 list.set_current_job(i10).unwrap();
1392 list.update_status(Pid(10), ProcessState::Running);
1393 assert_eq!(list.current_job(), Some(i20));
1395 assert_eq!(list.previous_job(), Some(i10));
1396 }
1397
1398 #[test]
1399 fn resuming_current_job_with_other_suspended_jobs() {
1400 let mut list = JobList::default();
1401 let mut suspended_1 = Job::new(Pid(10));
1402 let mut suspended_2 = Job::new(Pid(20));
1403 let mut suspended_3 = Job::new(Pid(30));
1404 suspended_1.state = ProcessState::stopped(SIGTSTP);
1405 suspended_2.state = ProcessState::stopped(SIGTSTP);
1406 suspended_3.state = ProcessState::stopped(SIGTSTP);
1407 list.add(suspended_1);
1408 list.add(suspended_2);
1409 list.add(suspended_3);
1410 let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1411 let ex_previous_job_index = list.previous_job().unwrap();
1412
1413 list.update_status(ex_current_job_pid, ProcessState::Running);
1414 let now_current_job_index = list.current_job().unwrap();
1415 let now_previous_job_index = list.previous_job().unwrap();
1416 assert_eq!(now_current_job_index, ex_previous_job_index);
1417 assert_ne!(now_previous_job_index, now_current_job_index);
1418 let now_previous_job = &list[now_previous_job_index];
1420 assert!(
1421 now_previous_job.is_suspended(),
1422 "now_previous_job = {now_previous_job:?}"
1423 );
1424 }
1425
1426 #[test]
1427 fn resuming_previous_job() {
1428 let mut list = JobList::default();
1429 let mut suspended_1 = Job::new(Pid(10));
1430 let mut suspended_2 = Job::new(Pid(20));
1431 let mut suspended_3 = Job::new(Pid(30));
1432 suspended_1.state = ProcessState::stopped(SIGTSTP);
1433 suspended_2.state = ProcessState::stopped(SIGTSTP);
1434 suspended_3.state = ProcessState::stopped(SIGTSTP);
1435 list.add(suspended_1);
1436 list.add(suspended_2);
1437 list.add(suspended_3);
1438 let ex_current_job_index = list.current_job().unwrap();
1439 let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1440
1441 list.update_status(ex_previous_job_pid, ProcessState::Running);
1442 let now_current_job_index = list.current_job().unwrap();
1443 let now_previous_job_index = list.previous_job().unwrap();
1444 assert_eq!(now_current_job_index, ex_current_job_index);
1445 assert_ne!(now_previous_job_index, now_current_job_index);
1446 let now_previous_job = &list[now_previous_job_index];
1448 assert!(
1449 now_previous_job.is_suspended(),
1450 "now_previous_job = {now_previous_job:?}"
1451 );
1452 }
1453
1454 #[test]
1455 fn resuming_other_job() {
1456 let mut list = JobList::default();
1457 let mut suspended_1 = Job::new(Pid(10));
1458 let mut suspended_2 = Job::new(Pid(20));
1459 let mut suspended_3 = Job::new(Pid(30));
1460 suspended_1.state = ProcessState::stopped(SIGTSTP);
1461 suspended_2.state = ProcessState::stopped(SIGTSTP);
1462 suspended_3.state = ProcessState::stopped(SIGTSTP);
1463 let i10 = list.add(suspended_1);
1464 let i20 = list.add(suspended_2);
1465 let _i30 = list.add(suspended_3);
1466 list.set_current_job(i20).unwrap();
1467 list.set_current_job(i10).unwrap();
1468 list.update_status(Pid(30), ProcessState::Running);
1469 assert_eq!(list.current_job(), Some(i10));
1470 assert_eq!(list.previous_job(), Some(i20));
1471 }
1472
1473 #[test]
1474 fn suspending_current_job() {
1475 let mut list = JobList::default();
1476 let i11 = list.add(Job::new(Pid(11)));
1477 let i12 = list.add(Job::new(Pid(12)));
1478 list.set_current_job(i11).unwrap();
1479 list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1480 assert_eq!(list.current_job(), Some(i11));
1481 assert_eq!(list.previous_job(), Some(i12));
1482 }
1483
1484 #[test]
1485 fn suspending_previous_job() {
1486 let mut list = JobList::default();
1487 let i11 = list.add(Job::new(Pid(11)));
1488 let i12 = list.add(Job::new(Pid(12)));
1489 list.set_current_job(i11).unwrap();
1490 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1491 assert_eq!(list.current_job(), Some(i12));
1492 assert_eq!(list.previous_job(), Some(i11));
1493 }
1494
1495 #[test]
1496 fn suspending_job_with_running_current_job() {
1497 let mut list = JobList::default();
1498 let i10 = list.add(Job::new(Pid(10)));
1499 let _i11 = list.add(Job::new(Pid(11)));
1500 let i12 = list.add(Job::new(Pid(12)));
1501 list.set_current_job(i10).unwrap();
1502 list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1503 assert_eq!(list.current_job(), Some(i12));
1504 assert_eq!(list.previous_job(), Some(i10));
1505 }
1506
1507 #[test]
1508 fn suspending_job_with_running_previous_job() {
1509 let mut list = JobList::default();
1510 let i11 = list.add(Job::new(Pid(11)));
1511 let i12 = list.add(Job::new(Pid(12)));
1512 let mut suspended = Job::new(Pid(10));
1513 suspended.state = ProcessState::stopped(SIGTTIN);
1514 let i10 = list.add(suspended);
1515 assert_eq!(list.current_job(), Some(i10));
1516 assert_eq!(list.previous_job(), Some(i11));
1517
1518 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1519 assert_eq!(list.current_job(), Some(i12));
1520 assert_eq!(list.previous_job(), Some(i10));
1521 }
1522}