#![cfg(all(feature = "embed", feature = "test-support"))]
#![allow(dead_code)]
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use mxsh::ShellBuilder;
use mxsh::advanced::SessionState;
use mxsh::ast::Program;
use mxsh::runtime::Runtime;
pub const READ_VAR_NAMES: &[&str] = &["X", "Y", "Z"];
pub fn fresh_state() -> SessionState {
ShellBuilder::new()
.new_session()
.expect("session should build")
}
pub fn fresh_state_with_builder(builder: ShellBuilder) -> SessionState {
builder.new_session().expect("session should build")
}
pub fn run_script<R: Runtime>(script: &str, state: &mut SessionState, runtime: &mut R) -> i32 {
let program = Program::parse(script).expect("script should parse");
state.run_program(runtime, &program).status
}
pub fn temp_path(label: &str) -> PathBuf {
static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
std::env::temp_dir().join(format!(
"mxsh-{label}-{}-{}",
std::process::id(),
NEXT_ID.fetch_add(1, Ordering::Relaxed)
))
}
pub fn shell_quote(s: &str) -> String {
format!("'{}'", s.replace('\'', "'\\''"))
}
pub fn shell_program(program: &str) -> &str {
if program == "mxsh" {
env!("CARGO_BIN_EXE_mxsh")
} else {
program
}
}
fn spawn_shell(program: &str, args: &[&str], stdin: &str) -> std::process::Child {
let mut child = Command::new(shell_program(program))
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("failed to spawn {program}: {err}"));
child
.stdin
.take()
.expect("stdin must be piped")
.write_all(stdin.as_bytes())
.unwrap_or_else(|err| panic!("failed to write stdin for {program}: {err}"));
child
}
pub fn run_shell(program: &str, args: &[&str], stdin: &str) -> Output {
spawn_shell(program, args, stdin)
.wait_with_output()
.unwrap_or_else(|err| panic!("failed to wait for {program}: {err}"))
}
pub fn run_shell_with_timeout(
program: &str,
args: &[&str],
stdin: &str,
timeout: Duration,
) -> Output {
let child = spawn_shell(program, args, stdin);
let pid = child.id();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let result = child.wait_with_output();
let _ = tx.send(result);
});
match rx.recv_timeout(timeout) {
Ok(Ok(output)) => output,
Ok(Err(err)) => panic!("failed to wait for {program}: {err}"),
Err(mpsc::RecvTimeoutError::Timeout) => {
unsafe {
libc::kill(pid as i32, libc::SIGKILL);
}
match rx.recv() {
Ok(Ok(output)) => panic!(
"{program} timed out after {:?} with stderr={:?}",
timeout,
String::from_utf8_lossy(&output.stderr),
),
Ok(Err(err)) => panic!("failed to collect timed out {program}: {err}"),
Err(err) => panic!("failed to receive timed out {program} output: {err}"),
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
panic!("wait thread disconnected for {program}")
}
}
}
pub fn run_shell_script(program: &str, script: &str) -> Output {
run_shell(program, &["-s"], script)
}
pub fn semantic_output(output: &Output) -> (i32, String) {
(
output.status.code().unwrap_or(-1),
String::from_utf8_lossy(&output.stdout).into_owned(),
)
}
pub fn read_var_names(var_count: usize) -> &'static [&'static str] {
&READ_VAR_NAMES[..var_count]
}
pub fn append_read_command(script: &mut String, ifs: &str, raw_mode: bool, var_names: &[&str]) {
script.push_str("IFS=");
script.push_str(&shell_quote(ifs));
script.push_str("; read");
if raw_mode {
script.push_str(" -r");
}
for name in var_names {
script.push(' ');
script.push_str(name);
}
}
pub fn append_read_results(script: &mut String, var_names: &[&str]) {
script.push_str("; printf '%s\\n' \"$?\"");
for name in var_names {
script.push_str("; if [ \"${");
script.push_str(name);
script.push_str("+set}\" = set ]; then printf 'set:%s\\n' \"$");
script.push_str(name);
script.push_str("\"; else printf 'unset\\n'; fi");
}
}