Skip to main content

yash_env/trap/
state.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 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//! Items that manage the state of a single signal
18
19#[cfg(doc)]
20use super::TrapSet;
21use super::{Condition, SignalSystem};
22use crate::source::Location;
23use crate::system::{Disposition, Errno};
24use std::collections::btree_map::{Entry, VacantEntry};
25use std::rc::Rc;
26use thiserror::Error;
27
28/// Action performed when a [`Condition`] is met
29#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
30pub enum Action {
31    /// Performs the default action.
32    ///
33    /// For signal conditions, the behavior depends on the signal delivered.
34    /// For other conditions, this is equivalent to `Ignore`.
35    #[default]
36    Default,
37
38    /// Pretends as if the condition was not met.
39    Ignore,
40
41    /// Executes a command string.
42    Command(Rc<str>),
43}
44
45impl From<&Action> for Disposition {
46    fn from(trap: &Action) -> Self {
47        match trap {
48            Action::Default => Disposition::Default,
49            Action::Ignore => Disposition::Ignore,
50            Action::Command(_) => Disposition::Catch,
51        }
52    }
53}
54
55/// Error that may happen in [`TrapSet::set_action`]
56#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
57pub enum SetActionError {
58    /// Attempt to set a trap that has been ignored since the shell startup.
59    #[error("the signal has been ignored since startup")]
60    InitiallyIgnored,
61
62    /// Attempt to set a trap for the `SIGKILL` signal.
63    #[error("cannot set a trap for SIGKILL")]
64    SIGKILL,
65
66    /// Attempt to set a trap for the `SIGSTOP` signal.
67    #[error("cannot set a trap for SIGSTOP")]
68    SIGSTOP,
69
70    /// Error from the underlying system interface.
71    #[error(transparent)]
72    SystemError(#[from] Errno),
73}
74
75/// Origin of the current trap action
76///
77/// The `Origin` enum indicates how the current trap action was set.
78#[derive(Clone, Debug, Default, Eq, PartialEq)]
79pub enum Origin {
80    /// The current trap action was inherited from the previous process that
81    /// `exec`ed the shell.
82    ///
83    /// This is the default value.
84    #[default]
85    Inherited,
86
87    /// The current trap action was set by the shell when entering a subshell.
88    Subshell,
89
90    /// The current trap action was set by the user.
91    ///
92    /// The location indicates the simple command that invoked the trap built-in
93    /// that set the current action.
94    User(Location),
95}
96
97/// State of the trap action for a condition
98#[derive(Clone, Debug, Default, Eq, PartialEq)]
99pub struct TrapState {
100    /// Action taken when the condition is met
101    pub action: Action,
102
103    /// Origin of the current action
104    pub origin: Origin,
105
106    /// True iff a signal specified by the condition has been caught and the
107    /// action command has not yet executed.
108    pub pending: bool,
109}
110
111impl TrapState {
112    fn from_initial_disposition(disposition: Disposition) -> Self {
113        let action = match disposition {
114            Disposition::Default => Action::Default,
115            Disposition::Ignore => Action::Ignore,
116            // The Rust runtime installs a handler for SIGSEGV and SIGBUS before
117            // the shell is initialized. We should treat these signals as if
118            // they have the default disposition.
119            // Disposition::Catch => panic!("initial disposition cannot be `Catch`"),
120            Disposition::Catch => Action::Default,
121        };
122        TrapState {
123            action,
124            origin: Origin::Inherited,
125            pending: false,
126        }
127    }
128}
129
130/// Option for [`GrandState::enter_subshell`]
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum EnterSubshellOption {
133    /// Keeps the current internal disposition configuration.
134    KeepInternalDisposition,
135    /// Resets the internal disposition configuration to the default.
136    ClearInternalDisposition,
137    /// Resets the internal disposition configuration to the default and sets the
138    /// signal disposition to `Ignore`.
139    Ignore,
140}
141
142/// Whole configuration and state for a trap condition
143#[derive(Clone, Debug)]
144pub struct GrandState {
145    /// Current trap state
146    current_state: TrapState,
147
148    /// Trap state that was effective in the parent environment
149    parent_state: Option<TrapState>,
150
151    /// Current internal disposition
152    ///
153    /// The internal disposition is the signal disposition that is set by the
154    /// shell itself, not by the user. This is used to handle some specific
155    /// signals like `SIGCHLD` and `SIGTSTP`. When the user sets a trap for
156    /// these signals, the actual signal disposition registered in the system
157    /// is computed as the maximum of the user-defined disposition and the
158    /// internal disposition.
159    internal_disposition: Disposition,
160}
161
162impl GrandState {
163    /// Returns the current trap state.
164    #[inline]
165    #[must_use]
166    pub fn current_state(&self) -> &TrapState {
167        &self.current_state
168    }
169
170    /// Returns the parent trap state.
171    ///
172    /// This is the trap state that was effective in the parent environment
173    /// before the current environment was created as a subshell of the parent.
174    /// Returns `None` if the current environment is not a subshell or the parent
175    /// state was [cleared](Self::clear_parent_state).
176    #[inline]
177    #[must_use]
178    pub fn parent_state(&self) -> Option<&TrapState> {
179        self.parent_state.as_ref()
180    }
181
182    /// Clears the parent trap state.
183    pub fn clear_parent_state(&mut self) {
184        self.parent_state = None;
185    }
186
187    /// Inserts a new entry if the entry is vacant.
188    ///
189    /// If the condition is a signal, the new entry is initialized with the
190    /// current signal disposition obtained from the system.
191    pub fn insert_from_system_if_vacant<'a, S: SignalSystem>(
192        system: &S,
193        entry: Entry<'a, Condition, GrandState>,
194    ) -> Result<&'a GrandState, Errno> {
195        match entry {
196            Entry::Vacant(vacant) => {
197                let disposition = match *vacant.key() {
198                    Condition::Signal(signal) => system.get_disposition(signal)?,
199                    Condition::Exit => Disposition::Default,
200                };
201                let state = GrandState {
202                    current_state: TrapState::from_initial_disposition(disposition),
203                    parent_state: None,
204                    internal_disposition: Disposition::Default,
205                };
206                Ok(vacant.insert(state))
207            }
208
209            Entry::Occupied(occupied) => Ok(occupied.into_mut()),
210        }
211    }
212
213    /// Updates the entry with the new action.
214    pub async fn set_action<S: SignalSystem>(
215        system: &S,
216        entry: Entry<'_, Condition, GrandState>,
217        action: Action,
218        origin: Location,
219        override_ignore: bool,
220    ) -> Result<(), SetActionError> {
221        let cond = *entry.key();
222        let disposition = (&action).into();
223        let new_state = TrapState {
224            action,
225            origin: Origin::User(origin),
226            pending: false,
227        };
228
229        match entry {
230            Entry::Vacant(vacant) => {
231                if let Condition::Signal(signal) = cond {
232                    if !override_ignore {
233                        let initial_disposition =
234                            system.set_disposition(signal, Disposition::Ignore).await?;
235                        if initial_disposition == Disposition::Ignore {
236                            vacant.insert(GrandState {
237                                current_state: TrapState::from_initial_disposition(
238                                    initial_disposition,
239                                ),
240                                parent_state: None,
241                                internal_disposition: Disposition::Default,
242                            });
243                            return Err(SetActionError::InitiallyIgnored);
244                        }
245                    }
246
247                    if override_ignore || disposition != Disposition::Ignore {
248                        system.set_disposition(signal, disposition).await?;
249                    }
250                }
251
252                vacant.insert(GrandState {
253                    current_state: new_state,
254                    parent_state: None,
255                    internal_disposition: Disposition::Default,
256                });
257            }
258
259            Entry::Occupied(mut occupied) => {
260                let state = occupied.get_mut();
261                if !override_ignore
262                    && state.current_state.action == Action::Ignore
263                    && state.current_state.origin == Origin::Inherited
264                {
265                    return Err(SetActionError::InitiallyIgnored);
266                }
267
268                if let Condition::Signal(signal) = cond {
269                    let internal = state.internal_disposition;
270                    let old_disposition = internal.max((&state.current_state.action).into());
271                    let new_disposition = internal.max(disposition);
272                    if old_disposition != new_disposition {
273                        system.set_disposition(signal, new_disposition).await?;
274                    }
275                }
276
277                state.current_state = new_state;
278            }
279        }
280
281        Ok(())
282    }
283
284    /// Returns the current internal disposition.
285    #[must_use]
286    pub fn internal_disposition(&self) -> Disposition {
287        self.internal_disposition
288    }
289
290    /// Sets the internal disposition.
291    ///
292    /// The condition of the given entry must be a signal, or this function
293    /// panics.
294    pub async fn set_internal_disposition<S: SignalSystem>(
295        system: &S,
296        entry: Entry<'_, Condition, GrandState>,
297        disposition: Disposition,
298    ) -> Result<(), Errno> {
299        let signal = match *entry.key() {
300            Condition::Signal(signal) => signal,
301            Condition::Exit => panic!("exit condition cannot have an internal disposition"),
302        };
303
304        match entry {
305            Entry::Vacant(_) if disposition == Disposition::Default => (),
306
307            Entry::Vacant(vacant) => {
308                let initial_disposition = system.set_disposition(signal, disposition).await?;
309                vacant.insert(GrandState {
310                    current_state: TrapState::from_initial_disposition(initial_disposition),
311                    parent_state: None,
312                    internal_disposition: disposition,
313                });
314            }
315
316            Entry::Occupied(mut occupied) => {
317                let state = occupied.get_mut();
318                let setting = (&state.current_state.action).into();
319                let old_disposition = state.internal_disposition.max(setting);
320                let new_disposition = disposition.max(setting);
321                if old_disposition != new_disposition {
322                    system.set_disposition(signal, new_disposition).await?;
323                }
324                state.internal_disposition = disposition;
325            }
326        }
327
328        Ok(())
329    }
330
331    /// Updates the trap states and gets ready for executing the body a
332    /// subshell.
333    ///
334    /// If the current state has a user-specified command
335    /// (`Action::Command(_)`), it is saved in the parent state and reset to the
336    /// default. Additionally, the signal disposition is updated depending on the
337    /// `option`.
338    pub async fn enter_subshell<S: SignalSystem>(
339        &mut self,
340        system: &S,
341        cond: Condition,
342        option: EnterSubshellOption,
343    ) -> Result<(), Errno> {
344        let old_setting = (&self.current_state.action).into();
345        let old_disposition = self.internal_disposition.max(old_setting);
346
347        if matches!(self.current_state.action, Action::Command(_)) {
348            self.parent_state = Some(std::mem::replace(
349                &mut self.current_state,
350                TrapState {
351                    action: Action::Default,
352                    origin: Origin::Subshell,
353                    pending: false,
354                },
355            ));
356        }
357        if option == EnterSubshellOption::Ignore {
358            self.current_state.action = Action::Ignore;
359        }
360
361        let new_setting = (&self.current_state.action).into();
362        let new_disposition = match option {
363            EnterSubshellOption::KeepInternalDisposition => {
364                self.internal_disposition.max(new_setting)
365            }
366            EnterSubshellOption::ClearInternalDisposition => new_setting,
367            EnterSubshellOption::Ignore => Disposition::Ignore,
368        };
369        if old_disposition != new_disposition {
370            if let Condition::Signal(signal) = cond {
371                system.set_disposition(signal, new_disposition).await?;
372            }
373        }
374        self.internal_disposition = match option {
375            EnterSubshellOption::KeepInternalDisposition => self.internal_disposition,
376            EnterSubshellOption::ClearInternalDisposition | EnterSubshellOption::Ignore => {
377                Disposition::Default
378            }
379        };
380        Ok(())
381    }
382
383    /// Sets the disposition to `Ignore` for the given signal condition.
384    ///
385    /// This function creates a new `GrandState` entry with the current state
386    /// having `Action::Ignore`. If the signal disposition is default, the shell
387    /// sets the signal disposition to `Ignore` and the origin to `Subshell`. If
388    /// the signal disposition is already `Ignore`, the origin is set to
389    /// `Inherited` to disallow changing the action in a non-interactive shell.
390    ///
391    /// You should call this function in place of [`Self::enter_subshell`] if
392    /// there is no entry for the condition yet.
393    ///
394    /// This function panics if the condition is not a signal.
395    pub async fn ignore<S: SignalSystem>(
396        system: &S,
397        vacant: VacantEntry<'_, Condition, GrandState>,
398    ) -> Result<(), Errno> {
399        let signal = match *vacant.key() {
400            Condition::Signal(signal) => signal,
401            Condition::Exit => panic!("exit condition cannot be ignored"),
402        };
403        let initial_disposition = system.set_disposition(signal, Disposition::Ignore).await?;
404        let origin = match initial_disposition {
405            Disposition::Default => Origin::Subshell,
406            Disposition::Ignore => Origin::Inherited,
407            // The Rust runtime installs a handler for SIGSEGV and SIGBUS before
408            // the shell is initialized. We should treat these signals as if
409            // they have the default disposition (though the ignore function is
410            // normally not called for these signals).
411            // Disposition::Catch => panic!("initial disposition cannot be `Catch`"),
412            Disposition::Catch => Origin::Subshell,
413        };
414        vacant.insert(GrandState {
415            current_state: TrapState {
416                action: Action::Ignore,
417                origin,
418                pending: false,
419            },
420            parent_state: None,
421            internal_disposition: Disposition::Default,
422        });
423        Ok(())
424    }
425
426    /// Marks this signal as caught.
427    pub fn mark_as_caught(&mut self) {
428        self.current_state.pending = true;
429    }
430
431    /// Clears the mark of this signal being caught and returns the trap state.
432    pub fn handle_if_caught(&mut self) -> Option<&TrapState> {
433        if self.current_state.pending {
434            self.current_state.pending = false;
435            Some(&self.current_state)
436        } else {
437            None
438        }
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::super::tests::DummySystem;
445    use super::*;
446    use crate::system::Signals;
447    use crate::system::r#virtual::{
448        SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIOT,
449        SIGKILL, SIGPIPE, SIGPROF, SIGQUIT, SIGSEGV, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP,
450        SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ,
451    };
452    use assert_matches::assert_matches;
453    use futures_util::FutureExt as _;
454    use std::borrow::Cow;
455    use std::{collections::BTreeMap, ops::RangeInclusive};
456
457    struct UnusedSystem;
458
459    impl Signals for UnusedSystem {
460        const SIGABRT: crate::signal::Number = SIGABRT;
461        const SIGALRM: crate::signal::Number = SIGALRM;
462        const SIGBUS: crate::signal::Number = SIGBUS;
463        const SIGCHLD: crate::signal::Number = SIGCHLD;
464        const SIGCLD: Option<crate::signal::Number> = None;
465        const SIGCONT: crate::signal::Number = SIGCONT;
466        const SIGEMT: Option<crate::signal::Number> = None;
467        const SIGFPE: crate::signal::Number = SIGFPE;
468        const SIGHUP: crate::signal::Number = SIGHUP;
469        const SIGILL: crate::signal::Number = SIGILL;
470        const SIGINFO: Option<crate::signal::Number> = None;
471        const SIGINT: crate::signal::Number = SIGINT;
472        const SIGIO: Option<crate::signal::Number> = None;
473        const SIGIOT: crate::signal::Number = SIGIOT;
474        const SIGKILL: crate::signal::Number = SIGKILL;
475        const SIGLOST: Option<crate::signal::Number> = None;
476        const SIGPIPE: crate::signal::Number = SIGPIPE;
477        const SIGPOLL: Option<crate::signal::Number> = None;
478        const SIGPROF: crate::signal::Number = SIGPROF;
479        const SIGPWR: Option<crate::signal::Number> = None;
480        const SIGQUIT: crate::signal::Number = SIGQUIT;
481        const SIGSEGV: crate::signal::Number = SIGSEGV;
482        const SIGSTKFLT: Option<crate::signal::Number> = None;
483        const SIGSTOP: crate::signal::Number = SIGSTOP;
484        const SIGSYS: crate::signal::Number = SIGSYS;
485        const SIGTERM: crate::signal::Number = SIGTERM;
486        const SIGTHR: Option<crate::signal::Number> = None;
487        const SIGTRAP: crate::signal::Number = SIGTRAP;
488        const SIGTSTP: crate::signal::Number = SIGTSTP;
489        const SIGTTIN: crate::signal::Number = SIGTTIN;
490        const SIGTTOU: crate::signal::Number = SIGTTOU;
491        const SIGURG: crate::signal::Number = SIGURG;
492        const SIGUSR1: crate::signal::Number = SIGUSR1;
493        const SIGUSR2: crate::signal::Number = SIGUSR2;
494        const SIGVTALRM: crate::signal::Number = SIGVTALRM;
495        const SIGWINCH: crate::signal::Number = SIGWINCH;
496        const SIGXCPU: crate::signal::Number = SIGXCPU;
497        const SIGXFSZ: crate::signal::Number = SIGXFSZ;
498
499        fn sigrt_range(&self) -> Option<RangeInclusive<crate::signal::Number>> {
500            unreachable!()
501        }
502        fn iter_sigrt(&self) -> impl DoubleEndedIterator<Item = crate::signal::Number> + use<> {
503            unreachable!() as std::iter::Empty<_>
504        }
505        fn sig2str<S: Into<crate::signal::RawNumber>>(
506            &self,
507            signal: S,
508        ) -> Option<Cow<'static, str>> {
509            unreachable!("sig2str({:?})", signal.into())
510        }
511        fn str2sig(&self, name: &str) -> Option<crate::signal::Number> {
512            unreachable!("str2sig({name:?})")
513        }
514        fn validate_signal(
515            &self,
516            number: crate::signal::RawNumber,
517        ) -> Option<(crate::signal::Name, crate::signal::Number)> {
518            unreachable!("validate_signal({number:?})")
519        }
520        fn signal_name_from_number(&self, number: crate::signal::Number) -> crate::signal::Name {
521            unreachable!("signal_name_from_number({number:?})")
522        }
523        fn signal_number_from_name(
524            &self,
525            name: crate::signal::Name,
526        ) -> Option<crate::signal::Number> {
527            unreachable!("signal_number_from_name({name:?})")
528        }
529    }
530
531    impl SignalSystem for UnusedSystem {
532        fn get_disposition(&self, signal: crate::signal::Number) -> Result<Disposition, Errno> {
533            unreachable!("get_disposition({signal})")
534        }
535        fn set_disposition(
536            &self,
537            signal: crate::signal::Number,
538            disposition: Disposition,
539        ) -> impl Future<Output = Result<Disposition, Errno>> + use<> {
540            unreachable!("set_disposition({signal}, {disposition:?})") as std::future::Ready<_>
541        }
542    }
543
544    #[test]
545    fn insertion_with_default_inherited_disposition() {
546        let system = DummySystem::default();
547        let mut map = BTreeMap::new();
548        let entry = map.entry(SIGCHLD.into());
549        let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
550        assert_eq!(
551            state.current_state(),
552            &TrapState {
553                action: Action::Default,
554                origin: Origin::Inherited,
555                pending: false,
556            }
557        );
558        assert_eq!(state.parent_state(), None);
559        assert_eq!(state.internal_disposition(), Disposition::Default);
560    }
561
562    #[test]
563    fn insertion_with_inherited_disposition_of_ignore() {
564        let system = DummySystem::default();
565        system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
566        let mut map = BTreeMap::new();
567        let entry = map.entry(SIGCHLD.into());
568        let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
569        assert_eq!(
570            state.current_state(),
571            &TrapState {
572                action: Action::Ignore,
573                origin: Origin::Inherited,
574                pending: false,
575            }
576        );
577        assert_eq!(state.parent_state(), None);
578        assert_eq!(state.internal_disposition(), Disposition::Default);
579    }
580
581    #[test]
582    fn insertion_with_occupied_entry() {
583        let system = DummySystem::default();
584        let mut map = BTreeMap::new();
585        let entry = map.entry(SIGCHLD.into());
586        let origin = Location::dummy("origin");
587        let action = Action::Command("echo".into());
588        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
589            .now_or_never()
590            .unwrap()
591            .unwrap();
592
593        // If the entry is occupied, the function should return the existing
594        // state without accessing the system.
595        let entry = map.entry(SIGCHLD.into());
596        let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
597        assert_eq!(
598            state.current_state(),
599            &TrapState {
600                action,
601                origin: Origin::User(origin),
602                pending: false,
603            }
604        );
605        assert_eq!(state.parent_state(), None);
606        assert_eq!(state.internal_disposition(), Disposition::Default);
607    }
608
609    #[test]
610    fn insertion_with_non_signal_condition() {
611        let mut map = BTreeMap::new();
612        let entry = map.entry(Condition::Exit);
613        let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
614        assert_eq!(
615            state.current_state(),
616            &TrapState {
617                action: Action::Default,
618                origin: Origin::Inherited,
619                pending: false,
620            }
621        );
622        assert_eq!(state.parent_state(), None);
623        assert_eq!(state.internal_disposition(), Disposition::Default);
624    }
625
626    #[test]
627    fn setting_trap_to_ignore_without_override_ignore() {
628        let system = DummySystem::default();
629        let mut map = BTreeMap::new();
630        let entry = map.entry(SIGCHLD.into());
631        let origin = Location::dummy("origin");
632
633        let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
634            .now_or_never()
635            .unwrap();
636        assert_eq!(result, Ok(()));
637        assert_eq!(
638            map[&SIGCHLD.into()].current_state(),
639            &TrapState {
640                action: Action::Ignore,
641                origin: Origin::User(origin),
642                pending: false
643            }
644        );
645        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
646        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
647    }
648
649    #[test]
650    fn setting_trap_to_ignore_with_override_ignore() {
651        let system = DummySystem::default();
652        let mut map = BTreeMap::new();
653        let entry = map.entry(SIGCHLD.into());
654        let origin = Location::dummy("origin");
655
656        let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), true)
657            .now_or_never()
658            .unwrap();
659        assert_eq!(result, Ok(()));
660        assert_eq!(
661            map[&SIGCHLD.into()].current_state(),
662            &TrapState {
663                action: Action::Ignore,
664                origin: Origin::User(origin),
665                pending: false
666            }
667        );
668        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
669        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
670    }
671
672    #[test]
673    fn setting_trap_to_command() {
674        let system = DummySystem::default();
675        let mut map = BTreeMap::new();
676        let entry = map.entry(SIGCHLD.into());
677        let action = Action::Command("echo".into());
678        let origin = Location::dummy("origin");
679
680        let result = GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
681            .now_or_never()
682            .unwrap();
683        assert_eq!(result, Ok(()));
684        assert_eq!(
685            map[&SIGCHLD.into()].current_state(),
686            &TrapState {
687                action,
688                origin: Origin::User(origin),
689                pending: false
690            }
691        );
692        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
693        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
694    }
695
696    #[test]
697    fn setting_trap_to_default() {
698        let system = DummySystem::default();
699        let mut map = BTreeMap::new();
700        let entry = map.entry(SIGCHLD.into());
701        let origin = Location::dummy("foo");
702        GrandState::set_action(&system, entry, Action::Ignore, origin, false)
703            .now_or_never()
704            .unwrap()
705            .unwrap();
706
707        let entry = map.entry(SIGCHLD.into());
708        let origin = Location::dummy("bar");
709        let result = GrandState::set_action(&system, entry, Action::Default, origin.clone(), false)
710            .now_or_never()
711            .unwrap();
712        assert_eq!(result, Ok(()));
713        assert_eq!(
714            map[&SIGCHLD.into()].current_state(),
715            &TrapState {
716                action: Action::Default,
717                origin: Origin::User(origin),
718                pending: false
719            }
720        );
721        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
722        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
723    }
724
725    #[test]
726    fn resetting_trap_from_ignore_no_override() {
727        let system = DummySystem::default();
728        system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
729        let mut map = BTreeMap::new();
730        let entry = map.entry(SIGCHLD.into());
731        let origin = Location::dummy("foo");
732        let result = GrandState::set_action(&system, entry, Action::Ignore, origin, false)
733            .now_or_never()
734            .unwrap();
735        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
736
737        // Idempotence
738        let entry = map.entry(SIGCHLD.into());
739        let origin = Location::dummy("bar");
740        let result = GrandState::set_action(&system, entry, Action::Ignore, origin, false)
741            .now_or_never()
742            .unwrap();
743        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
744
745        assert_eq!(
746            map[&SIGCHLD.into()].current_state(),
747            &TrapState {
748                action: Action::Ignore,
749                origin: Origin::Inherited,
750                pending: false
751            }
752        );
753        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
754        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
755    }
756
757    #[test]
758    fn resetting_trap_from_ignore_override() {
759        let system = DummySystem::default();
760        system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
761        let mut map = BTreeMap::new();
762        let entry = map.entry(SIGCHLD.into());
763        let origin = Location::dummy("origin");
764        let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), true)
765            .now_or_never()
766            .unwrap();
767        assert_eq!(result, Ok(()));
768        assert_eq!(
769            map[&SIGCHLD.into()].current_state(),
770            &TrapState {
771                action: Action::Ignore,
772                origin: Origin::User(origin),
773                pending: false
774            }
775        );
776        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
777        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
778    }
779
780    #[test]
781    fn internal_disposition_ignore() {
782        let system = DummySystem::default();
783        let mut map = BTreeMap::new();
784        let entry = map.entry(SIGCHLD.into());
785
786        let result = GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
787            .now_or_never()
788            .unwrap();
789        assert_eq!(result, Ok(()));
790        assert_eq!(
791            map[&SIGCHLD.into()].internal_disposition(),
792            Disposition::Ignore
793        );
794        assert_eq!(
795            map[&SIGCHLD.into()].current_state(),
796            &TrapState {
797                action: Action::Default,
798                origin: Origin::Inherited,
799                pending: false
800            }
801        );
802        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
803        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
804    }
805
806    #[test]
807    fn internal_disposition_catch() {
808        let system = DummySystem::default();
809        let mut map = BTreeMap::new();
810        let entry = map.entry(SIGCHLD.into());
811
812        let result = GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
813            .now_or_never()
814            .unwrap();
815        assert_eq!(result, Ok(()));
816        assert_eq!(
817            map[&SIGCHLD.into()].internal_disposition(),
818            Disposition::Catch
819        );
820        assert_eq!(
821            map[&SIGCHLD.into()].current_state(),
822            &TrapState {
823                action: Action::Default,
824                origin: Origin::Inherited,
825                pending: false
826            }
827        );
828        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
829        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
830    }
831
832    #[test]
833    fn action_ignore_and_internal_disposition_catch() {
834        let system = DummySystem::default();
835        let mut map = BTreeMap::new();
836        let entry = map.entry(SIGCHLD.into());
837        let origin = Location::dummy("origin");
838        GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
839            .now_or_never()
840            .unwrap()
841            .unwrap();
842        let entry = map.entry(SIGCHLD.into());
843
844        let result = GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
845            .now_or_never()
846            .unwrap();
847        assert_eq!(result, Ok(()));
848        assert_eq!(
849            map[&SIGCHLD.into()].internal_disposition(),
850            Disposition::Catch
851        );
852        assert_eq!(
853            map[&SIGCHLD.into()].current_state(),
854            &TrapState {
855                action: Action::Ignore,
856                origin: Origin::User(origin),
857                pending: false
858            }
859        );
860        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
861        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
862    }
863
864    #[test]
865    fn action_catch_and_internal_disposition_ignore() {
866        let system = DummySystem::default();
867        let mut map = BTreeMap::new();
868        let entry = map.entry(SIGCHLD.into());
869        let origin = Location::dummy("origin");
870        let action = Action::Command("echo".into());
871        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
872            .now_or_never()
873            .unwrap()
874            .unwrap();
875        let entry = map.entry(SIGCHLD.into());
876
877        let result = GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
878            .now_or_never()
879            .unwrap();
880        assert_eq!(result, Ok(()));
881        assert_eq!(
882            map[&SIGCHLD.into()].internal_disposition(),
883            Disposition::Ignore
884        );
885        assert_eq!(
886            map[&SIGCHLD.into()].current_state(),
887            &TrapState {
888                action,
889                origin: Origin::User(origin),
890                pending: false
891            }
892        );
893        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
894        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
895    }
896
897    #[test]
898    fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
899        let system = DummySystem::default();
900        let mut map = BTreeMap::new();
901        let entry = map.entry(SIGTTOU.into());
902        GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
903            .now_or_never()
904            .unwrap()
905            .unwrap();
906        let entry = map.entry(SIGTTOU.into());
907        let origin = Location::dummy("origin");
908        let action = Action::Command("echo".into());
909
910        let result = GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
911            .now_or_never()
912            .unwrap();
913        assert_eq!(result, Ok(()));
914        assert_eq!(
915            map[&SIGTTOU.into()].internal_disposition(),
916            Disposition::Ignore
917        );
918        assert_eq!(
919            map[&SIGTTOU.into()].current_state(),
920            &TrapState {
921                action,
922                origin: Origin::User(origin),
923                pending: false
924            }
925        );
926        assert_eq!(map[&SIGTTOU.into()].parent_state(), None);
927        assert_eq!(system.0.borrow()[&SIGTTOU], Disposition::Catch);
928    }
929
930    #[test]
931    fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
932        let system = DummySystem::default();
933        system.0.borrow_mut().insert(SIGTTOU, Disposition::Ignore);
934        let mut map = BTreeMap::new();
935        let cond = SIGTTOU.into();
936        let entry = map.entry(cond);
937        GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
938            .now_or_never()
939            .unwrap()
940            .unwrap();
941        let entry = map.entry(cond);
942        let origin = Location::dummy("origin");
943        let action = Action::Command("echo".into());
944
945        let result = GrandState::set_action(&system, entry, action, origin, false)
946            .now_or_never()
947            .unwrap();
948        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
949        assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
950        assert_eq!(
951            map[&cond].current_state(),
952            &TrapState {
953                action: Action::Ignore,
954                origin: Origin::Inherited,
955                pending: false
956            }
957        );
958        assert_eq!(map[&cond].parent_state(), None);
959        assert_eq!(system.0.borrow()[&SIGTTOU], Disposition::Ignore);
960    }
961
962    #[test]
963    fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
964        let system = DummySystem::default();
965        let mut map = BTreeMap::new();
966        let cond = SIGCHLD.into();
967        GrandState::set_internal_disposition(&system, map.entry(cond), Disposition::Catch)
968            .now_or_never()
969            .unwrap()
970            .unwrap();
971
972        let result = map
973            .get_mut(&cond)
974            .unwrap()
975            .enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
976            .now_or_never()
977            .unwrap();
978        assert_eq!(result, Ok(()));
979        assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
980        assert_eq!(
981            map[&cond].current_state(),
982            &TrapState {
983                action: Action::Default,
984                origin: Origin::Inherited,
985                pending: false
986            }
987        );
988        assert_eq!(map[&cond].parent_state(), None);
989        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
990    }
991
992    #[test]
993    fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
994        let system = DummySystem::default();
995        let mut map = BTreeMap::new();
996        let cond = SIGCHLD.into();
997        let entry = map.entry(cond);
998        GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
999            .now_or_never()
1000            .unwrap()
1001            .unwrap();
1002
1003        let result = map
1004            .get_mut(&cond)
1005            .unwrap()
1006            .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1007            .now_or_never()
1008            .unwrap();
1009        assert_eq!(result, Ok(()));
1010        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1011        assert_eq!(
1012            map[&cond].current_state(),
1013            &TrapState {
1014                action: Action::Default,
1015                origin: Origin::Inherited,
1016                pending: false
1017            }
1018        );
1019        assert_eq!(map[&cond].parent_state(), None);
1020        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
1021    }
1022
1023    #[test]
1024    fn enter_subshell_with_ignore_and_no_internal_disposition() {
1025        let system = DummySystem::default();
1026        let mut map = BTreeMap::new();
1027        let cond = SIGCHLD.into();
1028        let entry = map.entry(cond);
1029        let origin = Location::dummy("foo");
1030        GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
1031            .now_or_never()
1032            .unwrap()
1033            .unwrap();
1034
1035        let result = map
1036            .get_mut(&cond)
1037            .unwrap()
1038            .enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
1039            .now_or_never()
1040            .unwrap();
1041        assert_eq!(result, Ok(()));
1042        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1043        assert_eq!(
1044            map[&cond].current_state(),
1045            &TrapState {
1046                action: Action::Ignore,
1047                origin: Origin::User(origin),
1048                pending: false
1049            }
1050        );
1051        assert_eq!(map[&cond].parent_state(), None);
1052        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
1053    }
1054
1055    #[test]
1056    fn enter_subshell_with_ignore_clearing_internal_disposition() {
1057        let system = DummySystem::default();
1058        let mut map = BTreeMap::new();
1059        let cond = SIGCHLD.into();
1060        let entry = map.entry(cond);
1061        let origin = Location::dummy("foo");
1062        GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
1063            .now_or_never()
1064            .unwrap()
1065            .unwrap();
1066        let entry = map.entry(cond);
1067        GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
1068            .now_or_never()
1069            .unwrap()
1070            .unwrap();
1071
1072        let result = map
1073            .get_mut(&cond)
1074            .unwrap()
1075            .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1076            .now_or_never()
1077            .unwrap();
1078        assert_eq!(result, Ok(()));
1079        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1080        assert_eq!(
1081            map[&cond].current_state(),
1082            &TrapState {
1083                action: Action::Ignore,
1084                origin: Origin::User(origin),
1085                pending: false
1086            }
1087        );
1088        assert_eq!(map[&cond].parent_state(), None);
1089        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
1090    }
1091
1092    #[test]
1093    fn enter_subshell_with_command_and_no_internal_disposition() {
1094        let system = DummySystem::default();
1095        let mut map = BTreeMap::new();
1096        let cond = SIGCHLD.into();
1097        let entry = map.entry(cond);
1098        let origin = Location::dummy("foo");
1099        let action = Action::Command("echo".into());
1100        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1101            .now_or_never()
1102            .unwrap()
1103            .unwrap();
1104
1105        let result = map
1106            .get_mut(&cond)
1107            .unwrap()
1108            .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1109            .now_or_never()
1110            .unwrap();
1111        assert_eq!(result, Ok(()));
1112        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1113        assert_eq!(
1114            map[&cond].current_state(),
1115            &TrapState {
1116                action: Action::Default,
1117                origin: Origin::Subshell,
1118                pending: false
1119            }
1120        );
1121        assert_eq!(
1122            map[&cond].parent_state(),
1123            Some(&TrapState {
1124                action,
1125                origin: Origin::User(origin),
1126                pending: false
1127            })
1128        );
1129        assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
1130    }
1131
1132    #[test]
1133    fn enter_subshell_with_command_keeping_internal_disposition() {
1134        let system = DummySystem::default();
1135        let mut map = BTreeMap::new();
1136        let cond = SIGTSTP.into();
1137        let entry = map.entry(cond);
1138        let origin = Location::dummy("foo");
1139        let action = Action::Command("echo".into());
1140        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1141            .now_or_never()
1142            .unwrap()
1143            .unwrap();
1144        let entry = map.entry(cond);
1145        GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
1146            .now_or_never()
1147            .unwrap()
1148            .unwrap();
1149
1150        let result = map
1151            .get_mut(&cond)
1152            .unwrap()
1153            .enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
1154            .now_or_never()
1155            .unwrap();
1156        assert_eq!(result, Ok(()));
1157        assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
1158        assert_eq!(
1159            map[&cond].current_state(),
1160            &TrapState {
1161                action: Action::Default,
1162                origin: Origin::Subshell,
1163                pending: false
1164            }
1165        );
1166        assert_eq!(
1167            map[&cond].parent_state(),
1168            Some(&TrapState {
1169                action,
1170                origin: Origin::User(origin),
1171                pending: false
1172            })
1173        );
1174        assert_eq!(system.0.borrow()[&SIGTSTP], Disposition::Ignore);
1175    }
1176
1177    #[test]
1178    fn enter_subshell_with_command_clearing_internal_disposition() {
1179        let system = DummySystem::default();
1180        let mut map = BTreeMap::new();
1181        let cond = SIGTSTP.into();
1182        let entry = map.entry(cond);
1183        let origin = Location::dummy("foo");
1184        let action = Action::Command("echo".into());
1185        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1186            .now_or_never()
1187            .unwrap()
1188            .unwrap();
1189        let entry = map.entry(cond);
1190        GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
1191            .now_or_never()
1192            .unwrap()
1193            .unwrap();
1194
1195        let result = map
1196            .get_mut(&cond)
1197            .unwrap()
1198            .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1199            .now_or_never()
1200            .unwrap();
1201        assert_eq!(result, Ok(()));
1202        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1203        assert_eq!(
1204            map[&cond].current_state(),
1205            &TrapState {
1206                action: Action::Default,
1207                origin: Origin::Subshell,
1208                pending: false
1209            }
1210        );
1211        assert_eq!(
1212            map[&cond].parent_state(),
1213            Some(&TrapState {
1214                action,
1215                origin: Origin::User(origin),
1216                pending: false
1217            })
1218        );
1219        assert_eq!(system.0.borrow()[&SIGTSTP], Disposition::Default);
1220    }
1221
1222    #[test]
1223    fn enter_subshell_with_command_ignoring() {
1224        let system = DummySystem::default();
1225        let mut map = BTreeMap::new();
1226        let cond = SIGQUIT.into();
1227        let entry = map.entry(cond);
1228        let origin = Location::dummy("foo");
1229        let action = Action::Command("echo".into());
1230        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1231            .now_or_never()
1232            .unwrap()
1233            .unwrap();
1234
1235        let result = map
1236            .get_mut(&cond)
1237            .unwrap()
1238            .enter_subshell(&system, cond, EnterSubshellOption::Ignore)
1239            .now_or_never()
1240            .unwrap();
1241        assert_eq!(result, Ok(()));
1242        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1243        assert_eq!(
1244            map[&cond].current_state(),
1245            &TrapState {
1246                action: Action::Ignore,
1247                origin: Origin::Subshell,
1248                pending: false
1249            }
1250        );
1251        assert_eq!(
1252            map[&cond].parent_state(),
1253            Some(&TrapState {
1254                action,
1255                origin: Origin::User(origin),
1256                pending: false
1257            })
1258        );
1259        assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
1260    }
1261
1262    #[test]
1263    fn ignoring_initially_defaulted_signal() {
1264        let system = DummySystem::default();
1265        let mut map = BTreeMap::new();
1266        let cond = SIGQUIT.into();
1267        let entry = map.entry(cond);
1268        let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
1269
1270        let result = GrandState::ignore(&system, vacant).now_or_never().unwrap();
1271        assert_eq!(result, Ok(()));
1272        assert_eq!(
1273            map[&cond].current_state(),
1274            &TrapState {
1275                action: Action::Ignore,
1276                origin: Origin::Subshell,
1277                pending: false
1278            }
1279        );
1280        assert_eq!(map[&cond].parent_state(), None);
1281        assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
1282
1283        let entry = map.entry(cond);
1284        let origin = Location::dummy("foo");
1285        let action = Action::Command("echo".into());
1286        let result = GrandState::set_action(&system, entry, action, origin, false)
1287            .now_or_never()
1288            .unwrap();
1289        assert_eq!(result, Ok(()));
1290    }
1291
1292    #[test]
1293    fn ignoring_initially_ignored_signal() {
1294        let system = DummySystem::default();
1295        system.0.borrow_mut().insert(SIGQUIT, Disposition::Ignore);
1296        let mut map = BTreeMap::new();
1297        let cond = SIGQUIT.into();
1298        let entry = map.entry(cond);
1299        let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
1300
1301        let result = GrandState::ignore(&system, vacant).now_or_never().unwrap();
1302        assert_eq!(result, Ok(()));
1303        assert_eq!(
1304            map[&cond].current_state(),
1305            &TrapState {
1306                action: Action::Ignore,
1307                origin: Origin::Inherited,
1308                pending: false
1309            }
1310        );
1311        assert_eq!(map[&cond].parent_state(), None);
1312        assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
1313
1314        let entry = map.entry(cond);
1315        let origin = Location::dummy("foo");
1316        let action = Action::Command("echo".into());
1317        let result = GrandState::set_action(&system, entry, action, origin, false)
1318            .now_or_never()
1319            .unwrap();
1320        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
1321    }
1322
1323    #[test]
1324    fn clearing_parent_setting() {
1325        let system = DummySystem::default();
1326        let mut map = BTreeMap::new();
1327        let cond = SIGCHLD.into();
1328        let entry = map.entry(cond);
1329        let origin = Location::dummy("foo");
1330        let action = Action::Command("echo".into());
1331        GrandState::set_action(&system, entry, action, origin, false)
1332            .now_or_never()
1333            .unwrap()
1334            .unwrap();
1335        let state = map.get_mut(&cond).unwrap();
1336        state
1337            .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1338            .now_or_never()
1339            .unwrap()
1340            .unwrap();
1341
1342        state.clear_parent_state();
1343        assert_eq!(
1344            state.current_state(),
1345            &TrapState {
1346                action: Action::Default,
1347                origin: Origin::Subshell,
1348                pending: false
1349            }
1350        );
1351        assert_eq!(state.parent_state(), None);
1352    }
1353
1354    #[test]
1355    fn marking_as_caught_and_handling() {
1356        let system = DummySystem::default();
1357        let mut map = BTreeMap::new();
1358        let cond = SIGUSR1.into();
1359        let entry = map.entry(cond);
1360        let origin = Location::dummy("foo");
1361        let action = Action::Command("echo".into());
1362        GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1363            .now_or_never()
1364            .unwrap()
1365            .unwrap();
1366
1367        let state = &mut map.get_mut(&cond).unwrap();
1368        state.mark_as_caught();
1369        let expected_trap = TrapState {
1370            action,
1371            origin: Origin::User(origin),
1372            pending: true,
1373        };
1374        assert_eq!(state.current_state(), &expected_trap);
1375        assert_eq!(state.parent_state(), None);
1376
1377        let trap = state.handle_if_caught();
1378        let expected_trap = TrapState {
1379            pending: false,
1380            ..expected_trap
1381        };
1382        assert_eq!(trap, Some(&expected_trap));
1383
1384        let trap = state.handle_if_caught();
1385        assert_eq!(trap, None);
1386    }
1387}