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