yash_env/
lib.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! This crate defines the shell execution environment.
18//!
19//! A shell execution environment, [`Env`], is a collection of data that may
20//! affect or be affected by the execution of commands. The environment consists
21//! of application-managed parts and system-managed parts. Application-managed
22//! parts are implemented in pure Rust in this crate. Many application-managed
23//! parts like [function]s and [variable]s can be manipulated independently of
24//! interactions with the underlying system. System-managed parts, on the other
25//! hand, depend on the underlying system. Attributes like the working directory
26//! and umask are managed by the system to be accessed only by interaction with
27//! the system interface.
28//!
29//! The [`System`] trait is the interface to the system-managed parts.
30//! [`RealSystem`] provides an implementation for `System` that interacts with
31//! the underlying system. [`VirtualSystem`] is a dummy for simulating the
32//! system's behavior without affecting the actual system.
33
34use self::builtin::getopts::GetoptsState;
35use self::builtin::Builtin;
36use self::function::FunctionSet;
37use self::io::Fd;
38use self::job::JobList;
39use self::job::Pid;
40use self::job::ProcessState;
41use self::option::On;
42use self::option::OptionSet;
43use self::option::{AllExport, ErrExit, Interactive, Monitor};
44use self::semantics::Divert;
45use self::semantics::ExitStatus;
46use self::stack::Frame;
47use self::stack::Stack;
48pub use self::system::r#virtual::VirtualSystem;
49#[cfg(unix)]
50pub use self::system::real::RealSystem;
51use self::system::Errno;
52pub use self::system::SharedSystem;
53pub use self::system::System;
54use self::system::SystemEx;
55use self::trap::TrapSet;
56use self::variable::Scope;
57use self::variable::VariableRefMut;
58use self::variable::VariableSet;
59use self::variable::PPID;
60use futures_util::task::noop_waker_ref;
61use std::collections::HashMap;
62use std::fmt::Debug;
63use std::future::Future;
64use std::ops::ControlFlow::{self, Break, Continue};
65use std::rc::Rc;
66use std::task::Context;
67use std::task::Poll;
68pub use unix_path as path;
69pub use unix_str as str;
70use yash_syntax::alias::AliasSet;
71
72/// Whole shell execution environment.
73///
74/// The shell execution environment consists of application-managed parts and
75/// system-managed parts. Application-managed parts are directly implemented in
76/// the `Env` instance. System-managed parts are managed by a [`SharedSystem`]
77/// that contains an instance of [`System`].
78///
79/// # Cloning
80///
81/// `Env::clone` effectively clones the application-managed parts of the
82/// environment. Since [`SharedSystem`] is reference-counted, you will not get a
83/// deep copy of the system-managed parts. See also
84/// [`clone_with_system`](Self::clone_with_system).
85#[derive(Clone, Debug)]
86#[non_exhaustive]
87pub struct Env {
88    /// Aliases defined in the environment
89    pub aliases: AliasSet,
90
91    /// Name of the current shell executable or shell script
92    ///
93    /// Special parameter `0` expands to this value.
94    pub arg0: String,
95
96    /// Built-in utilities available in the environment
97    pub builtins: HashMap<&'static str, Builtin>,
98
99    /// Exit status of the last executed command
100    pub exit_status: ExitStatus,
101
102    /// Functions defined in the environment
103    pub functions: FunctionSet,
104
105    /// State of the previous invocation of the `getopts` built-in
106    pub getopts_state: Option<GetoptsState>,
107
108    /// Jobs managed in the environment
109    pub jobs: JobList,
110
111    /// Process group ID of the main shell process
112    pub main_pgid: Pid,
113
114    /// Process ID of the main shell process
115    ///
116    /// This PID represents the value of the `$` special parameter.
117    pub main_pid: Pid,
118
119    /// Shell option settings
120    pub options: OptionSet,
121
122    /// Runtime execution context stack
123    pub stack: Stack,
124
125    /// Traps defined in the environment
126    pub traps: TrapSet,
127
128    /// File descriptor to the controlling terminal
129    ///
130    /// [`get_tty`](Self::get_tty) saves a file descriptor in this variable, so
131    /// you don't have to prepare it yourself.
132    pub tty: Option<Fd>,
133
134    /// Variables and positional parameters defined in the environment
135    pub variables: VariableSet,
136
137    /// Interface to the system-managed parts of the environment
138    pub system: SharedSystem,
139}
140
141impl Env {
142    /// Creates a new environment with the given system.
143    ///
144    /// Members of the new environments are default-constructed except that:
145    /// - `main_pid` is initialized as `system.getpid()`
146    /// - `system` is initialized as `SharedSystem::new(system)`
147    #[must_use]
148    pub fn with_system(system: Box<dyn System>) -> Env {
149        Env {
150            aliases: Default::default(),
151            arg0: Default::default(),
152            builtins: Default::default(),
153            exit_status: Default::default(),
154            functions: Default::default(),
155            getopts_state: Default::default(),
156            jobs: Default::default(),
157            main_pgid: system.getpgrp(),
158            main_pid: system.getpid(),
159            options: Default::default(),
160            stack: Default::default(),
161            traps: Default::default(),
162            tty: Default::default(),
163            variables: Default::default(),
164            system: SharedSystem::new(system),
165        }
166    }
167
168    /// Creates a new environment with a default-constructed [`VirtualSystem`].
169    #[must_use]
170    pub fn new_virtual() -> Env {
171        Env::with_system(Box::<VirtualSystem>::default())
172    }
173
174    /// Clones this environment.
175    ///
176    /// The application-managed parts of the environment are cloned normally.
177    /// The system-managed parts are replaced with the provided `System`
178    /// instance.
179    #[must_use]
180    pub fn clone_with_system(&self, system: Box<dyn System>) -> Env {
181        Env {
182            aliases: self.aliases.clone(),
183            arg0: self.arg0.clone(),
184            builtins: self.builtins.clone(),
185            exit_status: self.exit_status,
186            functions: self.functions.clone(),
187            getopts_state: self.getopts_state.clone(),
188            jobs: self.jobs.clone(),
189            main_pgid: self.main_pgid,
190            main_pid: self.main_pid,
191            options: self.options,
192            stack: self.stack.clone(),
193            traps: self.traps.clone(),
194            tty: self.tty,
195            variables: self.variables.clone(),
196            system: SharedSystem::new(system),
197        }
198    }
199
200    /// Initializes default variables.
201    ///
202    /// This function assigns the following variables to `self`:
203    ///
204    /// - `IFS=' \t\n'`
205    /// - `OPTIND=1`
206    /// - `PS1='$ '`
207    /// - `PS2='> '`
208    /// - `PS4='+ '`
209    /// - `PPID=(parent process ID)`
210    /// - `PWD=(current working directory)` (See [`Env::prepare_pwd`])
211    ///
212    /// This function ignores any errors that may occur.
213    ///
214    /// TODO: PS1 should be set to `"# "` for root users.
215    pub fn init_variables(&mut self) {
216        self.variables.init();
217
218        self.variables
219            .get_or_new(PPID, Scope::Global)
220            .assign(self.system.getppid().to_string(), None)
221            .ok();
222
223        self.prepare_pwd().ok();
224    }
225
226    /// Waits for some signals to be caught in the current process.
227    ///
228    /// Returns an array of signals caught.
229    ///
230    /// This function is a wrapper for [`SharedSystem::wait_for_signals`].
231    /// Before the function returns, it passes the results to
232    /// [`TrapSet::catch_signal`] so the trap set can remember the signals
233    /// caught to be handled later.
234    pub async fn wait_for_signals(&mut self) -> Rc<[signal::Number]> {
235        let result = self.system.wait_for_signals().await;
236        for signal in result.iter().copied() {
237            self.traps.catch_signal(signal);
238        }
239        result
240    }
241
242    /// Waits for a specific signal to be caught in the current process.
243    ///
244    /// This function calls [`wait_for_signals`](Self::wait_for_signals)
245    /// repeatedly until it returns results containing the specified `signal`.
246    pub async fn wait_for_signal(&mut self, signal: signal::Number) {
247        while !self.wait_for_signals().await.contains(&signal) {}
248    }
249
250    /// Returns signals that have been caught.
251    ///
252    /// This function is similar to
253    /// [`wait_for_signals`](Self::wait_for_signals) but does not wait for
254    /// signals to be caught. Instead, it only checks if any signals have been
255    /// caught but not yet consumed in the [`SharedSystem`].
256    pub fn poll_signals(&mut self) -> Option<Rc<[signal::Number]>> {
257        let system = self.system.clone();
258
259        let mut future = std::pin::pin!(self.wait_for_signals());
260
261        let mut context = Context::from_waker(noop_waker_ref());
262        if let Poll::Ready(signals) = future.as_mut().poll(&mut context) {
263            return Some(signals);
264        }
265
266        system.select(true).ok();
267        if let Poll::Ready(signals) = future.poll(&mut context) {
268            return Some(signals);
269        }
270        None
271    }
272
273    /// Whether error messages should be printed in color
274    ///
275    /// This function decides whether messages printed to the standard error
276    /// should contain ANSI color escape sequences. The result is true only if
277    /// the standard error is a terminal.
278    ///
279    /// The current implementaion simply checks if the standard error is a
280    /// terminal. This will be changed in the future to support user
281    /// configuration.
282    #[must_use]
283    fn should_print_error_in_color(&self) -> bool {
284        // TODO Enable color depending on user config (force/auto/never)
285        // TODO Check if the terminal really supports color (needs terminfo)
286        self.system.isatty(Fd::STDERR)
287    }
288
289    /// Returns a file descriptor to the controlling terminal.
290    ///
291    /// This function returns `self.tty` if it is `Some` FD. Otherwise, it
292    /// opens `/dev/tty` and saves the new FD to `self.tty` before returning it.
293    pub fn get_tty(&mut self) -> Result<Fd, Errno> {
294        if let Some(fd) = self.tty {
295            return Ok(fd);
296        }
297
298        let first_fd = self.system.open(
299            c"/dev/tty",
300            crate::system::OfdAccess::ReadWrite,
301            crate::system::OpenFlag::CloseOnExec.into(),
302            crate::system::Mode::empty(),
303        )?;
304        let final_fd = self.system.move_fd_internal(first_fd);
305        self.tty = final_fd.ok();
306        final_fd
307    }
308
309    /// Tests whether the current environment is an interactive shell.
310    ///
311    /// This function returns true if and only if:
312    ///
313    /// - the [`Interactive`] option is `On` in `self.options`, and
314    /// - the current context is not in a subshell (no `Frame::Subshell` in `self.stack`).
315    #[must_use]
316    pub fn is_interactive(&self) -> bool {
317        self.options.get(Interactive) == On && !self.stack.contains(&Frame::Subshell)
318    }
319
320    /// Tests whether the shell is performing job control.
321    ///
322    /// This function returns true if and only if:
323    ///
324    /// - the [`Monitor`] option is `On` in `self.options`, and
325    /// - the current context is not in a subshell (no `Frame::Subshell` in `self.stack`).
326    #[must_use]
327    pub fn controls_jobs(&self) -> bool {
328        self.options.get(Monitor) == On && !self.stack.contains(&Frame::Subshell)
329    }
330
331    /// Waits for a subshell to terminate, suspend, or resume.
332    ///
333    /// This function waits for a subshell to change its execution state. The
334    /// `target` parameter specifies which child to wait for:
335    ///
336    /// - `-1`: any child
337    /// - `0`: any child in the same process group as the current process
338    /// - `pid`: the child whose process ID is `pid`
339    /// - `-pgid`: any child in the process group whose process group ID is `pgid`
340    ///
341    /// When [`self.system.wait`](System::wait) returned a new state of the
342    /// target, it is sent to `self.jobs` ([`JobList::update_status`]) before
343    /// being returned from this function.
344    ///
345    /// If there is no matching target, this function returns
346    /// `Err(Errno::ECHILD)`.
347    ///
348    /// If the target subshell is not job-controlled, you may want to use
349    /// [`wait_for_subshell_to_finish`](Self::wait_for_subshell_to_finish)
350    /// instead.
351    pub async fn wait_for_subshell(&mut self, target: Pid) -> Result<(Pid, ProcessState), Errno> {
352        // We need to set the internal disposition before calling `wait` so we don't
353        // miss any `SIGCHLD` that may arrive between `wait` and `wait_for_signal`.
354        self.traps
355            .enable_internal_disposition_for_sigchld(&mut self.system)?;
356
357        let sigchld = self
358            .system
359            .signal_number_from_name(signal::Name::Chld)
360            .unwrap();
361        loop {
362            if let Some((pid, state)) = self.system.wait(target)? {
363                self.jobs.update_status(pid, state);
364                return Ok((pid, state));
365            }
366            self.wait_for_signal(sigchld).await;
367        }
368    }
369
370    /// Wait for a subshell to terminate.
371    ///
372    /// This function is similar to
373    /// [`wait_for_subshell`](Self::wait_for_subshell), but returns only when
374    /// the target is finished (either exited or killed by a signal).
375    ///
376    /// Returns the process ID of the awaited process and its exit status.
377    pub async fn wait_for_subshell_to_finish(
378        &mut self,
379        target: Pid,
380    ) -> Result<(Pid, ExitStatus), Errno> {
381        loop {
382            let (pid, state) = self.wait_for_subshell(target).await?;
383            if let Ok(exit_status) = state.try_into() {
384                if !state.is_alive() {
385                    return Ok((pid, exit_status));
386                }
387            }
388        }
389    }
390
391    /// Applies all job status updates to jobs in `self.jobs`.
392    ///
393    /// This function calls [`self.system.wait`](System::wait) repeatedly until
394    /// all status updates available are applied to `self.jobs`
395    /// ([`JobList::update_status`]).
396    ///
397    /// Note that updates of subshells that are not managed in `self.jobs` are
398    /// lost when you call this function.
399    pub fn update_all_subshell_statuses(&mut self) {
400        while let Ok(Some((pid, state))) = self.system.wait(Pid::ALL) {
401            self.jobs.update_status(pid, state);
402        }
403    }
404
405    /// Get an existing variable or create a new one.
406    ///
407    /// This method is a thin wrapper around [`VariableSet::get_or_new`].
408    /// If the [`AllExport`] option is on, the variable is
409    /// [exported](VariableRefMut::export) before being returned from the
410    /// method.
411    ///
412    /// You should prefer using this method over [`VariableSet::get_or_new`] to
413    /// make sure that the [`AllExport`] option is applied.
414    pub fn get_or_create_variable<S>(&mut self, name: S, scope: Scope) -> VariableRefMut
415    where
416        S: Into<String>,
417    {
418        let mut variable = self.variables.get_or_new(name, scope);
419        if self.options.get(AllExport) == On {
420            variable.export(true);
421        }
422        variable
423    }
424
425    /// Tests whether the [`ErrExit`] option is applicable in the current context.
426    ///
427    /// This function returns true if and only if:
428    /// - the [`ErrExit`] option is on, and
429    /// - the current stack has no [`Condition`] frame.
430    ///
431    /// [`Condition`]: Frame::Condition
432    pub fn errexit_is_applicable(&self) -> bool {
433        self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
434    }
435
436    /// Returns a `Divert` if the shell should exit because of the [`ErrExit`]
437    /// shell option.
438    ///
439    /// The function returns `Break(Divert::Exit(None))` if the [`errexit`
440    /// option is applicable](Self::errexit_is_applicable) and the current
441    /// `self.exit_status` is non-zero. Otherwise, it returns `Continue(())`.
442    pub fn apply_errexit(&self) -> ControlFlow<Divert> {
443        if !self.exit_status.is_successful() && self.errexit_is_applicable() {
444            Break(Divert::Exit(None))
445        } else {
446            Continue(())
447        }
448    }
449
450    /// Updates the exit status from the given result.
451    ///
452    /// If `result` is a `Break(divert)` where `divert.exit_status()` is `Some`
453    /// exit status, this function sets `self.exit_status` to that exit status.
454    pub fn apply_result(&mut self, result: crate::semantics::Result) {
455        match result {
456            Continue(_) => {}
457            Break(divert) => {
458                if let Some(exit_status) = divert.exit_status() {
459                    self.exit_status = exit_status;
460                }
461            }
462        }
463    }
464}
465
466mod alias;
467pub mod builtin;
468pub mod function;
469pub mod input;
470pub mod io;
471pub mod job;
472pub mod option;
473pub mod pwd;
474pub mod semantics;
475pub mod signal;
476pub mod stack;
477pub mod subshell;
478pub mod system;
479pub mod trap;
480pub mod variable;
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485    use crate::io::MIN_INTERNAL_FD;
486    use crate::job::Job;
487    use crate::subshell::Subshell;
488    use crate::system::r#virtual::FileBody;
489    use crate::system::r#virtual::Inode;
490    use crate::system::r#virtual::SystemState;
491    use crate::system::r#virtual::SIGCHLD;
492    use crate::trap::Action;
493    use assert_matches::assert_matches;
494    use futures_executor::LocalPool;
495    use futures_util::task::LocalSpawnExt as _;
496    use futures_util::FutureExt as _;
497    use std::cell::RefCell;
498    use std::str::from_utf8;
499    use yash_syntax::source::Location;
500
501    /// Helper function to perform a test in a virtual system with an executor.
502    pub fn in_virtual_system<F, Fut, T>(f: F) -> T
503    where
504        F: FnOnce(Env, Rc<RefCell<SystemState>>) -> Fut,
505        Fut: Future<Output = T> + 'static,
506        T: 'static,
507    {
508        let system = VirtualSystem::new();
509        let state = Rc::clone(&system.state);
510        let mut executor = futures_executor::LocalPool::new();
511        state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
512
513        let env = Env::with_system(Box::new(system));
514        let shared_system = env.system.clone();
515        let task = f(env, Rc::clone(&state));
516        let mut task = executor.spawner().spawn_local_with_handle(task).unwrap();
517        loop {
518            if let Some(result) = (&mut task).now_or_never() {
519                return result;
520            }
521            executor.run_until_stalled();
522            shared_system.select(false).unwrap();
523            SystemState::select_all(&state);
524        }
525    }
526
527    /// Helper function for asserting on the content of /dev/stderr.
528    pub fn assert_stderr<F, T>(state: &RefCell<SystemState>, f: F) -> T
529    where
530        F: FnOnce(&str) -> T,
531    {
532        let stderr = state.borrow().file_system.get("/dev/stderr").unwrap();
533        let stderr = stderr.borrow();
534        assert_matches!(&stderr.body, FileBody::Regular { content, .. } => {
535            f(from_utf8(content).unwrap())
536        })
537    }
538
539    #[test]
540    fn wait_for_signal_remembers_signal_in_trap_set() {
541        in_virtual_system(|mut env, state| async move {
542            env.traps
543                .set_action(
544                    &mut env.system,
545                    SIGCHLD,
546                    Action::Command("".into()),
547                    Location::dummy(""),
548                    false,
549                )
550                .unwrap();
551            {
552                let mut state = state.borrow_mut();
553                let process = state.processes.get_mut(&env.main_pid).unwrap();
554                assert!(process.blocked_signals().contains(&SIGCHLD));
555                let _ = process.raise_signal(SIGCHLD);
556            }
557            env.wait_for_signal(SIGCHLD).await;
558
559            let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
560            assert!(trap_state.pending);
561        })
562    }
563
564    fn poll_signals_env() -> (Env, VirtualSystem) {
565        let system = VirtualSystem::new();
566        let shared_system = SharedSystem::new(Box::new(system.clone()));
567        let mut env = Env::with_system(Box::new(shared_system));
568        env.traps
569            .set_action(
570                &mut env.system,
571                SIGCHLD,
572                Action::Command("".into()),
573                Location::dummy(""),
574                false,
575            )
576            .unwrap();
577        (env, system)
578    }
579
580    #[test]
581    fn poll_signals_none() {
582        let mut env = poll_signals_env().0;
583        let result = env.poll_signals();
584        assert_eq!(result, None);
585    }
586
587    #[test]
588    fn poll_signals_some() {
589        let (mut env, system) = poll_signals_env();
590        {
591            let mut state = system.state.borrow_mut();
592            let process = state.processes.get_mut(&system.process_id).unwrap();
593            assert!(process.blocked_signals().contains(&SIGCHLD));
594            let _ = process.raise_signal(SIGCHLD);
595        }
596
597        let result = env.poll_signals().unwrap();
598        assert_eq!(*result, [SIGCHLD]);
599    }
600
601    #[test]
602    fn get_tty_opens_tty() {
603        let system = VirtualSystem::new();
604        let tty = Rc::new(RefCell::new(Inode::new([])));
605        system
606            .state
607            .borrow_mut()
608            .file_system
609            .save("/dev/tty", Rc::clone(&tty))
610            .unwrap();
611        let mut env = Env::with_system(Box::new(system.clone()));
612
613        let fd = env.get_tty().unwrap();
614        assert!(
615            fd >= MIN_INTERNAL_FD,
616            "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
617        );
618        system
619            .with_open_file_description(fd, |ofd| {
620                assert!(Rc::ptr_eq(ofd.inode(), &tty));
621                Ok(())
622            })
623            .unwrap();
624
625        system.state.borrow_mut().file_system = Default::default();
626
627        // get_tty returns cached FD
628        let fd = env.get_tty().unwrap();
629        system
630            .with_open_file_description(fd, |ofd| {
631                assert!(Rc::ptr_eq(ofd.inode(), &tty));
632                Ok(())
633            })
634            .unwrap();
635    }
636
637    #[test]
638    fn start_and_wait_for_subshell() {
639        in_virtual_system(|mut env, _state| async move {
640            let subshell = Subshell::new(|env, _job_control| {
641                Box::pin(async { env.exit_status = ExitStatus(42) })
642            });
643            let (pid, _) = subshell.start(&mut env).await.unwrap();
644            let result = env.wait_for_subshell(pid).await;
645            assert_eq!(result, Ok((pid, ProcessState::exited(42))));
646        });
647    }
648
649    #[test]
650    fn start_and_wait_for_subshell_with_job_list() {
651        in_virtual_system(|mut env, _state| async move {
652            let subshell = Subshell::new(|env, _job_control| {
653                Box::pin(async { env.exit_status = ExitStatus(42) })
654            });
655            let (pid, _) = subshell.start(&mut env).await.unwrap();
656            let mut job = Job::new(pid);
657            job.name = "my job".to_string();
658            let job_index = env.jobs.add(job.clone());
659            let result = env.wait_for_subshell(pid).await;
660            assert_eq!(result, Ok((pid, ProcessState::exited(42))));
661            job.state = ProcessState::exited(42);
662            assert_eq!(env.jobs[job_index], job);
663        });
664    }
665
666    #[test]
667    fn wait_for_subshell_no_subshell() {
668        let system = VirtualSystem::new();
669        let mut executor = LocalPool::new();
670        system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
671        let mut env = Env::with_system(Box::new(system));
672        executor.run_until(async move {
673            let result = env.wait_for_subshell(Pid::ALL).await;
674            assert_eq!(result, Err(Errno::ECHILD));
675        });
676    }
677
678    #[test]
679    fn update_all_subshell_statuses_without_subshells() {
680        let mut env = Env::new_virtual();
681        env.update_all_subshell_statuses();
682    }
683
684    #[test]
685    fn update_all_subshell_statuses_with_subshells() {
686        let system = VirtualSystem::new();
687        let mut executor = futures_executor::LocalPool::new();
688        system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
689
690        let mut env = Env::with_system(Box::new(system));
691
692        let [job_1, job_2, job_3] = executor.run_until(async {
693            // Run a subshell.
694            let subshell_1 = Subshell::new(|env, _job_control| {
695                Box::pin(async { env.exit_status = ExitStatus(12) })
696            });
697            let (pid_1, _) = subshell_1.start(&mut env).await.unwrap();
698
699            // Run another subshell.
700            let subshell_2 = Subshell::new(|env, _job_control| {
701                Box::pin(async { env.exit_status = ExitStatus(35) })
702            });
703            let (pid_2, _) = subshell_2.start(&mut env).await.unwrap();
704
705            // This one will never finish.
706            let subshell_3 =
707                Subshell::new(|_env, _job_control| Box::pin(futures_util::future::pending()));
708            let (pid_3, _) = subshell_3.start(&mut env).await.unwrap();
709
710            // Yet another subshell. We don't make this into a job.
711            let subshell_4 = Subshell::new(|env, _job_control| {
712                Box::pin(async { env.exit_status = ExitStatus(100) })
713            });
714            let (_pid_4, _) = subshell_4.start(&mut env).await.unwrap();
715
716            // Create jobs.
717            let job_1 = env.jobs.add(Job::new(pid_1));
718            let job_2 = env.jobs.add(Job::new(pid_2));
719            let job_3 = env.jobs.add(Job::new(pid_3));
720            [job_1, job_2, job_3]
721        });
722
723        // Let the jobs (except job_3) finish.
724        executor.run_until_stalled();
725
726        // We're not yet updated.
727        assert_eq!(env.jobs[job_1].state, ProcessState::Running);
728        assert_eq!(env.jobs[job_2].state, ProcessState::Running);
729        assert_eq!(env.jobs[job_3].state, ProcessState::Running);
730
731        env.update_all_subshell_statuses();
732
733        // Now we have the results.
734        // TODO assert_eq!(env.jobs[job_1].state, ProcessState::Exited(ExitStatus(12)));
735        // TODO assert_eq!(env.jobs[job_2].state, ProcessState::Exited(ExitStatus(35)));
736        assert_eq!(env.jobs[job_3].state, ProcessState::Running);
737    }
738
739    #[test]
740    fn get_or_create_variable_with_all_export_off() {
741        let mut env = Env::new_virtual();
742        let mut a = env.get_or_create_variable("a", Scope::Global);
743        assert!(!a.is_exported);
744        a.export(true);
745        let a = env.get_or_create_variable("a", Scope::Global);
746        assert!(a.is_exported);
747    }
748
749    #[test]
750    fn get_or_create_variable_with_all_export_on() {
751        let mut env = Env::new_virtual();
752        env.options.set(AllExport, On);
753        let mut a = env.get_or_create_variable("a", Scope::Global);
754        assert!(a.is_exported);
755        a.export(false);
756        let a = env.get_or_create_variable("a", Scope::Global);
757        assert!(a.is_exported);
758    }
759
760    #[test]
761    fn errexit_on() {
762        let mut env = Env::new_virtual();
763        env.exit_status = ExitStatus::FAILURE;
764        env.options.set(ErrExit, On);
765        assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
766    }
767
768    #[test]
769    fn errexit_with_zero_exit_status() {
770        let mut env = Env::new_virtual();
771        env.options.set(ErrExit, On);
772        assert_eq!(env.apply_errexit(), Continue(()));
773    }
774
775    #[test]
776    fn errexit_in_condition() {
777        let mut env = Env::new_virtual();
778        env.exit_status = ExitStatus::FAILURE;
779        env.options.set(ErrExit, On);
780        let env = env.push_frame(Frame::Condition);
781        assert_eq!(env.apply_errexit(), Continue(()));
782    }
783
784    #[test]
785    fn errexit_off() {
786        let mut env = Env::new_virtual();
787        env.exit_status = ExitStatus::FAILURE;
788        assert_eq!(env.apply_errexit(), Continue(()));
789    }
790
791    #[test]
792    fn apply_result_with_continue() {
793        let mut env = Env::new_virtual();
794        env.apply_result(Continue(()));
795        assert_eq!(env.exit_status, ExitStatus::default());
796    }
797
798    #[test]
799    fn apply_result_with_divert_without_exit_status() {
800        let mut env = Env::new_virtual();
801        env.apply_result(Break(Divert::Exit(None)));
802        assert_eq!(env.exit_status, ExitStatus::default());
803    }
804
805    #[test]
806    fn apply_result_with_divert_with_exit_status() {
807        let mut env = Env::new_virtual();
808        env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
809        assert_eq!(env.exit_status, ExitStatus(67));
810    }
811}