1use self::alias::AliasSet;
46use self::any::DataSet;
47use self::builtin::Builtin;
48use self::fork::ForkEnvState;
49use self::function::FunctionSet;
50use self::io::Fd;
51use self::job::JobList;
52use self::job::Pid;
53use self::job::ProcessResult;
54use self::job::ProcessState;
55use self::job::RunBlocking;
56use self::job::RunUnblocking;
57use self::option::On;
58use self::option::OptionSet;
59use self::option::{AllExport, ErrExit, Interactive, Monitor};
60use self::semantics::Divert;
61use self::semantics::ExitStatus;
62use self::stack::Frame;
63use self::stack::Stack;
64use self::system::Close;
65use self::system::Concurrent;
66use self::system::Dup;
67use self::system::Errno;
68use self::system::Fork;
69use self::system::Fstat;
70use self::system::GetCwd;
71use self::system::GetPid;
72use self::system::Isatty;
73use self::system::Mode;
74use self::system::OfdAccess;
75use self::system::Open;
76use self::system::OpenFlag;
77use self::system::SignalList;
78#[allow(deprecated, reason = "for backward compatible API")]
79pub use self::system::System;
80use self::system::TcSetPgrp;
81use self::system::Wait;
82use self::system::concurrency::Select;
83use self::system::concurrency::WaitForSignals;
84#[cfg(unix)]
85pub use self::system::real::RealSystem;
86pub use self::system::r#virtual::VirtualSystem;
87use self::trap::SignalSystem;
88use self::trap::TrapSet;
89use self::variable::PPID;
90use self::variable::Scope;
91use self::variable::VariableRefMut;
92use self::variable::VariableSet;
93use std::collections::HashMap;
94use std::fmt::Debug;
95use std::ops::ControlFlow::{self, Break, Continue};
96use std::pin::pin;
97use std::rc::Rc;
98use std::task::Context;
99use std::task::Poll;
100use std::task::Waker;
101pub use unix_path as path;
102pub use unix_str as str;
103
104#[derive(Clone, Debug)]
114#[non_exhaustive]
115pub struct Env<S> {
116 pub aliases: AliasSet,
118
119 pub arg0: String,
123
124 pub builtins: HashMap<&'static str, Builtin<S>>,
126
127 pub exit_status: ExitStatus,
129
130 pub functions: FunctionSet<S>,
132
133 pub jobs: JobList,
135
136 pub main_pgid: Pid,
138
139 pub main_pid: Pid,
143
144 pub options: OptionSet,
146
147 pub stack: Stack,
149
150 pub traps: TrapSet,
152
153 pub tty: Option<Fd>,
158
159 pub variables: VariableSet,
161
162 pub any: DataSet,
164
165 pub system: S,
167}
168
169impl<S> Env<S> {
170 #[must_use]
177 pub fn with_system(system: S) -> Self
178 where
179 S: GetPid,
180 {
181 Env {
182 aliases: Default::default(),
183 arg0: Default::default(),
184 builtins: Default::default(),
185 exit_status: Default::default(),
186 functions: Default::default(),
187 jobs: Default::default(),
188 main_pgid: system.getpgrp(),
189 main_pid: system.getpid(),
190 options: Default::default(),
191 stack: Default::default(),
192 traps: Default::default(),
193 tty: Default::default(),
194 variables: Default::default(),
195 any: Default::default(),
196 system,
197 }
198 }
199
200 #[must_use]
206 pub fn clone_with_system(&self, system: S) -> Self {
207 Env {
208 aliases: self.aliases.clone(),
209 arg0: self.arg0.clone(),
210 builtins: self.builtins.clone(),
211 exit_status: self.exit_status,
212 functions: self.functions.clone(),
213 jobs: self.jobs.clone(),
214 main_pgid: self.main_pgid,
215 main_pid: self.main_pid,
216 options: self.options,
217 stack: self.stack.clone(),
218 traps: self.traps.clone(),
219 tty: self.tty,
220 variables: self.variables.clone(),
221 any: self.any.clone(),
222 system,
223 }
224 }
225}
226
227impl<S> Default for Env<S>
228where
229 S: Default + GetPid,
230{
231 fn default() -> Self {
235 Self::with_system(S::default())
236 }
237}
238
239impl Env<Rc<Concurrent<VirtualSystem>>> {
240 #[must_use]
246 pub fn new_virtual() -> Self {
247 Self::default()
248 }
249}
250
251impl<S> Env<S> {
252 pub fn init_variables(&mut self)
268 where
269 S: Fstat + GetCwd + GetPid,
270 {
271 self.variables.init();
272
273 self.variables
274 .get_or_new(PPID, Scope::Global)
275 .assign(self.system.getppid().to_string(), None)
276 .ok();
277
278 self.prepare_pwd().ok();
279 }
280
281 pub async fn wait_for_signals(&mut self) -> Rc<SignalList>
290 where
291 S: WaitForSignals,
292 {
293 let result = self.system.wait_for_signals().await;
294 for signal in result.iter().copied() {
295 self.traps.catch_signal(signal);
296 }
297 result
298 }
299
300 pub async fn wait_for_signal(&mut self, signal: signal::Number)
305 where
306 S: WaitForSignals,
307 {
308 while !self.wait_for_signals().await.contains(&signal) {}
309 }
310
311 pub fn poll_signals(&mut self) -> Option<Rc<SignalList>>
323 where
324 S: WaitForSignals + Select,
325 {
326 fn poll<S: WaitForSignals + Select>(system: &S) -> Option<Rc<SignalList>> {
327 let mut future = pin!(system.wait_for_signals());
328 let mut context = Context::from_waker(Waker::noop());
329
330 if let Poll::Ready(signals) = future.as_mut().poll(&mut context) {
332 return Some(signals);
333 }
334
335 system.peek();
337
338 if let Poll::Ready(signals) = future.poll(&mut context) {
340 return Some(signals);
341 }
342
343 None
344 }
345
346 let signals = poll(&self.system);
347 if let Some(signals) = &signals {
348 for signal in signals.iter().copied() {
349 self.traps.catch_signal(signal);
350 }
351 }
352 signals
353 }
354
355 #[must_use]
365 fn should_print_error_in_color(&self) -> bool
366 where
367 S: Isatty,
368 {
369 self.system.isatty(Fd::STDERR)
372 }
373
374 pub async fn get_tty(&mut self) -> Result<Fd, Errno>
379 where
380 S: Open + Dup + Close,
381 {
382 if let Some(fd) = self.tty {
383 return Ok(fd);
384 }
385
386 let first_fd = {
387 let mut result = self
392 .system
393 .open(
394 c"/dev/tty",
395 OfdAccess::ReadWrite,
396 OpenFlag::CloseOnExec | OpenFlag::NoCtty,
397 Mode::empty(),
398 )
399 .await;
400 if result == Err(Errno::EINVAL) {
401 result = self
404 .system
405 .open(
406 c"/dev/tty",
407 OfdAccess::ReadWrite,
408 OpenFlag::CloseOnExec.into(),
409 Mode::empty(),
410 )
411 .await;
412 }
413 result?
414 };
415
416 let final_fd = io::move_fd_internal(&self.system, first_fd);
417 self.tty = final_fd.ok();
418 final_fd
419 }
420
421 pub async fn ensure_foreground(&mut self) -> Result<(), Errno>
442 where
443 S: Open + Dup + Close + GetPid + RunBlocking + RunUnblocking + TcSetPgrp,
444 {
445 let fd = self.get_tty().await?;
446
447 if self.system.getsid(Pid(0)) == Ok(self.main_pgid) {
448 job::tcsetpgrp_with_block(&self.system, fd, self.main_pgid).await
449 } else {
450 job::tcsetpgrp_without_block(&self.system, fd, self.main_pgid).await
451 }
452 }
453
454 pub fn run_in_child_process<D, F>(
467 &mut self,
468 shared_data: D,
469 child_task: F,
470 ) -> (Result<Pid, Errno>, D)
471 where
472 S: Fork + 'static,
473 D: Clone + 'static,
474 F: AsyncFnOnce(Self, D) + 'static,
475 {
476 let state = ForkEnvState::extract_from_env(self);
477
478 let (pid_or_error, (state, shared_data)) = self.system.run_in_child_process(
479 (state, shared_data),
480 |child_system, (state, shared_data): (ForkEnvState<S>, D)| async move {
481 let child_env = state.into_env_with_system(child_system);
482 child_task(child_env, shared_data).await
483 },
484 );
485
486 state.restore_into_env(self);
487
488 (pid_or_error, shared_data)
489 }
490
491 pub async fn wait_for_subshell(&mut self, target: Pid) -> Result<(Pid, ProcessState), Errno>
512 where
513 S: SignalSystem + WaitForSignals + Wait,
514 {
515 self.traps
518 .enable_internal_disposition_for_sigchld(&self.system)
519 .await?;
520
521 loop {
522 if let Some((pid, state)) = self.system.wait(target)? {
523 self.jobs.update_status(pid, state);
524 return Ok((pid, state));
525 }
526 self.wait_for_signal(S::SIGCHLD).await;
527 }
528 }
529
530 pub async fn wait_for_subshell_to_halt(
537 &mut self,
538 target: Pid,
539 ) -> Result<(Pid, ProcessResult), Errno>
540 where
541 S: SignalSystem + WaitForSignals + Wait,
542 {
543 loop {
544 let (pid, state) = self.wait_for_subshell(target).await?;
545 if let ProcessState::Halted(result) = state {
546 return Ok((pid, result));
547 }
548 }
549 }
550
551 pub async fn wait_for_subshell_to_finish(
559 &mut self,
560 target: Pid,
561 ) -> Result<(Pid, ExitStatus), Errno>
562 where
563 S: SignalSystem + WaitForSignals + Wait,
564 {
565 loop {
566 let (pid, result) = self.wait_for_subshell_to_halt(target).await?;
567 if !result.is_stopped() {
568 return Ok((pid, result.into()));
569 }
570 }
571 }
572
573 pub fn update_all_subshell_statuses(&mut self)
582 where
583 S: Wait,
584 {
585 while let Ok(Some((pid, state))) = self.system.wait(Pid::ALL) {
586 self.jobs.update_status(pid, state);
587 }
588 }
589
590 #[must_use]
597 pub fn is_interactive(&self) -> bool {
598 self.options.get(Interactive) == On && !self.stack.contains(&Frame::Subshell)
599 }
600
601 #[must_use]
608 pub fn controls_jobs(&self) -> bool {
609 self.options.get(Monitor) == On && !self.stack.contains(&Frame::Subshell)
610 }
611
612 pub fn get_or_create_variable<N>(&mut self, name: N, scope: Scope) -> VariableRefMut<'_>
622 where
623 N: Into<String>,
624 {
625 let mut variable = self.variables.get_or_new(name, scope);
626 if self.options.get(AllExport) == On {
627 variable.export(true);
628 }
629 variable
630 }
631
632 pub fn errexit_is_applicable(&self) -> bool {
640 self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
641 }
642
643 pub fn apply_errexit(&self) -> ControlFlow<Divert> {
650 if !self.exit_status.is_successful() && self.errexit_is_applicable() {
651 Break(Divert::Exit(None))
652 } else {
653 Continue(())
654 }
655 }
656
657 pub fn apply_result(&mut self, result: crate::semantics::Result) {
662 match result {
663 Continue(_) => {}
664 Break(divert) => {
665 if let Some(exit_status) = divert.exit_status() {
666 self.exit_status = exit_status;
667 }
668 }
669 }
670 }
671}
672
673pub mod alias;
674pub mod any;
675pub mod builtin;
676pub mod decl_util;
677pub mod function;
678pub mod input;
679pub mod io;
680pub mod job;
681pub mod option;
682pub mod parser;
683pub mod prompt;
684pub mod pwd;
685pub mod semantics;
686pub mod signal;
687pub mod source;
688pub mod stack;
689pub mod subshell;
690pub mod system;
691pub mod trap;
692pub mod variable;
693pub mod waker;
694
695mod fork;
696
697#[cfg(any(test, feature = "yash-executor"))]
698mod executor_helper;
699#[cfg(any(test, feature = "test-helper"))]
700pub mod test_helper;
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705 use crate::io::MIN_INTERNAL_FD;
706 use crate::job::Job;
707 use crate::source::Location;
708 use crate::subshell::Config;
709 use crate::system::Exit as _;
710 use crate::system::Sigset as _;
711 use crate::system::r#virtual::Inode;
712 use crate::system::r#virtual::SIGCHLD;
713 use crate::test_helper::in_virtual_system;
714 use crate::trap::Action;
715 use futures_executor::LocalPool;
716 use futures_util::FutureExt as _;
717 use std::cell::Cell;
718 use std::cell::RefCell;
719
720 #[test]
721 fn wait_for_signal_remembers_signal_in_trap_set() {
722 in_virtual_system(|mut env, state| async move {
723 env.traps
724 .set_action(
725 &env.system,
726 SIGCHLD,
727 Action::Command("".into()),
728 Location::dummy(""),
729 false,
730 )
731 .await
732 .unwrap();
733 {
734 let mut state = state.borrow_mut();
735 let process = state.processes.get_mut(&env.main_pid).unwrap();
736 assert_eq!(process.blocked_signals().contains(SIGCHLD), Ok(true));
737 let _ = process.raise_signal(SIGCHLD);
738 }
739 env.wait_for_signal(SIGCHLD).await;
740
741 let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
742 assert!(trap_state.pending);
743 })
744 }
745
746 fn poll_signals_env() -> (Env<Rc<Concurrent<VirtualSystem>>>, VirtualSystem) {
747 let system = VirtualSystem::new();
748 let mut env = Env::with_system(Rc::new(Concurrent::new(system.clone())));
749 env.traps
750 .set_action(
751 &env.system,
752 SIGCHLD,
753 Action::Command("".into()),
754 Location::dummy(""),
755 false,
756 )
757 .now_or_never()
758 .unwrap()
759 .unwrap();
760 (env, system)
761 }
762
763 #[test]
764 fn poll_signals_none() {
765 let mut env = poll_signals_env().0;
766 let result = env.poll_signals();
767 assert_eq!(result, None);
768 }
769
770 #[test]
771 fn poll_signals_some() {
772 let (mut env, system) = poll_signals_env();
773 {
774 let mut state = system.state.borrow_mut();
775 let process = state.processes.get_mut(&system.process_id).unwrap();
776 assert_eq!(process.blocked_signals().contains(SIGCHLD), Ok(true));
777 let _ = process.raise_signal(SIGCHLD);
778 }
779
780 let result = env.poll_signals().unwrap();
781 assert_eq!(result.as_slice(), [SIGCHLD]);
782 }
783
784 #[test]
785 fn get_tty_opens_tty() {
786 let system = VirtualSystem::new();
787 let tty = Rc::new(RefCell::new(Inode::new([])));
788 system
789 .state
790 .borrow_mut()
791 .file_system
792 .save("/dev/tty", Rc::clone(&tty))
793 .unwrap();
794 let mut env = Env::with_system(system.clone());
795
796 let fd = env.get_tty().now_or_never().unwrap().unwrap();
797 assert!(
798 fd >= MIN_INTERNAL_FD,
799 "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
800 );
801 system
802 .with_open_file_description(fd, |ofd| {
803 assert!(Rc::ptr_eq(ofd.inode(), &tty));
804 Ok(())
805 })
806 .unwrap();
807
808 system.state.borrow_mut().file_system = Default::default();
809
810 let fd = env.get_tty().now_or_never().unwrap().unwrap();
812 system
813 .with_open_file_description(fd, |ofd| {
814 assert!(Rc::ptr_eq(ofd.inode(), &tty));
815 Ok(())
816 })
817 .unwrap();
818 }
819
820 #[test]
821 fn run_in_child_process_runs_child_and_returns_shared_data() {
822 in_virtual_system(|mut env, _state| async move {
823 let parent_pid = env.system.getpid();
824 let child_seen_pid = Rc::new(Cell::new(None));
825 let child_seen_ppid = Rc::new(Cell::new(None));
826 let child_seen_pid_2 = Rc::clone(&child_seen_pid);
827 let child_seen_ppid_2 = Rc::clone(&child_seen_ppid);
828
829 let (result, shared_data) = env.run_in_child_process(
830 42_u32,
831 |child_env: Env<Rc<Concurrent<VirtualSystem>>>, data| async move {
832 assert_eq!(data, 42);
833 child_seen_pid_2.set(Some(child_env.system.getpid()));
834 child_seen_ppid_2.set(Some(child_env.system.getppid()));
835 child_env.system.exit(ExitStatus(13)).await;
836 },
837 );
838
839 assert_eq!(shared_data, 42);
840 let child_pid = result.unwrap();
841 assert_ne!(child_pid, parent_pid);
842 assert_eq!(
843 env.wait_for_subshell(child_pid).await,
844 Ok((child_pid, ProcessState::exited(13)))
845 );
846 assert_eq!(child_seen_pid.get(), Some(child_pid));
847 assert_eq!(child_seen_ppid.get(), Some(parent_pid));
848 });
849 }
850
851 #[test]
852 fn run_in_child_process_passes_clone_of_parent_env_to_child() {
853 in_virtual_system(|mut env, _state| async move {
854 env.arg0 = "parent-shell".to_string();
855 env.exit_status = ExitStatus(5);
856 env.variables
857 .get_or_new("PARENT", Scope::Global)
858 .assign("before", None)
859 .unwrap();
860
861 let (result, ()) = env.run_in_child_process(
862 (),
863 |child_env: Env<Rc<Concurrent<VirtualSystem>>>, ()| async move {
864 assert_eq!(child_env.arg0, "parent-shell");
865 assert_eq!(child_env.exit_status, ExitStatus(5));
866 assert_eq!(child_env.variables.get_scalar("PARENT"), Some("before"));
867 child_env.system.exit(ExitStatus(0)).await;
868 },
869 );
870
871 let child_pid = result.unwrap();
872 _ = env.wait_for_subshell(child_pid).await;
873 })
874 }
875
876 #[test]
877 fn run_in_child_process_restores_parent_environment() {
878 in_virtual_system(|mut env, _state| async move {
879 env.arg0 = "parent-shell".to_string();
880 env.exit_status = ExitStatus(5);
881 env.variables
882 .get_or_new("PARENT", Scope::Global)
883 .assign("before", None)
884 .unwrap();
885
886 let (result, ()) = env.run_in_child_process(
887 (),
888 |mut child_env: Env<Rc<Concurrent<VirtualSystem>>>, ()| async move {
889 child_env
890 .variables
891 .get_or_new("PARENT", Scope::Global)
892 .assign("after", None)
893 .unwrap();
894 child_env.system.exit(ExitStatus(0)).await;
895 },
896 );
897
898 let child_pid = result.unwrap();
899 assert_eq!(
900 env.wait_for_subshell(child_pid).await,
901 Ok((child_pid, ProcessState::exited(0)))
902 );
903 assert_eq!(env.arg0, "parent-shell");
904 assert_eq!(env.exit_status, ExitStatus(5));
905 assert_eq!(env.variables.get_scalar("PARENT"), Some("before"));
906 });
907 }
908
909 #[test]
910 fn start_and_wait_for_subshell() {
911 in_virtual_system(|mut env, _state| async move {
912 let (pid, _) = Config::new()
913 .start(&mut env, async |env, _job_control| {
914 env.exit_status = ExitStatus(42)
915 })
916 .await
917 .unwrap();
918 let result = env.wait_for_subshell(pid).await;
919 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
920 });
921 }
922
923 #[test]
924 fn start_and_wait_for_subshell_with_job_list() {
925 in_virtual_system(|mut env, _state| async move {
926 let (pid, _) = Config::new()
927 .start(&mut env, async |env, _job_control| {
928 env.exit_status = ExitStatus(42)
929 })
930 .await
931 .unwrap();
932 let mut job = Job::new(pid);
933 job.name = "my job".to_string();
934 let job_index = env.jobs.add(job.clone());
935 let result = env.wait_for_subshell(pid).await;
936 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
937 job.state = ProcessState::exited(42);
938 assert_eq!(env.jobs[job_index], job);
939 });
940 }
941
942 #[test]
943 fn wait_for_subshell_no_subshell() {
944 let system = VirtualSystem::new();
945 let mut executor = LocalPool::new();
946 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
947 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
948 executor.run_until(async move {
949 let result = env.wait_for_subshell(Pid::ALL).await;
950 assert_eq!(result, Err(Errno::ECHILD));
951 });
952 }
953
954 #[test]
955 fn update_all_subshell_statuses_without_subshells() {
956 let mut env = Env::new_virtual();
957 env.update_all_subshell_statuses();
958 }
959
960 #[test]
961 fn update_all_subshell_statuses_with_subshells() {
962 let system = VirtualSystem::new();
963 let mut executor = futures_executor::LocalPool::new();
964 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
965
966 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
967
968 let [job_1, job_2, job_3] = executor.run_until(async {
969 let (pid_1, _) = Config::new()
971 .start(&mut env, async |env, _job_control| {
972 env.exit_status = ExitStatus(12)
973 })
974 .await
975 .unwrap();
976
977 let (pid_2, _) = Config::new()
979 .start(&mut env, async |env, _job_control| {
980 env.exit_status = ExitStatus(35)
981 })
982 .await
983 .unwrap();
984
985 let (pid_3, _) = Config::new()
987 .start(&mut env, async |_env, _job_control| {
988 futures_util::future::pending().await
989 })
990 .await
991 .unwrap();
992
993 let (_pid_4, _) = Config::new()
995 .start(&mut env, async |env, _job_control| {
996 env.exit_status = ExitStatus(100)
997 })
998 .await
999 .unwrap();
1000
1001 let job_1 = env.jobs.add(Job::new(pid_1));
1003 let job_2 = env.jobs.add(Job::new(pid_2));
1004 let job_3 = env.jobs.add(Job::new(pid_3));
1005 [job_1, job_2, job_3]
1006 });
1007
1008 executor.run_until_stalled();
1010
1011 assert_eq!(env.jobs[job_1].state, ProcessState::Running);
1013 assert_eq!(env.jobs[job_2].state, ProcessState::Running);
1014 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
1015
1016 env.update_all_subshell_statuses();
1017
1018 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
1022 }
1023
1024 #[test]
1025 fn get_or_create_variable_with_all_export_off() {
1026 let mut env = Env::new_virtual();
1027 let mut a = env.get_or_create_variable("a", Scope::Global);
1028 assert!(!a.is_exported);
1029 a.export(true);
1030 let a = env.get_or_create_variable("a", Scope::Global);
1031 assert!(a.is_exported);
1032 }
1033
1034 #[test]
1035 fn get_or_create_variable_with_all_export_on() {
1036 let mut env = Env::new_virtual();
1037 env.options.set(AllExport, On);
1038 let mut a = env.get_or_create_variable("a", Scope::Global);
1039 assert!(a.is_exported);
1040 a.export(false);
1041 let a = env.get_or_create_variable("a", Scope::Global);
1042 assert!(a.is_exported);
1043 }
1044
1045 #[test]
1046 fn errexit_on() {
1047 let mut env = Env::new_virtual();
1048 env.exit_status = ExitStatus::FAILURE;
1049 env.options.set(ErrExit, On);
1050 assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
1051 }
1052
1053 #[test]
1054 fn errexit_with_zero_exit_status() {
1055 let mut env = Env::new_virtual();
1056 env.options.set(ErrExit, On);
1057 assert_eq!(env.apply_errexit(), Continue(()));
1058 }
1059
1060 #[test]
1061 fn errexit_in_condition() {
1062 let mut env = Env::new_virtual();
1063 env.exit_status = ExitStatus::FAILURE;
1064 env.options.set(ErrExit, On);
1065 let env = env.push_frame(Frame::Condition);
1066 assert_eq!(env.apply_errexit(), Continue(()));
1067 }
1068
1069 #[test]
1070 fn errexit_off() {
1071 let mut env = Env::new_virtual();
1072 env.exit_status = ExitStatus::FAILURE;
1073 assert_eq!(env.apply_errexit(), Continue(()));
1074 }
1075
1076 #[test]
1077 fn apply_result_with_continue() {
1078 let mut env = Env::new_virtual();
1079 env.apply_result(Continue(()));
1080 assert_eq!(env.exit_status, ExitStatus::default());
1081 }
1082
1083 #[test]
1084 fn apply_result_with_divert_without_exit_status() {
1085 let mut env = Env::new_virtual();
1086 env.apply_result(Break(Divert::Exit(None)));
1087 assert_eq!(env.exit_status, ExitStatus::default());
1088 }
1089
1090 #[test]
1091 fn apply_result_with_divert_with_exit_status() {
1092 let mut env = Env::new_virtual();
1093 env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
1094 assert_eq!(env.exit_status, ExitStatus(67));
1095 }
1096}