feat(daemon): add BD_SOCKET env var for test isolation (#914)
Add BD_SOCKET environment variable support to override daemon socket path, enabling parallel test isolation via t.TempDir() + t.Setenv(). Changes: - getSocketPath() checks BD_SOCKET first, falls back to dbPath-derived path - getSocketPathForPID() checks BD_SOCKET first (for consistency) - Add daemon_socket_test.go with isolation pattern examples This is a minimal tracer bullet to validate the approach before expanding to full test isolation infrastructure. Backward compatible: default behavior unchanged without env var set.
This commit is contained in:
committed by
GitHub
parent
5dfb838d60
commit
e9e0d7f1e5
@@ -455,9 +455,14 @@ func recordDaemonStartFailure() {
|
|||||||
// No cap needed - backoff is capped at 120s in canRetryDaemonStart
|
// No cap needed - backoff is capped at 120s in canRetryDaemonStart
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSocketPath returns the daemon socket path based on the database location
|
// getSocketPath returns the daemon socket path based on the database location.
|
||||||
|
// If BD_SOCKET env var is set, uses that value instead (enables test isolation).
|
||||||
// Returns local socket path (.beads/bd.sock relative to database)
|
// Returns local socket path (.beads/bd.sock relative to database)
|
||||||
func getSocketPath() string {
|
func getSocketPath() string {
|
||||||
|
// Check environment variable first (enables test isolation)
|
||||||
|
if socketPath := os.Getenv("BD_SOCKET"); socketPath != "" {
|
||||||
|
return socketPath
|
||||||
|
}
|
||||||
return filepath.Join(filepath.Dir(dbPath), "bd.sock")
|
return filepath.Join(filepath.Dir(dbPath), "bd.sock")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,13 @@ func getEnvBool(key string, defaultValue bool) bool {
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSocketPathForPID determines the socket path for a given PID file
|
// getSocketPathForPID determines the socket path for a given PID file.
|
||||||
|
// If BD_SOCKET env var is set, uses that value instead.
|
||||||
func getSocketPathForPID(pidFile string) string {
|
func getSocketPathForPID(pidFile string) string {
|
||||||
|
// Check environment variable first (enables test isolation)
|
||||||
|
if socketPath := os.Getenv("BD_SOCKET"); socketPath != "" {
|
||||||
|
return socketPath
|
||||||
|
}
|
||||||
// Socket is in same directory as PID file
|
// Socket is in same directory as PID file
|
||||||
return filepath.Join(filepath.Dir(pidFile), "bd.sock")
|
return filepath.Join(filepath.Dir(pidFile), "bd.sock")
|
||||||
}
|
}
|
||||||
|
|||||||
75
cmd/bd/daemon_socket_test.go
Normal file
75
cmd/bd/daemon_socket_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSocketPathEnvOverride verifies that BD_SOCKET env var overrides default socket path.
|
||||||
|
func TestSocketPathEnvOverride(t *testing.T) {
|
||||||
|
// Create isolated temp directory
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
customSocket := filepath.Join(tmpDir, "custom.sock")
|
||||||
|
|
||||||
|
// Set environment for isolation
|
||||||
|
t.Setenv("BD_SOCKET", customSocket)
|
||||||
|
|
||||||
|
// Verify getSocketPath returns custom path
|
||||||
|
got := getSocketPath()
|
||||||
|
assert.Equal(t, customSocket, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSocketPathForPIDEnvOverride verifies that BD_SOCKET env var overrides PID-derived path.
|
||||||
|
func TestSocketPathForPIDEnvOverride(t *testing.T) {
|
||||||
|
// Create isolated temp directory
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
customSocket := filepath.Join(tmpDir, "custom.sock")
|
||||||
|
|
||||||
|
// Set environment for isolation
|
||||||
|
t.Setenv("BD_SOCKET", customSocket)
|
||||||
|
|
||||||
|
// Verify getSocketPathForPID returns custom path (ignoring pidFile)
|
||||||
|
pidFile := "/some/other/path/daemon.pid"
|
||||||
|
got := getSocketPathForPID(pidFile)
|
||||||
|
assert.Equal(t, customSocket, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSocketPathDefaultBehavior verifies default behavior when BD_SOCKET is not set.
|
||||||
|
func TestSocketPathDefaultBehavior(t *testing.T) {
|
||||||
|
// Ensure BD_SOCKET is not set (t.Setenv restores after test)
|
||||||
|
t.Setenv("BD_SOCKET", "")
|
||||||
|
|
||||||
|
// Verify getSocketPathForPID derives from PID file path
|
||||||
|
pidFile := "/path/to/.beads/daemon.pid"
|
||||||
|
got := getSocketPathForPID(pidFile)
|
||||||
|
assert.Equal(t, "/path/to/.beads/bd.sock", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDaemonSocketIsolation demonstrates that two test instances can use different sockets.
|
||||||
|
// This is the key pattern for parallel test isolation.
|
||||||
|
func TestDaemonSocketIsolation(t *testing.T) {
|
||||||
|
// Simulate two parallel tests with different socket paths
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sockSuffix string
|
||||||
|
}{
|
||||||
|
{"instance_a", "a.sock"},
|
||||||
|
{"instance_b", "b.sock"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Each sub-test gets isolated socket path in its own temp dir
|
||||||
|
socketPath := filepath.Join(t.TempDir(), tt.sockSuffix)
|
||||||
|
t.Setenv("BD_SOCKET", socketPath)
|
||||||
|
|
||||||
|
got := getSocketPath()
|
||||||
|
assert.Equal(t, socketPath, got)
|
||||||
|
|
||||||
|
// Verify paths are unique per instance
|
||||||
|
assert.Contains(t, got, tt.sockSuffix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
7
go.mod
7
go.mod
@@ -10,9 +10,12 @@ require (
|
|||||||
github.com/charmbracelet/huh v0.8.0
|
github.com/charmbracelet/huh v0.8.0
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
|
github.com/muesli/termenv v0.16.0
|
||||||
github.com/ncruces/go-sqlite3 v0.30.4
|
github.com/ncruces/go-sqlite3 v0.30.4
|
||||||
|
github.com/olebedev/when v1.1.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tetratelabs/wazero v1.11.0
|
github.com/tetratelabs/wazero v1.11.0
|
||||||
golang.org/x/mod v0.31.0
|
golang.org/x/mod v0.31.0
|
||||||
golang.org/x/sys v0.39.0
|
golang.org/x/sys v0.39.0
|
||||||
@@ -34,6 +37,7 @@ require (
|
|||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
@@ -46,11 +50,10 @@ require (
|
|||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/ncruces/julianday v1.0.0 // indirect
|
github.com/ncruces/julianday v1.0.0 // indirect
|
||||||
github.com/olebedev/when v1.1.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pkg/errors v0.8.1 // indirect
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
|||||||
Reference in New Issue
Block a user