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;
78use self::system::Signals;
79use self::system::TcSetPgrp;
80use self::system::Wait;
81use self::system::concurrency::Select;
82use self::system::concurrency::WaitForSignals;
83#[cfg(unix)]
84pub use self::system::real::RealSystem;
85pub use self::system::r#virtual::VirtualSystem;
86use self::trap::Action;
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 #[must_use]
625 pub fn sigint_has_default_action(&self) -> bool
626 where
627 S: Signals,
628 {
629 let state = self.traps.get_state(S::SIGINT).0;
630 state.is_none_or(|state| state.action == Action::Default)
631 }
632
633 pub fn get_or_create_variable<N>(&mut self, name: N, scope: Scope) -> VariableRefMut<'_>
643 where
644 N: Into<String>,
645 {
646 let mut variable = self.variables.get_or_new(name, scope);
647 if self.options.get(AllExport) == On {
648 variable.export(true);
649 }
650 variable
651 }
652
653 pub fn errexit_is_applicable(&self) -> bool {
661 self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
662 }
663
664 pub fn apply_errexit(&self) -> ControlFlow<Divert> {
671 if !self.exit_status.is_successful() && self.errexit_is_applicable() {
672 Break(Divert::Exit(None))
673 } else {
674 Continue(())
675 }
676 }
677
678 pub fn apply_result(&mut self, result: crate::semantics::Result) {
683 match result {
684 Continue(_) => {}
685 Break(divert) => {
686 if let Some(exit_status) = divert.exit_status() {
687 self.exit_status = exit_status;
688 }
689 }
690 }
691 }
692}
693
694pub mod alias;
695pub mod any;
696pub mod builtin;
697pub mod decl_util;
698pub mod function;
699pub mod input;
700pub mod io;
701pub mod job;
702pub mod option;
703pub mod parser;
704pub mod prompt;
705pub mod pwd;
706pub mod semantics;
707pub mod signal;
708pub mod source;
709pub mod stack;
710pub mod subshell;
711pub mod system;
712pub mod trap;
713pub mod variable;
714pub mod waker;
715
716mod fork;
717
718#[cfg(any(test, feature = "yash-executor"))]
719mod executor_helper;
720#[cfg(any(test, feature = "test-helper"))]
721pub mod test_helper;
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726 use crate::io::MIN_INTERNAL_FD;
727 use crate::job::Job;
728 use crate::source::Location;
729 use crate::subshell::Config;
730 use crate::system::Exit as _;
731 use crate::system::Sigset as _;
732 use crate::system::r#virtual::Inode;
733 use crate::system::r#virtual::SIGCHLD;
734 use crate::test_helper::in_virtual_system;
735 use crate::trap::Action;
736 use futures_executor::LocalPool;
737 use futures_util::FutureExt as _;
738 use std::cell::Cell;
739 use std::cell::RefCell;
740
741 #[test]
742 fn wait_for_signal_remembers_signal_in_trap_set() {
743 in_virtual_system(|mut env, state| async move {
744 env.traps
745 .set_action(
746 &env.system,
747 SIGCHLD,
748 Action::Command("".into()),
749 Location::dummy(""),
750 false,
751 )
752 .await
753 .unwrap();
754 {
755 let mut state = state.borrow_mut();
756 let process = state.processes.get_mut(&env.main_pid).unwrap();
757 assert_eq!(process.blocked_signals().contains(SIGCHLD), Ok(true));
758 let _ = process.raise_signal(SIGCHLD);
759 }
760 env.wait_for_signal(SIGCHLD).await;
761
762 let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
763 assert!(trap_state.pending);
764 })
765 }
766
767 fn poll_signals_env() -> (Env<Rc<Concurrent<VirtualSystem>>>, VirtualSystem) {
768 let system = VirtualSystem::new();
769 let mut env = Env::with_system(Rc::new(Concurrent::new(system.clone())));
770 env.traps
771 .set_action(
772 &env.system,
773 SIGCHLD,
774 Action::Command("".into()),
775 Location::dummy(""),
776 false,
777 )
778 .now_or_never()
779 .unwrap()
780 .unwrap();
781 (env, system)
782 }
783
784 #[test]
785 fn poll_signals_none() {
786 let mut env = poll_signals_env().0;
787 let result = env.poll_signals();
788 assert_eq!(result, None);
789 }
790
791 #[test]
792 fn poll_signals_some() {
793 let (mut env, system) = poll_signals_env();
794 {
795 let mut state = system.state.borrow_mut();
796 let process = state.processes.get_mut(&system.process_id).unwrap();
797 assert_eq!(process.blocked_signals().contains(SIGCHLD), Ok(true));
798 let _ = process.raise_signal(SIGCHLD);
799 }
800
801 let result = env.poll_signals().unwrap();
802 assert_eq!(result.as_slice(), [SIGCHLD]);
803 }
804
805 #[test]
806 fn get_tty_opens_tty() {
807 let system = VirtualSystem::new();
808 let tty = Rc::new(RefCell::new(Inode::new([])));
809 system
810 .state
811 .borrow_mut()
812 .file_system
813 .save("/dev/tty", Rc::clone(&tty))
814 .unwrap();
815 let mut env = Env::with_system(system.clone());
816
817 let fd = env.get_tty().now_or_never().unwrap().unwrap();
818 assert!(
819 fd >= MIN_INTERNAL_FD,
820 "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
821 );
822 system
823 .with_open_file_description(fd, |ofd| {
824 assert!(Rc::ptr_eq(ofd.inode(), &tty));
825 Ok(())
826 })
827 .unwrap();
828
829 system.state.borrow_mut().file_system = Default::default();
830
831 let fd = env.get_tty().now_or_never().unwrap().unwrap();
833 system
834 .with_open_file_description(fd, |ofd| {
835 assert!(Rc::ptr_eq(ofd.inode(), &tty));
836 Ok(())
837 })
838 .unwrap();
839 }
840
841 #[test]
842 fn run_in_child_process_runs_child_and_returns_shared_data() {
843 in_virtual_system(|mut env, _state| async move {
844 let parent_pid = env.system.getpid();
845 let child_seen_pid = Rc::new(Cell::new(None));
846 let child_seen_ppid = Rc::new(Cell::new(None));
847 let child_seen_pid_2 = Rc::clone(&child_seen_pid);
848 let child_seen_ppid_2 = Rc::clone(&child_seen_ppid);
849
850 let (result, shared_data) = env.run_in_child_process(
851 42_u32,
852 |child_env: Env<Rc<Concurrent<VirtualSystem>>>, data| async move {
853 assert_eq!(data, 42);
854 child_seen_pid_2.set(Some(child_env.system.getpid()));
855 child_seen_ppid_2.set(Some(child_env.system.getppid()));
856 child_env.system.exit(ExitStatus(13)).await;
857 },
858 );
859
860 assert_eq!(shared_data, 42);
861 let child_pid = result.unwrap();
862 assert_ne!(child_pid, parent_pid);
863 assert_eq!(
864 env.wait_for_subshell(child_pid).await,
865 Ok((child_pid, ProcessState::exited(13)))
866 );
867 assert_eq!(child_seen_pid.get(), Some(child_pid));
868 assert_eq!(child_seen_ppid.get(), Some(parent_pid));
869 });
870 }
871
872 #[test]
873 fn run_in_child_process_passes_clone_of_parent_env_to_child() {
874 in_virtual_system(|mut env, _state| async move {
875 env.arg0 = "parent-shell".to_string();
876 env.exit_status = ExitStatus(5);
877 env.variables
878 .get_or_new("PARENT", Scope::Global)
879 .assign("before", None)
880 .unwrap();
881
882 let (result, ()) = env.run_in_child_process(
883 (),
884 |child_env: Env<Rc<Concurrent<VirtualSystem>>>, ()| async move {
885 assert_eq!(child_env.arg0, "parent-shell");
886 assert_eq!(child_env.exit_status, ExitStatus(5));
887 assert_eq!(child_env.variables.get_scalar("PARENT"), Some("before"));
888 child_env.system.exit(ExitStatus(0)).await;
889 },
890 );
891
892 let child_pid = result.unwrap();
893 _ = env.wait_for_subshell(child_pid).await;
894 })
895 }
896
897 #[test]
898 fn run_in_child_process_restores_parent_environment() {
899 in_virtual_system(|mut env, _state| async move {
900 env.arg0 = "parent-shell".to_string();
901 env.exit_status = ExitStatus(5);
902 env.variables
903 .get_or_new("PARENT", Scope::Global)
904 .assign("before", None)
905 .unwrap();
906
907 let (result, ()) = env.run_in_child_process(
908 (),
909 |mut child_env: Env<Rc<Concurrent<VirtualSystem>>>, ()| async move {
910 child_env
911 .variables
912 .get_or_new("PARENT", Scope::Global)
913 .assign("after", None)
914 .unwrap();
915 child_env.system.exit(ExitStatus(0)).await;
916 },
917 );
918
919 let child_pid = result.unwrap();
920 assert_eq!(
921 env.wait_for_subshell(child_pid).await,
922 Ok((child_pid, ProcessState::exited(0)))
923 );
924 assert_eq!(env.arg0, "parent-shell");
925 assert_eq!(env.exit_status, ExitStatus(5));
926 assert_eq!(env.variables.get_scalar("PARENT"), Some("before"));
927 });
928 }
929
930 #[test]
931 fn start_and_wait_for_subshell() {
932 in_virtual_system(|mut env, _state| async move {
933 let (pid, _) = Config::new()
934 .start(&mut env, async |env, _job_control| {
935 env.exit_status = ExitStatus(42)
936 })
937 .await
938 .unwrap();
939 let result = env.wait_for_subshell(pid).await;
940 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
941 });
942 }
943
944 #[test]
945 fn start_and_wait_for_subshell_with_job_list() {
946 in_virtual_system(|mut env, _state| async move {
947 let (pid, _) = Config::new()
948 .start(&mut env, async |env, _job_control| {
949 env.exit_status = ExitStatus(42)
950 })
951 .await
952 .unwrap();
953 let mut job = Job::new(pid);
954 job.name = "my job".to_string();
955 let job_index = env.jobs.insert(job.clone());
956 let result = env.wait_for_subshell(pid).await;
957 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
958 job.state = ProcessState::exited(42);
959 assert_eq!(env.jobs[job_index], job);
960 });
961 }
962
963 #[test]
964 fn wait_for_subshell_no_subshell() {
965 let system = VirtualSystem::new();
966 let mut executor = LocalPool::new();
967 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
968 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
969 executor.run_until(async move {
970 let result = env.wait_for_subshell(Pid::ALL).await;
971 assert_eq!(result, Err(Errno::ECHILD));
972 });
973 }
974
975 #[test]
976 fn update_all_subshell_statuses_without_subshells() {
977 let mut env = Env::new_virtual();
978 env.update_all_subshell_statuses();
979 }
980
981 #[test]
982 fn update_all_subshell_statuses_with_subshells() {
983 let system = VirtualSystem::new();
984 let mut executor = futures_executor::LocalPool::new();
985 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
986
987 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
988
989 let [job_1, job_2, job_3] = executor.run_until(async {
990 let (pid_1, _) = Config::new()
992 .start(&mut env, async |env, _job_control| {
993 env.exit_status = ExitStatus(12)
994 })
995 .await
996 .unwrap();
997
998 let (pid_2, _) = Config::new()
1000 .start(&mut env, async |env, _job_control| {
1001 env.exit_status = ExitStatus(35)
1002 })
1003 .await
1004 .unwrap();
1005
1006 let (pid_3, _) = Config::new()
1008 .start(&mut env, async |_env, _job_control| {
1009 futures_util::future::pending().await
1010 })
1011 .await
1012 .unwrap();
1013
1014 let (_pid_4, _) = Config::new()
1016 .start(&mut env, async |env, _job_control| {
1017 env.exit_status = ExitStatus(100)
1018 })
1019 .await
1020 .unwrap();
1021
1022 let job_1 = env.jobs.insert(Job::new(pid_1));
1024 let job_2 = env.jobs.insert(Job::new(pid_2));
1025 let job_3 = env.jobs.insert(Job::new(pid_3));
1026 [job_1, job_2, job_3]
1027 });
1028
1029 executor.run_until_stalled();
1031
1032 assert_eq!(env.jobs[job_1].state, ProcessState::Running);
1034 assert_eq!(env.jobs[job_2].state, ProcessState::Running);
1035 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
1036
1037 env.update_all_subshell_statuses();
1038
1039 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
1043 }
1044
1045 #[test]
1046 fn get_or_create_variable_with_all_export_off() {
1047 let mut env = Env::new_virtual();
1048 let mut a = env.get_or_create_variable("a", Scope::Global);
1049 assert!(!a.is_exported);
1050 a.export(true);
1051 let a = env.get_or_create_variable("a", Scope::Global);
1052 assert!(a.is_exported);
1053 }
1054
1055 #[test]
1056 fn get_or_create_variable_with_all_export_on() {
1057 let mut env = Env::new_virtual();
1058 env.options.set(AllExport, On);
1059 let mut a = env.get_or_create_variable("a", Scope::Global);
1060 assert!(a.is_exported);
1061 a.export(false);
1062 let a = env.get_or_create_variable("a", Scope::Global);
1063 assert!(a.is_exported);
1064 }
1065
1066 #[test]
1067 fn errexit_on() {
1068 let mut env = Env::new_virtual();
1069 env.exit_status = ExitStatus::FAILURE;
1070 env.options.set(ErrExit, On);
1071 assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
1072 }
1073
1074 #[test]
1075 fn errexit_with_zero_exit_status() {
1076 let mut env = Env::new_virtual();
1077 env.options.set(ErrExit, On);
1078 assert_eq!(env.apply_errexit(), Continue(()));
1079 }
1080
1081 #[test]
1082 fn errexit_in_condition() {
1083 let mut env = Env::new_virtual();
1084 env.exit_status = ExitStatus::FAILURE;
1085 env.options.set(ErrExit, On);
1086 let env = env.push_frame(Frame::Condition);
1087 assert_eq!(env.apply_errexit(), Continue(()));
1088 }
1089
1090 #[test]
1091 fn errexit_off() {
1092 let mut env = Env::new_virtual();
1093 env.exit_status = ExitStatus::FAILURE;
1094 assert_eq!(env.apply_errexit(), Continue(()));
1095 }
1096
1097 #[test]
1098 fn apply_result_with_continue() {
1099 let mut env = Env::new_virtual();
1100 env.apply_result(Continue(()));
1101 assert_eq!(env.exit_status, ExitStatus::default());
1102 }
1103
1104 #[test]
1105 fn apply_result_with_divert_without_exit_status() {
1106 let mut env = Env::new_virtual();
1107 env.apply_result(Break(Divert::Exit(None)));
1108 assert_eq!(env.exit_status, ExitStatus::default());
1109 }
1110
1111 #[test]
1112 fn apply_result_with_divert_with_exit_status() {
1113 let mut env = Env::new_virtual();
1114 env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
1115 assert_eq!(env.exit_status, ExitStatus(67));
1116 }
1117}