mod common;
use common::Repo;
use std::io::Write;
use std::process::{Command, Stdio};
fn pre_push(r: &Repo, tip: &str, base: &str) -> (i32, String) {
let line = format!("refs/heads/feat/x {tip} refs/heads/feat/x {base}\n");
let mut child = Command::new(env!("CARGO_BIN_EXE_amont"))
.arg("--hooks-dir")
.arg(r.path(".git/hooks"))
.arg("pre-push")
.current_dir(&r.dir)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn");
child
.stdin
.as_mut()
.expect("stdin")
.write_all(line.as_bytes())
.expect("write");
let out = child.wait_with_output().expect("wait");
(
out.status.code().unwrap_or(-1),
format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
),
)
}
fn rev(r: &Repo, what: &str) -> String {
String::from_utf8_lossy(&r.git(&["rev-parse", what]).stdout)
.trim()
.to_string()
}
fn diverging(r: &Repo) -> (String, String) {
r.stage(
"package.json",
"{ \"name\": \"x\", \"scripts\": { \"test\": \"node check.js\" } }\n",
);
r.stage("check.js", "process.exit(0);\n");
r.commit("chore: seed");
let base = rev(r, "HEAD");
r.git(&["checkout", "-q", "-b", "feat/x"]);
r.stage("check.js", "process.exit(1);\n");
r.commit("feat: break the suite");
let tip = rev(r, "HEAD");
r.write("check.js", "process.exit(0);\n");
(tip, base)
}
#[test]
fn by_default_it_tests_the_working_tree_and_says_so() {
let r = Repo::new();
let (tip, base) = diverging(&r);
let (code, out) = pre_push(&r, &tip, &base);
assert_eq!(code, 0, "the working tree passes:\n{out}");
assert!(
out.contains("WORKING TREE"),
"it must say what it tested:\n{out}"
);
}
#[test]
fn opted_in_it_tests_the_pushed_commits() {
let r = Repo::new();
let (tip, base) = diverging(&r);
r.git(&["config", "amont.testPushedTree", "true"]);
let (code, out) = pre_push(&r, &tip, &base);
assert_ne!(
code, 0,
"the pushed commit is broken and this let it through:\n{out}"
);
assert!(
!out.contains("WORKING TREE"),
"it should not be warning about the tree it did not use:\n{out}"
);
}
#[test]
fn gits_own_spelling_of_true_turns_it_on() {
let r = Repo::new();
let (tip, base) = diverging(&r);
r.git(&["config", "amont.testPushedTree", "on"]);
let (code, out) = pre_push(&r, &tip, &base);
assert_ne!(code, 0, "`on` did not enable the pushed-tree run:\n{out}");
assert!(
!out.contains("WORKING TREE"),
"it fell back to the working tree:\n{out}"
);
}
#[test]
fn the_working_tree_is_untouched_and_nothing_is_left_behind() {
let r = Repo::new();
let (tip, base) = diverging(&r);
r.git(&["config", "amont.testPushedTree", "true"]);
let before = std::fs::read_to_string(r.path("check.js")).expect("read");
let (_, _) = pre_push(&r, &tip, &base);
assert_eq!(
std::fs::read_to_string(r.path("check.js")).expect("read"),
before,
"the push modified the working tree"
);
let worktrees = r.git(&["worktree", "list"]);
let listed = String::from_utf8_lossy(&worktrees.stdout);
assert_eq!(
listed.lines().count(),
1,
"a worktree outlived the push:\n{listed}"
);
}