Files
orchestra/internal/herdr/tmux_test.go
T
kami a0209a2951 Keep the launch dump out of the session's git status
The .orchestra directory showed up as untracked work in the very worktree whose
own launch context said "uncommitted changes: false". It would have polluted
the quality gate, the review diff, and the agent's reading of git status. A
.gitignore of "*" inside the directory ignores it including itself. The
worker's done marker lives there too and had the same problem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 22:57:39 +04:00

128 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package herdr
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
func TestTmuxBackendStartsCapturesPromptsAndKillsClaude(t *testing.T) {
if testing.Short() {
t.Skip("requires tmux")
}
dir := t.TempDir()
harness := filepath.Join(dir, "fake-claude")
script := "#!/bin/sh\nprintf ' ready\\n'\nwhile IFS= read -r line; do printf 'GOT:%s\\n' \"$line\"; done\n"
if err := os.WriteFile(harness, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
b := NewTmuxBackend(filepath.Join(t.TempDir(), "tmux.sock"), harness)
if err := b.Check(context.Background()); err != nil {
t.Fatal(err)
}
s, err := b.StartAgent(context.Background(), dir, dir, "", "claude", "tmux-backend-test")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = b.Kill(context.Background(), s) })
if s.Worktree != dir || s.Harness != "claude" || !strings.Contains(s.PaneID, ":") || !strings.Contains(s.PaneID, ".") {
t.Fatalf("unexpected session: %+v", s)
}
if err := b.Prompt(context.Background(), s.PaneID, "hello from Orchestra", 0); err != nil {
t.Fatal(err)
}
deadline := time.Now().Add(2 * time.Second)
for {
capture, err := b.PaneCapture(context.Background(), s, "recent")
if err != nil {
t.Fatal(err)
}
if strings.Contains(capture, "GOT:hello from Orchestra") {
break
}
if time.Now().After(deadline) {
t.Fatalf("prompt was not captured: %q", capture)
}
time.Sleep(20 * time.Millisecond)
}
for _, line := range []string{"/clear", "@HANDOFF.md"} {
if err := b.SendText(context.Background(), s, line); err != nil {
t.Fatal(err)
}
if err := b.SendKeys(context.Background(), s, []string{"ENTER"}); err != nil {
t.Fatal(err)
}
}
deadline = time.Now().Add(2 * time.Second)
for {
capture, err := b.PaneCapture(context.Background(), s, "recent")
if err != nil {
t.Fatal(err)
}
if strings.Contains(capture, "GOT:/clear") && strings.Contains(capture, "GOT:@HANDOFF.md") {
break
}
if time.Now().After(deadline) {
t.Fatalf("Claude rollover lines were not captured: %q", capture)
}
time.Sleep(20 * time.Millisecond)
}
if status, err := b.AgentStatus(context.Background(), s); err != nil || status != "idle" {
t.Fatalf("status=%q err=%v", status, err)
}
if err := b.ReleaseAgent(context.Background(), s, "claude"); err != nil {
t.Fatal(err)
}
if err := b.Kill(context.Background(), s); err != nil {
t.Fatal(err)
}
if _, err := b.PaneCapture(context.Background(), s, "recent"); err == nil {
t.Fatal("killed tmux session remained readable")
}
}
func TestTmuxBackendRefusesUnverifiedHarnesses(t *testing.T) {
b := NewTmuxBackend("test", "true")
if _, err := b.StartAgent(context.Background(), "", t.TempDir(), "", "codex", "task"); err == nil {
t.Fatal("tmux backend accepted Codex before its terminal behavior was implemented")
}
}
func TestTmuxSessionNameKeepsCollisionResistantSuffix(t *testing.T) {
a := tmuxSessionName(strings.Repeat("same-prefix", 10) + "-one")
b := tmuxSessionName(strings.Repeat("same-prefix", 10) + "-two")
if a == b || len(a) > 64 || len(b) > 64 {
t.Fatalf("unsafe tmux session names %q %q", a, b)
}
}
// The launch dump is Orchestra's scratch space, not the session's work. A
// worktree that starts dirty pollutes the gate, the review diff, and the
// agent's own reading of `git status`.
func TestWriteLaunchContextLeavesTheWorktreeClean(t *testing.T) {
dir := t.TempDir()
for _, args := range [][]string{{"init"}, {"config", "user.email", "t@t"}, {"config", "user.name", "t"}, {"commit", "--allow-empty", "-m", "init"}} {
if out, err := exec.Command("git", append([]string{"-C", dir}, args...)...).CombinedOutput(); err != nil {
t.Fatalf("git %v: %v: %s", args, err, out)
}
}
if err := WriteLaunchContext(dir, "the instruction"); err != nil {
t.Fatal(err)
}
b, err := os.ReadFile(filepath.Join(dir, LaunchContextFile))
if err != nil || string(b) != "the instruction" {
t.Fatalf("launch context = %q, err %v", b, err)
}
out, err := exec.Command("git", "-C", dir, "status", "--short").CombinedOutput()
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(string(out)) != "" {
t.Fatalf("worktree is dirty after a launch dump:\n%s", out)
}
}