eval_magic/core/runtime.rs
1//! Runtime helpers.
2//!
3//! The synchronous git invocation helper. No runtime asset locator lives here:
4//! the schemas are bundled into the binary at compile time (`include_str!`),
5//! `clap` owns argument parsing, and the `error: <msg>` + exit(1) contract
6//! lives in `src/main.rs`.
7
8use std::path::Path;
9use std::process::Command;
10
11/// Outcome of a git invocation.
12///
13/// `status` is `None` when git could not be spawned at all (e.g. ENOENT, a
14/// nonexistent cwd, permission denied); the reason is surfaced into `stderr`,
15/// so callers have one channel for both git's own errors and spawn failures.
16/// `stdout` and
17/// `stderr` are raw bytes — callers that read file contents out of git
18/// (`git show`) need the undecoded buffer, not a lossy UTF-8 string.
19#[derive(Debug)]
20pub struct GitOutput {
21 pub status: Option<i32>,
22 pub stdout: Vec<u8>,
23 pub stderr: Vec<u8>,
24}
25
26/// Synchronously invoke `git` with `args` in `cwd`, returning its status and raw
27/// output. A failure to spawn git is not an error here: it yields `status: None`
28/// with the spawn error surfaced into `stderr`, so callers can handle it
29/// alongside git's own failures.
30pub fn run_git(args: &[&str], cwd: &Path) -> GitOutput {
31 match Command::new("git").args(args).current_dir(cwd).output() {
32 Ok(out) => GitOutput {
33 status: out.status.code(),
34 stdout: out.stdout,
35 stderr: out.stderr,
36 },
37 Err(err) => GitOutput {
38 status: None,
39 stdout: Vec::new(),
40 stderr: format!("{err}").into_bytes(),
41 },
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 /// A successful git command returns exit status 0 and writes to stdout.
50 /// Run against this crate's own repo, which has commit history.
51 #[test]
52 fn run_git_success_status_and_stdout() {
53 let res = run_git(
54 &["rev-parse", "--short", "HEAD"],
55 env!("CARGO_MANIFEST_DIR").as_ref(),
56 );
57 assert_eq!(res.status, Some(0));
58 assert!(String::from_utf8_lossy(&res.stdout).trim().len() > 3);
59 }
60
61 /// A git command that fails (bad ref) returns a non-zero status.
62 #[test]
63 fn run_git_failing_command_nonzero() {
64 let res = run_git(
65 &["rev-parse", "not-a-real-ref-xyz"],
66 env!("CARGO_MANIFEST_DIR").as_ref(),
67 );
68 assert_ne!(res.status, Some(0));
69 }
70
71 /// When git itself cannot be spawned (here, a nonexistent cwd), the status
72 /// is `None` and the spawn error is surfaced into stderr — the contract is
73 /// the null status plus a readable reason, not any particular error-code
74 /// spelling.
75 #[test]
76 fn run_git_spawn_error_surfaced() {
77 let res = run_git(
78 &["rev-parse", "HEAD"],
79 "/nonexistent-dir-for-rungit-test".as_ref(),
80 );
81 assert_eq!(res.status, None);
82 assert!(String::from_utf8_lossy(&res.stderr).contains("No such file or directory"));
83 }
84}