summaryrefslogtreecommitdiff
path: root/internal/executor/claude_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/claude_test.go')
-rw-r--r--internal/executor/claude_test.go103
1 files changed, 79 insertions, 24 deletions
diff --git a/internal/executor/claude_test.go b/internal/executor/claude_test.go
index 9bb873f..02d1b2e 100644
--- a/internal/executor/claude_test.go
+++ b/internal/executor/claude_test.go
@@ -173,8 +173,11 @@ func TestClaudeRunner_BuildArgs_PreamblePrepended(t *testing.T) {
if len(args) < 2 || args[0] != "-p" {
t.Fatalf("expected -p as first arg, got: %v", args)
}
- if !strings.HasPrefix(args[1], planningPreamble) {
- t.Errorf("instructions should start with planning preamble")
+ if !strings.HasPrefix(args[1], "## Runtime Environment") {
+ t.Errorf("instructions should start with planning preamble, got prefix: %q", args[1][:min(len(args[1]), 20)])
+ }
+ if !strings.Contains(args[1], "$CLAUDOMATOR_PROJECT_DIR") {
+ t.Errorf("preamble should mention $CLAUDOMATOR_PROJECT_DIR")
}
if !strings.HasSuffix(args[1], "fix the bug") {
t.Errorf("instructions should end with original instructions")
@@ -329,7 +332,7 @@ func TestExecOnce_NoGoroutineLeak_OnNaturalExit(t *testing.T) {
runtime.Gosched()
baseline := runtime.NumGoroutine()
- if err := r.execOnce(context.Background(), []string{}, "", e); err != nil {
+ if err := r.execOnce(context.Background(), []string{}, "", "", e); err != nil {
t.Fatalf("execOnce failed: %v", err)
}
@@ -350,16 +353,24 @@ func TestExecOnce_NoGoroutineLeak_OnNaturalExit(t *testing.T) {
func initGitRepo(t *testing.T, dir string) {
t.Helper()
cmds := [][]string{
- {"git", "-C", dir, "init"},
- {"git", "-C", dir, "config", "user.email", "test@test"},
- {"git", "-C", dir, "config", "user.name", "test"},
- {"git", "-C", dir, "commit", "--allow-empty", "-m", "init"},
+ {"git", "-c", "safe.directory=*", "-C", dir, "init", "-b", "main"},
+ {"git", "-c", "safe.directory=*", "-C", dir, "config", "user.email", "test@test"},
+ {"git", "-c", "safe.directory=*", "-C", dir, "config", "user.name", "test"},
}
for _, args := range cmds {
if out, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
t.Fatalf("%v: %v\n%s", args, err, out)
}
}
+ if err := os.WriteFile(filepath.Join(dir, "init.txt"), []byte("init"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if out, err := exec.Command("git", "-c", "safe.directory=*", "-C", dir, "add", ".").CombinedOutput(); err != nil {
+ t.Fatalf("git add: %v\n%s", err, out)
+ }
+ if out, err := exec.Command("git", "-c", "safe.directory=*", "-C", dir, "commit", "-m", "init").CombinedOutput(); err != nil {
+ t.Fatalf("git commit: %v\n%s", err, out)
+ }
}
func TestSandboxCloneSource_PrefersLocalRemote(t *testing.T) {
@@ -409,6 +420,13 @@ func TestSetupSandbox_ClonesGitRepo(t *testing.T) {
}
t.Cleanup(func() { os.RemoveAll(sandbox) })
+ // Force sandbox to master if it cloned as main
+ exec.Command("git", gitSafe("-C", sandbox, "checkout", "master")...).Run()
+
+ // Debug sandbox
+ logOut, _ := exec.Command("git", "-C", sandbox, "log", "-1").CombinedOutput()
+ fmt.Printf("DEBUG: sandbox log: %s\n", string(logOut))
+
// Verify sandbox is a git repo with at least one commit.
out, err := exec.Command("git", "-C", sandbox, "log", "--oneline").Output()
if err != nil {
@@ -434,31 +452,63 @@ func TestSetupSandbox_InitialisesNonGitDir(t *testing.T) {
}
}
-func TestTeardownSandbox_UncommittedChanges_ReturnsError(t *testing.T) {
- src := t.TempDir()
- initGitRepo(t, src)
- sandbox, err := setupSandbox(src)
+func TestTeardownSandbox_AutocommitsChanges(t *testing.T) {
+ // Create a bare repo as origin so push succeeds.
+ bare := t.TempDir()
+ if out, err := exec.Command("git", "init", "--bare", bare).CombinedOutput(); err != nil {
+ t.Fatalf("git init bare: %v\n%s", err, out)
+ }
+
+ // Create a sandbox directly.
+ sandbox := t.TempDir()
+ initGitRepo(t, sandbox)
+ if out, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "remote", "add", "origin", bare).CombinedOutput(); err != nil {
+ t.Fatalf("git remote add: %v\n%s", err, out)
+ }
+ // Initial push to establish origin/main
+ if out, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "push", "origin", "main").CombinedOutput(); err != nil {
+ t.Fatalf("git push initial: %v\n%s", err, out)
+ }
+
+ // Capture startHEAD
+ headOut, err := exec.Command("git", "-c", "safe.directory=*", "-C", sandbox, "rev-parse", "HEAD").Output()
if err != nil {
- t.Fatalf("setupSandbox: %v", err)
+ t.Fatalf("rev-parse HEAD: %v", err)
}
- t.Cleanup(func() { os.RemoveAll(sandbox) })
+ startHEAD := strings.TrimSpace(string(headOut))
// Leave an uncommitted file in the sandbox.
- if err := os.WriteFile(filepath.Join(sandbox, "dirty.txt"), []byte("oops"), 0644); err != nil {
+ if err := os.WriteFile(filepath.Join(sandbox, "dirty.txt"), []byte("autocommit me"), 0644); err != nil {
t.Fatal(err)
}
- logger := slog.New(slog.NewTextHandler(io.Discard, nil))
- err = teardownSandbox(src, sandbox, logger)
- if err == nil {
- t.Fatal("expected error for uncommitted changes, got nil")
+ logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))
+ execRecord := &storage.Execution{}
+
+ err = teardownSandbox("", sandbox, startHEAD, logger, execRecord)
+ if err != nil {
+ t.Fatalf("expected autocommit to succeed, got error: %v", err)
+ }
+
+ // Sandbox should be removed after successful autocommit and push.
+ if _, statErr := os.Stat(sandbox); !os.IsNotExist(statErr) {
+ t.Error("sandbox should have been removed after successful autocommit and push")
}
- if !strings.Contains(err.Error(), "uncommitted changes") {
- t.Errorf("expected 'uncommitted changes' in error, got: %v", err)
+
+ // Verify the commit exists in the bare repo.
+ out, err := exec.Command("git", "-C", bare, "log", "-1", "--pretty=%B").Output()
+ if err != nil {
+ t.Fatalf("git log in bare repo: %v", err)
}
- // Sandbox should be preserved (not removed) on error.
- if _, statErr := os.Stat(sandbox); os.IsNotExist(statErr) {
- t.Error("sandbox was removed despite error; should be preserved for debugging")
+ if !strings.Contains(string(out), "chore: autocommit uncommitted changes") {
+ t.Errorf("expected autocommit message in log, got: %q", string(out))
+ }
+
+ // Verify the commit was captured in execRecord.
+ if len(execRecord.Commits) == 0 {
+ t.Error("expected at least one commit in execRecord")
+ } else if !strings.Contains(execRecord.Commits[0].Message, "chore: autocommit uncommitted changes") {
+ t.Errorf("unexpected commit message: %q", execRecord.Commits[0].Message)
}
}
@@ -471,8 +521,13 @@ func TestTeardownSandbox_CleanSandboxWithNoNewCommits_RemovesSandbox(t *testing.
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
+ execRecord := &storage.Execution{}
+
+ headOut, _ := exec.Command("git", "-C", sandbox, "rev-parse", "HEAD").Output()
+ startHEAD := strings.TrimSpace(string(headOut))
+
// Sandbox has no new commits beyond origin; teardown should succeed and remove it.
- if err := teardownSandbox(src, sandbox, logger); err != nil {
+ if err := teardownSandbox(src, sandbox, startHEAD, logger, execRecord); err != nil {
t.Fatalf("teardownSandbox: %v", err)
}
if _, statErr := os.Stat(sandbox); !os.IsNotExist(statErr) {