1use crate::Env;
32use crate::job::Pid;
33use crate::job::ProcessResult;
34use crate::job::RunBlocking;
35use crate::job::RunUnblocking;
36use crate::system::Close;
37use crate::system::Dup;
38use crate::system::Errno;
39use crate::system::Exit;
40use crate::system::Fork;
41use crate::system::GetPid;
42use crate::system::Open;
43use crate::system::SendSignal;
44use crate::system::SetPgid;
45use crate::system::Sigmask;
46use crate::system::SigmaskOp;
47use crate::system::Signals;
48use crate::system::Sigset as _;
49use crate::system::TcSetPgrp;
50use crate::system::Wait;
51use crate::system::concurrency::WaitForSignals;
52use crate::system::resource::SetRlimit;
53use crate::trap::SignalSystem;
54use std::marker::PhantomData;
55use std::pin::Pin;
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum JobControl {
60 Foreground,
62 Background,
64}
65
66#[deprecated(
71 note = "use `Config` for subshell configuration instead",
72 since = "0.14.0"
73)]
74#[must_use = "a subshell is not started unless you call `Subshell::start`"]
75pub struct Subshell<S, F> {
76 task: F,
77 job_control: Option<JobControl>,
78 ignores_sigint_sigquit: bool,
79 phantom_data: PhantomData<fn(&mut Env<S>)>,
80}
81
82#[allow(deprecated, reason = "for backward compatible API")]
83impl<S, F> std::fmt::Debug for Subshell<S, F> {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("Subshell")
86 .field("job_control", &self.job_control)
87 .field("ignores_sigint_sigquit", &self.ignores_sigint_sigquit)
88 .finish_non_exhaustive()
89 }
90}
91
92#[allow(deprecated, reason = "for backward compatible API")]
93impl<S, F> Subshell<S, F>
94where
95 S: BlockSignals
96 + Close
97 + Dup
98 + Exit
99 + Fork
100 + GetPid
101 + Open
102 + RunBlocking
103 + RunUnblocking
104 + SendSignal
105 + SetPgid
106 + SetRlimit
107 + SignalSystem
108 + TcSetPgrp
109 + WaitForSignals
110 + 'static,
111 F: for<'a> FnOnce(&'a mut Env<S>, Option<JobControl>) -> Pin<Box<dyn Future<Output = ()> + 'a>>
112 + 'static,
113 {
115 pub fn new(task: F) -> Self {
129 Subshell {
130 task,
131 job_control: None,
132 ignores_sigint_sigquit: false,
133 phantom_data: PhantomData,
134 }
135 }
136
137 pub fn job_control<J: Into<Option<JobControl>>>(mut self, job_control: J) -> Self {
157 self.job_control = job_control.into();
158 self
159 }
160
161 pub fn ignore_sigint_sigquit(mut self, ignore: bool) -> Self {
168 self.ignores_sigint_sigquit = ignore;
169 self
170 }
171
172 fn into_config_and_task(self) -> (Config, F) {
173 let config = Config {
174 job_control: self.job_control,
175 ignores_sigint_sigquit: self.ignores_sigint_sigquit,
176 };
177 (config, self.task)
178 }
179
180 pub async fn start(self, env: &mut Env<S>) -> Result<(Pid, Option<JobControl>), Errno> {
202 let (config, task) = self.into_config_and_task();
203 config
204 .start(env, async move |env, job_control| {
205 task(env, job_control).await
206 })
207 .await
208 }
209
210 pub async fn start_and_wait(self, env: &mut Env<S>) -> Result<(Pid, ProcessResult), Errno>
230 where
231 S: Wait,
232 {
233 let (config, task) = self.into_config_and_task();
234 config
235 .start_and_wait(env, async move |env, job_control| {
236 task(env, job_control).await
237 })
238 .await
239 }
240}
241
242pub trait BlockSignals: Signals {
259 type SavedMask;
260
261 fn block_sigint_sigquit(
272 &self,
273 ) -> impl Future<Output = Result<Self::SavedMask, Errno>> + use<'_, Self>;
274
275 fn restore_sigmask(
281 &self,
282 mask: Self::SavedMask,
283 ) -> impl Future<Output = Result<(), Errno>> + use<'_, Self>;
284}
285
286impl<S> BlockSignals for S
287where
288 S: Sigmask + ?Sized,
289{
290 type SavedMask = S::Sigset;
291
292 async fn block_sigint_sigquit(&self) -> Result<Self::SavedMask, Errno> {
293 let mut old_mask = S::Sigset::new();
294 self.sigmask(
295 Some((
296 SigmaskOp::Add,
297 &S::Sigset::from_signals([S::SIGINT, S::SIGQUIT])?,
298 )),
299 Some(&mut old_mask),
300 )
301 .await?;
302 Ok(old_mask)
303 }
304
305 async fn restore_sigmask(&self, mask: Self::SavedMask) -> Result<(), Errno> {
306 self.sigmask(Some((SigmaskOp::Set, &mask)), None).await
307 }
308}
309
310mod config;
311pub use config::Config;
312
313#[allow(deprecated, reason = "for backward compatible API")]
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::job::{Job, ProcessState};
318 use crate::option::Option::{Interactive, Monitor};
319 use crate::option::State::On;
320 use crate::semantics::ExitStatus;
321 use crate::source::Location;
322 use crate::stack::Frame;
323 use crate::system::r#virtual::{Inode, SystemState, VirtualSystem};
324 use crate::system::r#virtual::{SIGCHLD, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU};
325 use crate::system::{Concurrent, Disposition};
326 use crate::test_helper::in_virtual_system;
327 use crate::trap::Action;
328 use assert_matches::assert_matches;
329 use futures_executor::LocalPool;
330 use std::cell::Cell;
331 use std::cell::RefCell;
332 use std::rc::Rc;
333
334 fn stub_tty(state: &RefCell<SystemState>) {
335 state
336 .borrow_mut()
337 .file_system
338 .save("/dev/tty", Rc::new(RefCell::new(Inode::new([]))))
339 .unwrap();
340 }
341
342 #[test]
343 fn subshell_start_returns_child_process_id() {
344 in_virtual_system(|mut env, _state| async move {
345 let parent_pid = env.main_pid;
346 let child_pid = Rc::new(Cell::new(None));
347 let child_pid_2 = Rc::clone(&child_pid);
348 let subshell = Subshell::new(
349 move |env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
350 Box::pin(async move {
351 child_pid_2.set(Some(env.system.getpid()));
352 assert_eq!(env.system.getppid(), parent_pid);
353 })
354 },
355 );
356 let result = subshell.start(&mut env).await.unwrap().0;
357 env.wait_for_subshell(result).await.unwrap();
358 assert_eq!(Some(result), child_pid.get());
359 });
360 }
361
362 #[test]
363 fn subshell_start_failing() {
364 let mut executor = LocalPool::new();
365 let env = &mut Env::new_virtual();
366 let subshell =
367 Subshell::new(|_env, _job_control| unreachable!("subshell not expected to run"));
368 let result = executor.run_until(subshell.start(env));
369 assert_eq!(result, Err(Errno::ENOSYS));
370 }
371
372 #[test]
373 fn stack_frame_in_subshell() {
374 in_virtual_system(|mut env, _state| async move {
375 let subshell = Subshell::new(|env, _job_control| {
376 Box::pin(async { assert_eq!(env.stack[..], [Frame::Subshell]) })
377 });
378 let pid = subshell.start(&mut env).await.unwrap().0;
379 assert_eq!(env.stack[..], []);
380
381 env.wait_for_subshell(pid).await.unwrap();
382 });
383 }
384
385 #[test]
386 fn jobs_disowned_in_subshell() {
387 in_virtual_system(|mut env, _state| async move {
388 let index = env.jobs.insert(Job::new(Pid(123)));
389 let subshell = Subshell::new(move |env, _job_control| {
390 Box::pin(async move { assert!(!env.jobs[index].is_owned) })
391 });
392 let pid = subshell.start(&mut env).await.unwrap().0;
393 env.wait_for_subshell(pid).await.unwrap();
394
395 assert!(env.jobs[index].is_owned);
396 });
397 }
398
399 #[test]
400 fn trap_reset_in_subshell() {
401 in_virtual_system(|mut env, _state| async move {
402 env.traps
403 .set_action(
404 &env.system,
405 SIGCHLD,
406 Action::Command("echo foo".into()),
407 Location::dummy(""),
408 false,
409 )
410 .await
411 .unwrap();
412 let subshell = Subshell::new(|env, _job_control| {
413 Box::pin(async {
414 let (current, parent) = env.traps.get_state(SIGCHLD);
415 assert_eq!(current.unwrap().action, Action::Default);
416 assert_matches!(
417 &parent.unwrap().action,
418 Action::Command(body) => assert_eq!(&**body, "echo foo")
419 );
420 })
421 });
422 let pid = subshell.start(&mut env).await.unwrap().0;
423 env.wait_for_subshell(pid).await.unwrap();
424 });
425 }
426
427 #[test]
428 fn subshell_with_no_job_control() {
429 in_virtual_system(|mut parent_env, state| async move {
430 parent_env.options.set(Monitor, On);
431
432 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
433 let state_2 = Rc::clone(&state);
434 let (child_pid, job_control) = Subshell::new(
435 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
436 Box::pin(async move {
437 let child_pid = child_env.system.getpid();
438 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
439 assert_eq!(state_2.borrow().foreground, None);
440 assert_eq!(job_control, None);
441 })
442 },
443 )
444 .job_control(None)
445 .start(&mut parent_env)
446 .await
447 .unwrap();
448 assert_eq!(job_control, None);
449 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
450 assert_eq!(state.borrow().foreground, None);
451
452 parent_env.wait_for_subshell(child_pid).await.unwrap();
453 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
454 assert_eq!(state.borrow().foreground, None);
455 });
456 }
457
458 #[test]
459 fn subshell_in_background() {
460 in_virtual_system(|mut parent_env, state| async move {
461 parent_env.options.set(Monitor, On);
462
463 let state_2 = Rc::clone(&state);
464 let (child_pid, job_control) = Subshell::new(
465 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
466 Box::pin(async move {
467 let child_pid = child_env.system.getpid();
468 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
469 assert_eq!(state_2.borrow().foreground, None);
470 assert_eq!(job_control, Some(JobControl::Background));
471 })
472 },
473 )
474 .job_control(JobControl::Background)
475 .start(&mut parent_env)
476 .await
477 .unwrap();
478 assert_eq!(job_control, Some(JobControl::Background));
479 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
480 assert_eq!(state.borrow().foreground, None);
481
482 parent_env.wait_for_subshell(child_pid).await.unwrap();
483 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
484 assert_eq!(state.borrow().foreground, None);
485 });
486 }
487
488 #[test]
489 fn subshell_in_foreground() {
490 in_virtual_system(|mut parent_env, state| async move {
491 parent_env.options.set(Monitor, On);
492 stub_tty(&state);
493
494 let state_2 = Rc::clone(&state);
495 let (child_pid, job_control) = Subshell::new(
496 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
497 Box::pin(async move {
498 let child_pid = child_env.system.getpid();
499 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
500 assert_eq!(state_2.borrow().foreground, Some(child_pid));
501 assert_eq!(job_control, Some(JobControl::Foreground));
502 })
503 },
504 )
505 .job_control(JobControl::Foreground)
506 .start(&mut parent_env)
507 .await
508 .unwrap();
509 assert_eq!(job_control, Some(JobControl::Foreground));
510 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
511 parent_env.wait_for_subshell(child_pid).await.unwrap();
515 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
516 assert_eq!(state.borrow().foreground, Some(child_pid));
517 });
518 }
519
520 #[test]
521 fn tty_after_starting_foreground_subshell() {
522 in_virtual_system(|mut parent_env, state| async move {
523 parent_env.options.set(Monitor, On);
524 stub_tty(&state);
525
526 let _ = Subshell::new(move |_, _| Box::pin(std::future::ready(())))
527 .job_control(JobControl::Foreground)
528 .start(&mut parent_env)
529 .await
530 .unwrap();
531 assert_matches!(parent_env.tty, Some(_));
532 });
533 }
534
535 #[test]
536 fn job_control_without_tty() {
537 in_virtual_system(async |mut parent_env, state| {
541 parent_env.options.set(Monitor, On);
542
543 let state_2 = Rc::clone(&state);
544 let (child_pid, job_control) = Subshell::new(
545 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, job_control| {
546 Box::pin(async move {
547 let child_pid = child_env.system.getpid();
548 assert_eq!(state_2.borrow().processes[&child_pid].pgid, child_pid);
549 assert_eq!(job_control, Some(JobControl::Foreground));
550 })
551 },
552 )
553 .job_control(JobControl::Foreground)
554 .start(&mut parent_env)
555 .await
556 .unwrap();
557 assert_eq!(job_control, Some(JobControl::Foreground));
558 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
559
560 parent_env.wait_for_subshell(child_pid).await.unwrap();
561 assert_eq!(state.borrow().processes[&child_pid].pgid, child_pid);
562 })
563 }
564
565 #[test]
566 fn no_job_control_with_option_disabled() {
567 in_virtual_system(|mut parent_env, state| async move {
568 stub_tty(&state);
569
570 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
571 let state_2 = Rc::clone(&state);
572 let (child_pid, job_control) = Subshell::new(
573 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
574 Box::pin(async move {
575 let child_pid = child_env.system.getpid();
576 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
577 assert_eq!(state_2.borrow().foreground, None);
578 })
579 },
580 )
581 .job_control(JobControl::Foreground)
582 .start(&mut parent_env)
583 .await
584 .unwrap();
585 assert_eq!(job_control, None);
586 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
587 assert_eq!(state.borrow().foreground, None);
588
589 parent_env.wait_for_subshell(child_pid).await.unwrap();
590 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
591 assert_eq!(state.borrow().foreground, None);
592 });
593 }
594
595 #[test]
596 fn no_job_control_for_nested_subshell() {
597 in_virtual_system(|mut parent_env, state| async move {
598 let mut parent_env = parent_env.push_frame(Frame::Subshell);
599 parent_env.options.set(Monitor, On);
600 stub_tty(&state);
601
602 let parent_pgid = state.borrow().processes[&parent_env.main_pid].pgid;
603 let state_2 = Rc::clone(&state);
604 let (child_pid, job_control) = Subshell::new(
605 move |child_env: &mut Env<Rc<Concurrent<VirtualSystem>>>, _job_control| {
606 Box::pin(async move {
607 let child_pid = child_env.system.getpid();
608 assert_eq!(state_2.borrow().processes[&child_pid].pgid, parent_pgid);
609 assert_eq!(state_2.borrow().foreground, None);
610 })
611 },
612 )
613 .job_control(JobControl::Foreground)
614 .start(&mut parent_env)
615 .await
616 .unwrap();
617 assert_eq!(job_control, None);
618 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
619 assert_eq!(state.borrow().foreground, None);
620
621 parent_env.wait_for_subshell(child_pid).await.unwrap();
622 assert_eq!(state.borrow().processes[&child_pid].pgid, parent_pgid);
623 assert_eq!(state.borrow().foreground, None);
624 });
625 }
626
627 #[test]
628 fn wait_without_job_control() {
629 in_virtual_system(|mut env, _state| async move {
630 let subshell = Subshell::new(|env, _job_control| {
631 Box::pin(async { env.exit_status = ExitStatus(42) })
632 });
633 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
634 assert_eq!(process_result, ProcessResult::exited(42));
635 });
636 }
637
638 #[test]
639 fn wait_for_foreground_job_to_exit() {
640 in_virtual_system(|mut env, state| async move {
641 env.options.set(Monitor, On);
642 stub_tty(&state);
643
644 let subshell = Subshell::new(|env, _job_control| {
645 Box::pin(async { env.exit_status = ExitStatus(123) })
646 })
647 .job_control(JobControl::Foreground);
648 let (_pid, process_result) = subshell.start_and_wait(&mut env).await.unwrap();
649 assert_eq!(process_result, ProcessResult::exited(123));
650 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
651 });
652 }
653
654 #[test]
658 fn sigint_sigquit_not_ignored_by_default() {
659 in_virtual_system(|mut parent_env, state| async move {
660 let (child_pid, _) = Subshell::new(|env, _job_control| {
661 Box::pin(async { env.exit_status = ExitStatus(123) })
662 })
663 .job_control(JobControl::Background)
664 .start(&mut parent_env)
665 .await
666 .unwrap();
667 parent_env.wait_for_subshell(child_pid).await.unwrap();
668
669 let state = state.borrow();
670 let process = &state.processes[&child_pid];
671 assert_eq!(process.disposition(SIGINT), Disposition::Default);
672 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
673 })
674 }
675
676 #[test]
677 fn sigint_sigquit_ignored_in_uncontrolled_job() {
678 in_virtual_system(|mut parent_env, state| async move {
679 let (child_pid, _) = Subshell::new(|env, _job_control| {
680 Box::pin(async { env.exit_status = ExitStatus(123) })
681 })
682 .job_control(JobControl::Background)
683 .ignore_sigint_sigquit(true)
684 .start(&mut parent_env)
685 .await
686 .unwrap();
687
688 parent_env
689 .system
690 .kill(child_pid, Some(SIGINT))
691 .await
692 .unwrap();
693 parent_env
694 .system
695 .kill(child_pid, Some(SIGQUIT))
696 .await
697 .unwrap();
698
699 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
700 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
701
702 let state = state.borrow();
703 let parent_process = &state.processes[&parent_env.main_pid];
704 assert_eq!(parent_process.blocked_signals().contains(SIGINT), Ok(false));
705 assert_eq!(
706 parent_process.blocked_signals().contains(SIGQUIT),
707 Ok(false)
708 );
709 let child_process = &state.processes[&child_pid];
710 assert_eq!(child_process.disposition(SIGINT), Disposition::Ignore);
711 assert_eq!(child_process.disposition(SIGQUIT), Disposition::Ignore);
712 })
713 }
714
715 #[test]
716 fn sigint_sigquit_not_ignored_if_job_controlled() {
717 in_virtual_system(|mut parent_env, state| async move {
718 parent_env.options.set(Monitor, On);
719 stub_tty(&state);
720
721 let (child_pid, _) = Subshell::new(|env, _job_control| {
722 Box::pin(async { env.exit_status = ExitStatus(123) })
723 })
724 .job_control(JobControl::Background)
725 .ignore_sigint_sigquit(true)
726 .start(&mut parent_env)
727 .await
728 .unwrap();
729 parent_env.wait_for_subshell(child_pid).await.unwrap();
730
731 let state = state.borrow();
732 let process = &state.processes[&child_pid];
733 assert_eq!(process.disposition(SIGINT), Disposition::Default);
734 assert_eq!(process.disposition(SIGQUIT), Disposition::Default);
735 })
736 }
737
738 #[test]
739 fn internal_dispositions_for_stoppers_kept_in_uncontrolled_subshell_of_controlling_interactive_shell()
740 {
741 in_virtual_system(|mut parent_env, state| async move {
742 parent_env.options.set(Interactive, On);
743 parent_env.options.set(Monitor, On);
744 parent_env
745 .traps
746 .enable_internal_dispositions_for_stoppers(&parent_env.system)
747 .await
748 .unwrap();
749 stub_tty(&state);
750
751 let (child_pid, _) = Subshell::new(|env, _job_control| {
752 Box::pin(async { env.exit_status = ExitStatus(123) })
753 })
754 .start(&mut parent_env)
755 .await
756 .unwrap();
757
758 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
759 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
760
761 let state = state.borrow();
762 let child_process = &state.processes[&child_pid];
763 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Ignore);
764 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Ignore);
765 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Ignore);
766 })
767 }
768
769 #[test]
770 fn internal_dispositions_for_stoppers_reset_in_controlled_subshell_of_interactive_shell() {
771 in_virtual_system(|mut parent_env, state| async move {
772 parent_env.options.set(Interactive, On);
773 parent_env.options.set(Monitor, On);
774 parent_env
775 .traps
776 .enable_internal_dispositions_for_stoppers(&parent_env.system)
777 .await
778 .unwrap();
779 stub_tty(&state);
780
781 let (child_pid, _) = Subshell::new(|env, _job_control| {
782 Box::pin(async { env.exit_status = ExitStatus(123) })
783 })
784 .job_control(JobControl::Background)
785 .start(&mut parent_env)
786 .await
787 .unwrap();
788
789 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
790 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
791
792 let state = state.borrow();
793 let child_process = &state.processes[&child_pid];
794 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
795 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
796 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
797 })
798 }
799
800 #[test]
801 fn internal_dispositions_for_stoppers_unset_in_subshell_of_non_controlling_interactive_shell() {
802 in_virtual_system(|mut parent_env, state| async move {
803 parent_env.options.set(Interactive, On);
804 stub_tty(&state);
805
806 let (child_pid, _) = Subshell::new(|env, _job_control| {
807 Box::pin(async { env.exit_status = ExitStatus(123) })
808 })
809 .start(&mut parent_env)
810 .await
811 .unwrap();
812
813 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
814 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
815
816 let state = state.borrow();
817 let child_process = &state.processes[&child_pid];
818 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
819 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
820 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
821 })
822 }
823
824 #[test]
825 fn internal_dispositions_for_stoppers_unset_in_uncontrolled_subshell_of_controlling_non_interactive_shell()
826 {
827 in_virtual_system(|mut parent_env, state| async move {
828 parent_env.options.set(Monitor, On);
829 stub_tty(&state);
830
831 let (child_pid, _) = Subshell::new(|env, _job_control| {
832 Box::pin(async { env.exit_status = ExitStatus(123) })
833 })
834 .start(&mut parent_env)
835 .await
836 .unwrap();
837
838 let child_result = parent_env.wait_for_subshell(child_pid).await.unwrap();
839 assert_eq!(child_result, (child_pid, ProcessState::exited(123)));
840
841 let state = state.borrow();
842 let child_process = &state.processes[&child_pid];
843 assert_eq!(child_process.disposition(SIGTSTP), Disposition::Default);
844 assert_eq!(child_process.disposition(SIGTTIN), Disposition::Default);
845 assert_eq!(child_process.disposition(SIGTTOU), Disposition::Default);
846 })
847 }
848}