mod common;
use common::{missing, Repo};
use std::process::{Command, Stdio};
fn trust_and_install(r: &Repo) {
for verb in ["trust", "init"] {
let out = Command::new(env!("CARGO_BIN_EXE_amont"))
.arg(verb)
.current_dir(&r.dir)
.stdin(Stdio::null())
.output()
.expect("amont");
assert!(
out.status.success(),
"amont {verb}: {}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
}
}
fn runs(r: &Repo) -> usize {
std::fs::read_to_string(r.dir.join("gate.log"))
.map(|s| s.len())
.unwrap_or(0)
}
fn commit(r: &Repo, msg: &str) -> (bool, String) {
let out = r.git(&["commit", "-q", "-m", msg]);
(
out.status.success(),
format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
),
)
}
fn gated_repo() -> Repo {
let r = Repo::new();
r.stage("gate.js", "require('fs').appendFileSync('gate.log','x')\n");
r.stage(
"amont.conf",
"pre-commit suite *.txt block node gate.js\n",
);
r.commit("chore: the gate");
trust_and_install(&r);
r.stage("a.txt", "hello\n");
r
}
const SKIP: &str = "passed on this exact tree earlier — not repeating it here";
#[test]
fn a_gate_that_passed_before_a_refused_message_is_not_repeated() {
if missing("node") {
return;
}
let r = gated_repo();
let (ok, out) = commit(&r, "just a message with no type");
assert!(!ok, "commit-msg was meant to refuse: {out}");
assert_eq!(runs(&r), 1, "the gate ran once");
assert!(!out.contains(SKIP), "nothing was vouched for yet: {out}");
let (ok, out) = commit(&r, "feat: through the gate");
assert!(ok, "{out}");
assert!(out.contains(SKIP), "the reuse is said out loud: {out}");
assert_eq!(runs(&r), 1, "the retry repeated nothing");
let head = String::from_utf8_lossy(&r.git(&["rev-parse", "HEAD"]).stdout)
.trim()
.to_string();
let note = r.git(&["notes", "--ref", "amont-gate", "show", &head]);
assert!(
note.status.success(),
"the retried commit has no gate stamp"
);
assert!(
String::from_utf8_lossy(¬e.stdout).contains("suite"),
"the stamp names the gate"
);
}
#[test]
fn a_changed_tree_runs_the_gate_again() {
if missing("node") {
return;
}
let r = gated_repo();
let (ok, _) = commit(&r, "just a message with no type");
assert!(!ok);
r.stage("a.txt", "hello again\n");
let (ok, out) = commit(&r, "feat: more");
assert!(ok, "{out}");
assert!(!out.contains(SKIP), "new content is not vouched for: {out}");
assert_eq!(runs(&r), 2);
}
#[test]
fn a_re_commit_of_a_stamped_tree_does_not_repeat_the_gate() {
if missing("node") {
return;
}
let r = gated_repo();
let (ok, out) = commit(&r, "feat: first try");
assert!(ok, "{out}");
assert_eq!(runs(&r), 1);
assert!(r.git(&["reset", "-q", "--soft", "HEAD~1"]).status.success());
let (ok, out) = commit(&r, "feat: same content, new commit");
assert!(ok, "{out}");
assert!(
out.contains(SKIP),
"the tree note vouches for the re-commit: {out}"
);
assert_eq!(runs(&r), 1);
}
#[test]
fn the_switch_turns_reuse_off() {
if missing("node") {
return;
}
let r = gated_repo();
r.git(&["config", "amont.commitStamps", "false"]);
let (ok, _) = commit(&r, "just a message with no type");
assert!(!ok);
let (ok, out) = commit(&r, "feat: through the gate");
assert!(ok, "{out}");
assert!(!out.contains(SKIP), "reuse is off: {out}");
assert_eq!(runs(&r), 2);
}
#[test]
fn a_failed_gate_is_not_vouched_for() {
if missing("node") {
return;
}
let r = Repo::new();
r.stage(
"gate.js",
"require('fs').appendFileSync('gate.log','x'); process.exit(require('fs').existsSync('pass') ? 0 : 1)\n",
);
r.stage(
"amont.conf",
"pre-commit suite *.txt block node gate.js\n",
);
r.commit("chore: the gate");
trust_and_install(&r);
r.stage("a.txt", "hello\n");
let (ok, _) = commit(&r, "feat: blocked");
assert!(!ok, "the gate was meant to block");
assert_eq!(runs(&r), 1);
r.write("pass", "");
let (ok, out) = commit(&r, "feat: through");
assert!(ok, "{out}");
assert!(!out.contains(SKIP), "a rejection is not a stamp: {out}");
assert_eq!(runs(&r), 2);
}