#[cfg(doc)]
use super::TrapSet;
use super::{Condition, SignalSystem};
use crate::system::{Disposition, Errno};
use std::collections::btree_map::{Entry, VacantEntry};
use std::rc::Rc;
use thiserror::Error;
use yash_syntax::source::Location;
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub enum Action {
#[default]
Default,
Ignore,
Command(Rc<str>),
}
impl From<&Action> for Disposition {
fn from(trap: &Action) -> Self {
match trap {
Action::Default => Disposition::Default,
Action::Ignore => Disposition::Ignore,
Action::Command(_) => Disposition::Catch,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum SetActionError {
#[error("the signal has been ignored since startup")]
InitiallyIgnored,
#[error("cannot set a trap for SIGKILL")]
SIGKILL,
#[error("cannot set a trap for SIGSTOP")]
SIGSTOP,
#[error(transparent)]
SystemError(#[from] Errno),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TrapState {
pub action: Action,
pub origin: Location,
pub pending: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Setting {
InitiallyDefaulted,
InitiallyIgnored,
UserSpecified(TrapState),
}
impl Setting {
pub fn as_trap(&self) -> Option<&TrapState> {
if let Setting::UserSpecified(trap) = self {
Some(trap)
} else {
None
}
}
fn is_user_defined_command(&self) -> bool {
matches!(
self,
Setting::UserSpecified(TrapState {
action: Action::Command(_),
..
})
)
}
pub fn from_initial_disposition(disposition: Disposition) -> Self {
match disposition {
Disposition::Default | Disposition::Catch => Self::InitiallyDefaulted,
Disposition::Ignore => Self::InitiallyIgnored,
}
}
}
impl From<&Setting> for Disposition {
fn from(state: &Setting) -> Self {
match state {
Setting::InitiallyDefaulted => Disposition::Default,
Setting::InitiallyIgnored => Disposition::Ignore,
Setting::UserSpecified(trap) => (&trap.action).into(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EnterSubshellOption {
KeepInternalDisposition,
ClearInternalDisposition,
Ignore,
}
#[derive(Clone, Debug)]
pub struct GrandState {
current_setting: Setting,
parent_setting: Option<Setting>,
internal_disposition: Disposition,
}
impl GrandState {
#[must_use]
pub fn get_state(&self) -> (Option<&TrapState>, Option<&TrapState>) {
let current = self.current_setting.as_trap();
let parent = self.parent_setting.as_ref().and_then(Setting::as_trap);
(current, parent)
}
pub fn clear_parent_setting(&mut self) {
self.parent_setting = None;
}
pub fn set_action<S: SignalSystem>(
system: &mut S,
entry: Entry<Condition, GrandState>,
action: Action,
origin: Location,
override_ignore: bool,
) -> Result<(), SetActionError> {
let cond = *entry.key();
let setting = Setting::UserSpecified(TrapState {
action,
origin,
pending: false,
});
let disposition = (&setting).into();
match entry {
Entry::Vacant(vacant) => {
if let Condition::Signal(signal) = cond {
if !override_ignore {
let initial_disposition =
system.set_disposition(signal, Disposition::Ignore)?;
if initial_disposition == Disposition::Ignore {
vacant.insert(GrandState {
current_setting: Setting::InitiallyIgnored,
parent_setting: None,
internal_disposition: Disposition::Default,
});
return Err(SetActionError::InitiallyIgnored);
}
}
if override_ignore || disposition != Disposition::Ignore {
system.set_disposition(signal, disposition)?;
}
}
vacant.insert(GrandState {
current_setting: setting,
parent_setting: None,
internal_disposition: Disposition::Default,
});
}
Entry::Occupied(mut occupied) => {
let state = occupied.get_mut();
if !override_ignore && state.current_setting == Setting::InitiallyIgnored {
return Err(SetActionError::InitiallyIgnored);
}
if let Condition::Signal(signal) = cond {
let internal = state.internal_disposition;
let old_disposition = internal.max((&state.current_setting).into());
let new_disposition = internal.max(disposition);
if old_disposition != new_disposition {
system.set_disposition(signal, new_disposition)?;
}
}
state.current_setting = setting;
}
}
Ok(())
}
#[must_use]
pub fn internal_disposition(&self) -> Disposition {
self.internal_disposition
}
pub fn set_internal_disposition<S: SignalSystem>(
system: &mut S,
entry: Entry<Condition, GrandState>,
disposition: Disposition,
) -> Result<(), Errno> {
let signal = match *entry.key() {
Condition::Signal(signal) => signal,
Condition::Exit => panic!("exit condition cannot have an internal disposition"),
};
match entry {
Entry::Vacant(_) if disposition == Disposition::Default => (),
Entry::Vacant(vacant) => {
let initial_disposition = system.set_disposition(signal, disposition)?;
vacant.insert(GrandState {
current_setting: Setting::from_initial_disposition(initial_disposition),
parent_setting: None,
internal_disposition: disposition,
});
}
Entry::Occupied(mut occupied) => {
let state = occupied.get_mut();
let setting = (&state.current_setting).into();
let old_disposition = state.internal_disposition.max(setting);
let new_disposition = disposition.max(setting);
if old_disposition != new_disposition {
system.set_disposition(signal, new_disposition)?;
}
state.internal_disposition = disposition;
}
}
Ok(())
}
pub fn enter_subshell<S: SignalSystem>(
&mut self,
system: &mut S,
cond: Condition,
option: EnterSubshellOption,
) -> Result<(), Errno> {
let old_setting = (&self.current_setting).into();
let old_disposition = self.internal_disposition.max(old_setting);
if self.current_setting.is_user_defined_command() {
self.parent_setting = Some(std::mem::replace(
&mut self.current_setting,
Setting::InitiallyDefaulted,
));
}
let new_setting = (&self.current_setting).into();
let new_disposition = match option {
EnterSubshellOption::KeepInternalDisposition => {
self.internal_disposition.max(new_setting)
}
EnterSubshellOption::ClearInternalDisposition => new_setting,
EnterSubshellOption::Ignore => Disposition::Ignore,
};
if old_disposition != new_disposition {
if let Condition::Signal(signal) = cond {
system.set_disposition(signal, new_disposition)?;
}
}
self.internal_disposition = match option {
EnterSubshellOption::KeepInternalDisposition => self.internal_disposition,
EnterSubshellOption::ClearInternalDisposition | EnterSubshellOption::Ignore => {
Disposition::Default
}
};
Ok(())
}
pub fn ignore<S: SignalSystem>(
system: &mut S,
vacant: VacantEntry<Condition, GrandState>,
) -> Result<(), Errno> {
let signal = match *vacant.key() {
Condition::Signal(signal) => signal,
Condition::Exit => panic!("exit condition cannot be ignored"),
};
let initial_disposition = system.set_disposition(signal, Disposition::Ignore)?;
vacant.insert(GrandState {
current_setting: Setting::from_initial_disposition(initial_disposition),
parent_setting: None,
internal_disposition: Disposition::Default,
});
Ok(())
}
pub fn mark_as_caught(&mut self) {
if let Setting::UserSpecified(state) = &mut self.current_setting {
state.pending = true;
}
}
pub fn handle_if_caught(&mut self) -> Option<&TrapState> {
match &mut self.current_setting {
Setting::UserSpecified(trap) if trap.pending => {
trap.pending = false;
Some(trap)
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::super::tests::DummySystem;
use super::*;
use crate::system::r#virtual::{SIGCHLD, SIGQUIT, SIGTSTP, SIGTTOU, SIGUSR1};
use assert_matches::assert_matches;
use std::collections::BTreeMap;
#[test]
fn setting_trap_to_ignore_without_override_ignore() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let result =
GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].get_state(),
(
Some(&TrapState {
action: Action::Ignore,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn setting_trap_to_ignore_with_override_ignore() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let result =
GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].get_state(),
(
Some(&TrapState {
action: Action::Ignore,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn setting_trap_to_command() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let action = Action::Command("echo".into());
let origin = Location::dummy("origin");
let result =
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].get_state(),
(
Some(&TrapState {
action,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
}
#[test]
fn setting_trap_to_default() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("foo");
GrandState::set_action(&mut system, entry, Action::Ignore, origin, false).unwrap();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("bar");
let result =
GrandState::set_action(&mut system, entry, Action::Default, origin.clone(), false);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].get_state(),
(
Some(&TrapState {
action: Action::Default,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Default);
}
#[test]
fn resetting_trap_from_ignore_no_override() {
let mut system = DummySystem::default();
system.0.insert(SIGCHLD, Disposition::Ignore);
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("foo");
let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("bar");
let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
assert_eq!(map[&SIGCHLD.into()].get_state(), (None, None));
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn resetting_trap_from_ignore_override() {
let mut system = DummySystem::default();
system.0.insert(SIGCHLD, Disposition::Ignore);
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let result =
GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].get_state(),
(
Some(&TrapState {
action: Action::Ignore,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn internal_disposition_ignore() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Ignore
);
assert_eq!(map[&SIGCHLD.into()].get_state(), (None, None));
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn internal_disposition_catch() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Catch
);
assert_eq!(map[&SIGCHLD.into()].get_state(), (None, None));
assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
}
#[test]
fn action_ignore_and_internal_disposition_catch() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let _ = GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Catch
);
assert_matches!(map[&SIGCHLD.into()].get_state(), (Some(state), None) => {
assert_eq!(state.action, Action::Ignore);
assert_eq!(state.origin, origin);
});
assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
}
#[test]
fn action_catch_and_internal_disposition_ignore() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
let _ = GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Ignore
);
assert_matches!(map[&SIGCHLD.into()].get_state(), (Some(state), None) => {
assert_eq!(state.action, action);
assert_eq!(state.origin, origin);
});
assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
}
#[test]
fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGTTOU.into());
let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
let entry = map.entry(SIGTTOU.into());
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
let result =
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGTTOU.into()].internal_disposition(),
Disposition::Ignore
);
assert_eq!(
map[&SIGTTOU.into()].get_state(),
(
Some(&TrapState {
action,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGTTOU], Disposition::Catch);
}
#[test]
fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
let mut system = DummySystem::default();
system.0.insert(SIGTTOU, Disposition::Ignore);
let mut map = BTreeMap::new();
let cond = SIGTTOU.into();
let entry = map.entry(cond);
let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
let entry = map.entry(cond);
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&mut system, entry, action, origin, false);
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
assert_eq!(map[&cond].get_state(), (None, None));
assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
GrandState::set_internal_disposition(&mut system, map.entry(cond), Disposition::Catch)
.unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::KeepInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
assert_eq!(map[&cond].get_state(), (None, None));
assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
}
#[test]
fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::ClearInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Default
);
assert_eq!(map[&cond].get_state(), (None, None));
assert_eq!(system.0[&SIGCHLD], Disposition::Default);
}
#[test]
fn enter_subshell_with_ignore_and_no_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::KeepInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].get_state(),
(
Some(&TrapState {
action: Action::Ignore,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_ignore_clearing_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::ClearInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].get_state(),
(
Some(&TrapState {
action: Action::Ignore,
origin,
pending: false
}),
None
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_command_and_no_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::ClearInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].get_state(),
(
None,
Some(&TrapState {
action,
origin,
pending: false
}),
)
);
assert_eq!(system.0[&SIGCHLD], Disposition::Default);
}
#[test]
fn enter_subshell_with_command_keeping_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGTSTP.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::KeepInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
assert_eq!(
map[&cond].get_state(),
(
None,
Some(&TrapState {
action,
origin,
pending: false
}),
)
);
assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_command_clearing_internal_disposition() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGTSTP.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::ClearInternalDisposition,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].get_state(),
(
None,
Some(&TrapState {
action,
origin,
pending: false
}),
)
);
assert_eq!(system.0[&SIGTSTP], Disposition::Default);
}
#[test]
fn enter_subshell_with_command_ignoring() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGQUIT.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
let result = map.get_mut(&cond).unwrap().enter_subshell(
&mut system,
cond,
EnterSubshellOption::Ignore,
);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].get_state(),
(
None,
Some(&TrapState {
action,
origin,
pending: false
}),
)
);
assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
}
#[test]
fn ignoring_initially_defaulted_signal() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGQUIT.into();
let entry = map.entry(cond);
let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
let result = GrandState::ignore(&mut system, vacant);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].get_state(), (None, None));
assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&mut system, entry, action, origin, false);
assert_eq!(result, Ok(()));
}
#[test]
fn ignoring_initially_ignored_signal() {
let mut system = DummySystem::default();
system.0.insert(SIGQUIT, Disposition::Ignore);
let mut map = BTreeMap::new();
let cond = SIGQUIT.into();
let entry = map.entry(cond);
let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
let result = GrandState::ignore(&mut system, vacant);
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].get_state(), (None, None));
assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&mut system, entry, action, origin, false);
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
}
#[test]
fn clearing_parent_setting() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&mut system, entry, action, origin, false).unwrap();
let state = map.get_mut(&cond).unwrap();
state
.enter_subshell(
&mut system,
cond,
EnterSubshellOption::ClearInternalDisposition,
)
.unwrap();
state.clear_parent_setting();
assert_eq!(state.get_state(), (None, None));
}
#[test]
fn marking_as_caught_and_handling() {
let mut system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGUSR1.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
let state = &mut map.get_mut(&cond).unwrap();
state.mark_as_caught();
let expected_trap = TrapState {
action,
origin,
pending: true,
};
assert_eq!(state.get_state(), (Some(&expected_trap), None));
let trap = state.handle_if_caught();
let expected_trap = TrapState {
pending: false,
..expected_trap
};
assert_eq!(trap, Some(&expected_trap));
let trap = state.handle_if_caught();
assert_eq!(trap, None);
}
}