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>
274 lines
6.5 KiB
Go
274 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestVersionCommand(t *testing.T) {
|
|
// Save original stdout
|
|
oldStdout := os.Stdout
|
|
defer func() { os.Stdout = oldStdout }()
|
|
|
|
t.Run("plain text version output", func(t *testing.T) {
|
|
// Create a pipe to capture output
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pipe: %v", err)
|
|
}
|
|
os.Stdout = w
|
|
jsonOutput = false
|
|
|
|
// Run version command
|
|
versionCmd.Run(versionCmd, []string{})
|
|
|
|
// Close writer and read output
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
// Verify output contains version info
|
|
if !strings.Contains(output, "bd version") {
|
|
t.Errorf("Expected output to contain 'bd version', got: %s", output)
|
|
}
|
|
if !strings.Contains(output, Version) {
|
|
t.Errorf("Expected output to contain version %s, got: %s", Version, output)
|
|
}
|
|
})
|
|
|
|
t.Run("json version output", func(t *testing.T) {
|
|
// Create a pipe to capture output
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pipe: %v", err)
|
|
}
|
|
os.Stdout = w
|
|
jsonOutput = true
|
|
|
|
// Run version command
|
|
versionCmd.Run(versionCmd, []string{})
|
|
|
|
// Close writer and read output
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
// Parse JSON output
|
|
var result map[string]string
|
|
if err := json.Unmarshal([]byte(output), &result); err != nil {
|
|
t.Fatalf("Failed to parse JSON output: %v", err)
|
|
}
|
|
|
|
// Verify JSON contains version and build
|
|
if result["version"] != Version {
|
|
t.Errorf("Expected version %s, got %s", Version, result["version"])
|
|
}
|
|
if result["build"] == "" {
|
|
t.Error("Expected build field to be non-empty")
|
|
}
|
|
})
|
|
|
|
// Restore default
|
|
jsonOutput = false
|
|
}
|
|
|
|
func TestResolveCommitHash(t *testing.T) {
|
|
// Save original Commit value
|
|
origCommit := Commit
|
|
defer func() { Commit = origCommit }()
|
|
|
|
t.Run("returns ldflag value when set", func(t *testing.T) {
|
|
testCommit := "abc123def456"
|
|
Commit = testCommit
|
|
result := resolveCommitHash()
|
|
if result != testCommit {
|
|
t.Errorf("Expected %q, got %q", testCommit, result)
|
|
}
|
|
})
|
|
|
|
t.Run("returns empty string when not set", func(t *testing.T) {
|
|
Commit = ""
|
|
result := resolveCommitHash()
|
|
// Result could be from git or empty - just verify it doesn't panic
|
|
if result == "" || len(result) >= 7 {
|
|
// Either empty or looks like a git hash
|
|
return
|
|
}
|
|
t.Errorf("Unexpected result format: %q", result)
|
|
})
|
|
}
|
|
|
|
func TestResolveBranch(t *testing.T) {
|
|
// Save original Branch value
|
|
origBranch := Branch
|
|
defer func() { Branch = origBranch }()
|
|
|
|
t.Run("returns ldflag value when set", func(t *testing.T) {
|
|
testBranch := "main"
|
|
Branch = testBranch
|
|
result := resolveBranch()
|
|
if result != testBranch {
|
|
t.Errorf("Expected %q, got %q", testBranch, result)
|
|
}
|
|
})
|
|
|
|
t.Run("returns empty string or git branch when not set", func(t *testing.T) {
|
|
Branch = ""
|
|
result := resolveBranch()
|
|
// Result could be from git or empty - just verify it doesn't panic
|
|
if result == "" || result == "main" || strings.Contains(result, "detached") {
|
|
return
|
|
}
|
|
t.Logf("Got branch: %q", result)
|
|
})
|
|
}
|
|
|
|
func TestVersionOutputWithCommitAndBranch(t *testing.T) {
|
|
// Save original values
|
|
oldStdout := os.Stdout
|
|
origCommit := Commit
|
|
origBranch := Branch
|
|
defer func() {
|
|
os.Stdout = oldStdout
|
|
Commit = origCommit
|
|
Branch = origBranch
|
|
}()
|
|
|
|
t.Run("text output includes commit and branch when available", func(t *testing.T) {
|
|
Commit = "7e709405b38c472d8cbc996c7cd26df7e3b438d0"
|
|
Branch = "main"
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pipe: %v", err)
|
|
}
|
|
os.Stdout = w
|
|
jsonOutput = false
|
|
|
|
versionCmd.Run(versionCmd, []string{})
|
|
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
// Should contain both branch and commit
|
|
if !strings.Contains(output, "main@") {
|
|
t.Errorf("Expected output to contain 'main@', got: %s", output)
|
|
}
|
|
if !strings.Contains(output, "7e70940") { // first 7 chars of commit
|
|
t.Errorf("Expected output to contain commit hash, got: %s", output)
|
|
}
|
|
})
|
|
|
|
t.Run("json output includes commit and branch when available", func(t *testing.T) {
|
|
Commit = "7e709405b38c472d8cbc996c7cd26df7e3b438d0"
|
|
Branch = "main"
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pipe: %v", err)
|
|
}
|
|
os.Stdout = w
|
|
jsonOutput = true
|
|
|
|
versionCmd.Run(versionCmd, []string{})
|
|
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
var result map[string]string
|
|
if err := json.Unmarshal([]byte(output), &result); err != nil {
|
|
t.Fatalf("Failed to parse JSON output: %v", err)
|
|
}
|
|
|
|
if result["commit"] != Commit {
|
|
t.Errorf("Expected commit %q, got %q", Commit, result["commit"])
|
|
}
|
|
if result["branch"] != Branch {
|
|
t.Errorf("Expected branch %q, got %q", Branch, result["branch"])
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestVersionFlag(t *testing.T) {
|
|
// Reset global state for test isolation
|
|
ensureCleanGlobalState(t)
|
|
|
|
// Ensure cleanup after running cobra commands
|
|
t.Cleanup(func() {
|
|
resetCommandContext()
|
|
})
|
|
|
|
// Save original stdout
|
|
oldStdout := os.Stdout
|
|
defer func() { os.Stdout = oldStdout }()
|
|
|
|
t.Run("--version flag", func(t *testing.T) {
|
|
// Create a pipe to capture output
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pipe: %v", err)
|
|
}
|
|
os.Stdout = w
|
|
|
|
// Set version flag and run root command
|
|
rootCmd.SetArgs([]string{"--version"})
|
|
rootCmd.Execute()
|
|
|
|
// Close writer and read output
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
// Verify output contains version info
|
|
if !strings.Contains(output, "bd version") {
|
|
t.Errorf("Expected output to contain 'bd version', got: %s", output)
|
|
}
|
|
if !strings.Contains(output, Version) {
|
|
t.Errorf("Expected output to contain version %s, got: %s", Version, output)
|
|
}
|
|
|
|
// Reset args
|
|
rootCmd.SetArgs(nil)
|
|
})
|
|
|
|
t.Run("-v shorthand", func(t *testing.T) {
|
|
// Create a pipe to capture output
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("Failed to create pipe: %v", err)
|
|
}
|
|
os.Stdout = w
|
|
|
|
// Set version flag and run root command
|
|
rootCmd.SetArgs([]string{"-v"})
|
|
rootCmd.Execute()
|
|
|
|
// Close writer and read output
|
|
w.Close()
|
|
var buf bytes.Buffer
|
|
buf.ReadFrom(r)
|
|
output := buf.String()
|
|
|
|
// Verify output contains version info
|
|
if !strings.Contains(output, "bd version") {
|
|
t.Errorf("Expected output to contain 'bd version', got: %s", output)
|
|
}
|
|
if !strings.Contains(output, Version) {
|
|
t.Errorf("Expected output to contain version %s, got: %s", Version, output)
|
|
}
|
|
|
|
// Reset args
|
|
rootCmd.SetArgs(nil)
|
|
})
|
|
}
|