#![allow(
clippy::expect_used,
clippy::unwrap_used,
clippy::tests_outside_test_module,
reason = "integration test: fail-fast unwrap/expect are idiomatic, and test fns live at crate root by construction"
)]
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
const BIN: &str = env!("CARGO_BIN_EXE_doctrine");
fn git(dir: &Path, args: &[&str]) -> String {
let out = Command::new("git")
.arg("-C")
.arg(dir)
.args(args)
.output()
.expect("spawn git");
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn init_repo(dir: &Path) {
std::fs::create_dir_all(dir).unwrap();
git(dir, &["init", "-q", "-b", "main"]);
git(dir, &["config", "user.email", "t@example.com"]);
git(dir, &["config", "user.name", "Test"]);
std::fs::create_dir_all(dir.join(".doctrine")).unwrap();
std::fs::write(dir.join("a.txt"), "hello").unwrap();
git(dir, &["add", "."]);
git(dir, &["commit", "-q", "-m", "base"]);
}
fn add_fork(src: &Path, holder: &Path, branch: &str) -> PathBuf {
let base = git(src, &["rev-parse", "HEAD"]);
let fork = holder.join("fork");
git(
src,
&[
"worktree",
"add",
"-b",
branch,
fork.to_str().unwrap(),
&base,
],
);
fork
}
fn stamp_marker(root: &Path) {
let dir = root.join(".doctrine/state/dispatch");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("worker"), b"").unwrap();
}
fn marker_exists(root: &Path) -> bool {
root.join(".doctrine/state/dispatch/worker").exists()
}
fn run(cwd: &Path, worker: Option<bool>, args: &[&str]) -> Output {
let mut cmd = Command::new(BIN);
cmd.args(args).current_dir(cwd);
match worker {
Some(true) => {
cmd.env("DOCTRINE_WORKER", "1");
}
Some(false) | None => {
cmd.env_remove("DOCTRINE_WORKER");
}
}
cmd.output().expect("spawn doctrine")
}
fn stdout(out: &Output) -> String {
String::from_utf8(out.stdout.clone()).expect("utf8 stdout")
}
fn stderr(out: &Output) -> String {
String::from_utf8(out.stderr.clone()).expect("utf8 stderr")
}
fn branch_exists(src: &Path, branch: &str) -> bool {
Command::new("git")
.arg("-C")
.arg(src)
.args(["rev-parse", "--verify", "--quiet", branch])
.output()
.expect("spawn git")
.status
.success()
}
fn worktree_registered(src: &Path, dir: &Path) -> bool {
git(src, &["worktree", "list"]).contains(&dir.to_string_lossy().into_owned())
}
#[test]
fn fork_happy_path_solo_and_worker() {
let src = tempfile::tempdir().unwrap();
init_repo(src.path());
let base = git(src.path(), &["rev-parse", "HEAD"]);
let holder = tempfile::tempdir().unwrap();
let solo_dir = holder.path().join("solo");
let out = run(
src.path(),
None,
&[
"worktree",
"fork",
"--base",
&base,
"--branch",
"wkr-solo",
"--dir",
solo_dir.to_str().unwrap(),
],
);
assert!(
out.status.success(),
"solo fork must succeed; stderr: {}",
stderr(&out)
);
assert!(
stdout(&out).contains("CARGO_TARGET_DIR="),
"env contract on stdout; got: {}",
stdout(&out)
);
assert!(
stdout(&out).contains("wt/wkr-solo"),
"contract maps target to wt/<branch>; got: {}",
stdout(&out)
);
assert!(
stderr(&out).contains("forked wkr-solo"),
"human status on stderr; got: {}",
stderr(&out)
);
assert!(
worktree_registered(src.path(), &solo_dir),
"worktree exists"
);
assert!(branch_exists(src.path(), "wkr-solo"), "branch exists");
assert_eq!(
git(&solo_dir, &["rev-parse", "HEAD"]),
base,
"fork branch sits at B"
);
assert!(!marker_exists(&solo_dir), "solo fork has no marker");
let wkr_dir = holder.path().join("wkr");
let out = run(
src.path(),
None,
&[
"worktree",
"fork",
"--base",
&base,
"--branch",
"wkr-worker",
"--dir",
wkr_dir.to_str().unwrap(),
"--worker",
],
);
assert!(
out.status.success(),
"worker fork must succeed; stderr: {}",
stderr(&out)
);
assert!(
marker_exists(&wkr_dir),
"worker fork stamps the marker before returning"
);
assert_eq!(
git(&wkr_dir, &["rev-parse", "HEAD"]),
base,
"worker fork at B"
);
}
#[test]
fn fork_pre_add_refusals_leave_no_fork() {
let src = tempfile::tempdir().unwrap();
init_repo(src.path());
let base = git(src.path(), &["rev-parse", "HEAD"]);
let holder = tempfile::tempdir().unwrap();
let existing = holder.path().join("exists");
std::fs::create_dir_all(&existing).unwrap();
let out = run(
src.path(),
None,
&[
"worktree",
"fork",
"--base",
&base,
"--branch",
"r-dir",
"--dir",
existing.to_str().unwrap(),
],
);
assert!(!out.status.success(), "dir-exists must refuse");
assert!(stderr(&out).contains("already exists"), "names dir-exists");
assert!(!branch_exists(src.path(), "r-dir"), "no branch created");
let _ = add_fork(src.path(), holder.path(), "taken");
let dir_b = holder.path().join("b");
let out = run(
src.path(),
None,
&[
"worktree",
"fork",
"--base",
&base,
"--branch",
"taken",
"--dir",
dir_b.to_str().unwrap(),
],
);
assert!(!out.status.success(), "branch-exists must refuse");
assert!(stderr(&out).contains("branch taken"), "names branch-exists");
assert!(!dir_b.exists(), "no fork dir created");
let dir_c = holder.path().join("c");
let out = run(
src.path(),
None,
&[
"worktree",
"fork",
"--base",
"deadbeefdeadbeef",
"--branch",
"r-base",
"--dir",
dir_c.to_str().unwrap(),
],
);
assert!(!out.status.success(), "B-not-a-commit must refuse");
assert!(stderr(&out).contains("not a commit"), "names bad base");
assert!(!branch_exists(src.path(), "r-base"), "no branch created");
assert!(!dir_c.exists(), "no fork dir created");
}
#[test]
fn fork_rolls_back_on_provision_failure() {
let src = tempfile::tempdir().unwrap();
init_repo(src.path());
std::fs::write(src.path().join(".worktreeinclude"), ".doctrine/state/**\n").unwrap();
git(src.path(), &["add", ".worktreeinclude"]);
git(src.path(), &["commit", "-q", "-m", "bad allowlist"]);
let holder = tempfile::tempdir().unwrap();
let dir = holder.path().join("rb");
let head_before = git(src.path(), &["rev-parse", "HEAD"]);
let out = run(
src.path(),
None,
&[
"worktree",
"fork",
"--base",
&head_before,
"--branch",
"wkr-rb",
"--dir",
dir.to_str().unwrap(),
],
);
assert!(
!out.status.success(),
"provision failure must fail the fork; stdout: {}",
stdout(&out)
);
assert!(
!worktree_registered(src.path(), &dir),
"worktree rolled back (gone); worktree list: {}",
git(src.path(), &["worktree", "list"])
);
assert!(
!branch_exists(src.path(), "wkr-rb"),
"branch rolled back (gone)"
);
assert!(!dir.exists(), "fork dir reaped");
}
#[test]
fn fork_refused_under_worker_mode() {
let src = tempfile::tempdir().unwrap();
init_repo(src.path());
let base = git(src.path(), &["rev-parse", "HEAD"]);
let holder = tempfile::tempdir().unwrap();
let fork = add_fork(src.path(), holder.path(), "wkr-guard");
stamp_marker(&fork);
let target = holder.path().join("nope1");
let out = run(
&fork,
None,
&[
"worktree",
"fork",
"--base",
&base,
"--branch",
"child1",
"--dir",
target.to_str().unwrap(),
],
);
assert!(
!out.status.success(),
"fork refused from a marked linked worktree; stdout: {}",
stdout(&out)
);
assert!(
stderr(&out).contains("fork"),
"refusal names the verb; stderr: {}",
stderr(&out)
);
assert!(!target.exists(), "refused fork creates nothing");
let target = holder.path().join("nope2");
let out = run(
src.path(),
Some(true),
&[
"worktree",
"fork",
"--base",
&base,
"--branch",
"child2",
"--dir",
target.to_str().unwrap(),
],
);
assert!(
!out.status.success(),
"fork refused when DOCTRINE_WORKER set; stdout: {}",
stdout(&out)
);
assert!(
stderr(&out).contains("DOCTRINE_WORKER"),
"env-on-nonlinked carries the dual-cause; stderr: {}",
stderr(&out)
);
assert!(!target.exists(), "refused fork creates nothing");
}