#![cfg(unix)]
use super::{
ProcessFacts, STARTED_AS_GIT_VARIABLE, StartMarker, StartMode, marker_of, marker_value, mark_real_git_start,
parse_marker, refusal_text, refusal_when_started_as_git, started_as_git,
};
use std::ffi::OsString;
use std::path::Path;
use std::process::Command;
const STARTER_IDENTITY: &str = "linux:1000";
fn marker(pid: u32, mode: StartMode) -> StartMarker {
return StartMarker { starter_pid: pid, starter_identity: STARTER_IDENTITY.to_string(), mode };
}
fn started(marker: &StartMarker, own: u32, parent: u32, identity_of_candidate: Option<&str>) -> bool {
let answer = |_pid: u32| -> Option<String> { return identity_of_candidate.map(str::to_string); };
let facts: ProcessFacts<'_> = ProcessFacts { own_pid: own, parent_pid: parent, identity_of: &answer };
return started_as_git(marker, &facts);
}
#[test]
fn a_marker_value_is_read_back_and_other_text_is_no_marker() {
for mode in [StartMode::Replace, StartMode::Child] {
let value: OsString = marker_value(mode, 4242, STARTER_IDENTITY);
assert_eq!(parse_marker(value.as_os_str()), Some(marker(4242, mode)), "{mode:?}");
}
let empty_identity: OsString = marker_value(StartMode::Child, 7, "");
assert_eq!(parse_marker(empty_identity.as_os_str()).map(|read| { return read.starter_identity; }), Some(String::new()));
for other in ["", "1234", "abc\u{1f}x\u{1f}child", "12\u{1f}x\u{1f}sideways", "12\u{1f}x", "12\u{1f}x\u{1f}child\u{1f}extra", "-5\u{1f}x\u{1f}child"] {
assert_eq!(parse_marker(OsString::from(other).as_os_str()), None, "{other:?}");
}
}
#[test]
fn a_replaced_start_is_the_process_the_marker_names() {
let replace: StartMarker = marker(100, StartMode::Replace);
assert!(started(&replace, 100, 1, Some(STARTER_IDENTITY)), "same process");
assert!(!started(&replace, 101, 100, Some(STARTER_IDENTITY)), "a child of the real Git that replaced the wrapper is a hook, not the start");
assert!(!started(&replace, 101, 1, Some(STARTER_IDENTITY)), "another process");
}
#[test]
fn a_child_start_is_a_child_of_the_process_the_marker_names() {
let child: StartMarker = marker(100, StartMode::Child);
assert!(started(&child, 200, 100, Some(STARTER_IDENTITY)), "child of the starter");
assert!(!started(&child, 200, 150, Some(STARTER_IDENTITY)), "something stands between: a hook or an alias of the real Git");
assert!(!started(&child, 100, 1, Some(STARTER_IDENTITY)), "the number alone is not the child start");
}
#[test]
fn a_reused_number_is_not_the_starter() {
let child: StartMarker = marker(100, StartMode::Child);
assert!(!started(&child, 200, 100, Some("linux:2000")), "another process holds the number now");
assert!(started(&child, 200, 100, None), "unreadable identity decides by the numbers");
let without: StartMarker = StartMarker { starter_pid: 100, starter_identity: String::new(), mode: StartMode::Child };
assert!(started(&without, 200, 100, Some("linux:2000")), "no recorded identity to compare");
}
#[test]
fn the_environment_marker_is_read_by_name() {
let value: OsString = marker_value(StartMode::Child, 9, STARTER_IDENTITY);
let present: Vec<(OsString, OsString)> = vec![(OsString::from(STARTED_AS_GIT_VARIABLE), value.clone())];
assert_eq!(marker_of(present.as_slice()), Some(marker(9, StartMode::Child)));
let misnamed: Vec<(OsString, OsString)> = vec![(OsString::from("CLI_GIT_STARTED_AS_GIT_NOT"), value)];
assert_eq!(marker_of(misnamed.as_slice()), None);
let garbled: Vec<(OsString, OsString)> = vec![(OsString::from(STARTED_AS_GIT_VARIABLE), OsString::from("1"))];
assert_eq!(marker_of(garbled.as_slice()), None);
assert_eq!(marker_of(&[]), None);
}
#[test]
fn the_live_check_reads_this_process() {
let own_pid: u32 = std::process::id();
let identity: String = crate::process_identity::current_birth_identity().expect("this process has an identity");
let own_marker = |pid: u32, mode: StartMode| -> Vec<(OsString, OsString)> {
return vec![(OsString::from(STARTED_AS_GIT_VARIABLE), marker_value(mode, pid, identity.as_str()))];
};
let elsewhere: u32 = own_pid.wrapping_add(7);
assert_eq!(refusal_when_started_as_git(own_marker(elsewhere, StartMode::Replace).as_slice(), Path::new("/wrapper")), None);
assert_eq!(refusal_when_started_as_git(own_marker(elsewhere, StartMode::Child).as_slice(), Path::new("/wrapper")), None);
let said: String = refusal_when_started_as_git(own_marker(own_pid, StartMode::Replace).as_slice(), Path::new("/wrapper"))
.expect("this process is the replaced starter");
assert!(said.contains("started as Git"), "{said}");
assert!(said.contains("/wrapper"), "{said}");
let parent: u32 = u32::try_from(unsafe { libc::getppid() }).expect("a parent number");
let parent_identity: String = crate::process_identity::process_birth_identity(i64::from(parent))
.expect("readable").expect("parent runs");
let named_parent: Vec<(OsString, OsString)> =
vec![(OsString::from(STARTED_AS_GIT_VARIABLE), marker_value(StartMode::Child, parent, parent_identity.as_str()))];
assert!(refusal_when_started_as_git(named_parent.as_slice(), Path::new("/wrapper")).is_some());
}
#[test]
fn the_refusal_names_what_a_person_needs() {
let environment: Vec<(OsString, OsString)> = vec![
(OsString::from("CLI_GIT_NATIVE_FORWARD_TARGET"), OsString::from("/opt/launchers/git")),
(OsString::from("PATH"), OsString::from("/opt/launchers")),
];
let said: String = refusal_text(&marker(321, StartMode::Child), Path::new("/opt/other-release/cli-git-native"), environment.as_slice());
for needle in [
"/opt/other-release/cli-git-native",
"was started as Git by a cli-git wrapper",
"process 321",
"waits for its answer",
"/opt/launchers/git",
"PATH here is /opt/launchers",
"never runs itself as Git",
"(for example /usr/bin)",
"remove the entry that leads back to cli-git",
] {
assert!(said.contains(needle), "{needle}: {said}");
}
let replaced: String = refusal_text(&marker(321, StartMode::Replace), Path::new("/x"), &[]);
assert!(replaced.contains("replaced with this program"), "{replaced}");
assert!(replaced.contains("PATH is not set here"), "{replaced}");
}
#[test]
fn marking_a_command_names_this_process_and_the_last_mark_wins() {
let mut command: Command = Command::new("/bin/true");
mark_real_git_start(&mut command, StartMode::Child);
mark_real_git_start(&mut command, StartMode::Replace);
let value: OsString = command
.get_envs()
.find(|(name, _value)| { return *name == STARTED_AS_GIT_VARIABLE; })
.and_then(|(_name, value)| { return value.map(OsString::from); })
.expect("the command carries the marker");
let read: StartMarker = parse_marker(value.as_os_str()).expect("a marker");
assert_eq!(read.starter_pid, std::process::id());
assert_eq!(read.mode, StartMode::Replace);
assert_eq!(command.get_envs().filter(|(name, _value)| { return *name == STARTED_AS_GIT_VARIABLE; }).count(), 1);
}