1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//! Pluggable subprocess runner trait.
//!
//! Production code uses [`DefaultRunner`] (which delegates to [`Cmd::run`]).
//! Test code can substitute [`MockRunner`](crate::testing::MockRunner) — gated
//! behind the `testing` feature — or any custom implementation.
//!
//! # Pattern
//!
//! Take `&dyn Runner` in functions that shell out so callers can swap in a
//! mock for unit tests:
//!
//! ```no_run
//! use procpilot::{Cmd, Runner, RunError};
//! use std::path::Path;
//!
//! fn current_branch(runner: &dyn Runner, repo: &Path) -> Result<String, RunError> {
//! let cmd = Cmd::new("git").args(["branch", "--show-current"]).in_dir(repo);
//! let out = runner.run(cmd)?;
//! Ok(out.stdout_lossy().trim().to_string())
//! }
//! ```
//!
//! In production, pass `&DefaultRunner` (or a long-lived global). In tests,
//! pass a [`MockRunner`](crate::testing::MockRunner) configured with the
//! commands you expect and the canned outputs to return.
use crate;
use crateRunError;
/// Pluggable backend for executing a [`Cmd`]. See the module-level docs.
///
/// `Send + Sync` so a `&dyn Runner` can cross thread / task boundaries.
///
/// # Scope
///
/// This trait only abstracts the sync [`Cmd::run`] path. Code that calls
/// [`Cmd::spawn`], [`Cmd::spawn_and_collect_lines`], or (with the
/// `tokio` feature) [`Cmd::run_async`] / [`Cmd::spawn_async`] still
/// invokes the real subprocess machinery — those paths aren't mockable
/// through this trait in procpilot 0.7. If you want your code fully
/// testable without spawning processes, route shell-outs through `run`
/// (via a `&dyn Runner`) for now. Spawn-handle mocking is tracked as
/// follow-up work.
/// Default `Runner` that delegates to [`Cmd::run`].
///
/// What `Cmd::run()` invokes internally, exposed as a `Runner` so callers
/// can pass `&DefaultRunner` where a `&dyn Runner` is expected.
;