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
141 lines
3.7 KiB
Rust
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");
|
|
}
|