use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
struct TempDir(PathBuf);
impl TempDir {
fn new(tag: &str) -> TempDir {
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let dir = std::env::temp_dir().join(format!("sluuz-{tag}-{stamp}"));
fs::create_dir_all(&dir).expect("temp dir");
TempDir(dir)
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[test]
fn a_scan_that_finds_no_repo_exits_non_zero() {
let dir = TempDir::new("norepo");
let ceiling = dir.0.parent().expect("temp dir has a parent");
for cmd in ["repos", "sync", "tidy"] {
let out = Command::new(env!("CARGO_BIN_EXE_slu"))
.arg(cmd)
.current_dir(&dir.0)
.env("GIT_CEILING_DIRECTORIES", ceiling)
.output()
.expect("run slu");
assert!(!out.status.success(), "`slu {cmd}` with no repo exited 0");
assert!(
out.stdout.is_empty(),
"`slu {cmd}` put its failure on stdout"
);
assert!(
!out.stderr.is_empty(),
"`slu {cmd}` failed without saying why"
);
}
}
#[test]
fn a_reader_that_stops_early_ends_the_output_quietly() {
let dir = TempDir::new("pipe");
let git = |args: &[&str]| {
let ok = Command::new("git")
.args(args)
.current_dir(&dir.0)
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.status()
.expect("run git")
.success();
assert!(ok, "`git {}` failed", args.join(" "));
};
git(&["init", "-q"]);
git(&[
"-c",
"user.name=Test",
"-c",
"user.email=test@example.com",
"commit",
"-q",
"--allow-empty",
"-m",
"one",
]);
let (reader, writer) = std::io::pipe().expect("pipe");
drop(reader);
let out = Command::new(env!("CARGO_BIN_EXE_slu"))
.arg("trace")
.current_dir(&dir.0)
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.stdout(writer)
.output()
.expect("run slu");
assert!(
out.status.success(),
"`slu trace` into a closed pipe exited {:?}",
out.status.code()
);
assert!(
out.stderr.is_empty(),
"`slu trace` into a closed pipe complained: {}",
String::from_utf8_lossy(&out.stderr)
);
}
fn slu(dir: &std::path::Path, args: &[&str]) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_slu"))
.args(args)
.current_dir(dir)
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.output()
.expect("run slu")
}
#[test]
fn each_dash_c_is_relative_to_the_one_before_and_applied_once() {
let dir = TempDir::new("dash-c");
let repo = dir.0.join("a").join("b");
fs::create_dir_all(&repo).expect("repo dir");
let ok = Command::new("git")
.args(["init", "-q"])
.current_dir(&repo)
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.status()
.expect("run git")
.success();
assert!(ok, "`git init` failed");
let out = slu(
&dir.0,
&["-C", "a", "-C", "b", "rev-parse", "--show-toplevel"],
);
assert!(
out.status.success(),
"`slu -C a -C b rev-parse` failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let top = String::from_utf8_lossy(&out.stdout);
assert_eq!(
fs::canonicalize(top.trim()).expect("toplevel exists"),
fs::canonicalize(&repo).expect("repo exists"),
"`-C a -C b` did not land in a/b"
);
}
#[test]
fn git_options_before_the_command_reach_git() {
let dir = TempDir::new("git-opts");
let out = slu(
&dir.0,
&[
"--no-pager",
"-c",
"sluuz.probe=yes",
"config",
"sluuz.probe",
],
);
assert!(
out.status.success(),
"`slu --no-pager -c … config` failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
String::from_utf8_lossy(&out.stdout).trim(),
"yes",
"the `-c` value never reached git"
);
}
#[cfg(unix)]
#[test]
fn sync_never_runs_more_than_eight_fetches_at_once() {
use std::os::unix::fs::PermissionsExt;
let dir = TempDir::new("fetches");
let (bin, running, log, tree) = (
dir.0.join("bin"),
dir.0.join("running"),
dir.0.join("log"),
dir.0.join("tree"),
);
fs::create_dir_all(&bin).expect("bin dir");
fs::create_dir_all(&running).expect("running dir");
let fake_git = format!(
"#!/bin/sh\n\
case \" $* \" in *\" fetch \"*) ;; *) exit 0 ;; esac\n\
: > '{running}/'$$\n\
ls '{running}' | wc -l >> '{log}'\n\
sleep 0.2\n\
rm -f '{running}/'$$\n",
running = running.display(),
log = log.display(),
);
let git = bin.join("git");
fs::write(&git, fake_git).expect("write the fake git");
fs::set_permissions(&git, fs::Permissions::from_mode(0o755)).expect("make it runnable");
const REPOS: usize = 24;
for i in 0..REPOS {
fs::create_dir_all(tree.join(format!("r{i}/.git"))).expect("repo dir");
}
let path = format!(
"{}:{}",
bin.display(),
std::env::var("PATH").expect("PATH is set")
);
let out = Command::new(env!("CARGO_BIN_EXE_slu"))
.arg("sync")
.arg(&tree)
.env("PATH", path)
.env("RAYON_NUM_THREADS", "32")
.output()
.expect("run slu");
assert!(
out.status.success(),
"`slu sync` failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let seen: Vec<usize> = fs::read_to_string(&log)
.expect("the fake git was never asked to fetch")
.lines()
.map(|l| l.trim().parse().expect("a count"))
.collect();
assert_eq!(seen.len(), REPOS, "every repo gets exactly one fetch");
let most = seen.iter().copied().max().unwrap_or(0);
assert!(
most <= 8,
"{most} fetches ran at once, which is how a server's MaxStartups gets tripped"
);
assert!(most > 1, "the fetches ran one at a time, so a tree is slow");
}