Files
beads/cmd/bd/test_repo_beads_guard_test.go
Steve Yegge f91d4fa975 Refactor: Introduce CommandContext to consolidate global variables (bd-qobn)
This addresses the code smell of 20+ global variables in main.go by:

1. Creating CommandContext struct in context.go that groups all runtime state:
   - Configuration (DBPath, Actor, JSONOutput, etc.)
   - Runtime state (Store, DaemonClient, HookRunner, etc.)
   - Auto-flush/import state
   - Version tracking
   - Profiling handles

2. Adding accessor functions (getStore, getActor, getDaemonClient, etc.)
   that provide backward-compatible access to the state while allowing
   gradual migration to CommandContext.

3. Updating direct_mode.go to demonstrate the migration pattern using
   accessor functions instead of direct global access.

4. Adding test isolation helpers (ensureCleanGlobalState, enableTestModeGlobals)
   to prevent test interference when multiple tests manipulate global state.

Benefits:
- Reduces global count from 20+ to 1 (cmdCtx)
- Better testability (can inject mock contexts)
- Clearer state ownership (all state in one place)
- Thread safety (mutexes grouped with the data they protect)

Note: Two pre-existing test failures (TestTrackBdVersion_*) are unrelated to
this change and fail both with and without these modifications.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 16:29:28 -08:00

123 lines
2.6 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"
)
// Guardrail: ensure the cmd/bd test suite does not touch the real repo .beads state.
// Disable with BEADS_TEST_GUARD_DISABLE=1 (useful when running tests while actively using beads).
func TestMain(m *testing.M) {
// Enable test mode that forces accessor functions to use legacy globals.
// This ensures backward compatibility with tests that manipulate globals directly.
enableTestModeGlobals()
if os.Getenv("BEADS_TEST_GUARD_DISABLE") != "" {
os.Exit(m.Run())
}
repoRoot := findRepoRoot()
if repoRoot == "" {
os.Exit(m.Run())
}
repoBeadsDir := filepath.Join(repoRoot, ".beads")
if _, err := os.Stat(repoBeadsDir); err != nil {
os.Exit(m.Run())
}
watch := []string{
"beads.db",
"beads.db-wal",
"beads.db-shm",
"beads.db-journal",
"issues.jsonl",
"beads.jsonl",
"metadata.json",
"interactions.jsonl",
"deletions.jsonl",
"molecules.jsonl",
"daemon.lock",
"daemon.pid",
"bd.sock",
}
before := snapshotFiles(repoBeadsDir, watch)
code := m.Run()
after := snapshotFiles(repoBeadsDir, watch)
if diff := diffSnapshots(before, after); diff != "" {
fmt.Fprintf(os.Stderr, "ERROR: test suite modified repo .beads state:\n%s\n", diff)
if code == 0 {
code = 1
}
}
os.Exit(code)
}
type fileSnap struct {
exists bool
size int64
modUnix int64
}
func snapshotFiles(dir string, names []string) map[string]fileSnap {
out := make(map[string]fileSnap, len(names))
for _, name := range names {
p := filepath.Join(dir, name)
info, err := os.Stat(p)
if err != nil {
out[name] = fileSnap{exists: false}
continue
}
out[name] = fileSnap{exists: true, size: info.Size(), modUnix: info.ModTime().UnixNano()}
}
return out
}
func diffSnapshots(before, after map[string]fileSnap) string {
var out string
for name, b := range before {
a := after[name]
if b.exists != a.exists {
out += fmt.Sprintf("- %s: exists %v → %v\n", name, b.exists, a.exists)
continue
}
if !b.exists {
continue
}
if b.size != a.size || b.modUnix != a.modUnix {
out += fmt.Sprintf("- %s: size %d → %d, mtime %s → %s\n",
name,
b.size,
a.size,
time.Unix(0, b.modUnix).UTC().Format(time.RFC3339Nano),
time.Unix(0, a.modUnix).UTC().Format(time.RFC3339Nano),
)
}
}
return out
}
func findRepoRoot() string {
wd, err := os.Getwd()
if err != nil {
return ""
}
for i := 0; i < 25; i++ {
if _, err := os.Stat(filepath.Join(wd, "go.mod")); err == nil {
return wd
}
parent := filepath.Dir(wd)
if parent == wd {
break
}
wd = parent
}
return ""
}