diff --git a/p/src/commands/run.rs b/p/src/commands/run.rs index c4296bb..cf584a1 100644 --- a/p/src/commands/run.rs +++ b/p/src/commands/run.rs @@ -148,15 +148,8 @@ pub fn execute(worker_name: Option<&str>, cmd: Vec, no_sync: bool) -> Re /// Build the shell script that runs inside the tmux pane. /// -/// The script: -/// 1. Configures the tmux status bar (job ID, worker, running/done state). -/// 2. Runs the user's command, capturing output to output.log via tee. -/// 3. Writes the exit code to the exitcode file. -/// 4. Updates the status bar to reflect completion. -/// 5. Shows a "press any key to detach" prompt and waits. -/// 6. Calls `tmux detach-client` — the SSH session returns cleanly with no -/// [exited] or [detached] message flash. -fn build_run_sh(session: &str, job_dir: &str, work_dir: &str, worker: &str) -> String { +/// `pub(crate)` so it can be tested. +pub(crate) fn build_run_sh(session: &str, job_dir: &str, work_dir: &str, worker: &str) -> String { // Truncate worker name for display to avoid overflowing the status bar. let worker_display = if worker.len() > 20 { &worker[..20] @@ -205,3 +198,75 @@ fn build_run_sh(session: &str, job_dir: &str, work_dir: &str, worker: &str) -> S work_dir = work_dir, ) } + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_script() -> String { + build_run_sh( + "p-a3f2b091", + "/home/ubuntu/.p/jobs/a3f2b091-0000-0000-0000-000000000000", + "/home/ubuntu/.p/workdirs/a3f2b091-0000-0000-0000-000000000000", + "beefy", + ) + } + + #[test] + fn script_starts_with_shebang() { + assert!(sample_script().starts_with("#!/bin/bash\n")); + } + + #[test] + fn script_contains_session_name() { + assert!(sample_script().contains("p-a3f2b091")); + } + + #[test] + fn script_contains_job_and_work_dirs() { + let s = sample_script(); + assert!(s.contains("/home/ubuntu/.p/jobs/a3f2b091")); + assert!(s.contains("/home/ubuntu/.p/workdirs/a3f2b091")); + } + + #[test] + fn script_captures_exit_code_via_pipestatus() { + // Must use PIPESTATUS[0] to get the command's exit code, not tee's. + assert!(sample_script().contains("PIPESTATUS[0]")); + } + + #[test] + fn script_writes_exitcode_file() { + assert!(sample_script().contains("exitcode")); + } + + #[test] + fn script_tees_output_to_log() { + assert!(sample_script().contains("tee")); + assert!(sample_script().contains("output.log")); + } + + #[test] + fn script_waits_for_keypress_before_detach() { + let s = sample_script(); + assert!(s.contains("read -rn 1 -s")); + assert!(s.contains("tmux detach-client")); + } + + #[test] + fn script_sets_up_status_bar() { + let s = sample_script(); + assert!(s.contains("status on")); + assert!(s.contains("beefy")); + } + + #[test] + fn worker_name_truncated_at_20_chars() { + let s = build_run_sh("p-test", "/j", "/w", "a-very-long-worker-name-here"); + // "a-very-long-worker-name-here" is 28 chars; only first 20 should appear. + assert!(s.contains("a-very-long-worker-n")); + assert!(!s.contains("a-very-long-worker-name-here")); + } +}