1use crate::Env;
45use crate::semantics::{Divert, ExitStatus};
46use crate::signal;
47use crate::system::Signals;
48#[cfg(any(doc, test))]
49use crate::trap::Action;
50use slab::Slab;
51use std::collections::HashMap;
52use std::iter::FusedIterator;
53use std::ops::ControlFlow::{Break, Continue};
54use std::ops::Deref;
55use thiserror::Error;
56
57#[cfg(unix)]
58type RawPidDef = libc::pid_t;
59#[cfg(not(unix))]
60type RawPidDef = i32;
61
62pub type RawPid = RawPidDef;
72
73#[repr(transparent)]
92#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
93pub struct Pid(pub RawPid);
94
95impl std::fmt::Display for Pid {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 self.0.fmt(f)
98 }
99}
100
101impl std::ops::Neg for Pid {
102 type Output = Self;
103 fn neg(self) -> Self {
104 Self(-self.0)
105 }
106}
107
108impl Pid {
109 pub const MY_PROCESS_GROUP: Self = Pid(0);
115
116 pub const ALL: Self = Pid(-1);
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub enum ProcessResult {
135 Stopped(signal::Number),
137 Exited(ExitStatus),
139 Signaled {
141 signal: signal::Number,
142 core_dump: bool,
143 },
144}
145
146impl ProcessResult {
147 #[inline]
149 #[must_use]
150 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
151 Self::Exited(exit_status.into())
152 }
153
154 #[must_use]
156 pub fn is_stopped(&self) -> bool {
157 matches!(self, ProcessResult::Stopped(_))
158 }
159}
160
161impl From<ProcessResult> for ExitStatus {
163 fn from(result: ProcessResult) -> Self {
164 match result {
165 ProcessResult::Exited(exit_status) => exit_status,
166 ProcessResult::Stopped(signal) | ProcessResult::Signaled { signal, .. } => {
167 ExitStatus::from(signal)
168 }
169 }
170 }
171}
172
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
183pub enum ProcessState {
184 Running,
186 Halted(ProcessResult),
188}
189
190impl ProcessState {
191 #[inline]
193 #[must_use]
194 pub fn stopped(signal: signal::Number) -> Self {
195 Self::Halted(ProcessResult::Stopped(signal))
196 }
197
198 #[inline]
200 #[must_use]
201 pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
202 Self::Halted(ProcessResult::exited(exit_status))
203 }
204
205 #[must_use]
207 pub fn is_alive(&self) -> bool {
208 match self {
209 ProcessState::Running => true,
210 ProcessState::Halted(result) => result.is_stopped(),
211 }
212 }
213
214 #[must_use]
216 pub fn is_stopped(&self) -> bool {
217 matches!(self, Self::Halted(result) if result.is_stopped())
218 }
219}
220
221impl From<ProcessResult> for ProcessState {
222 #[inline]
223 fn from(result: ProcessResult) -> Self {
224 Self::Halted(result)
225 }
226}
227
228#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
232pub struct RunningProcess;
233
234impl TryFrom<ProcessState> for ExitStatus {
238 type Error = RunningProcess;
239 fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
240 match state {
241 ProcessState::Halted(result) => Ok(result.into()),
242 ProcessState::Running => Err(RunningProcess),
243 }
244 }
245}
246
247#[derive(Clone, Debug, Eq, PartialEq)]
254#[non_exhaustive]
255pub struct Job {
256 pub pid: Pid,
260
261 pub job_controlled: bool,
266
267 pub state: ProcessState,
269
270 pub expected_state: Option<ProcessState>,
274
275 pub state_changed: bool,
280
281 pub is_owned: bool,
287
288 pub name: String,
290}
291
292impl Job {
293 pub fn new(pid: Pid) -> Self {
298 Job {
299 pid,
300 job_controlled: false,
301 state: ProcessState::Running,
302 expected_state: None,
303 state_changed: true,
304 is_owned: true,
305 name: String::new(),
306 }
307 }
308
309 #[must_use]
311 fn is_suspended(&self) -> bool {
312 self.state.is_stopped()
313 }
314}
315
316#[derive(Debug, Eq, PartialEq)]
322pub struct JobRefMut<'a>(&'a mut Job);
323
324impl JobRefMut<'_> {
325 pub fn expect<S>(&mut self, state: S)
336 where
337 S: Into<Option<ProcessState>>,
338 {
339 self.0.expected_state = state.into();
340 }
341
342 pub fn state_reported(&mut self) {
347 self.0.state_changed = false
348 }
349}
350
351impl Deref for JobRefMut<'_> {
352 type Target = Job;
353 fn deref(&self) -> &Job {
354 self.0
355 }
356}
357
358#[derive(Clone, Debug)]
362pub struct Iter<'a>(slab::Iter<'a, Job>);
363
364impl<'a> Iterator for Iter<'a> {
365 type Item = (usize, &'a Job);
366
367 #[inline(always)]
368 fn next(&mut self) -> Option<(usize, &'a Job)> {
369 self.0.next()
370 }
371
372 #[inline(always)]
373 fn size_hint(&self) -> (usize, Option<usize>) {
374 self.0.size_hint()
375 }
376}
377
378impl<'a> DoubleEndedIterator for Iter<'a> {
379 #[inline(always)]
380 fn next_back(&mut self) -> Option<(usize, &'a Job)> {
381 self.0.next_back()
382 }
383}
384
385impl ExactSizeIterator for Iter<'_> {
386 #[inline(always)]
387 fn len(&self) -> usize {
388 self.0.len()
389 }
390}
391
392impl FusedIterator for Iter<'_> {}
393
394#[derive(Debug)]
398pub struct IterMut<'a>(slab::IterMut<'a, Job>);
399
400impl<'a> Iterator for IterMut<'a> {
401 type Item = (usize, JobRefMut<'a>);
402
403 #[inline]
404 fn next(&mut self) -> Option<(usize, JobRefMut<'a>)> {
405 self.0.next().map(|(index, job)| (index, JobRefMut(job)))
406 }
407
408 #[inline(always)]
409 fn size_hint(&self) -> (usize, Option<usize>) {
410 self.0.size_hint()
411 }
412}
413
414impl<'a> DoubleEndedIterator for IterMut<'a> {
415 fn next_back(&mut self) -> Option<(usize, JobRefMut<'a>)> {
416 self.0
417 .next_back()
418 .map(|(index, job)| (index, JobRefMut(job)))
419 }
420}
421
422impl ExactSizeIterator for IterMut<'_> {
423 #[inline(always)]
424 fn len(&self) -> usize {
425 self.0.len()
426 }
427}
428
429impl FusedIterator for IterMut<'_> {}
430
431#[derive(Clone, Debug)]
435pub struct JobList {
436 jobs: Slab<Job>,
438
439 pids_to_indices: HashMap<Pid, usize>,
443
444 current_job_index: usize,
446
447 previous_job_index: usize,
449
450 last_async_pid: Pid,
452}
453
454impl Default for JobList {
455 fn default() -> Self {
456 JobList {
457 jobs: Slab::new(),
458 pids_to_indices: HashMap::new(),
459 current_job_index: usize::default(),
460 previous_job_index: usize::default(),
461 last_async_pid: Pid(0),
462 }
463 }
464}
465
466impl JobList {
467 #[inline]
469 #[must_use]
470 pub fn new() -> Self {
471 Self::default()
472 }
473
474 #[inline]
478 pub fn get(&self, index: usize) -> Option<&Job> {
479 self.jobs.get(index)
480 }
481
482 #[inline]
486 pub fn get_mut(&mut self, index: usize) -> Option<JobRefMut<'_>> {
487 self.jobs.get_mut(index).map(JobRefMut)
488 }
489
490 #[inline]
492 pub fn len(&self) -> usize {
493 self.jobs.len()
494 }
495
496 #[inline]
498 pub fn is_empty(&self) -> bool {
499 self.len() == 0
500 }
501
502 #[inline]
507 pub fn iter(&self) -> Iter<'_> {
508 Iter(self.jobs.iter())
509 }
510
511 #[inline]
519 pub fn iter_mut(&mut self) -> IterMut<'_> {
520 IterMut(self.jobs.iter_mut())
521 }
522
523 pub fn find_by_pid(&self, pid: Pid) -> Option<usize> {
531 self.pids_to_indices.get(&pid).copied()
532 }
533}
534
535impl<'a> IntoIterator for &'a JobList {
536 type Item = (usize, &'a Job);
537 type IntoIter = Iter<'a>;
538 #[inline(always)]
539 fn into_iter(self) -> Iter<'a> {
540 self.iter()
541 }
542}
543
544impl<'a> IntoIterator for &'a mut JobList {
545 type Item = (usize, JobRefMut<'a>);
546 type IntoIter = IterMut<'a>;
547 #[inline(always)]
548 fn into_iter(self) -> IterMut<'a> {
549 self.iter_mut()
550 }
551}
552
553impl std::ops::Index<usize> for JobList {
555 type Output = Job;
556
557 fn index(&self, index: usize) -> &Job {
561 &self.jobs[index]
562 }
563}
564
565#[derive(Debug)]
569pub struct ExtractIf<'a, F>
570where
571 F: FnMut(usize, JobRefMut) -> bool,
572{
573 list: &'a mut JobList,
574 should_remove: F,
575 next_index: usize,
576 len: usize,
577}
578
579impl<F> Iterator for ExtractIf<'_, F>
580where
581 F: FnMut(usize, JobRefMut) -> bool,
582{
583 type Item = (usize, Job);
584
585 fn next(&mut self) -> Option<(usize, Job)> {
586 while self.len > 0 {
587 let index = self.next_index;
588 self.next_index += 1;
589 if let Some(job) = self.list.get_mut(index) {
590 self.len -= 1;
591 if (self.should_remove)(index, job) {
592 let job = self.list.remove(index).unwrap();
593 return Some((index, job));
594 }
595 }
596 }
597 None
598 }
599
600 fn size_hint(&self) -> (usize, Option<usize>) {
601 (0, Some(self.len))
602 }
603}
604
605impl<F> FusedIterator for ExtractIf<'_, F> where F: FnMut(usize, JobRefMut) -> bool {}
606
607impl JobList {
608 pub fn insert(&mut self, job: Job) -> usize {
620 let new_job_is_suspended = job.is_suspended();
621 let ex_current_job_is_suspended =
622 self.current_job().map(|index| self[index].is_suspended());
623 let ex_previous_job_is_suspended =
624 self.previous_job().map(|index| self[index].is_suspended());
625
626 use std::collections::hash_map::Entry::*;
628 let index = match self.pids_to_indices.entry(job.pid) {
629 Vacant(entry) => {
630 let index = self.jobs.insert(job);
631 entry.insert(index);
632 index
633 }
634 Occupied(entry) => {
635 let index = *entry.get();
636 self.jobs[index] = job;
637 index
638 }
639 };
640 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
641
642 match ex_current_job_is_suspended {
644 None => self.current_job_index = index,
645 Some(false) if new_job_is_suspended => self.set_current_job(index).unwrap(),
646 Some(_) => match ex_previous_job_is_suspended {
647 None => self.previous_job_index = index,
648 Some(false) if new_job_is_suspended => self.previous_job_index = index,
649 Some(_) => (),
650 },
651 }
652
653 index
654 }
655
656 #[deprecated(since = "0.15.0", note = "use `insert` instead")]
660 #[inline(always)]
661 pub fn add(&mut self, job: Job) -> usize {
662 self.insert(job)
663 }
664
665 pub fn remove(&mut self, index: usize) -> Option<Job> {
676 let job = self.jobs.try_remove(index);
677
678 if let Some(job) = &job {
679 self.pids_to_indices.remove(&job.pid);
681
682 if self.jobs.is_empty() {
683 self.jobs.clear();
687 }
688
689 let previous_job_becomes_current_job = index == self.current_job_index;
691 if previous_job_becomes_current_job {
692 self.current_job_index = self.previous_job_index;
693 }
694 if previous_job_becomes_current_job || index == self.previous_job_index {
695 self.previous_job_index = self
696 .any_suspended_job_but_current()
697 .unwrap_or_else(|| self.any_job_but_current().unwrap_or_default());
698 }
699 }
700 debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
701
702 job
703 }
704
705 pub fn remove_if<F>(&mut self, should_remove: F)
718 where
719 F: FnMut(usize, JobRefMut) -> bool,
720 {
721 self.extract_if(should_remove).for_each(drop)
722 }
723
724 pub fn extract_if<F>(&mut self, should_remove: F) -> ExtractIf<'_, F>
740 where
741 F: FnMut(usize, JobRefMut) -> bool,
742 {
743 let len = self.len();
744 ExtractIf {
745 list: self,
746 should_remove,
747 next_index: 0,
748 len,
749 }
750 }
751}
752
753impl JobList {
754 pub fn update_status(&mut self, pid: Pid, state: ProcessState) -> Option<usize> {
779 let index = self.find_by_pid(pid)?;
780
781 let job = &mut self.jobs[index];
783 let was_suspended = job.is_suspended();
784 job.state = state;
785 job.state_changed |= job.expected_state != Some(state);
786 job.expected_state = None;
787
788 if !was_suspended && job.is_suspended() {
790 if index != self.current_job_index {
791 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
792 }
793 } else if was_suspended
794 && !job.is_suspended()
795 && let Some(prev_index) = self.previous_job()
796 {
797 let previous_job_becomes_current_job =
798 index == self.current_job_index && self[prev_index].is_suspended();
799 if previous_job_becomes_current_job {
800 self.current_job_index = prev_index;
801 }
802 if previous_job_becomes_current_job || index == prev_index {
803 self.previous_job_index = self.any_suspended_job_but_current().unwrap_or(index);
804 }
805 }
806
807 Some(index)
808 }
809
810 pub fn disown_all(&mut self) {
814 for (_, job) in &mut self.jobs {
815 job.is_owned = false;
816 }
817 }
818}
819
820#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
822pub enum SetCurrentJobError {
823 #[error("no such job")]
825 NoSuchJob,
826
827 #[error("the current job must be selected from suspended jobs")]
830 NotSuspended,
831}
832
833impl JobList {
834 pub fn set_current_job(&mut self, index: usize) -> Result<(), SetCurrentJobError> {
846 let job = self.get(index).ok_or(SetCurrentJobError::NoSuchJob)?;
847 if !job.is_suspended() && self.iter().any(|(_, job)| job.is_suspended()) {
848 return Err(SetCurrentJobError::NotSuspended);
849 }
850
851 if index != self.current_job_index {
852 self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
853 }
854 Ok(())
855 }
856
857 pub fn current_job(&self) -> Option<usize> {
870 if self.jobs.contains(self.current_job_index) {
871 Some(self.current_job_index)
872 } else {
873 None
874 }
875 }
876
877 pub fn previous_job(&self) -> Option<usize> {
893 if self.previous_job_index != self.current_job_index
894 && self.jobs.contains(self.previous_job_index)
895 {
896 Some(self.previous_job_index)
897 } else {
898 None
899 }
900 }
901
902 fn any_suspended_job_but_current(&self) -> Option<usize> {
904 self.iter()
905 .filter(|&(index, job)| index != self.current_job_index && job.is_suspended())
906 .map(|(index, _)| index)
907 .next()
908 }
909
910 fn any_job_but_current(&self) -> Option<usize> {
912 self.iter()
913 .filter(|&(index, _)| index != self.current_job_index)
914 .map(|(index, _)| index)
915 .next()
916 }
917}
918
919impl JobList {
920 pub fn last_async_pid(&self) -> Pid {
927 self.last_async_pid
928 }
929
930 pub fn set_last_async_pid(&mut self, pid: Pid) {
935 self.last_async_pid = pid;
936 }
937}
938
939#[deprecated(since = "0.15.0", note = "use `handle_job_status` instead")]
957pub fn add_job_if_suspended<S, F>(
958 env: &mut Env<S>,
959 pid: Pid,
960 result: ProcessResult,
961 name: F,
962) -> crate::semantics::Result<ExitStatus>
963where
964 F: FnOnce() -> String,
965{
966 let exit_status = result.into();
967
968 if result.is_stopped() {
969 let mut job = Job::new(pid);
970 job.job_controlled = true;
971 job.state = result.into();
972 job.name = name();
973 env.jobs.insert(job);
974
975 if env.is_interactive() {
976 return Break(Divert::Interrupt(Some(exit_status)));
977 }
978 }
979
980 Continue(exit_status)
981}
982
983pub fn handle_job_status<S, F>(
1007 env: &mut Env<S>,
1008 pid: Pid,
1009 result: ProcessResult,
1010 job_name: F,
1011) -> crate::semantics::Result<ExitStatus>
1012where
1013 S: Signals,
1014 F: FnOnce() -> String,
1015{
1016 let exit_status = result.into();
1017
1018 if result.is_stopped() {
1019 let mut job = Job::new(pid);
1020 job.job_controlled = true;
1021 job.state = result.into();
1022 job.name = job_name();
1023 env.jobs.insert(job);
1024
1025 if env.is_interactive() {
1026 return Break(Divert::Interrupt(Some(exit_status)));
1027 }
1028 }
1029
1030 if let ProcessResult::Signaled { signal, .. } = result
1031 && signal == S::SIGINT
1032 && env.is_interactive()
1035 && env.sigint_has_default_action()
1036 {
1037 return Break(Divert::Interrupt(Some(exit_status)));
1038 }
1039
1040 Continue(exit_status)
1041}
1042
1043pub mod fmt;
1044pub mod id;
1045mod tcsetpgrp;
1046
1047pub use self::tcsetpgrp::*;
1048
1049#[cfg(test)]
1050mod tests {
1051 use super::*;
1052 use crate::option::Option::Interactive;
1053 use crate::option::State::On;
1054 use crate::signal;
1055 use crate::system::r#virtual::{SIGINT, SIGSTOP, SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU};
1056 use std::num::NonZero;
1057
1058 #[test]
1059 fn job_list_find_by_pid() {
1060 let mut list = JobList::default();
1061 assert_eq!(list.find_by_pid(Pid(10)), None);
1062
1063 let i10 = list.insert(Job::new(Pid(10)));
1064 let i20 = list.insert(Job::new(Pid(20)));
1065 let i30 = list.insert(Job::new(Pid(30)));
1066 assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
1067 assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
1068 assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
1069 assert_eq!(list.find_by_pid(Pid(40)), None);
1070
1071 list.remove(i10);
1072 assert_eq!(list.find_by_pid(Pid(10)), None);
1073 }
1074
1075 #[test]
1076 fn job_list_add_and_remove() {
1077 let mut list = JobList::default();
1079
1080 assert_eq!(list.insert(Job::new(Pid(10))), 0);
1081 assert_eq!(list.insert(Job::new(Pid(11))), 1);
1082 assert_eq!(list.insert(Job::new(Pid(12))), 2);
1083
1084 assert_eq!(list.remove(0).unwrap().pid, Pid(10));
1085 assert_eq!(list.remove(1).unwrap().pid, Pid(11));
1086
1087 assert_eq!(list.insert(Job::new(Pid(13))), 1);
1089 assert_eq!(list.insert(Job::new(Pid(14))), 0);
1090
1091 assert_eq!(list.remove(0).unwrap().pid, Pid(14));
1092 assert_eq!(list.remove(1).unwrap().pid, Pid(13));
1093 assert_eq!(list.remove(2).unwrap().pid, Pid(12));
1094
1095 assert_eq!(list.insert(Job::new(Pid(13))), 0);
1097 assert_eq!(list.insert(Job::new(Pid(14))), 1);
1098 }
1099
1100 #[test]
1101 fn job_list_add_same_pid() {
1102 let mut list = JobList::default();
1103
1104 let mut job = Job::new(Pid(10));
1105 job.name = "first job".to_string();
1106 let i_first = list.insert(job);
1107
1108 let mut job = Job::new(Pid(10));
1109 job.name = "second job".to_string();
1110 let i_second = list.insert(job);
1111
1112 let job = &list[i_second];
1113 assert_eq!(job.pid, Pid(10));
1114 assert_eq!(job.name, "second job");
1115
1116 assert_ne!(
1117 list.get(i_first).map(|job| job.name.as_str()),
1118 Some("first job")
1119 );
1120 }
1121
1122 #[test]
1123 fn job_list_extract_if() {
1124 let mut list = JobList::default();
1125 let i21 = list.insert(Job::new(Pid(21)));
1126 let i22 = list.insert(Job::new(Pid(22)));
1127 let i23 = list.insert(Job::new(Pid(23)));
1128 let i24 = list.insert(Job::new(Pid(24)));
1129 let i25 = list.insert(Job::new(Pid(25)));
1130 let i26 = list.insert(Job::new(Pid(26)));
1131 list.remove(i23).unwrap();
1132
1133 let mut i = list.extract_if(|index, mut job| {
1134 assert_ne!(index, i23);
1135 if index % 2 == 0 {
1136 job.state_reported();
1137 }
1138 index == 0 || job.pid == Pid(26)
1139 });
1140
1141 let mut expected_job_21 = Job::new(Pid(21));
1142 expected_job_21.state_changed = false;
1143 assert_eq!(i.next(), Some((i21, expected_job_21)));
1144 assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1145 assert_eq!(i.next(), None);
1146 assert_eq!(i.next(), None); let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1149 assert_eq!(indices, [i22, i24, i25]);
1150 assert!(list[i22].state_changed);
1151 assert!(list[i24].state_changed);
1152 assert!(!list[i25].state_changed);
1153 }
1154
1155 #[test]
1156 #[allow(
1157 clippy::bool_assert_comparison,
1158 reason = "to make the expected values clearer"
1159 )]
1160 fn updating_job_status_without_expected_state() {
1161 let mut list = JobList::default();
1162 let state = ProcessState::exited(15);
1163 assert_eq!(list.update_status(Pid(20), state), None);
1164
1165 let i10 = list.insert(Job::new(Pid(10)));
1166 let i20 = list.insert(Job::new(Pid(20)));
1167 let i30 = list.insert(Job::new(Pid(30)));
1168 assert_eq!(list[i20].state, ProcessState::Running);
1169
1170 list.get_mut(i20).unwrap().state_reported();
1171 assert_eq!(list[i20].state_changed, false);
1172
1173 assert_eq!(list.update_status(Pid(20), state), Some(i20));
1174 assert_eq!(list[i20].state, ProcessState::exited(15));
1175 assert_eq!(list[i20].state_changed, true);
1176
1177 assert_eq!(list[i10].state, ProcessState::Running);
1178 assert_eq!(list[i30].state, ProcessState::Running);
1179 }
1180
1181 #[test]
1182 #[allow(
1183 clippy::bool_assert_comparison,
1184 reason = "to make the expected values clearer"
1185 )]
1186 fn updating_job_status_with_matching_expected_state() {
1187 let mut list = JobList::default();
1188 let pid = Pid(20);
1189 let mut job = Job::new(pid);
1190 job.expected_state = Some(ProcessState::Running);
1191 job.state_changed = false;
1192 let i20 = list.insert(job);
1193
1194 assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1195
1196 let job = &list[i20];
1197 assert_eq!(job.state, ProcessState::Running);
1198 assert_eq!(job.expected_state, None);
1199 assert_eq!(job.state_changed, false);
1200 }
1201
1202 #[test]
1203 #[allow(
1204 clippy::bool_assert_comparison,
1205 reason = "to make the expected values clearer"
1206 )]
1207 fn updating_job_status_with_unmatched_expected_state() {
1208 let mut list = JobList::default();
1209 let pid = Pid(20);
1210 let mut job = Job::new(pid);
1211 job.expected_state = Some(ProcessState::Running);
1212 job.state_changed = false;
1213 let i20 = list.insert(job);
1214
1215 let result = list.update_status(pid, ProcessState::exited(0));
1216 assert_eq!(result, Some(i20));
1217
1218 let job = &list[i20];
1219 assert_eq!(job.state, ProcessState::exited(0));
1220 assert_eq!(job.expected_state, None);
1221 assert_eq!(job.state_changed, true);
1222 }
1223
1224 #[test]
1225 #[allow(
1226 clippy::bool_assert_comparison,
1227 reason = "to make the expected values clearer"
1228 )]
1229 fn disowning_jobs() {
1230 let mut list = JobList::default();
1231 let i10 = list.insert(Job::new(Pid(10)));
1232 let i20 = list.insert(Job::new(Pid(20)));
1233 let i30 = list.insert(Job::new(Pid(30)));
1234
1235 list.disown_all();
1236
1237 assert_eq!(list[i10].is_owned, false);
1238 assert_eq!(list[i20].is_owned, false);
1239 assert_eq!(list[i30].is_owned, false);
1240 }
1241
1242 #[test]
1243 fn no_current_and_previous_job_in_empty_job_list() {
1244 let list = JobList::default();
1245 assert_eq!(list.current_job(), None);
1246 assert_eq!(list.previous_job(), None);
1247 }
1248
1249 #[test]
1250 fn current_and_previous_job_in_job_list_with_one_job() {
1251 let mut list = JobList::default();
1252 let i10 = list.insert(Job::new(Pid(10)));
1253 assert_eq!(list.current_job(), Some(i10));
1254 assert_eq!(list.previous_job(), None);
1255 }
1256
1257 #[test]
1258 fn current_and_previous_job_in_job_list_with_two_job() {
1259 let mut list = JobList::default();
1262 let mut suspended = Job::new(Pid(10));
1263 suspended.state = ProcessState::stopped(SIGSTOP);
1264 let running = Job::new(Pid(20));
1265 let i10 = list.insert(suspended.clone());
1266 let i20 = list.insert(running.clone());
1267 assert_eq!(list.current_job(), Some(i10));
1268 assert_eq!(list.previous_job(), Some(i20));
1269
1270 list = JobList::default();
1272 let i20 = list.insert(running);
1273 let i10 = list.insert(suspended);
1274 assert_eq!(list.current_job(), Some(i10));
1275 assert_eq!(list.previous_job(), Some(i20));
1276 }
1277
1278 #[test]
1279 fn adding_suspended_job_with_running_current_and_previous_job() {
1280 let mut list = JobList::default();
1281 let running_1 = Job::new(Pid(11));
1282 let running_2 = Job::new(Pid(12));
1283 list.insert(running_1);
1284 list.insert(running_2);
1285 let ex_current_job_index = list.current_job().unwrap();
1286 let ex_previous_job_index = list.previous_job().unwrap();
1287 assert_ne!(ex_current_job_index, ex_previous_job_index);
1288
1289 let mut suspended = Job::new(Pid(20));
1290 suspended.state = ProcessState::stopped(SIGSTOP);
1291 let i20 = list.insert(suspended);
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, i20);
1295 assert_eq!(now_previous_job_index, ex_current_job_index);
1296 }
1297
1298 #[test]
1299 fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1300 let mut list = JobList::default();
1301
1302 let running = Job::new(Pid(18));
1303 let i18 = list.insert(running);
1304
1305 let mut suspended_1 = Job::new(Pid(19));
1306 suspended_1.state = ProcessState::stopped(SIGSTOP);
1307 let i19 = list.insert(suspended_1);
1308
1309 let ex_current_job_index = list.current_job().unwrap();
1310 let ex_previous_job_index = list.previous_job().unwrap();
1311 assert_eq!(ex_current_job_index, i19);
1312 assert_eq!(ex_previous_job_index, i18);
1313
1314 let mut suspended_2 = Job::new(Pid(20));
1315 suspended_2.state = ProcessState::stopped(SIGSTOP);
1316 let i20 = list.insert(suspended_2);
1317
1318 let now_current_job_index = list.current_job().unwrap();
1319 let now_previous_job_index = list.previous_job().unwrap();
1320 assert_eq!(now_current_job_index, ex_current_job_index);
1321 assert_eq!(now_previous_job_index, i20);
1322 }
1323
1324 #[test]
1325 fn removing_current_job() {
1326 let mut list = JobList::default();
1327
1328 let running = Job::new(Pid(10));
1329 let i10 = list.insert(running);
1330
1331 let mut suspended_1 = Job::new(Pid(11));
1332 let mut suspended_2 = Job::new(Pid(12));
1333 let mut suspended_3 = Job::new(Pid(13));
1334 suspended_1.state = ProcessState::stopped(SIGSTOP);
1335 suspended_2.state = ProcessState::stopped(SIGSTOP);
1336 suspended_3.state = ProcessState::stopped(SIGSTOP);
1337 list.insert(suspended_1);
1338 list.insert(suspended_2);
1339 list.insert(suspended_3);
1340
1341 let current_job_index_1 = list.current_job().unwrap();
1342 let previous_job_index_1 = list.previous_job().unwrap();
1343 assert_ne!(current_job_index_1, i10);
1344 assert_ne!(previous_job_index_1, i10);
1345
1346 list.remove(current_job_index_1);
1347 let current_job_index_2 = list.current_job().unwrap();
1348 let previous_job_index_2 = list.previous_job().unwrap();
1349 assert_eq!(current_job_index_2, previous_job_index_1);
1350 assert_ne!(previous_job_index_2, current_job_index_2);
1351 let previous_job_2 = &list[previous_job_index_2];
1353 assert!(
1354 previous_job_2.is_suspended(),
1355 "previous_job_2 = {previous_job_2:?}"
1356 );
1357
1358 list.remove(current_job_index_2);
1359 let current_job_index_3 = list.current_job().unwrap();
1360 let previous_job_index_3 = list.previous_job().unwrap();
1361 assert_eq!(current_job_index_3, previous_job_index_2);
1362 assert_eq!(previous_job_index_3, i10);
1364
1365 list.remove(current_job_index_3);
1366 let current_job_index_4 = list.current_job().unwrap();
1367 assert_eq!(current_job_index_4, i10);
1368 assert_eq!(list.previous_job(), None);
1370 }
1371
1372 #[test]
1373 fn removing_previous_job_with_suspended_job() {
1374 let mut list = JobList::default();
1375
1376 let running = Job::new(Pid(10));
1377 let i10 = list.insert(running);
1378
1379 let mut suspended_1 = Job::new(Pid(11));
1380 let mut suspended_2 = Job::new(Pid(12));
1381 let mut suspended_3 = Job::new(Pid(13));
1382 suspended_1.state = ProcessState::stopped(SIGSTOP);
1383 suspended_2.state = ProcessState::stopped(SIGSTOP);
1384 suspended_3.state = ProcessState::stopped(SIGSTOP);
1385 list.insert(suspended_1);
1386 list.insert(suspended_2);
1387 list.insert(suspended_3);
1388
1389 let ex_current_job_index = list.current_job().unwrap();
1390 let ex_previous_job_index = list.previous_job().unwrap();
1391 assert_ne!(ex_current_job_index, i10);
1392 assert_ne!(ex_previous_job_index, i10);
1393
1394 list.remove(ex_previous_job_index);
1395 let now_current_job_index = list.current_job().unwrap();
1396 let now_previous_job_index = list.previous_job().unwrap();
1397 assert_eq!(now_current_job_index, ex_current_job_index);
1398 assert_ne!(now_previous_job_index, now_current_job_index);
1399 let now_previous_job = &list[now_previous_job_index];
1401 assert!(
1402 now_previous_job.is_suspended(),
1403 "now_previous_job = {now_previous_job:?}"
1404 );
1405 }
1406
1407 #[test]
1408 fn removing_previous_job_with_running_job() {
1409 let mut list = JobList::default();
1410
1411 let running = Job::new(Pid(10));
1412 let i10 = list.insert(running);
1413
1414 let mut suspended_1 = Job::new(Pid(11));
1415 let mut suspended_2 = Job::new(Pid(12));
1416 suspended_1.state = ProcessState::stopped(SIGSTOP);
1417 suspended_2.state = ProcessState::stopped(SIGSTOP);
1418 list.insert(suspended_1);
1419 list.insert(suspended_2);
1420
1421 let ex_current_job_index = list.current_job().unwrap();
1422 let ex_previous_job_index = list.previous_job().unwrap();
1423 assert_ne!(ex_current_job_index, i10);
1424 assert_ne!(ex_previous_job_index, i10);
1425
1426 list.remove(ex_previous_job_index);
1427 let now_current_job_index = list.current_job().unwrap();
1428 let now_previous_job_index = list.previous_job().unwrap();
1429 assert_eq!(now_current_job_index, ex_current_job_index);
1430 assert_eq!(now_previous_job_index, i10);
1433 }
1434
1435 #[test]
1436 fn set_current_job_with_running_jobs_only() {
1437 let mut list = JobList::default();
1438 let i21 = list.insert(Job::new(Pid(21)));
1439 let i22 = list.insert(Job::new(Pid(22)));
1440
1441 assert_eq!(list.set_current_job(i21), Ok(()));
1442 assert_eq!(list.current_job(), Some(i21));
1443
1444 assert_eq!(list.set_current_job(i22), Ok(()));
1445 assert_eq!(list.current_job(), Some(i22));
1446 }
1447
1448 #[test]
1449 fn set_current_job_to_suspended_job() {
1450 let mut list = JobList::default();
1451 list.insert(Job::new(Pid(20)));
1452
1453 let mut suspended_1 = Job::new(Pid(21));
1454 let mut suspended_2 = Job::new(Pid(22));
1455 suspended_1.state = ProcessState::stopped(SIGSTOP);
1456 suspended_2.state = ProcessState::stopped(SIGSTOP);
1457 let i21 = list.insert(suspended_1);
1458 let i22 = list.insert(suspended_2);
1459
1460 assert_eq!(list.set_current_job(i21), Ok(()));
1461 assert_eq!(list.current_job(), Some(i21));
1462
1463 assert_eq!(list.set_current_job(i22), Ok(()));
1464 assert_eq!(list.current_job(), Some(i22));
1465 }
1466
1467 #[test]
1468 fn set_current_job_no_such_job() {
1469 let mut list = JobList::default();
1470 assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1471 assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1472 assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1473 }
1474
1475 #[test]
1476 fn set_current_job_not_suspended() {
1477 let mut list = JobList::default();
1478 let mut suspended = Job::new(Pid(10));
1479 suspended.state = ProcessState::stopped(SIGTSTP);
1480 let running = Job::new(Pid(20));
1481 let i10 = list.insert(suspended);
1482 let i20 = list.insert(running);
1483 assert_eq!(
1484 list.set_current_job(i20),
1485 Err(SetCurrentJobError::NotSuspended)
1486 );
1487 assert_eq!(list.current_job(), Some(i10));
1488 }
1489
1490 #[test]
1491 fn set_current_job_no_change() {
1492 let mut list = JobList::default();
1493 list.insert(Job::new(Pid(5)));
1494 list.insert(Job::new(Pid(6)));
1495 let old_current_job_index = list.current_job().unwrap();
1496 let old_previous_job_index = list.previous_job().unwrap();
1497 list.set_current_job(old_current_job_index).unwrap();
1498 let new_current_job_index = list.current_job().unwrap();
1499 let new_previous_job_index = list.previous_job().unwrap();
1500 assert_eq!(new_current_job_index, old_current_job_index);
1501 assert_eq!(new_previous_job_index, old_previous_job_index);
1502 }
1503
1504 #[test]
1505 fn resuming_current_job_without_other_suspended_jobs() {
1506 let mut list = JobList::default();
1507 let mut suspended = Job::new(Pid(10));
1508 suspended.state = ProcessState::stopped(SIGTSTP);
1509 let running = Job::new(Pid(20));
1510 let i10 = list.insert(suspended);
1511 let i20 = list.insert(running);
1512 list.update_status(Pid(10), ProcessState::Running);
1513 assert_eq!(list.current_job(), Some(i10));
1514 assert_eq!(list.previous_job(), Some(i20));
1515 }
1516
1517 #[test]
1518 fn resuming_current_job_with_another_suspended_job() {
1519 let mut list = JobList::default();
1520 let mut suspended_1 = Job::new(Pid(10));
1521 let mut suspended_2 = Job::new(Pid(20));
1522 suspended_1.state = ProcessState::stopped(SIGTSTP);
1523 suspended_2.state = ProcessState::stopped(SIGTSTP);
1524 let i10 = list.insert(suspended_1);
1525 let i20 = list.insert(suspended_2);
1526 list.set_current_job(i10).unwrap();
1527 list.update_status(Pid(10), ProcessState::Running);
1528 assert_eq!(list.current_job(), Some(i20));
1530 assert_eq!(list.previous_job(), Some(i10));
1531 }
1532
1533 #[test]
1534 fn resuming_current_job_with_other_suspended_jobs() {
1535 let mut list = JobList::default();
1536 let mut suspended_1 = Job::new(Pid(10));
1537 let mut suspended_2 = Job::new(Pid(20));
1538 let mut suspended_3 = Job::new(Pid(30));
1539 suspended_1.state = ProcessState::stopped(SIGTSTP);
1540 suspended_2.state = ProcessState::stopped(SIGTSTP);
1541 suspended_3.state = ProcessState::stopped(SIGTSTP);
1542 list.insert(suspended_1);
1543 list.insert(suspended_2);
1544 list.insert(suspended_3);
1545 let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1546 let ex_previous_job_index = list.previous_job().unwrap();
1547
1548 list.update_status(ex_current_job_pid, ProcessState::Running);
1549 let now_current_job_index = list.current_job().unwrap();
1550 let now_previous_job_index = list.previous_job().unwrap();
1551 assert_eq!(now_current_job_index, ex_previous_job_index);
1552 assert_ne!(now_previous_job_index, now_current_job_index);
1553 let now_previous_job = &list[now_previous_job_index];
1555 assert!(
1556 now_previous_job.is_suspended(),
1557 "now_previous_job = {now_previous_job:?}"
1558 );
1559 }
1560
1561 #[test]
1562 fn resuming_previous_job() {
1563 let mut list = JobList::default();
1564 let mut suspended_1 = Job::new(Pid(10));
1565 let mut suspended_2 = Job::new(Pid(20));
1566 let mut suspended_3 = Job::new(Pid(30));
1567 suspended_1.state = ProcessState::stopped(SIGTSTP);
1568 suspended_2.state = ProcessState::stopped(SIGTSTP);
1569 suspended_3.state = ProcessState::stopped(SIGTSTP);
1570 list.insert(suspended_1);
1571 list.insert(suspended_2);
1572 list.insert(suspended_3);
1573 let ex_current_job_index = list.current_job().unwrap();
1574 let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1575
1576 list.update_status(ex_previous_job_pid, ProcessState::Running);
1577 let now_current_job_index = list.current_job().unwrap();
1578 let now_previous_job_index = list.previous_job().unwrap();
1579 assert_eq!(now_current_job_index, ex_current_job_index);
1580 assert_ne!(now_previous_job_index, now_current_job_index);
1581 let now_previous_job = &list[now_previous_job_index];
1583 assert!(
1584 now_previous_job.is_suspended(),
1585 "now_previous_job = {now_previous_job:?}"
1586 );
1587 }
1588
1589 #[test]
1590 fn resuming_other_job() {
1591 let mut list = JobList::default();
1592 let mut suspended_1 = Job::new(Pid(10));
1593 let mut suspended_2 = Job::new(Pid(20));
1594 let mut suspended_3 = Job::new(Pid(30));
1595 suspended_1.state = ProcessState::stopped(SIGTSTP);
1596 suspended_2.state = ProcessState::stopped(SIGTSTP);
1597 suspended_3.state = ProcessState::stopped(SIGTSTP);
1598 let i10 = list.insert(suspended_1);
1599 let i20 = list.insert(suspended_2);
1600 let _i30 = list.insert(suspended_3);
1601 list.set_current_job(i20).unwrap();
1602 list.set_current_job(i10).unwrap();
1603 list.update_status(Pid(30), ProcessState::Running);
1604 assert_eq!(list.current_job(), Some(i10));
1605 assert_eq!(list.previous_job(), Some(i20));
1606 }
1607
1608 #[test]
1609 fn suspending_current_job() {
1610 let mut list = JobList::default();
1611 let i11 = list.insert(Job::new(Pid(11)));
1612 let i12 = list.insert(Job::new(Pid(12)));
1613 list.set_current_job(i11).unwrap();
1614 list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1615 assert_eq!(list.current_job(), Some(i11));
1616 assert_eq!(list.previous_job(), Some(i12));
1617 }
1618
1619 #[test]
1620 fn suspending_previous_job() {
1621 let mut list = JobList::default();
1622 let i11 = list.insert(Job::new(Pid(11)));
1623 let i12 = list.insert(Job::new(Pid(12)));
1624 list.set_current_job(i11).unwrap();
1625 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1626 assert_eq!(list.current_job(), Some(i12));
1627 assert_eq!(list.previous_job(), Some(i11));
1628 }
1629
1630 #[test]
1631 fn suspending_job_with_running_current_job() {
1632 let mut list = JobList::default();
1633 let i10 = list.insert(Job::new(Pid(10)));
1634 let _i11 = list.insert(Job::new(Pid(11)));
1635 let i12 = list.insert(Job::new(Pid(12)));
1636 list.set_current_job(i10).unwrap();
1637 list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1638 assert_eq!(list.current_job(), Some(i12));
1639 assert_eq!(list.previous_job(), Some(i10));
1640 }
1641
1642 #[test]
1643 fn suspending_job_with_running_previous_job() {
1644 let mut list = JobList::default();
1645 let i11 = list.insert(Job::new(Pid(11)));
1646 let i12 = list.insert(Job::new(Pid(12)));
1647 let mut suspended = Job::new(Pid(10));
1648 suspended.state = ProcessState::stopped(SIGTTIN);
1649 let i10 = list.insert(suspended);
1650 assert_eq!(list.current_job(), Some(i10));
1651 assert_eq!(list.previous_job(), Some(i11));
1652
1653 list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1654 assert_eq!(list.current_job(), Some(i12));
1655 assert_eq!(list.previous_job(), Some(i10));
1656 }
1657
1658 #[allow(deprecated, reason = "to test the deprecated function")]
1659 mod add_job_if_suspended {
1660 use super::*;
1661
1662 #[test]
1663 fn do_not_add_job_if_exited() {
1664 let mut env = Env::new_virtual();
1665 let result = add_job_if_suspended(
1666 &mut env,
1667 Pid(123),
1668 ProcessResult::Exited(ExitStatus(42)),
1669 || "foo".to_string(),
1670 );
1671 assert_eq!(result, Continue(ExitStatus(42)));
1672 assert_eq!(env.jobs.len(), 0);
1673 }
1674
1675 #[test]
1676 fn do_not_add_job_if_signaled() {
1677 let mut env = Env::new_virtual();
1678 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1679 let result = add_job_if_suspended(
1680 &mut env,
1681 Pid(123),
1682 ProcessResult::Signaled {
1683 signal,
1684 core_dump: false,
1685 },
1686 || "foo".to_string(),
1687 );
1688 assert_eq!(result, Continue(ExitStatus::from(signal)));
1689 assert_eq!(env.jobs.len(), 0);
1690 }
1691
1692 #[test]
1693 fn add_job_if_stopped() {
1694 let mut env = Env::new_virtual();
1695 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1696 let process_result = ProcessResult::Stopped(signal);
1697 let result =
1698 add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1699 assert_eq!(result, Continue(ExitStatus::from(signal)));
1700 assert_eq!(env.jobs.len(), 1);
1701 let job = env.jobs.get(0).unwrap();
1702 assert_eq!(job.pid, Pid(123));
1703 assert!(job.job_controlled);
1704 assert_eq!(job.state, ProcessState::Halted(process_result));
1705 assert_eq!(job.name, "foo");
1706 }
1707
1708 #[test]
1709 fn break_if_stopped_and_interactive() {
1710 let mut env = Env::new_virtual();
1711 env.options.set(Interactive, On);
1712 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1713 let process_result = ProcessResult::Stopped(signal);
1714 let result =
1715 add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1716 assert_eq!(
1717 result,
1718 Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1719 );
1720 assert_eq!(env.jobs.len(), 1);
1721 }
1722 }
1723
1724 mod handle_job_status {
1725 use super::*;
1726 use crate::source::Location;
1727 use futures_util::FutureExt as _;
1728
1729 #[test]
1730 fn do_not_insert_job_if_exited() {
1731 let mut env = Env::new_virtual();
1732 let result = handle_job_status(
1733 &mut env,
1734 Pid(123),
1735 ProcessResult::Exited(ExitStatus(42)),
1736 || unreachable!(),
1737 );
1738 assert_eq!(result, Continue(ExitStatus(42)));
1739 assert_eq!(env.jobs.len(), 0);
1740 }
1741
1742 #[test]
1743 fn do_not_insert_job_if_signaled() {
1744 let mut env = Env::new_virtual();
1745 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1746 let result = handle_job_status(
1747 &mut env,
1748 Pid(123),
1749 ProcessResult::Signaled {
1750 signal,
1751 core_dump: false,
1752 },
1753 || unreachable!(),
1754 );
1755 assert_eq!(result, Continue(ExitStatus::from(signal)));
1756 assert_eq!(env.jobs.len(), 0);
1757 }
1758
1759 #[test]
1760 fn insert_job_if_stopped() {
1761 let mut env = Env::new_virtual();
1762 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1763 let process_result = ProcessResult::Stopped(signal);
1764 let result =
1765 handle_job_status(&mut env, Pid(123), process_result, || "foo".to_string());
1766 assert_eq!(result, Continue(ExitStatus::from(signal)));
1767 assert_eq!(env.jobs.len(), 1);
1768 let job = &env.jobs[0];
1769 assert_eq!(job.pid, Pid(123));
1770 assert!(job.job_controlled);
1771 assert_eq!(job.state, ProcessState::Halted(process_result));
1772 assert_eq!(job.name, "foo");
1773 }
1774
1775 #[test]
1776 fn interrupt_if_stopped_and_interactive() {
1777 let mut env = Env::new_virtual();
1778 env.options.set(Interactive, On);
1779 let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1780 let process_result = ProcessResult::Stopped(signal);
1781 let result =
1782 handle_job_status(&mut env, Pid(123), process_result, || "foo".to_string());
1783 assert_eq!(
1784 result,
1785 Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1786 );
1787 assert_eq!(env.jobs.len(), 1);
1788 }
1789
1790 #[test]
1791 fn do_not_interrupt_if_stopped_but_non_interactive() {
1792 let mut env = Env::new_virtual();
1793 let process_result = ProcessResult::Stopped(SIGTSTP);
1795 let result =
1796 handle_job_status(&mut env, Pid(123), process_result, || "job".to_string());
1797 assert_eq!(result, Continue(ExitStatus::from(SIGTSTP)));
1798 }
1799
1800 #[test]
1801 fn interrupt_interactive_shell_for_default_sigint_action() {
1802 let mut env = Env::new_virtual();
1803 env.options.set(Interactive, On);
1804 let process_result = ProcessResult::Signaled {
1806 signal: SIGINT,
1807 core_dump: false,
1808 };
1809 let result = handle_job_status(&mut env, Pid(123), process_result, || unreachable!());
1810 assert_eq!(
1811 result,
1812 Break(Divert::Interrupt(Some(ExitStatus::from(SIGINT))))
1813 );
1814 assert_eq!(env.jobs.len(), 0);
1815 }
1816
1817 #[test]
1818 fn do_not_interrupt_if_signaled_by_other_than_sigint() {
1819 let mut env = Env::new_virtual();
1820 env.options.set(Interactive, On);
1821 let process_result = ProcessResult::Signaled {
1822 signal: SIGTERM,
1823 core_dump: false,
1824 };
1825 let result = handle_job_status(&mut env, Pid(123), process_result, || unreachable!());
1826 assert_eq!(result, Continue(ExitStatus::from(SIGTERM)));
1827 assert_eq!(env.jobs.len(), 0);
1828 }
1829
1830 #[test]
1831 fn do_not_interrupt_non_interactive_shell_for_default_sigint_action() {
1832 let mut env = Env::new_virtual();
1833 let process_result = ProcessResult::Signaled {
1835 signal: SIGINT,
1836 core_dump: false,
1837 };
1838 let result = handle_job_status(&mut env, Pid(123), process_result, || unreachable!());
1839 assert_eq!(result, Continue(ExitStatus::from(SIGINT)));
1840 assert_eq!(env.jobs.len(), 0);
1841 }
1842
1843 #[test]
1844 fn do_not_interrupt_interactive_shell_for_ignore_sigint_action() {
1845 let mut env = Env::new_virtual();
1846 env.options.set(Interactive, On);
1847 env.traps
1848 .set_action(
1849 &env.system,
1850 SIGINT,
1851 Action::Ignore,
1852 Location::dummy(""),
1853 false,
1854 )
1855 .now_or_never()
1856 .unwrap()
1857 .unwrap();
1858 let process_result = ProcessResult::Signaled {
1859 signal: SIGINT,
1860 core_dump: false,
1861 };
1862 let result = handle_job_status(&mut env, Pid(123), process_result, || unreachable!());
1863 assert_eq!(result, Continue(ExitStatus::from(SIGINT)));
1864 }
1865
1866 #[test]
1867 fn do_not_interrupt_interactive_shell_for_custom_sigint_action() {
1868 let mut env = Env::new_virtual();
1869 env.options.set(Interactive, On);
1870 env.traps
1871 .set_action(
1872 &env.system,
1873 SIGINT,
1874 Action::Command("echo interrupt".into()),
1875 Location::dummy(""),
1876 false,
1877 )
1878 .now_or_never()
1879 .unwrap()
1880 .unwrap();
1881 let process_result = ProcessResult::Signaled {
1882 signal: SIGINT,
1883 core_dump: false,
1884 };
1885 let result = handle_job_status(&mut env, Pid(123), process_result, || unreachable!());
1886 assert_eq!(result, Continue(ExitStatus::from(SIGINT)));
1887 }
1888 }
1889}