#![cfg(unix)]
use super::{ChildOutcome, exit_code, git_command, replace_process_with_real_git, run_real_git};
use crate::test_support::{executable, fixture, remove};
use std::ffi::{OsStr, OsString};
use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
use std::process::Command;
#[test]
fn exit_codes_follow_git_child_convention() {
for (outcome, expected) in [
(ChildOutcome::Exited(0), 0),
(ChildOutcome::Exited(1), 1),
(ChildOutcome::Exited(128), 128),
(ChildOutcome::Exited(255), 255),
(ChildOutcome::Signaled(2), 130),
(ChildOutcome::Signaled(9), 137),
(ChildOutcome::Signaled(13), 141),
(ChildOutcome::Signaled(15), 143),
] {
assert_eq!(exit_code(outcome), expected, "{outcome:?}");
}
}
#[test]
fn command_preserves_program_arguments_and_overlay() {
let arguments: Vec<OsString> = vec![
OsString::from("-C"),
OsString::from_vec(b"dir-\xff\xfe".to_vec()),
OsString::new(),
OsString::from("a b"),
OsString::from("--"),
OsString::from("$(touch x); 'q' \"d\" \\ \n*"),
OsString::from("--"),
];
let overlay: Vec<(OsString, OsString)> = vec![
(OsString::from("GIT_CONFIG_COUNT"), OsString::from("1")),
(
OsString::from("MARKER"),
OsString::from_vec(b"/opt/\xff/git".to_vec()),
),
];
let command: Command = git_command(
Path::new("/opt/real git/git"),
arguments.as_slice(),
overlay.as_slice(),
);
assert_eq!(command.get_program(), OsStr::new("/opt/real git/git"));
let mut seen_arguments: Vec<OsString> = Vec::<OsString>::new();
for argument in command.get_args() {
seen_arguments.push(argument.to_os_string());
}
assert_eq!(seen_arguments, arguments);
let mut seen_environment: Vec<(OsString, OsString)> = Vec::<(OsString, OsString)>::new();
let mut start_marker: Option<OsString> = None;
for (name, found) in command.get_envs() {
let text: OsString = found.expect("overlay sets, never removes").to_os_string();
if name == crate::started_as_git::STARTED_AS_GIT_VARIABLE {
start_marker = Some(text);
continue;
}
seen_environment.push((name.to_os_string(), text));
}
seen_environment.sort();
assert_eq!(seen_environment, overlay);
let marker = crate::started_as_git::parse_marker(start_marker.expect("the start carries the marker").as_os_str())
.expect("a well-formed marker");
assert_eq!(marker.starter_pid, std::process::id());
assert_eq!(marker.mode, crate::started_as_git::StartMode::Child);
let bare: Command = git_command(Path::new("git"), &[], &[]);
assert_eq!(bare.get_envs().count(), 1);
assert_eq!(bare.get_envs().next().map(|(name, _value)| { return name.to_os_string(); }), Some(OsString::from(crate::started_as_git::STARTED_AS_GIT_VARIABLE)));
}
#[test]
fn run_reports_exit_codes_and_signals() {
let root: PathBuf = fixture("forward-run");
for (name, script, expected) in [
("zero", "#!/bin/sh\nexit 0\n", ChildOutcome::Exited(0)),
("seven", "#!/bin/sh\nexit 7\n", ChildOutcome::Exited(7)),
(
"git-128",
"#!/bin/sh\nexit 128\n",
ChildOutcome::Exited(128),
),
(
"term",
"#!/bin/sh\nkill -TERM $$\n",
ChildOutcome::Signaled(15),
),
(
"kill",
"#!/bin/sh\nkill -KILL $$\n",
ChildOutcome::Signaled(9),
),
(
"argv",
"#!/bin/sh\n[ \"$#\" = 3 ] && [ \"$1\" = 'a b' ] && [ \"$2\" = '' ] && [ \"$3\" = '--' ] && [ \"$MARKER\" = 'm v' ] && exit 42\nexit 1\n",
ChildOutcome::Exited(42),
),
] {
let program: PathBuf = root.join(name);
executable(program.as_path(), script.as_bytes());
let outcome: ChildOutcome = run_real_git(
program.as_path(),
&[OsString::from("a b"), OsString::new(), OsString::from("--")],
&[(OsString::from("MARKER"), OsString::from("m v"))],
)
.expect("child starts");
assert_eq!(outcome, expected, "{name}");
}
remove(root.as_path());
}
#[test]
fn unstartable_program_is_an_error() {
let missing: &Path = Path::new("/nonexistent-directory/git");
assert_eq!(
run_real_git(missing, &[], &[])
.expect_err("missing program")
.kind(),
std::io::ErrorKind::NotFound
);
assert_eq!(
replace_process_with_real_git(missing, &[], &[]).kind(),
std::io::ErrorKind::NotFound
);
}