#![cfg(all(feature = "embed", feature = "frontend", feature = "test-support"))]
use std::fs;
#[cfg(feature = "cli")]
use std::os::unix::fs::symlink;
#[cfg(feature = "cli")]
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "cli")]
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
use mxsh::ShellBuilder;
use mxsh::embed::StdioConfig;
use mxsh::policy::{StartupPolicy, VariableAttributes};
use mxsh::runtime::testing::{InMemoryRuntime, StringStdioIn, StringStdioOut};
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)
))
}
#[cfg(feature = "cli")]
fn mxsh_command() -> Command {
Command::new(env!("CARGO_BIN_EXE_mxsh"))
}
#[cfg(feature = "cli")]
fn real_cwd_fixture(label: &str) -> (PathBuf, PathBuf) {
let root = temp_path(label);
let real = root.join("real");
fs::create_dir_all(&real).expect("real cwd should be creatable");
(root, real)
}
#[cfg(feature = "cli")]
fn mxsh_with_pwd(current_dir: &Path, pwd: &Path) -> Command {
let mut command = mxsh_command();
command.current_dir(current_dir).env("PWD", pwd);
command
}
#[test]
#[cfg(feature = "cli")]
fn startup_rejects_inherited_pwd_that_does_not_name_process_cwd() {
let (root, real) = real_cwd_fixture("stale-startup-pwd");
let stale = root.join("stale");
fs::create_dir_all(&stale).expect("stale cwd should be creatable");
let output = mxsh_with_pwd(&real, &stale)
.arg("-c")
.arg(
"printf 'env=<%s>\\n' \"$PWD\"; \
printf 'builtin=<%s>\\n' \"$(pwd)\"; \
printf 'external=<%s>\\n' \"$(/bin/pwd)\"; \
printf redirected >relative.out",
)
.output()
.expect("mxsh should run");
let expected_cwd = fs::canonicalize(&real).expect("real cwd should canonicalize");
let expected = expected_cwd.display();
assert_eq!(output.status.code(), Some(0));
assert_eq!(
String::from_utf8_lossy(&output.stdout),
format!("env=<{expected}>\nbuiltin=<{expected}>\nexternal=<{expected}>\n")
);
assert_eq!(
fs::read_to_string(real.join("relative.out")).expect("redirected output in real cwd"),
"redirected"
);
assert!(
!stale.join("relative.out").exists(),
"relative redirection should not use stale inherited PWD"
);
let _ = fs::remove_dir_all(root);
}
#[test]
#[cfg(feature = "cli")]
fn startup_uses_validated_cwd_for_script_file_lookup() {
let (root, real) = real_cwd_fixture("script-startup-pwd");
let stale = root.join("stale");
fs::create_dir_all(&stale).expect("stale cwd should be creatable");
fs::write(real.join("script.sh"), "echo real\n").expect("real script should be writable");
fs::write(stale.join("script.sh"), "echo stale\n").expect("stale script should be writable");
let output = mxsh_with_pwd(&real, &stale)
.arg("script.sh")
.output()
.expect("mxsh should run script");
assert_eq!(output.status.code(), Some(0));
assert_eq!(String::from_utf8_lossy(&output.stdout), "real\n");
assert_eq!(String::from_utf8_lossy(&output.stderr), "");
let _ = fs::remove_dir_all(root);
}
#[test]
#[cfg(feature = "cli")]
fn startup_preserves_inherited_pwd_when_it_resolves_to_process_cwd() {
let (root, real) = real_cwd_fixture("symlink-startup-pwd");
let logical = root.join("logical");
symlink(&real, &logical).expect("logical cwd symlink should be creatable");
let output = mxsh_with_pwd(&real, &logical)
.arg("-c")
.arg("printf 'env=<%s>\\n' \"$PWD\"; printf 'builtin=<%s>\\n' \"$(pwd)\"")
.output()
.expect("mxsh should run");
let expected = logical.display();
assert_eq!(output.status.code(), Some(0));
assert_eq!(
String::from_utf8_lossy(&output.stdout),
format!("env=<{expected}>\nbuiltin=<{expected}>\n")
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn explicit_none_startup_policy_disables_cli_env_hook_defaults() {
let input = StringStdioIn::new("helper\nexit\n");
let stdout = StringStdioOut::new();
let stderr = StringStdioOut::new();
let env_path = temp_path("cli-explicit-startup-policy");
fs::write(&env_path, "helper() { echo from_env; }\n").expect("write ENV helper");
let argv = vec!["mxsh".to_string()];
let mut shell = ShellBuilder::new()
.interactive(true)
.startup_policy(StartupPolicy::None)
.clear_inherited_env()
.env(
"ENV",
env_path.display().to_string(),
VariableAttributes::empty(),
)
.env("PS1", "", VariableAttributes::empty())
.env("PS2", "", VariableAttributes::empty())
.stdio(StdioConfig {
stdin: input.fd(),
stdout: stdout.fd(),
stderr: stderr.fd(),
})
.build(InMemoryRuntime::new())
.expect("shell should build");
let outcome = shell.run_cli(&argv);
input.join();
let _ = fs::remove_file(&env_path);
assert_eq!(outcome.status, 127);
assert_eq!(outcome.exit_code, Some(127));
assert_eq!(stdout.collect(), "");
assert!(stderr.collect().contains("helper: command not found"));
}