- sync_git_test.go: getCurrentBranchOrHEAD now returns 1 value (not 2) - daemon_basics_test.go: getPIDFileForSocket uses dbPath not socketPath - daemon_autostart.go: mark unused socketPath param with underscore These tests were broken by recent refactors that changed function signatures but didn't update the corresponding test files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
327 lines
8.1 KiB
Go
327 lines
8.1 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// TestComputeDaemonParentPID tests the parent PID computation logic
|
|
func TestComputeDaemonParentPID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
envValue string
|
|
expectedPID int
|
|
expectsGetppid bool // whether we expect os.Getppid() to be called
|
|
}{
|
|
{
|
|
name: "BD_DAEMON_FOREGROUND not set",
|
|
envValue: "",
|
|
expectedPID: 0, // Placeholder - will be replaced with actual Getppid()
|
|
expectsGetppid: true,
|
|
},
|
|
{
|
|
name: "BD_DAEMON_FOREGROUND=1",
|
|
envValue: "1",
|
|
expectedPID: 0,
|
|
expectsGetppid: false,
|
|
},
|
|
{
|
|
name: "BD_DAEMON_FOREGROUND=0",
|
|
envValue: "0",
|
|
expectedPID: 0, // Placeholder - will be replaced with actual Getppid()
|
|
expectsGetppid: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Save original env
|
|
oldVal, wasSet := os.LookupEnv("BD_DAEMON_FOREGROUND")
|
|
defer func() {
|
|
if wasSet {
|
|
os.Setenv("BD_DAEMON_FOREGROUND", oldVal)
|
|
} else {
|
|
os.Unsetenv("BD_DAEMON_FOREGROUND")
|
|
}
|
|
}()
|
|
|
|
// Set test env
|
|
if tt.envValue != "" {
|
|
os.Setenv("BD_DAEMON_FOREGROUND", tt.envValue)
|
|
} else {
|
|
os.Unsetenv("BD_DAEMON_FOREGROUND")
|
|
}
|
|
|
|
result := computeDaemonParentPID()
|
|
|
|
if tt.name == "BD_DAEMON_FOREGROUND=1" {
|
|
if result != 0 {
|
|
t.Errorf("computeDaemonParentPID() = %d, want 0", result)
|
|
}
|
|
} else if tt.expectsGetppid {
|
|
// When BD_DAEMON_FOREGROUND is not "1", we should get os.Getppid()
|
|
expectedPID := os.Getppid()
|
|
if result != expectedPID {
|
|
t.Errorf("computeDaemonParentPID() = %d, want %d (os.Getppid())", result, expectedPID)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCheckParentProcessAlive tests parent process alive checking
|
|
func TestCheckParentProcessAlive(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
parentPID int
|
|
expected bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "PID 0 (not tracked)",
|
|
parentPID: 0,
|
|
expected: true,
|
|
description: "Should return true for untracked (PID 0)",
|
|
},
|
|
{
|
|
name: "PID 1 (init/launchd)",
|
|
parentPID: 1,
|
|
expected: true,
|
|
description: "Should return true for init process",
|
|
},
|
|
{
|
|
name: "current process PID",
|
|
parentPID: os.Getpid(),
|
|
expected: true,
|
|
description: "Should return true for current running process",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := checkParentProcessAlive(tt.parentPID)
|
|
if result != tt.expected {
|
|
t.Errorf("checkParentProcessAlive(%d) = %v, want %v (%s)",
|
|
tt.parentPID, result, tt.expected, tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCheckParentProcessAlive_DeadProcess tests with an invalid PID
|
|
func TestCheckParentProcessAlive_InvalidPID(t *testing.T) {
|
|
// Use a very high PID that's unlikely to exist
|
|
invalidPID := 999999
|
|
result := checkParentProcessAlive(invalidPID)
|
|
|
|
// This should return false since the process doesn't exist
|
|
if result == true {
|
|
t.Errorf("checkParentProcessAlive(%d) = true, want false (process should not exist)", invalidPID)
|
|
}
|
|
}
|
|
|
|
// TestGetPIDFileForSocket tests PID file path derivation from dbPath
|
|
// Note: The socketPath parameter is ignored; PID file is always in .beads directory
|
|
func TestGetPIDFileForSocket(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dbPath string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "typical beads directory",
|
|
dbPath: "/home/user/.beads/issues.db",
|
|
expected: "/home/user/.beads/daemon.pid",
|
|
},
|
|
{
|
|
name: "root beads directory",
|
|
dbPath: "/root/.beads/issues.db",
|
|
expected: "/root/.beads/daemon.pid",
|
|
},
|
|
{
|
|
name: "nested project beads",
|
|
dbPath: "/home/user/projects/myapp/.beads/issues.db",
|
|
expected: "/home/user/projects/myapp/.beads/daemon.pid",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Save and restore global dbPath
|
|
oldDbPath := dbPath
|
|
defer func() { dbPath = oldDbPath }()
|
|
|
|
dbPath = tt.dbPath
|
|
result := getPIDFileForSocket("ignored")
|
|
if result != tt.expected {
|
|
t.Errorf("getPIDFileForSocket() with dbPath=%q = %q, want %q", tt.dbPath, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestReadPIDFromFile tests reading PID from file
|
|
func TestReadPIDFromFile(t *testing.T) {
|
|
t.Run("valid PID", func(t *testing.T) {
|
|
// Create a temporary file
|
|
tmpFile, err := os.CreateTemp("", "pid")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp file: %v", err)
|
|
}
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
// Write a PID
|
|
if _, err := tmpFile.WriteString("12345\n"); err != nil {
|
|
t.Fatalf("Failed to write PID: %v", err)
|
|
}
|
|
tmpFile.Close()
|
|
|
|
// Read it back
|
|
pid, err := readPIDFromFile(tmpFile.Name())
|
|
if err != nil {
|
|
t.Errorf("readPIDFromFile() returned error: %v", err)
|
|
}
|
|
if pid != 12345 {
|
|
t.Errorf("readPIDFromFile() = %d, want 12345", pid)
|
|
}
|
|
})
|
|
|
|
t.Run("nonexistent file", func(t *testing.T) {
|
|
_, err := readPIDFromFile("/nonexistent/path/to/file")
|
|
if err == nil {
|
|
t.Error("readPIDFromFile() should return error for nonexistent file")
|
|
}
|
|
})
|
|
|
|
t.Run("invalid PID content", func(t *testing.T) {
|
|
tmpFile, err := os.CreateTemp("", "pid")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp file: %v", err)
|
|
}
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
if _, err := tmpFile.WriteString("not-a-number\n"); err != nil {
|
|
t.Fatalf("Failed to write content: %v", err)
|
|
}
|
|
tmpFile.Close()
|
|
|
|
_, err = readPIDFromFile(tmpFile.Name())
|
|
if err == nil {
|
|
t.Error("readPIDFromFile() should return error for invalid content")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestIsPIDAlive tests PID alive checking
|
|
func TestIsPIDAlive(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pid int
|
|
expected bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "zero PID",
|
|
pid: 0,
|
|
expected: false,
|
|
description: "PID 0 is invalid",
|
|
},
|
|
{
|
|
name: "negative PID",
|
|
pid: -1,
|
|
expected: false,
|
|
description: "Negative PID is invalid",
|
|
},
|
|
{
|
|
name: "current process",
|
|
pid: os.Getpid(),
|
|
expected: true,
|
|
description: "Current process should be alive",
|
|
},
|
|
{
|
|
name: "invalid PID",
|
|
pid: 999999,
|
|
expected: false,
|
|
description: "Non-existent process should not be alive",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isPIDAlive(tt.pid)
|
|
if result != tt.expected {
|
|
t.Errorf("isPIDAlive(%d) = %v, want %v (%s)",
|
|
tt.pid, result, tt.expected, tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestShouldAutoStartDaemon_Disabled tests BEADS_NO_DAEMON environment variable handling
|
|
func TestShouldAutoStartDaemon_Disabled(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
noDaemonValue string
|
|
shouldDisable bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "BEADS_NO_DAEMON=1",
|
|
noDaemonValue: "1",
|
|
shouldDisable: true,
|
|
description: "Should be disabled for BEADS_NO_DAEMON=1",
|
|
},
|
|
{
|
|
name: "BEADS_NO_DAEMON=true",
|
|
noDaemonValue: "true",
|
|
shouldDisable: true,
|
|
description: "Should be disabled for BEADS_NO_DAEMON=true",
|
|
},
|
|
{
|
|
name: "BEADS_NO_DAEMON=yes",
|
|
noDaemonValue: "yes",
|
|
shouldDisable: true,
|
|
description: "Should be disabled for BEADS_NO_DAEMON=yes",
|
|
},
|
|
{
|
|
name: "BEADS_NO_DAEMON=on",
|
|
noDaemonValue: "on",
|
|
shouldDisable: true,
|
|
description: "Should be disabled for BEADS_NO_DAEMON=on",
|
|
},
|
|
{
|
|
name: "BEADS_NO_DAEMON=0",
|
|
noDaemonValue: "0",
|
|
shouldDisable: false,
|
|
description: "Should NOT be disabled for BEADS_NO_DAEMON=0",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Save original env
|
|
oldVal, wasSet := os.LookupEnv("BEADS_NO_DAEMON")
|
|
defer func() {
|
|
if wasSet {
|
|
os.Setenv("BEADS_NO_DAEMON", oldVal)
|
|
} else {
|
|
os.Unsetenv("BEADS_NO_DAEMON")
|
|
}
|
|
}()
|
|
|
|
// Set test env
|
|
os.Setenv("BEADS_NO_DAEMON", tt.noDaemonValue)
|
|
|
|
result := shouldAutoStartDaemon()
|
|
|
|
if tt.shouldDisable && result != false {
|
|
t.Errorf("shouldAutoStartDaemon() = %v, want false (%s)",
|
|
result, tt.description)
|
|
}
|
|
if !tt.shouldDisable && result == false {
|
|
t.Logf("shouldAutoStartDaemon() = %v (config-dependent, check passed)", result)
|
|
}
|
|
})
|
|
}
|
|
}
|