#![cfg(unix)]
use std::sync::Arc;
use filetime::FileTime;
use harn_hostlib::process::{install_spawner, MockProcessConfig, MockSpawner};
use harn_hostlib::tools::ToolsCapability;
use harn_hostlib::{BuiltinRegistry, HostlibCapability, HostlibError};
use harn_vm::VmValue;
use tempfile::tempdir;
fn registry() -> BuiltinRegistry {
let mut registry = BuiltinRegistry::new();
ToolsCapability.register_builtins(&mut registry);
registry
}
fn call(builtin: &str, request: harn_vm::value::DictMap) -> Result<VmValue, HostlibError> {
harn_hostlib::tools::permissions::enable_for_test();
let registry = registry();
let entry = registry
.find(builtin)
.unwrap_or_else(|| panic!("builtin {builtin} not registered"));
(entry.handler)(&[VmValue::dict(request)])
}
fn dict() -> harn_vm::value::DictMap {
harn_vm::value::DictMap::new()
}
fn vstr(value: &str) -> VmValue {
VmValue::String(arcstr::ArcStr::from(value))
}
fn vlist_str(values: &[&str]) -> VmValue {
VmValue::List(Arc::new(values.iter().map(|s| vstr(s)).collect()))
}
fn require_dict(value: VmValue) -> harn_vm::value::DictMap {
match value {
VmValue::Dict(map) => (*map).clone(),
other => panic!("expected dict response, got {other:?}"),
}
}
fn require_str(map: &harn_vm::value::DictMap, key: &str) -> String {
match map.get(key) {
Some(VmValue::String(s)) => s.to_string(),
other => panic!("expected string at {key}, got {other:?}"),
}
}
fn unused_high_pid() -> u32 {
(900_000..=999_999)
.find(|pid| !process_is_alive(*pid))
.expect("test host should have an unused high pid")
}
fn process_is_alive(pid: u32) -> bool {
extern "C" {
fn kill(pid: i32, sig: i32) -> i32;
}
(unsafe { kill(pid as i32, 0) }) == 0
}
struct TmpdirEnvGuard(Option<std::ffi::OsString>);
impl Drop for TmpdirEnvGuard {
fn drop(&mut self) {
match self.0.as_ref() {
Some(value) => std::env::set_var("TMPDIR", value),
None => std::env::remove_var("TMPDIR"),
}
}
}
#[test]
fn command_creation_sweeps_stale_sibling_and_keeps_fresh_output_readable() {
let temp = tempdir().unwrap();
let _tmpdir_guard = TmpdirEnvGuard(std::env::var_os("TMPDIR"));
std::env::set_var("TMPDIR", temp.path());
let stale = temp
.path()
.join(format!("harn-command-cmd_{}_100_1", unused_high_pid()));
std::fs::create_dir(&stale).unwrap();
std::fs::write(stale.join("combined.txt"), "stale").unwrap();
let old = FileTime::from_unix_time(0, 0);
filetime::set_file_mtime(&stale, old).unwrap();
let spawner = Arc::new(MockSpawner::new());
let _guard = install_spawner(spawner.clone());
spawner.enqueue(MockProcessConfig::with_stdout(0, "fresh\n"));
let mut run_req = dict();
run_req.insert("argv".into(), vlist_str(&["bash", "-c", "echo fresh"]));
let run_resp = require_dict(call("hostlib_tools_run_command", run_req).unwrap());
assert!(!stale.exists());
let mut read_req = dict();
read_req.insert(
"command_id".into(),
vstr(&require_str(&run_resp, "command_id")),
);
let read_resp = require_dict(call("hostlib_tools_read_command_output", read_req).unwrap());
assert_eq!(require_str(&read_resp, "content"), "fresh\n");
}