GitBaby/tests/cmd_run.rs
zhenyi 680411c4fb chore: initialize gitbaby repository
gitbaby is an asynchronous Rust library wrapping git operations, built on
gix + tokio + async-trait.

Module layout:
- archive, blame, blob, branch, cleanup, commit, compare, config,
  conflict, diff, merge, refs, remote, setup, share, submodule, tags,
  tree (feature modules)
- command: command scheduler (cmd/env/pipe/context/error)
- repo: RepositoryFacade trait (consumers implement, exposing a
  gix::Repository)

Features:
- async API on tokio 1.53
- order-preserving Env (Vec<(String, String)>)
- hand-written BabyError / CmdError, not using thiserror derive
- integration tests covering cmd_run / context / env / pipe (env keys
  prefixed for isolation)

CI: none, standard cargo build / test / clippy / fmt only
2026-08-14 18:10:04 +08:00

141 lines
3.7 KiB
Rust

use gitbaby::command::cmd::Cmd;
use std::path::PathBuf;
macro_rules! unwrap_or_panic {
($expr:expr, $msg:literal) => {
match $expr {
Ok(v) => v,
Err(e) => panic!("{}: {:?}", $msg, e),
}
};
($expr:expr, $fmt:literal, $($arg:tt)*) => {
match $expr {
Ok(v) => v,
Err(e) => panic!("{}: {:?}", format_args!($fmt, $($arg)*), e),
}
};
}
#[tokio::test]
async fn run_echo_and_capture_output() {
let mut cmd = Cmd::new(
"test:echo",
"sh",
vec!["-c".to_string(), "echo hello".to_string()],
vec![],
PathBuf::from("."),
vec![],
None,
);
let out = unwrap_or_panic!(cmd.run().await, "run failed");
assert!(out.status.success(), "exit code should be 0");
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "hello");
}
#[tokio::test]
async fn run_with_non_zero_exit_returns_output() {
let mut cmd = Cmd::new(
"test:false",
"sh",
vec!["-c".to_string(), "echo oops 1>&2; exit 7".to_string()],
vec![],
PathBuf::from("."),
vec![],
None,
);
let res = cmd.run().await;
match res {
Ok(_) => panic!("run() with non-zero exit should fail"),
Err(gitbaby::command::error::CmdError::NonZeroExit { code, stderr, .. }) => {
assert_eq!(code, 7);
assert_eq!(stderr.trim(), "oops");
}
Err(e) => panic!("unexpected error: {:?}", e),
}
}
#[tokio::test]
async fn run_soft_keeps_non_zero_exit_in_output() {
let mut cmd = Cmd::new(
"test:false-soft",
"sh",
vec!["-c".to_string(), "echo oops 1>&2; exit 7".to_string()],
vec![],
PathBuf::from("."),
vec![],
None,
);
let out = unwrap_or_panic!(cmd.run_soft().await, "run_soft failed");
assert_eq!(out.status.code(), Some(7));
assert_eq!(String::from_utf8_lossy(&out.stderr).trim(), "oops");
}
#[tokio::test]
async fn feed_writes_to_stdin() {
let mut cmd = Cmd::new(
"test:cat",
"cat",
vec![],
vec![],
PathBuf::from("."),
vec![],
None,
);
unwrap_or_panic!(cmd.spawn().await, "spawn failed");
unwrap_or_panic!(cmd.feed(b"via-stdin").await, "feed failed");
let out = unwrap_or_panic!(cmd.run().await, "run failed");
assert_eq!(String::from_utf8_lossy(&out.stdout), "via-stdin");
}
#[tokio::test]
async fn env_vars_are_applied() {
let mut cmd = Cmd::new(
"test:env",
"sh",
vec!["-c".to_string(), "printf %s \"$GREETING\"".to_string()],
vec![],
PathBuf::from("."),
vec![("GREETING".to_string(), "hi".to_string())],
None,
);
let out = unwrap_or_panic!(cmd.run().await, "run failed");
assert!(out.status.success());
assert_eq!(String::from_utf8_lossy(&out.stdout), "hi");
}
#[tokio::test]
async fn config_args_prepend_to_command_line() {
let mut cmd = Cmd::new(
"test:cfg",
"echo",
vec!["world".to_string()],
vec!["ignored-no-flag-echo".to_string()],
PathBuf::from("."),
vec![],
None,
);
let rendered = format!("{cmd}");
assert_eq!(rendered, "echo ignored-no-flag-echo world");
let out = unwrap_or_panic!(cmd.run().await, "run failed");
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"ignored-no-flag-echo world"
);
}
#[tokio::test]
async fn timeout_kills_long_running_process() {
let mut cmd = Cmd::new(
"test:sleep",
"sleep",
vec!["5".to_string()],
vec![],
PathBuf::from("."),
vec![],
Some(1),
);
let res = cmd.run().await;
assert!(res.is_err(), "timeout should produce an error");
}