Skip to content

Commit

Permalink
Merge pull request GitoxideLabs#1712 from EliahKagan/run-ci/git-bash
Browse files Browse the repository at this point in the history
Run test fixture scripts on Windows with Git Bash
  • Loading branch information
Byron authored Dec 1, 2024
2 parents c146b7a + 1f6a866 commit fadf106
Showing 1 changed file with 95 additions and 25 deletions.
120 changes: 95 additions & 25 deletions tests/tools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ impl Drop for GitDaemon {
}

static SCRIPT_IDENTITY: Lazy<Mutex<BTreeMap<PathBuf, u32>>> = Lazy::new(|| Mutex::new(BTreeMap::new()));

static EXCLUDE_LUT: Lazy<Mutex<Option<gix_worktree::Stack>>> = Lazy::new(|| {
let cache = (|| {
let (repo_path, _) = gix_discover::upwards(Path::new(".")).ok()?;
Expand Down Expand Up @@ -86,8 +87,31 @@ static EXCLUDE_LUT: Lazy<Mutex<Option<gix_worktree::Stack>>> = Lazy::new(|| {
})();
Mutex::new(cache)
});

#[cfg(windows)]
const GIT_PROGRAM: &str = "git.exe";
#[cfg(not(windows))]
const GIT_PROGRAM: &str = "git";

static GIT_CORE_DIR: Lazy<PathBuf> = Lazy::new(|| {
let output = std::process::Command::new(GIT_PROGRAM)
.arg("--exec-path")
.output()
.expect("can execute `git --exec-path`");

assert!(output.status.success(), "`git --exec-path` failed");

output
.stdout
.strip_suffix(b"\n")
.expect("`git --exec-path` output to be well-formed")
.to_os_str()
.expect("no invalid UTF-8 in `--exec-path` except as OS allows")
.into()
});

/// The major, minor and patch level of the git version on the system.
pub static GIT_VERSION: Lazy<(u8, u8, u8)> = Lazy::new(|| parse_gix_version().expect("git version to be parsable"));
pub static GIT_VERSION: Lazy<(u8, u8, u8)> = Lazy::new(|| parse_git_version().expect("git version to be parsable"));

/// Define how [`scripted_fixture_writable_with_args()`] uses produces the writable copy.
pub enum Creation {
Expand Down Expand Up @@ -116,10 +140,8 @@ pub fn should_skip_as_git_version_is_smaller_than(major: u8, minor: u8, patch: u
*GIT_VERSION < (major, minor, patch)
}

fn parse_gix_version() -> Result<(u8, u8, u8)> {
let gix_program = cfg!(windows).then(|| "git.exe").unwrap_or("git");
let output = std::process::Command::new(gix_program).arg("--version").output()?;

fn parse_git_version() -> Result<(u8, u8, u8)> {
let output = std::process::Command::new(GIT_PROGRAM).arg("--version").output()?;
git_version_from_bytes(&output.stdout)
}

Expand Down Expand Up @@ -173,25 +195,14 @@ impl Drop for AutoRevertToPreviousCWD {

/// Run `git` in `working_dir` with all provided `args`.
pub fn run_git(working_dir: &Path, args: &[&str]) -> std::io::Result<std::process::ExitStatus> {
std::process::Command::new("git")
std::process::Command::new(GIT_PROGRAM)
.current_dir(working_dir)
.args(args)
.status()
}

/// Spawn a git daemon process to host all repository at or below `working_dir`.
pub fn spawn_git_daemon(working_dir: impl AsRef<Path>) -> std::io::Result<GitDaemon> {
static EXEC_PATH: Lazy<PathBuf> = Lazy::new(|| {
let path = std::process::Command::new("git")
.arg("--exec-path")
.stderr(std::process::Stdio::null())
.output()
.expect("can execute `git --exec-path`")
.stdout;
String::from_utf8(path.trim().into())
.expect("no invalid UTF8 in exec-path")
.into()
});
let mut ports: Vec<_> = (9419u16..9419 + 100).collect();
fastrand::shuffle(&mut ports);
let addr_at = |port| std::net::SocketAddr::from(([127, 0, 0, 1], port));
Expand All @@ -200,11 +211,12 @@ pub fn spawn_git_daemon(working_dir: impl AsRef<Path>) -> std::io::Result<GitDae
listener.local_addr().expect("listener address is available").port()
};

let child = std::process::Command::new(EXEC_PATH.join(if cfg!(windows) { "git-daemon.exe" } else { "git-daemon" }))
.current_dir(working_dir)
.args(["--verbose", "--base-path=.", "--export-all", "--user-path"])
.arg(format!("--port={free_port}"))
.spawn()?;
let child =
std::process::Command::new(GIT_CORE_DIR.join(if cfg!(windows) { "git-daemon.exe" } else { "git-daemon" }))
.current_dir(working_dir)
.args(["--verbose", "--base-path=.", "--export-all", "--user-path"])
.arg(format!("--port={free_port}"))
.spawn()?;

let server_addr = addr_at(free_port);
for time in gix_lock::backoff::Exponential::default_with_random() {
Expand Down Expand Up @@ -556,7 +568,7 @@ fn scripted_fixture_read_only_with_args_inner(
Err(err)
if err.kind() == std::io::ErrorKind::PermissionDenied || err.raw_os_error() == Some(193) /* windows */ =>
{
cmd = std::process::Command::new("bash");
cmd = std::process::Command::new(bash_program());
configure_command(cmd.arg(script_absolute_path), &args, &script_result_directory).output()?
}
Err(err) => return Err(err.into()),
Expand Down Expand Up @@ -632,6 +644,22 @@ fn configure_command<'a, I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
.env("GIT_CONFIG_VALUE_3", "always")
}

fn bash_program() -> &'static Path {
if cfg!(windows) {
static GIT_BASH: Lazy<Option<PathBuf>> = Lazy::new(|| {
GIT_CORE_DIR
.parent()?
.parent()?
.parent()
.map(|installation_dir| installation_dir.join("bin").join("bash.exe"))
.filter(|bash| bash.is_file())
});
GIT_BASH.as_deref().unwrap_or(Path::new("bash.exe"))
} else {
Path::new("bash")
}
}

fn write_failure_marker(failure_marker: &Path) {
std::fs::write(failure_marker, []).ok();
}
Expand Down Expand Up @@ -738,7 +766,10 @@ fn populate_meta_dir(destination_dir: &Path, script_identity: u32) -> std::io::R
)?;
std::fs::write(
meta_dir.join(META_GIT_VERSION),
std::process::Command::new("git").arg("--version").output()?.stdout,
std::process::Command::new(GIT_PROGRAM)
.arg("--version")
.output()?
.stdout,
)?;
Ok(meta_dir)
}
Expand Down Expand Up @@ -951,7 +982,7 @@ mod tests {
let temp = tempfile::TempDir::new().expect("can create temp dir");
populate_ad_hoc_config_files(temp.path());

let mut cmd = std::process::Command::new("git");
let mut cmd = std::process::Command::new(GIT_PROGRAM);
cmd.env("GIT_CONFIG_SYSTEM", SCOPE_ENV_VALUE);
cmd.env("GIT_CONFIG_GLOBAL", SCOPE_ENV_VALUE);
configure_command(&mut cmd, ["config", "-l", "--show-origin"], temp.path());
Expand All @@ -968,4 +999,43 @@ mod tests {
assert_eq!(lines, Vec::<&str>::new(), "should be no config variables from files");
assert_eq!(status, 0, "reading the config should succeed");
}

#[test]
#[cfg(windows)]
fn bash_program_ok_for_platform() {
let path = bash_program();
assert!(path.is_absolute());

let for_version = std::process::Command::new(path)
.arg("--version")
.output()
.expect("can pass it `--version`");
assert!(for_version.status.success(), "passing `--version` succeeds");
let version_line = for_version
.stdout
.lines()
.nth(0)
.expect("`--version` output has first line");
assert!(
version_line.ends_with(b"-pc-msys)"), // On Windows, "-pc-linux-gnu)" would be WSL.
"it is an MSYS bash (such as Git Bash)"
);

let for_uname_os = std::process::Command::new(path)
.args(["-c", "uname -o"])
.output()
.expect("can tell it to run `uname -o`");
assert!(for_uname_os.status.success(), "telling it to run `uname -o` succeeds");
assert_eq!(
for_uname_os.stdout.trim_end(),
b"Msys",
"it runs commands in an MSYS environment"
);
}

#[test]
#[cfg(not(windows))]
fn bash_program_ok_for_platform() {
assert_eq!(bash_program(), Path::new("bash"));
}
}

0 comments on commit fadf106

Please sign in to comment.