use super::support::{
Fixture, Observed, bounded, copy_executable, executable, fixture, link_real_git, observe, remove,
repository,
};
use git_policy_cli::service_provision::{ProvisionHost, SERVICE_UNIT_NAME, runtime_unit_dir, unit_texts};
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
const WRAPPER_STOP_CODE: i32 = 2;
const NOTICE_LEAD: &str = "cli-git: no real Git follows this wrapper on PATH";
const STAND_IN_VERSION: &str = "git version 2.56.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 counting_git(path: &Path, counter: &Path) {
let script: String = format!(
"#!/bin/sh\necho started >> '{counter}'\necho '{STAND_IN_VERSION}'\n",
counter = counter.display(),
);
executable(path, script.as_bytes());
}
fn starts(counter: &Path) -> usize {
return match std::fs::read_to_string(counter) {
Ok(text) => text.lines().count(),
Err(_) => 0,
};
}
fn write_record(runtime: &Path, git: &Path) {
let host: ProvisionHost = ProvisionHost {
runtime_dir: runtime.to_path_buf(),
user_unit_dir: None,
bus: PathBuf::from("/nonexistent/bus"),
service_binary: PathBuf::from("/nonexistent/cli-git-service"),
git: git.to_path_buf(),
instance: String::from("fixture"),
retirement_wait: std::time::Duration::ZERO,
};
let units = unit_texts(&host);
let service_text: String = units
.into_iter()
.find(|(name, _)| {
return *name == SERVICE_UNIT_NAME;
})
.map(|(_, text)| {
return text;
})
.expect("the service unit text");
let unit_directory: PathBuf = runtime_unit_dir(runtime);
std::fs::create_dir_all(&unit_directory).expect("unit directory");
std::fs::write(unit_directory.join(SERVICE_UNIT_NAME), service_text).expect("unit file");
}
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 run_with_only(
fixture: &Fixture,
repo: &Path,
path_directories: &[&Path],
runtime: &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)
.env("XDG_RUNTIME_DIR", runtime)
.args(arguments),
b"",
);
}
fn words(observed: &Observed) -> String {
return String::from_utf8_lossy(observed.stderr.as_slice()).into_owned();
}
fn output(observed: &Observed) -> String {
return String::from_utf8_lossy(observed.stdout.as_slice()).into_owned();
}
#[test]
fn a_path_with_only_the_wrapper_and_a_recorded_real_git_runs_the_command_once_through_it_with_the_notice() {
let fixture: Fixture = fixture("record-fallback-once");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let recorded_directory: PathBuf = directory(&fixture, "recorded");
let recorded: PathBuf = recorded_directory.join("git");
let counter: PathBuf = fixture.root.join("starts");
counting_git(recorded.as_path(), counter.as_path());
let runtime: PathBuf = directory(&fixture, "run");
write_record(runtime.as_path(), recorded.as_path());
let wrapper_directory: PathBuf = fixture.root.join("bin");
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[wrapper_directory.as_path()], runtime.as_path(), &["--version"]);
let text: String = words(&observed);
assert_eq!(observed.code, Some(0), "{text}");
assert_eq!(starts(counter.as_path()), 1, "the recorded Git is started once: {text}");
assert!(output(&observed).contains(STAND_IN_VERSION), "Git's own answer reaches the caller: {}", output(&observed));
assert!(text.starts_with(NOTICE_LEAD), "the notice leads standard error: {text}");
assert_eq!(text.lines().count(), 1, "the notice is one line: {text}");
assert!(text.contains(wrapper_directory.to_string_lossy().as_ref()), "names the PATH it read: {text}");
assert!(text.contains(recorded.to_string_lossy().as_ref()), "names the file it used: {text}");
assert!(
text.contains(&format!("add {} to the PATH", recorded_directory.display())),
"names the way to silence it: {text}"
);
remove(&fixture);
}
#[test]
fn the_recorded_real_git_answers_a_command_that_reads_the_repository_and_the_notice_stays_off_standard_output() {
let fixture: Fixture = fixture("record-fallback-real");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
link_real_git(&fixture);
let recorded: PathBuf = fixture.root.join("real/git");
let runtime: PathBuf = directory(&fixture, "run");
write_record(runtime.as_path(), recorded.as_path());
let wrapper_directory: PathBuf = fixture.root.join("bin");
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[wrapper_directory.as_path()], runtime.as_path(), &["rev-parse", "HEAD"]);
let expected: Observed = observe(
super::support::direct(&fixture).current_dir(&repo).args(["rev-parse", "HEAD"]),
b"",
);
let text: String = words(&observed);
assert_eq!(observed.code, Some(0), "{text}");
assert_eq!(observed.stdout, expected.stdout);
assert!(text.starts_with(NOTICE_LEAD), "{text}");
assert!(!output(&observed).contains("cli-git:"), "standard output carries Git's answer only");
remove(&fixture);
}
#[test]
fn a_real_git_on_path_is_used_and_the_record_is_neither_started_nor_announced() {
let fixture: Fixture = fixture("record-not-needed");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
link_real_git(&fixture);
let recorded_directory: PathBuf = directory(&fixture, "recorded");
let recorded: PathBuf = recorded_directory.join("git");
let counter: PathBuf = fixture.root.join("starts");
counting_git(recorded.as_path(), counter.as_path());
let runtime: PathBuf = directory(&fixture, "run");
write_record(runtime.as_path(), recorded.as_path());
let wrapper_directory: PathBuf = fixture.root.join("bin");
let real_directory: PathBuf = fixture.root.join("real");
let observed: Observed = run_with_only(
&fixture,
repo.as_path(),
&[wrapper_directory.as_path(), real_directory.as_path()],
runtime.as_path(),
&["--version"],
);
assert_eq!(observed.code, Some(0), "{}", words(&observed));
assert_eq!(starts(counter.as_path()), 0, "the recorded file is not started");
assert!(!words(&observed).contains(NOTICE_LEAD), "no notice: {}", words(&observed));
remove(&fixture);
}
fn assert_stopped(observed: &Observed, counter: &Path, path_directory: &Path) {
let text: String = words(observed);
assert_eq!(observed.code, Some(WRAPPER_STOP_CODE), "{text}");
assert!(observed.stdout.is_empty(), "{}", output(observed));
assert!(!text.contains(NOTICE_LEAD), "no fallback happened: {text}");
assert_eq!(starts(counter), 0, "nothing was started as Git: {text}");
assert!(text.contains(path_directory.to_string_lossy().as_ref()), "names the PATH it read: {text}");
assert!(text.contains("install Git") || text.contains("Install Git"), "names the remedy: {text}");
}
#[test]
fn no_record_keeps_the_stop() {
let fixture: Fixture = fixture("record-absent");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let counter: PathBuf = fixture.root.join("starts");
let runtime: PathBuf = directory(&fixture, "run");
let wrapper_directory: PathBuf = fixture.root.join("bin");
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[wrapper_directory.as_path()], runtime.as_path(), &["--version"]);
assert_stopped(&observed, counter.as_path(), wrapper_directory.as_path());
let unset: Observed = observe(
bounded(&fixture, fixture.root.join("bin/git").as_path(), wrapper_directory.as_os_str())
.current_dir(&repo)
.args(["--version"]),
b"",
);
assert_stopped(&unset, counter.as_path(), wrapper_directory.as_path());
remove(&fixture);
}
#[test]
fn a_record_naming_the_wrapper_or_a_copy_of_it_is_refused_and_keeps_the_stop() {
let fixture: Fixture = fixture("record-names-wrapper");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let counter: PathBuf = fixture.root.join("starts");
let copied_directory: PathBuf = directory(&fixture, "copied");
let copy: PathBuf = copied_directory.join("git");
copy_executable(fixture.wrapper.as_path(), copy.as_path());
let wrapper_directory: PathBuf = fixture.root.join("bin");
for (index, named) in [fixture.wrapper.clone(), fixture.root.join("bin/git"), copy].into_iter().enumerate() {
let runtime: PathBuf = directory(&fixture, format!("run-{index}").as_str());
write_record(runtime.as_path(), named.as_path());
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[wrapper_directory.as_path()], runtime.as_path(), &["--version"]);
assert_stopped(&observed, counter.as_path(), wrapper_directory.as_path());
assert!(words(&observed).contains(named.to_string_lossy().as_ref()), "names the recorded file: {}", words(&observed));
}
remove(&fixture);
}
#[test]
fn a_record_naming_a_launcher_of_the_wrapper_is_refused_without_a_start_and_keeps_the_stop() {
let fixture: Fixture = fixture("record-names-launcher");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let counter: PathBuf = fixture.root.join("starts");
let launchers: PathBuf = directory(&fixture, "launchers");
let launcher: PathBuf = launchers.join("cli-git-native");
counting_git(launcher.as_path(), counter.as_path());
write_launch_record(launcher.as_path(), fixture.wrapper.as_path());
let runtime: PathBuf = directory(&fixture, "run");
write_record(runtime.as_path(), launcher.as_path());
let wrapper_directory: PathBuf = fixture.root.join("bin");
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[wrapper_directory.as_path()], runtime.as_path(), &["--version"]);
assert_stopped(&observed, counter.as_path(), wrapper_directory.as_path());
assert!(words(&observed).contains(launcher.to_string_lossy().as_ref()), "names the recorded file: {}", words(&observed));
remove(&fixture);
}
#[test]
fn a_record_naming_a_missing_or_non_executable_file_is_refused_and_keeps_the_stop() {
let fixture: Fixture = fixture("record-names-missing");
let repo: PathBuf = repository(&fixture, OsStr::new("repo"));
let counter: PathBuf = fixture.root.join("starts");
let missing: PathBuf = fixture.root.join("gone/git");
let plain_directory: PathBuf = directory(&fixture, "plain");
let plain: PathBuf = plain_directory.join("git");
std::fs::write(&plain, b"#!/bin/sh\n").expect("a file without the executable bit");
let wrapper_directory: PathBuf = fixture.root.join("bin");
for (index, named) in [missing, plain].into_iter().enumerate() {
let runtime: PathBuf = directory(&fixture, format!("run-{index}").as_str());
write_record(runtime.as_path(), named.as_path());
let observed: Observed = run_with_only(&fixture, repo.as_path(), &[wrapper_directory.as_path()], runtime.as_path(), &["--version"]);
assert_stopped(&observed, counter.as_path(), wrapper_directory.as_path());
assert!(words(&observed).contains(named.to_string_lossy().as_ref()), "names the recorded file: {}", words(&observed));
}
remove(&fixture);
}