crates/cli/tests/sec08.rs 5.8 KiB 134 lines Raw
1 #![forbid(unsafe_code)]2 //! SEC-08: already-landed parity for pr merge / merge.3 use std::path::{Path, PathBuf};4 use std::process::Command;5 use std::sync::atomic::{AtomicU64, Ordering};6 7 static CTR: AtomicU64 = AtomicU64::new(0);8 9 fn bin() -> String {10     env!("CARGO_BIN_EXE_f0rge").to_string()11 }12 13 fn tmp_repo(tag: &str) -> PathBuf {14     let n = CTR.fetch_add(1, Ordering::Relaxed);15     let d = std::env::temp_dir().join(format!("f0rge-sec08-{}-{}-{n}", std::process::id(), tag));16     let _ = std::fs::remove_dir_all(&d);17     std::fs::create_dir_all(&d).unwrap();18     d19 }20 21 fn git(repo: &Path, args: &[&str]) {22     let o = Command::new("git").args(args).current_dir(repo).output().unwrap();23     assert!(o.status.success(), "git {args:?}: {}", String::from_utf8_lossy(&o.stderr));24 }25 26 fn f0rge(repo: &Path, args: &[&str]) -> (i32, String, String) {27     let o = Command::new(bin()).args(args).current_dir(repo).output().unwrap();28     (29         o.status.code().unwrap_or(-1),30         String::from_utf8_lossy(&o.stdout).into_owned(),31         String::from_utf8_lossy(&o.stderr).into_owned(),32     )33 }34 35 fn merges(repo: &Path) -> Vec<PathBuf> {36     std::fs::read_dir(repo.join(".forge").join("merges"))37         .unwrap()38         .flatten()39         .map(|e| e.path())40         .filter(|p| p.extension().map(|x| x == "md").unwrap_or(false))41         .collect()42 }43 44 #[test]45 fn sec08_double_merge_is_already_landed() {46     let repo = tmp_repo("dbl");47     git(&repo, &["init"]);48     git(&repo, &["config", "user.email", "[email protected]"]);49     git(&repo, &["config", "user.name", "t"]);50     git(&repo, &["config", "commit.gpgsign", "false"]);51     std::fs::write(repo.join("a.txt"), "line1\nline2\n").unwrap();52     git(&repo, &["add", "-A"]);53     git(&repo, &["commit", "-m", "init"]);54     git(&repo, &["checkout", "-b", "main"]);55     let (c, _, e) = f0rge(&repo, &["init"]);56     assert_eq!(c, 0, "init: {e}");57     git(&repo, &["checkout", "-b", "feat"]);58     std::fs::write(repo.join("a.txt"), "line1-feat\nline2\n").unwrap();59     git(&repo, &["add", "-A"]);60     git(&repo, &["commit", "-m", "feat"]);61     git(&repo, &["checkout", "main"]);62     let (c, o, e) = f0rge(&repo, &["pr", "create", "--base", "main", "--head", "feat", "--title", "T", "--json"]);63     assert_eq!(c, 0, "create: {e}");64     let v: serde_json::Value = serde_json::from_str(o.lines().last().unwrap().trim()).unwrap();65     let id = v["databaseId"].as_str().unwrap().to_string();66     // first land via pr merge.67     let (c, o, e) = f0rge(&repo, &["pr", "merge", &id]);68     assert_eq!(c, 0, "first pr merge: {e} {o}");69     assert_eq!(merges(&repo).len(), 1);70     let prmd = std::fs::read_to_string(repo.join(".forge").join("prs").join(&id).join("pr.md")).unwrap();71     assert!(prmd.contains("state: merged"), "pr flipped:\n{prmd}");72     assert!(prmd.contains("ci: pass"), "ci pass:\n{prmd}");73     let rec = std::fs::read_to_string(&merges(&repo)[0]).unwrap();74     assert!(rec.contains("patch: "), "record pins hash:\n{rec}");75     // second pr merge: already landed, exit 0.76     let (c, o, e) = f0rge(&repo, &["pr", "merge", &id]);77     assert_eq!(c, 0, "second pr merge exit 0, got {c} err: {e}");78     assert!(o.contains("already landed"), "out: {o}");79     assert!(o.contains(&id), "out names pr: {o}");80     assert_eq!(e.lines().count(), 0, "no stderr: {e}");81     assert_eq!(merges(&repo).len(), 1, "no duplicate record");82     // merge on canonical folder: already landed, exit 0.83     let folder = repo.join(".forge").join("prs").join(&id).to_string_lossy().into_owned();84     let (c, o, e) = f0rge(&repo, &["merge", &folder]);85     assert_eq!(c, 0, "merge folder repeat: {e}");86     assert!(o.contains("already landed"), "out: {o}");87     assert_eq!(merges(&repo).len(), 1);88     // merge branch repeat: already landed, exit 0.89     let (c, o, e) = f0rge(&repo, &["merge", "feat"]);90     assert_eq!(c, 0, "merge branch repeat: {e}");91     assert!(o.contains("already landed"), "out: {o}");92     assert_eq!(merges(&repo).len(), 1);93     let _ = std::fs::remove_dir_all(&repo);94 }95 #[test]96 fn sec08_branch_canonical_repeat_is_landed() {97     // #15 x SEC-08: branch merge records the canonical PR uuid; repeat is98     // already-landed (exit 0), names it, writes no second record.99     let repo = tmp_repo("canon");100     git(&repo, &["init"]);101     git(&repo, &["config", "user.email", "[email protected]"]);102     git(&repo, &["config", "user.name", "t"]);103     git(&repo, &["config", "commit.gpgsign", "false"]);104     std::fs::write(repo.join("a.txt"), "line1\nline2\n").unwrap();105     git(&repo, &["add", "-A"]);106     git(&repo, &["commit", "-m", "init"]);107     git(&repo, &["checkout", "-b", "main"]);108     let (c, _, e) = f0rge(&repo, &["init"]);109     assert_eq!(c, 0, "init: {e}");110     git(&repo, &["add", "-A"]);111     git(&repo, &["commit", "-m", "forge"]);112     git(&repo, &["checkout", "-b", "feat"]);113     std::fs::write(repo.join("a.txt"), "line1-feat\nline2\n").unwrap();114     git(&repo, &["add", "-A"]);115     git(&repo, &["commit", "-m", "feat"]);116     let (c, o, e) = f0rge(&repo, &["pr", "create", "--base", "main", "--head", "feat", "--title", "T", "--json"]);117     assert_eq!(c, 0, "create: {e}");118     let v: serde_json::Value = serde_json::from_str(o.lines().last().unwrap().trim()).unwrap();119     let id = v["databaseId"].as_str().unwrap().to_string();120     git(&repo, &["add", "-A"]);121     git(&repo, &["commit", "-m", "pr"]);122     git(&repo, &["checkout", "main"]);123     let (c, o, e) = f0rge(&repo, &["merge", "feat"]);124     assert_eq!(c, 0, "merge: {e}");125     assert!(o.trim() == format!("merged {id}"), "canonical: {o}");126     assert_eq!(merges(&repo).len(), 1);127     let (c, o, e) = f0rge(&repo, &["merge", "feat"]);128     assert_eq!(c, 0, "repeat exit 0, got {c} err: {e}");129     assert!(o.contains("already landed") && o.contains(&id), "names canonical: {o}");130     assert_eq!(e.lines().count(), 0, "no stderr: {e}");131     assert_eq!(merges(&repo).len(), 1, "no duplicate record");132     let _ = std::fs::remove_dir_all(&repo);133 }134