#![cfg(target_os = "windows")]
use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
fn bin_path() -> &'static str {
env!("CARGO_BIN_EXE_runex")
}
fn lsd_reachable_via_registry_path() -> Option<std::path::PathBuf> {
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
use winreg::RegKey;
fn read_path(hive: winreg::HKEY, sub: &str) -> Option<String> {
let k = RegKey::predef(hive).open_subkey(sub).ok()?;
k.get_value::<String, _>("Path").ok()
}
let user_profile = std::env::var("USERPROFILE").unwrap_or_default();
let local_app = std::env::var("LOCALAPPDATA").unwrap_or_default();
let mut sources = Vec::new();
if let Some(v) = read_path(HKEY_CURRENT_USER, "Environment") {
sources.push(v);
}
if let Some(v) = read_path(
HKEY_LOCAL_MACHINE,
r"System\CurrentControlSet\Control\Session Manager\Environment",
) {
sources.push(v);
}
for raw in sources {
for seg in raw.split(';') {
let expanded = seg
.replace("%UserProfile%", &user_profile)
.replace("%USERPROFILE%", &user_profile)
.replace("%LocalAppData%", &local_app)
.replace("%LOCALAPPDATA%", &local_app);
let candidate = std::path::PathBuf::from(&expanded).join("lsd.exe");
if candidate.is_file() {
return Some(std::path::PathBuf::from(&expanded));
}
}
}
None
}
fn write_config_with_ls_to_lsd() -> NamedTempFile {
let mut f = NamedTempFile::new().unwrap();
write!(
f,
"version = 1\n\n[[abbr]]\nkey = \"ls\"\nexpand = \"lsd\"\nwhen_command_exists = [\"lsd\"]\n"
)
.unwrap();
f.flush().unwrap();
f
}
#[test]
fn hook_resolves_user_path_binary_under_minimal_process_path() {
let Some(_lsd_dir) = lsd_reachable_via_registry_path() else {
eprintln!(
"skipping: this machine has no `lsd.exe` reachable via HKCU/HKLM Environment\\Path; \
the test scenario can't be observed here"
);
return;
};
let cfg = write_config_with_ls_to_lsd();
let bin = bin_path();
let minimal_path = r"C:\Windows\System32;C:\Windows";
let out = Command::new(bin)
.args([
"hook", "--shell", "clink", "--line", "ls", "--cursor", "2",
])
.env("PATH", minimal_path)
.env("RUNEX_CONFIG", cfg.path())
.output()
.expect("runex.exe must be runnable");
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let expected_replace_prefix = r#"return { line = "lsd ", cursor = 4 }"#;
let regression_no_op_form = r#"return { line = "ls ", cursor = 3 }"#;
assert!(
!stdout.trim().contains(regression_no_op_form),
"regression: hook fell back to InsertSpace because `lsd` was not \
resolvable in the degraded child PATH. Output: {stdout:?}"
);
assert!(
stdout.trim().starts_with(expected_replace_prefix),
"expected hook to expand `ls` -> `lsd ` (Replace action). Output: {stdout:?}"
);
}