Skip to main content

term_session_mock/
lib.rs

1//! Shared test helpers for the `term-session-mock` binary.
2//!
3//! Every test suite that needs the mock binary (the session server integration
4//! tests, the daemon tests, the PTY engine unit tests) resolves it through the
5//! single canonical [`get_mock_bin`] helper instead of re-implementing
6//! `current_exe()` path-walking in each crate. Keeping the resolution logic in
7//! the mock's own library means there is exactly one place that knows where the
8//! binary lives and how to find it on every platform.
9
10use std::path::PathBuf;
11
12/// Locate (building on demand if necessary) the compiled `term-session-mock`
13/// binary.
14///
15/// Resolution order:
16/// 1. `CARGO_BIN_EXE_term-session-mock`, which Cargo sets when this crate's
17///    *binary* is a dependency (e.g. for this crate's own integration tests).
18/// 2. The plain workspace build location: `target/debug/term-session-mock`
19///    (walking up from the test executable, skipping the `deps` directory).
20/// 3. The hashed dependency build location: `target/debug/deps/term-session-mock-*`.
21/// 4. If none exist, run `cargo build` to produce the binary, then resolve
22///    again.
23///
24/// Never returns a missing path: the binary is built on demand so tests can
25/// never silently skip. Panics if the build fails.
26pub fn get_mock_bin() -> PathBuf {
27    if let Ok(path) = std::env::var("CARGO_BIN_EXE_term-session-mock") {
28        return PathBuf::from(path);
29    }
30
31    let resolved = resolve_mock_bin();
32    if let Some(path) = resolved {
33        return path;
34    }
35
36    build_mock_bin();
37
38    match resolve_mock_bin() {
39        Some(path) => path,
40        None => panic!(
41            "term-session-mock binary still missing after `cargo build`; \
42             searched {:?} and {:?}",
43            mock_bin_candidates().0,
44            mock_bin_candidates().1,
45        ),
46    }
47}
48
49/// The two conventional locations for the compiled mock binary.
50fn mock_bin_candidates() -> (PathBuf, PathBuf) {
51    let mut path = std::env::current_exe().expect("test exe path");
52    path.pop();
53    if path.ends_with("deps") {
54        path.pop();
55    }
56    let plain = path.join(format!("term-session-mock{}", std::env::consts::EXE_SUFFIX));
57    let deps_dir = path.join("deps");
58    (plain, deps_dir)
59}
60
61fn resolve_mock_bin() -> Option<PathBuf> {
62    let (plain, deps_dir) = mock_bin_candidates();
63    if plain.exists() {
64        return Some(plain);
65    }
66
67    let suffix = std::env::consts::EXE_SUFFIX;
68    if let Ok(entries) = std::fs::read_dir(&deps_dir) {
69        for entry in entries.flatten() {
70            let name = entry.file_name();
71            let name = name.to_string_lossy();
72            if name.starts_with("term-session-mock-") && name.ends_with(suffix) {
73                return Some(entry.path());
74            }
75        }
76    }
77
78    None
79}
80
81/// Invoke `cargo build` for this crate so the binary exists in the target
82/// directory. Uses the crate's own `Cargo.toml` so it works regardless of
83/// which workspace directory the test process happens to run from.
84fn build_mock_bin() {
85    let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");
86    let status = std::process::Command::new(env!("CARGO"))
87        .arg("build")
88        .arg("--manifest-path")
89        .arg(&manifest)
90        .status()
91        .expect("failed to spawn `cargo build` for term-session-mock");
92    if !status.success() {
93        panic!(
94            "`cargo build --manifest-path {}` failed with {status}",
95            manifest.display()
96        );
97    }
98}
99
100// TODO: The following `process_is_alive` utils could be migrated somewhere else.
101// Here's an issue/comment with ideas: https://github.com/jzombie/term-wm/issues/204#issuecomment-5170285844
102
103/// Exit code for `check_pid` when the process is alive.
104pub const CHECK_PID_ALIVE: i32 = 0;
105/// Exit code for `check_pid` when the process is not running.
106pub const CHECK_PID_DEAD: i32 = 1;
107
108/// Whether a process with the given OS PID is currently running.
109#[cfg(windows)]
110pub fn process_is_alive(pid: u32) -> bool {
111    use windows_sys::Win32::Foundation::{CloseHandle, STILL_ACTIVE};
112    use windows_sys::Win32::System::Threading::{
113        GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
114    };
115
116    unsafe {
117        let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
118        if handle.is_null() {
119            return false;
120        }
121        let mut code: u32 = 0;
122        let ok = GetExitCodeProcess(handle, &mut code);
123        let _ = CloseHandle(handle);
124        ok != 0 && code == STILL_ACTIVE as u32
125    }
126}
127
128/// Whether a process with the given OS PID is currently running.
129#[cfg(not(windows))]
130pub fn process_is_alive(pid: u32) -> bool {
131    // kill(pid, 0) probes existence without signalling.
132    unsafe { libc::kill(pid as i32, 0) == 0 }
133}