use super::support::{Fixture, Observed, bounded, copy_executable, executable, fixture, observe, remove, repository};
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
const START_CAP: usize = 6;
const STAND_IN_STOP_CODE: i32 = 97;
const WRAPPER_STOP_CODE: i32 = 2;
const LAUNCHER_NAME: &str = "cli-git-native";
fn counting_stand_in(path: &Path, counter: &Path, target: &Path) {
let script: String = format!(
"#!/bin/sh\n\
echo started >> '{counter}'\n\
started=0\n\
while read -r line; do started=$((started + 1)); done < '{counter}'\n\
if [ \"$started\" -gt {START_CAP} ]; then\n\
echo 'stand-in: start cap reached' >&2\n\
exit {STAND_IN_STOP_CODE}\n\
fi\n\
exec '{target}' \"$@\"\n",
counter = counter.display(),
target = target.display(),
);
executable(path, script.as_bytes());
}
fn write_launch_record(launcher: &Path, real: &Path) {
let mut record_name: OsString = launcher.as_os_str().to_os_string();
record_name.push(".launch");
let record: String = format!(
"{{\"toolId\":\"cargo:monochromatic-git-policy-cli\",\"lockFile\":\"/nonexistent/toolchain.lock.json\",\"real\":\"{}\",\"scope\":\"repo\"}}",
real.display()
);
std::fs::write(PathBuf::from(record_name), record).expect("launcher record");
}
fn starts(counter: &Path) -> usize {
return match std::fs::read_to_string(counter) {
Ok(text) => text.lines().count(),
Err(_) => 0,
};
}
fn directory(fixture: &Fixture, name: &str) -> PathBuf {
let path: PathBuf = fixture.root.join(name);
std::fs::create_dir(&path).expect("fixture directory");
return path;
}
fn run_with_only(fixture: &Fixture, repo: &Path, path_directories: &[&Path], arguments: &[&str]) -> Observed {
let mut path: OsString = OsString::new();
for entry in path_directories {
if !path.is_empty() {
path.push(":");
}
path.push(entry);
}
return observe(
bounded(fixture, fixture.root.join("bin/git").as_path(), path.as_os_str())
.current_dir(repo)
.args(arguments),
b"",
);
}
fn words(observed: &Observed) -> String {
return String::from_utf8_lossy(observed.stderr.as_slice()).into_owned();
}
#[test]
fn a_symlink_to_the_toolchain_launcher_alone_on_path_starts_nothing_and_says_what_was_searched() {
let fixture: Fixture = fixture("launcher-symlink");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let launchers: PathBuf = directory(&fixture, "launchers");
let counter: PathBuf = fixture.root.join("starts");
let launcher: PathBuf = launchers.join(LAUNCHER_NAME);
counting_stand_in(launcher.as_path(), counter.as_path(), fixture.wrapper.as_path());
write_launch_record(launcher.as_path(), fixture.wrapper.as_path());
let path_directory: PathBuf = directory(&fixture, "only");
std::os::unix::fs::symlink(&launcher, path_directory.join("git")).expect("symlink to the launcher");
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[path_directory.as_path()], &["rev-parse", "HEAD"]);
let text: String = words(&observed);
assert_eq!(starts(counter.as_path()), 0, "the launcher must never be started: {text}");
assert_eq!(observed.code, Some(WRAPPER_STOP_CODE), "{text}");
assert!(observed.stdout.is_empty());
assert!(text.contains(path_directory.join("git").to_string_lossy().as_ref()), "names the candidate it skipped: {text}");
assert!(text.contains("PATH"), "names the input it searched: {text}");
assert!(text.contains("install Git") || text.contains("Install Git"), "names the remedy: {text}");
remove(&fixture);
}
#[test]
fn a_toolchain_launcher_alone_on_path_answers_version_with_a_stop_and_not_a_chain() {
let fixture: Fixture = fixture("launcher-version");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let launchers: PathBuf = directory(&fixture, "launchers");
let counter: PathBuf = fixture.root.join("starts");
let launcher: PathBuf = launchers.join(LAUNCHER_NAME);
counting_stand_in(launcher.as_path(), counter.as_path(), fixture.wrapper.as_path());
write_launch_record(launcher.as_path(), fixture.wrapper.as_path());
let path_directory: PathBuf = directory(&fixture, "only");
std::os::unix::fs::symlink(&launcher, path_directory.join("git")).expect("symlink to the launcher");
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[path_directory.as_path()], &["--version"]);
assert_eq!(starts(counter.as_path()), 0, "{}", words(&observed));
assert_eq!(observed.code, Some(WRAPPER_STOP_CODE), "{}", words(&observed));
remove(&fixture);
}
#[test]
fn a_launcher_named_git_and_a_symlink_chain_to_a_launcher_are_skipped() {
let fixture: Fixture = fixture("launcher-named-git");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let counter: PathBuf = fixture.root.join("starts");
let named: PathBuf = directory(&fixture, "named");
counting_stand_in(named.join("git").as_path(), counter.as_path(), fixture.wrapper.as_path());
write_launch_record(named.join("git").as_path(), fixture.wrapper.as_path());
let linked: PathBuf = directory(&fixture, "linked");
let launchers: PathBuf = directory(&fixture, "launchers");
let launcher: PathBuf = launchers.join(LAUNCHER_NAME);
counting_stand_in(launcher.as_path(), counter.as_path(), fixture.wrapper.as_path());
write_launch_record(launcher.as_path(), fixture.wrapper.as_path());
let middle: PathBuf = linked.join("middle");
std::os::unix::fs::symlink(&launcher, &middle).expect("first link");
std::os::unix::fs::symlink(&middle, linked.join("git")).expect("second link");
for directory_under_test in [named.as_path(), linked.as_path()] {
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[directory_under_test], &["rev-parse", "HEAD"]);
assert_eq!(starts(counter.as_path()), 0, "{}: {}", directory_under_test.display(), words(&observed));
assert_eq!(observed.code, Some(WRAPPER_STOP_CODE), "{}: {}", directory_under_test.display(), words(&observed));
}
remove(&fixture);
}
#[test]
fn another_wrapper_release_behind_an_unrecorded_launcher_stops_on_arrival() {
let fixture: Fixture = fixture("other-release-behind-launcher");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let other_directory: PathBuf = directory(&fixture, "other");
let other: PathBuf = other_directory.join("cli-git-native");
copy_executable(fixture.wrapper.as_path(), other.as_path());
let mut bytes: Vec<u8> = std::fs::read(&other).expect("copy bytes");
bytes.push(0);
executable(other.as_path(), bytes.as_slice());
let path_directory: PathBuf = directory(&fixture, "only");
let counter: PathBuf = fixture.root.join("starts");
counting_stand_in(path_directory.join("git").as_path(), counter.as_path(), other.as_path());
for arguments in [&["--version"][..], &["--exec-path"][..], &["rev-parse", "HEAD"][..]] {
std::fs::remove_file(&counter).ok();
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[path_directory.as_path()], arguments);
let text: String = words(&observed);
assert!(starts(counter.as_path()) <= 1, "{arguments:?}: {text}");
assert_eq!(observed.code, Some(WRAPPER_STOP_CODE), "{arguments:?}: {text}");
assert!(text.contains("started as Git"), "{arguments:?}: {text}");
assert!(text.contains(path_directory.join("git").to_string_lossy().as_ref()) || text.contains("PATH"), "{arguments:?}: {text}");
}
remove(&fixture);
}
#[test]
fn a_symlink_a_hard_link_and_a_copy_of_the_wrapper_alone_on_path_are_skipped() {
let fixture: Fixture = fixture("wrapper-forms-alone");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let symlinked: PathBuf = directory(&fixture, "symlinked");
std::os::unix::fs::symlink(&fixture.wrapper, symlinked.join("git")).expect("symlink");
let hard: PathBuf = directory(&fixture, "hard");
std::fs::hard_link(&fixture.wrapper, hard.join("git")).expect("hard link");
let copied: PathBuf = directory(&fixture, "copied");
copy_executable(fixture.wrapper.as_path(), copied.join("git").as_path());
for form in [symlinked.as_path(), hard.as_path(), copied.as_path()] {
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[form], &["rev-parse", "HEAD"]);
let text: String = words(&observed);
assert_eq!(observed.code, Some(WRAPPER_STOP_CODE), "{}: {text}", form.display());
assert!(observed.stdout.is_empty(), "{}", form.display());
assert!(text.contains(form.join("git").to_string_lossy().as_ref()), "{}: {text}", form.display());
}
remove(&fixture);
}