fix: improve integration test reliability (#13)
- Add custom types config after bd init in daemon tests - Replace fixed sleeps with poll-based waiting in tmux tests - Skip beads integration test for JSONL-only repos Fixes flaky test failures in parallel execution.
This commit is contained in:
committed by
Steve Yegge
parent
775af2973d
commit
c8c765a239
@@ -88,9 +88,9 @@ func TestWrapError(t *testing.T) {
|
|||||||
b := New("/test")
|
b := New("/test")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
stderr string
|
stderr string
|
||||||
wantErr error
|
wantErr error
|
||||||
wantNil bool
|
wantNil bool
|
||||||
}{
|
}{
|
||||||
{"not a beads repository", ErrNotARepo, false},
|
{"not a beads repository", ErrNotARepo, false},
|
||||||
{"No .beads directory found", ErrNotARepo, false},
|
{"No .beads directory found", ErrNotARepo, false},
|
||||||
@@ -127,7 +127,6 @@ func TestIntegration(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk up to find .beads
|
|
||||||
dir := cwd
|
dir := cwd
|
||||||
for {
|
for {
|
||||||
if _, err := os.Stat(filepath.Join(dir, ".beads")); err == nil {
|
if _, err := os.Stat(filepath.Join(dir, ".beads")); err == nil {
|
||||||
@@ -140,6 +139,11 @@ func TestIntegration(t *testing.T) {
|
|||||||
dir = parent
|
dir = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dir, ".beads", "beads.db")
|
||||||
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||||
|
t.Skip("no beads.db found (JSONL-only repo)")
|
||||||
|
}
|
||||||
|
|
||||||
b := New(dir)
|
b := New(dir)
|
||||||
|
|
||||||
// Sync database with JSONL before testing to avoid "Database out of sync" errors.
|
// Sync database with JSONL before testing to avoid "Database out of sync" errors.
|
||||||
@@ -201,10 +205,10 @@ func TestIntegration(t *testing.T) {
|
|||||||
// TestParseMRFields tests parsing MR fields from issue descriptions.
|
// TestParseMRFields tests parsing MR fields from issue descriptions.
|
||||||
func TestParseMRFields(t *testing.T) {
|
func TestParseMRFields(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
issue *Issue
|
issue *Issue
|
||||||
wantNil bool
|
wantNil bool
|
||||||
wantFields *MRFields
|
wantFields *MRFields
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nil issue",
|
name: "nil issue",
|
||||||
@@ -521,8 +525,8 @@ author: someone
|
|||||||
target: main`,
|
target: main`,
|
||||||
},
|
},
|
||||||
fields: &MRFields{
|
fields: &MRFields{
|
||||||
Branch: "polecat/Capable/gt-ghi",
|
Branch: "polecat/Capable/gt-ghi",
|
||||||
Target: "integration/epic",
|
Target: "integration/epic",
|
||||||
CloseReason: "merged",
|
CloseReason: "merged",
|
||||||
},
|
},
|
||||||
want: `branch: polecat/Capable/gt-ghi
|
want: `branch: polecat/Capable/gt-ghi
|
||||||
@@ -1032,10 +1036,10 @@ func TestParseAgentBeadID(t *testing.T) {
|
|||||||
// Parseable but not valid agent roles (IsAgentSessionBead will reject)
|
// Parseable but not valid agent roles (IsAgentSessionBead will reject)
|
||||||
{"gt-abc123", "", "abc123", "", true}, // Parses as town-level but not valid role
|
{"gt-abc123", "", "abc123", "", true}, // Parses as town-level but not valid role
|
||||||
// Other prefixes (bd-, hq-)
|
// Other prefixes (bd-, hq-)
|
||||||
{"bd-mayor", "", "mayor", "", true}, // bd prefix town-level
|
{"bd-mayor", "", "mayor", "", true}, // bd prefix town-level
|
||||||
{"bd-beads-witness", "beads", "witness", "", true}, // bd prefix rig-level singleton
|
{"bd-beads-witness", "beads", "witness", "", true}, // bd prefix rig-level singleton
|
||||||
{"bd-beads-polecat-pearl", "beads", "polecat", "pearl", true}, // bd prefix rig-level named
|
{"bd-beads-polecat-pearl", "beads", "polecat", "pearl", true}, // bd prefix rig-level named
|
||||||
{"hq-mayor", "", "mayor", "", true}, // hq prefix town-level
|
{"hq-mayor", "", "mayor", "", true}, // hq prefix town-level
|
||||||
// Truly invalid patterns
|
// Truly invalid patterns
|
||||||
{"x-mayor", "", "", "", false}, // Prefix too short (1 char)
|
{"x-mayor", "", "", "", false}, // Prefix too short (1 char)
|
||||||
{"abcd-mayor", "", "", "", false}, // Prefix too long (4 chars)
|
{"abcd-mayor", "", "", "", false}, // Prefix too long (4 chars)
|
||||||
|
|||||||
@@ -259,25 +259,36 @@ func writeTownJSON(t *testing.T, path string, data interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testTmuxSessionWithStubAgent tests that a tmux session runs the stub agent.
|
func pollForOutput(t *testing.T, sessionName, expected string, timeout time.Duration) (string, bool) {
|
||||||
|
t.Helper()
|
||||||
|
deadline := time.Now().Add(timeout)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
output := captureTmuxPane(t, sessionName, 50)
|
||||||
|
if strings.Contains(output, expected) {
|
||||||
|
return output, true
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return captureTmuxPane(t, sessionName, 50), false
|
||||||
|
}
|
||||||
|
|
||||||
func testTmuxSessionWithStubAgent(t *testing.T, tmpDir, stubAgentPath, rigName string) {
|
func testTmuxSessionWithStubAgent(t *testing.T, tmpDir, stubAgentPath, rigName string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
sessionName := fmt.Sprintf("gt-test-%d", time.Now().UnixNano())
|
sessionName := fmt.Sprintf("gt-test-pid%d-%d", os.Getpid(), time.Now().UnixNano())
|
||||||
workDir := tmpDir
|
workDir := tmpDir
|
||||||
|
|
||||||
// Cleanup session on exit
|
exec.Command("tmux", "kill-session", "-t", sessionName).Run()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
exec.Command("tmux", "kill-session", "-t", sessionName).Run()
|
exec.Command("tmux", "kill-session", "-t", sessionName).Run()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create tmux session
|
|
||||||
cmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName, "-c", workDir)
|
cmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName, "-c", workDir)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("Failed to create tmux session: %v", err)
|
t.Fatalf("Failed to create tmux session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set environment variables
|
|
||||||
envVars := map[string]string{
|
envVars := map[string]string{
|
||||||
"GT_ROLE": "polecat",
|
"GT_ROLE": "polecat",
|
||||||
"GT_POLECAT": "test-polecat",
|
"GT_POLECAT": "test-polecat",
|
||||||
@@ -291,42 +302,39 @@ func testTmuxSessionWithStubAgent(t *testing.T, tmpDir, stubAgentPath, rigName s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the stub agent command
|
|
||||||
agentCmd := fmt.Sprintf("%s --test-mode --stub", stubAgentPath)
|
agentCmd := fmt.Sprintf("%s --test-mode --stub", stubAgentPath)
|
||||||
cmd = exec.Command("tmux", "send-keys", "-t", sessionName, agentCmd, "Enter")
|
cmd = exec.Command("tmux", "send-keys", "-t", sessionName, agentCmd, "Enter")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("Failed to send keys: %v", err)
|
t.Fatalf("Failed to send keys: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, started := waitForTmuxOutputContains(t, sessionName, "STUB_AGENT_STARTED", 12*time.Second)
|
output, found := pollForOutput(t, sessionName, "STUB_AGENT_STARTED", 12*time.Second)
|
||||||
if !started {
|
if !found {
|
||||||
t.Skipf("stub agent output not detected; tmux capture unreliable. Output:\n%s", output)
|
t.Skipf("stub agent output not detected; tmux capture unreliable. Output:
|
||||||
|
%s", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify environment variables were visible to agent
|
|
||||||
if !strings.Contains(output, "GT_ROLE: polecat") {
|
if !strings.Contains(output, "GT_ROLE: polecat") {
|
||||||
t.Logf("Warning: GT_ROLE not visible in agent output (tmux env may not propagate to subshell)")
|
t.Logf("Warning: GT_ROLE not visible in agent output (tmux env may not propagate to subshell)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a question and verify response
|
|
||||||
cmd = exec.Command("tmux", "send-keys", "-t", sessionName, "ping", "Enter")
|
cmd = exec.Command("tmux", "send-keys", "-t", sessionName, "ping", "Enter")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Fatalf("Failed to send ping: %v", err)
|
t.Fatalf("Failed to send ping: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, pong := waitForTmuxOutputContains(t, sessionName, "STUB_AGENT_ANSWER: pong", 6*time.Second)
|
output, found = pollForOutput(t, sessionName, "STUB_AGENT_ANSWER: pong", 6*time.Second)
|
||||||
if !pong {
|
if !found {
|
||||||
t.Errorf("Expected 'pong' response, got:\n%s", output)
|
t.Errorf("Expected 'pong' response, got:\n%s", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send exit command
|
|
||||||
cmd = exec.Command("tmux", "send-keys", "-t", sessionName, "exit", "Enter")
|
cmd = exec.Command("tmux", "send-keys", "-t", sessionName, "exit", "Enter")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
t.Logf("Warning: failed to send exit: %v", err)
|
t.Logf("Warning: failed to send exit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, exited := waitForTmuxOutputContains(t, sessionName, "STUB_AGENT_EXITING", 3*time.Second)
|
output, found = pollForOutput(t, sessionName, "STUB_AGENT_EXITING", 3*time.Second)
|
||||||
if !exited {
|
if !found {
|
||||||
t.Logf("Note: Agent may have exited before capture. Output:\n%s", output)
|
t.Logf("Note: Agent may have exited before capture. Output:\n%s", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ func TestGetRoleConfigForIdentity_PrefersTownRoleBead(t *testing.T) {
|
|||||||
|
|
||||||
townRoot := t.TempDir()
|
townRoot := t.TempDir()
|
||||||
runBd(t, townRoot, "init", "--quiet", "--prefix", "hq")
|
runBd(t, townRoot, "init", "--quiet", "--prefix", "hq")
|
||||||
runBd(t, townRoot, "config", "set", "types.custom", "agent,role,rig,convoy,slot")
|
|
||||||
|
runBd(t, townRoot, "config", "set", "types.custom", "agent,role,rig,convoy,event")
|
||||||
|
|
||||||
|
runBd(t, townRoot, "config", "set", "types.custom", "agent,role,rig,convoy,event")
|
||||||
|
|
||||||
// Create canonical role bead.
|
// Create canonical role bead.
|
||||||
runBd(t, townRoot, "create",
|
runBd(t, townRoot, "create",
|
||||||
@@ -62,7 +65,10 @@ func TestGetRoleConfigForIdentity_FallsBackToLegacyRoleBead(t *testing.T) {
|
|||||||
|
|
||||||
townRoot := t.TempDir()
|
townRoot := t.TempDir()
|
||||||
runBd(t, townRoot, "init", "--quiet", "--prefix", "gt")
|
runBd(t, townRoot, "init", "--quiet", "--prefix", "gt")
|
||||||
runBd(t, townRoot, "config", "set", "types.custom", "agent,role,rig,convoy,slot")
|
|
||||||
|
runBd(t, townRoot, "config", "set", "types.custom", "agent,role,rig,convoy,event")
|
||||||
|
|
||||||
|
runBd(t, townRoot, "config", "set", "types.custom", "agent,role,rig,convoy,event")
|
||||||
|
|
||||||
// Only legacy role bead exists.
|
// Only legacy role bead exists.
|
||||||
runBd(t, townRoot, "create",
|
runBd(t, townRoot, "create",
|
||||||
|
|||||||
Reference in New Issue
Block a user