test(infra): agent_launch_worktree + agent_cleanup_worktree

Three tests for launch:
- creates worktree dir + branch off master
- ResetIfExists=true on existing branch+worktree succeeds
- returns Error when required args missing

Two tests for cleanup:
- removes worktree dir and branch after launch
- tolerates missing worktree/branch (cleanup called twice)

Uses initDummyRepo helper with isolated GIT_CONFIG_GLOBAL=/dev/null
so tests do not pick up user's signing/template config. Echo stub
fallback (claude not in PATH) keeps tests hermetic.
This commit is contained in:
2026-05-18 18:24:13 +02:00
parent a87c992cd9
commit 1b6d7940da
2 changed files with 175 additions and 0 deletions
@@ -0,0 +1,57 @@
package infra
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestAgentCleanupWorktree_RemovesWorktreeAndBranch(t *testing.T) {
repo := initDummyRepo(t)
wt := filepath.Join(t.TempDir(), "wt-cleanup")
log := filepath.Join(t.TempDir(), "claude.log")
res := AgentLaunchWorktree(WorktreeLaunchConfig{
RepoRoot: repo,
Branch: "auto/cleanup-test",
WorktreePath: wt,
Prompt: "x",
LogPath: log,
})
if res.Error != "" {
t.Fatalf("launch failed: %s", res.Error)
}
if err := AgentCleanupWorktree(repo, "auto/cleanup-test", wt, res.PID); err != nil {
t.Fatalf("cleanup returned error: %v", err)
}
// Worktree dir should be gone.
if _, err := os.Stat(wt); !os.IsNotExist(err) {
t.Fatalf("worktree dir should be removed, stat err=%v", err)
}
// Branch should be gone.
out, _ := exec.Command("git", "-C", repo, "branch", "--list", "auto/cleanup-test").CombinedOutput()
if strings.Contains(string(out), "auto/cleanup-test") {
t.Fatalf("branch should be deleted, got: %s", out)
}
}
func TestAgentCleanupWorktree_TolerantToMissing(t *testing.T) {
repo := initDummyRepo(t)
// Worktree path that never existed; branch that never existed; PID 0.
err := AgentCleanupWorktree(repo, "no-such-branch", filepath.Join(t.TempDir(), "ghost-wt"), 0)
if err == nil {
// All three steps failed individually but PID=0 skipped the kill, so
// killErr==nil and the combined check returns nil. That's the
// expected "tolerant" behaviour.
return
}
// If some git versions surface a different error path, just ensure we
// didn't panic and the error message is informative.
if !strings.Contains(err.Error(), "cleanup failed") {
t.Fatalf("unexpected error shape: %v", err)
}
}
@@ -0,0 +1,118 @@
package infra
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// initDummyRepo creates a tiny git repo with a master branch and one commit so
// that `git worktree add ... master` can succeed.
func initDummyRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
runIn := func(name string, args ...string) {
cmd := exec.Command(name, args...)
cmd.Dir = dir
// Detach from any global config that might inject signing/templates.
cmd.Env = append(os.Environ(),
"GIT_CONFIG_GLOBAL=/dev/null",
"GIT_CONFIG_SYSTEM=/dev/null",
"GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=t@t",
"GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=t@t",
)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%s %v: %v\n%s", name, args, err, out)
}
}
runIn("git", "init", "-b", "master")
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi"), 0o644); err != nil {
t.Fatal(err)
}
runIn("git", "add", "README.md")
runIn("git", "commit", "-m", "init")
return dir
}
func TestAgentLaunchWorktree_CreatesWorktreeAndBranch(t *testing.T) {
repo := initDummyRepo(t)
wt := filepath.Join(t.TempDir(), "wt-0001")
log := filepath.Join(t.TempDir(), "claude.log")
res := AgentLaunchWorktree(WorktreeLaunchConfig{
RepoRoot: repo,
Branch: "auto/test-0001",
WorktreePath: wt,
Prompt: "hello",
LogPath: log,
})
if res.Error != "" {
t.Fatalf("unexpected error: %s", res.Error)
}
if res.PID <= 0 {
t.Fatalf("expected PID>0, got %d", res.PID)
}
if _, err := os.Stat(wt); err != nil {
t.Fatalf("worktree dir missing: %v", err)
}
// Branch should exist in the main repo's refs.
out, err := exec.Command("git", "-C", repo, "branch", "--list", "auto/test-0001").CombinedOutput()
if err != nil || !strings.Contains(string(out), "auto/test-0001") {
t.Fatalf("branch not created: err=%v out=%s", err, out)
}
if _, err := os.Stat(log); err != nil {
t.Fatalf("log file missing: %v", err)
}
// Cleanup (best-effort, not asserted here).
_ = AgentCleanupWorktree(repo, "auto/test-0001", wt, res.PID)
}
func TestAgentLaunchWorktree_ResetIfExists(t *testing.T) {
repo := initDummyRepo(t)
wt := filepath.Join(t.TempDir(), "wt-reset")
log := filepath.Join(t.TempDir(), "claude.log")
// First launch.
res1 := AgentLaunchWorktree(WorktreeLaunchConfig{
RepoRoot: repo,
Branch: "auto/reset-test",
WorktreePath: wt,
Prompt: "first",
LogPath: log,
})
if res1.Error != "" {
t.Fatalf("first launch failed: %s", res1.Error)
}
// Second launch with ResetIfExists=true should succeed and recreate.
res2 := AgentLaunchWorktree(WorktreeLaunchConfig{
RepoRoot: repo,
Branch: "auto/reset-test",
WorktreePath: wt,
Prompt: "second",
LogPath: log,
ResetIfExists: true,
})
if res2.Error != "" {
t.Fatalf("reset launch failed: %s", res2.Error)
}
if _, err := os.Stat(wt); err != nil {
t.Fatalf("worktree dir missing after reset: %v", err)
}
_ = AgentCleanupWorktree(repo, "auto/reset-test", wt, res2.PID)
}
func TestAgentLaunchWorktree_MissingArgs(t *testing.T) {
res := AgentLaunchWorktree(WorktreeLaunchConfig{})
if res.Error == "" {
t.Fatal("expected error for empty config, got none")
}
}