#![cfg(all(feature = "embed", feature = "test-support", feature = "unix-runtime"))]
use mxsh::ShellBuilder;
use mxsh::embed::StdioConfig;
use mxsh::policy::ShellOptions;
use mxsh::runtime::process::{ProcessEvent, RuntimeSignal};
use mxsh::runtime::testing::{DeterministicRuntime, StringStdioOut};
mod support;
use support::{fresh_state, fresh_state_with_builder, run_script};
#[test]
fn background_job_semantics_are_non_blocking() {
let stdout = StringStdioOut::new();
let mut state = fresh_state_with_builder(ShellBuilder::new().stdio(StdioConfig {
stdout: stdout.fd(),
..StdioConfig::default()
}));
let mut runtime = DeterministicRuntime::new();
runtime.push_spawn(Some(1234), [], []);
let status = run_script("sleep 1 & echo ready\n", &mut state, &mut runtime);
assert_eq!(status, 0);
assert_eq!(stdout.collect().trim(), "ready");
assert_eq!(state.last_background_pid(), Some(1234));
}
#[test]
fn jobs_builtin_formats_deterministic_runtime_state() {
let stdout = StringStdioOut::new();
let mut state = fresh_state_with_builder(ShellBuilder::new().stdio(StdioConfig {
stdout: stdout.fd(),
..StdioConfig::default()
}));
let mut runtime = DeterministicRuntime::new();
runtime.push_spawn(Some(1111), [], []);
let status = run_script(
"sleep 1 &\njobs\njobs -p\njobs -l %1\n",
&mut state,
&mut runtime,
);
assert_eq!(status, 0);
let output = stdout.collect();
assert!(output.contains("[1] Running 1111"), "{output:?}");
assert!(
output.lines().any(|line| line.trim() == "1111"),
"{output:?}"
);
assert!(
output.lines().any(|line| line.trim() == "[1] 1111 Running"),
"{output:?}"
);
}
#[test]
fn wait_accepts_pid_and_job_spec() {
let mut state = fresh_state();
let mut runtime = DeterministicRuntime::new();
runtime.push_spawn(Some(2222), [], [ProcessEvent::Exited(0)]);
let status = run_script("sleep 1 & p=$!; wait $p\n", &mut state, &mut runtime);
assert_eq!(status, 0);
let mut state = fresh_state();
let mut runtime = DeterministicRuntime::new();
runtime.push_spawn(Some(3333), [], [ProcessEvent::Exited(7)]);
let status = run_script("sleep 1 & wait %1\n", &mut state, &mut runtime);
assert_eq!(status, 7);
assert_eq!(state.last_status(), 7);
let mut state = fresh_state();
let mut runtime = DeterministicRuntime::new();
runtime.push_spawn(Some(4444), [], [ProcessEvent::Exited(-1)]);
let status = run_script("sleep 1 & wait %1\n", &mut state, &mut runtime);
assert_eq!(status, 255);
assert_eq!(state.last_status(), 255);
}
#[test]
fn fg_resumes_job_and_waits_for_exit() {
let mut state = fresh_state();
let mut runtime = DeterministicRuntime::new();
let handle = runtime.push_spawn(
Some(4242),
[ProcessEvent::Stopped(libc::SIGSTOP)],
[ProcessEvent::Exited(7)],
);
let status = run_script("sleep 1 & fg %1\n", &mut state, &mut runtime);
assert_eq!(status, 7);
assert_eq!(
runtime.recorded_signals(),
&[(handle, RuntimeSignal::Continue)]
);
}
#[test]
fn fg_with_monitor_mode_claims_and_releases_foreground() {
let mut options = ShellOptions::default();
options.insert(ShellOptions::MONITOR);
let mut state = fresh_state_with_builder(ShellBuilder::new().options(options));
let mut runtime = DeterministicRuntime::new();
let handle = runtime.push_spawn(Some(5151), [], [ProcessEvent::Exited(0)]);
let status = run_script("sleep 1 & fg %1\n", &mut state, &mut runtime);
assert_eq!(status, 0);
assert_eq!(
runtime.foreground_claims(),
&[(handle, state.stdio().stdin)]
);
assert_eq!(runtime.foreground_releases(), 1);
}
#[test]
fn stopped_foreground_pipeline_is_recorded_as_job() {
let stdout = StringStdioOut::new();
let mut options = ShellOptions::default();
options.insert(ShellOptions::MONITOR);
let mut state = fresh_state_with_builder(
ShellBuilder::new()
.interactive(true)
.options(options)
.stdio(StdioConfig {
stdout: stdout.fd(),
..StdioConfig::default()
}),
);
let mut runtime = DeterministicRuntime::new();
let leader = runtime.push_spawn(Some(6161), [], [ProcessEvent::Stopped(libc::SIGTSTP)]);
runtime.push_spawn(Some(6162), [], [ProcessEvent::Stopped(libc::SIGTSTP)]);
let status = run_script("left | right\njobs\n", &mut state, &mut runtime);
assert_eq!(status, 0);
assert_eq!(
runtime.foreground_claims(),
&[(leader, state.stdio().stdin)]
);
assert_eq!(runtime.foreground_releases(), 1);
let output = stdout.collect();
let expected = format!("[1] Stopped({}) 6161", libc::SIGTSTP);
assert!(
output.contains(&expected),
"expected stopped pipeline job {expected:?}, got {output:?}"
);
}