test: cover daemon autostart paths

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
Jordan Hubbard
2025-12-26 10:45:42 -04:00
parent 6790fb2f2d
commit 1f5ed550fe
2 changed files with 363 additions and 20 deletions

View File

@@ -31,6 +31,19 @@ var (
daemonStartFailures int
)
var (
executableFn = os.Executable
execCommandFn = exec.Command
openFileFn = os.OpenFile
findProcessFn = os.FindProcess
removeFileFn = os.Remove
configureDaemonProcessFn = configureDaemonProcess
waitForSocketReadinessFn = waitForSocketReadiness
startDaemonProcessFn = startDaemonProcess
isDaemonRunningFn = isDaemonRunning
sendStopSignalFn = sendStopSignal
)
// shouldAutoStartDaemon checks if daemon auto-start is enabled
func shouldAutoStartDaemon() bool {
// Check BEADS_NO_DAEMON first (escape hatch for single-user workflows)
@@ -53,7 +66,6 @@ func shouldAutoStartDaemon() bool {
return config.GetBool("auto-start-daemon") // Defaults to true
}
// restartDaemonForVersionMismatch stops the old daemon and starts a new one
// Returns true if restart was successful
func restartDaemonForVersionMismatch() bool {
@@ -67,17 +79,17 @@ func restartDaemonForVersionMismatch() bool {
// Check if daemon is running and stop it
forcedKill := false
if isRunning, pid := isDaemonRunning(pidFile); isRunning {
if isRunning, pid := isDaemonRunningFn(pidFile); isRunning {
debug.Logf("stopping old daemon (PID %d)", pid)
process, err := os.FindProcess(pid)
process, err := findProcessFn(pid)
if err != nil {
debug.Logf("failed to find process: %v", err)
return false
}
// Send stop signal
if err := sendStopSignal(process); err != nil {
if err := sendStopSignalFn(process); err != nil {
debug.Logf("failed to signal daemon: %v", err)
return false
}
@@ -85,14 +97,14 @@ func restartDaemonForVersionMismatch() bool {
// Wait for daemon to stop, then force kill
for i := 0; i < daemonShutdownAttempts; i++ {
time.Sleep(daemonShutdownPollInterval)
if isRunning, _ := isDaemonRunning(pidFile); !isRunning {
if isRunning, _ := isDaemonRunningFn(pidFile); !isRunning {
debug.Logf("old daemon stopped successfully")
break
}
}
// Force kill if still running
if isRunning, _ := isDaemonRunning(pidFile); isRunning {
if isRunning, _ := isDaemonRunningFn(pidFile); isRunning {
debug.Logf("force killing old daemon")
_ = process.Kill()
forcedKill = true
@@ -101,19 +113,19 @@ func restartDaemonForVersionMismatch() bool {
// Clean up stale socket and PID file after force kill or if not running
if forcedKill || !isDaemonRunningQuiet(pidFile) {
_ = os.Remove(socketPath)
_ = os.Remove(pidFile)
_ = removeFileFn(socketPath)
_ = removeFileFn(pidFile)
}
// Start new daemon with current binary version
exe, err := os.Executable()
exe, err := executableFn()
if err != nil {
debug.Logf("failed to get executable path: %v", err)
return false
}
args := []string{"daemon", "--start"}
cmd := exec.Command(exe, args...)
cmd := execCommandFn(exe, args...)
cmd.Env = append(os.Environ(), "BD_DAEMON_FOREGROUND=1")
// Set working directory to database directory so daemon finds correct DB
@@ -121,9 +133,9 @@ func restartDaemonForVersionMismatch() bool {
cmd.Dir = filepath.Dir(dbPath)
}
configureDaemonProcess(cmd)
configureDaemonProcessFn(cmd)
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
devNull, err := openFileFn(os.DevNull, os.O_RDWR, 0)
if err == nil {
cmd.Stdin = devNull
cmd.Stdout = devNull
@@ -140,7 +152,7 @@ func restartDaemonForVersionMismatch() bool {
go func() { _ = cmd.Wait() }()
// Wait for daemon to be ready using shared helper
if waitForSocketReadiness(socketPath, 5*time.Second) {
if waitForSocketReadinessFn(socketPath, 5*time.Second) {
debug.Logf("new daemon started successfully")
return true
}
@@ -153,7 +165,7 @@ func restartDaemonForVersionMismatch() bool {
// isDaemonRunningQuiet checks if daemon is running without output
func isDaemonRunningQuiet(pidFile string) bool {
isRunning, _ := isDaemonRunning(pidFile)
isRunning, _ := isDaemonRunningFn(pidFile)
return isRunning
}
@@ -185,7 +197,7 @@ func tryAutoStartDaemon(socketPath string) bool {
}
socketPath = determineSocketPath(socketPath)
return startDaemonProcess(socketPath)
return startDaemonProcessFn(socketPath)
}
func debugLog(msg string, args ...interface{}) {
@@ -269,21 +281,21 @@ func determineSocketPath(socketPath string) string {
}
func startDaemonProcess(socketPath string) bool {
binPath, err := os.Executable()
binPath, err := executableFn()
if err != nil {
binPath = os.Args[0]
}
args := []string{"daemon", "--start"}
cmd := exec.Command(binPath, args...)
cmd := execCommandFn(binPath, args...)
setupDaemonIO(cmd)
if dbPath != "" {
cmd.Dir = filepath.Dir(dbPath)
}
configureDaemonProcess(cmd)
configureDaemonProcessFn(cmd)
if err := cmd.Start(); err != nil {
recordDaemonStartFailure()
debugLog("failed to start daemon: %v", err)
@@ -292,7 +304,7 @@ func startDaemonProcess(socketPath string) bool {
go func() { _ = cmd.Wait() }()
if waitForSocketReadiness(socketPath, 5*time.Second) {
if waitForSocketReadinessFn(socketPath, 5*time.Second) {
recordDaemonStartSuccess()
return true
}
@@ -306,7 +318,7 @@ func startDaemonProcess(socketPath string) bool {
}
func setupDaemonIO(cmd *exec.Cmd) {
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, 0)
devNull, err := openFileFn(os.DevNull, os.O_RDWR, 0)
if err == nil {
cmd.Stdout = devNull
cmd.Stderr = devNull