From 6f3309b3046c6f2e172b0c814d816f8bde491ff1 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sun, 14 Dec 2025 15:21:29 -0800 Subject: [PATCH] test(lockfile): improve coverage from 42% to 98% - Add TestTryDaemonLock with 6 test cases for lock detection scenarios - Add TestFlockFunctions for blocking/non-blocking lock operations - Add TestIsProcessRunning for process detection edge cases - Fix TestCheckPIDFile/current_process_is_running (was using invalid PID encoding) Closes bd-9w3s --- internal/lockfile/lock_test.go | 262 ++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 6 deletions(-) diff --git a/internal/lockfile/lock_test.go b/internal/lockfile/lock_test.go index 0af10d88..a099726d 100644 --- a/internal/lockfile/lock_test.go +++ b/internal/lockfile/lock_test.go @@ -2,6 +2,7 @@ package lockfile import ( "encoding/json" + "fmt" "os" "path/filepath" "testing" @@ -127,17 +128,266 @@ func TestCheckPIDFile(t *testing.T) { t.Run("current process is running", func(t *testing.T) { pidFile := filepath.Join(tmpDir, "daemon.pid") - // Use current process PID currentPID := os.Getpid() - if err := os.WriteFile(pidFile, []byte(string(rune(currentPID+'0'))), 0644); err != nil { + if err := os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", currentPID)), 0644); err != nil { t.Fatalf("failed to write PID file: %v", err) } running, pid := checkPIDFile(tmpDir) - // This might be true if the PID format is parsed correctly - // But with our test we're writing an invalid PID, so it should be false - if running && pid == 0 { - t.Error("inconsistent result: running but no PID") + if !running { + t.Error("expected running=true for current process") + } + if pid != currentPID { + t.Errorf("expected pid=%d, got %d", currentPID, pid) + } + }) +} + +func TestTryDaemonLock(t *testing.T) { + t.Run("no lock file exists", func(t *testing.T) { + tmpDir := t.TempDir() + + running, pid := TryDaemonLock(tmpDir) + if running { + t.Error("expected running=false when no lock file exists") + } + if pid != 0 { + t.Errorf("expected pid=0, got %d", pid) + } + }) + + t.Run("lock file exists but not locked - daemon not running", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "daemon.lock") + + lockInfo := LockInfo{ + PID: 12345, + Database: "/path/to/db", + Version: "1.0.0", + StartedAt: time.Now(), + } + data, _ := json.Marshal(lockInfo) + if err := os.WriteFile(lockPath, data, 0644); err != nil { + t.Fatalf("failed to write lock file: %v", err) + } + + running, _ := TryDaemonLock(tmpDir) + if running { + t.Error("expected running=false when lock file exists but is not locked") + } + }) + + t.Run("lock file held by another process - daemon running", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "daemon.lock") + + lockInfo := LockInfo{ + PID: os.Getpid(), + Database: "/path/to/db", + Version: "1.0.0", + StartedAt: time.Now(), + } + data, _ := json.Marshal(lockInfo) + if err := os.WriteFile(lockPath, data, 0644); err != nil { + t.Fatalf("failed to write lock file: %v", err) + } + + f, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open lock file: %v", err) + } + defer f.Close() + + if err := FlockExclusiveBlocking(f); err != nil { + t.Fatalf("failed to acquire lock: %v", err) + } + defer FlockUnlock(f) + + running, pid := TryDaemonLock(tmpDir) + if !running { + t.Error("expected running=true when lock is held") + } + if pid != os.Getpid() { + t.Errorf("expected pid=%d, got %d", os.Getpid(), pid) + } + }) + + t.Run("lock file with old format (plain PID)", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "daemon.lock") + + currentPID := os.Getpid() + if err := os.WriteFile(lockPath, []byte(fmt.Sprintf("%d", currentPID)), 0644); err != nil { + t.Fatalf("failed to write lock file: %v", err) + } + + f, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open lock file: %v", err) + } + defer f.Close() + + if err := FlockExclusiveBlocking(f); err != nil { + t.Fatalf("failed to acquire lock: %v", err) + } + defer FlockUnlock(f) + + running, pid := TryDaemonLock(tmpDir) + if !running { + t.Error("expected running=true when lock is held") + } + if pid != currentPID { + t.Errorf("expected pid=%d, got %d", currentPID, pid) + } + }) + + t.Run("lock file with invalid content falls back to PID file", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "daemon.lock") + pidFile := filepath.Join(tmpDir, "daemon.pid") + + if err := os.WriteFile(lockPath, []byte("invalid content"), 0644); err != nil { + t.Fatalf("failed to write lock file: %v", err) + } + + currentPID := os.Getpid() + if err := os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", currentPID)), 0644); err != nil { + t.Fatalf("failed to write PID file: %v", err) + } + + f, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open lock file: %v", err) + } + defer f.Close() + + if err := FlockExclusiveBlocking(f); err != nil { + t.Fatalf("failed to acquire lock: %v", err) + } + defer FlockUnlock(f) + + running, pid := TryDaemonLock(tmpDir) + if !running { + t.Error("expected running=true when lock is held") + } + if pid != currentPID { + t.Errorf("expected pid=%d from PID file fallback, got %d", currentPID, pid) + } + }) + + t.Run("falls back to PID file when no lock file exists", func(t *testing.T) { + tmpDir := t.TempDir() + pidFile := filepath.Join(tmpDir, "daemon.pid") + + currentPID := os.Getpid() + if err := os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", currentPID)), 0644); err != nil { + t.Fatalf("failed to write PID file: %v", err) + } + + running, pid := TryDaemonLock(tmpDir) + if !running { + t.Error("expected running=true when PID file has running process") + } + if pid != currentPID { + t.Errorf("expected pid=%d, got %d", currentPID, pid) + } + }) +} + +func TestFlockFunctions(t *testing.T) { + t.Run("FlockExclusiveBlocking and FlockUnlock", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "test.lock") + + if err := os.WriteFile(lockPath, []byte("test"), 0644); err != nil { + t.Fatalf("failed to create lock file: %v", err) + } + + f, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open lock file: %v", err) + } + defer f.Close() + + if err := FlockExclusiveBlocking(f); err != nil { + t.Errorf("FlockExclusiveBlocking failed: %v", err) + } + + if err := FlockUnlock(f); err != nil { + t.Errorf("FlockUnlock failed: %v", err) + } + }) + + t.Run("flockExclusive non-blocking succeeds on unlocked file", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "test.lock") + + if err := os.WriteFile(lockPath, []byte("test"), 0644); err != nil { + t.Fatalf("failed to create lock file: %v", err) + } + + f, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open lock file: %v", err) + } + defer f.Close() + + if err := flockExclusive(f); err != nil { + t.Errorf("flockExclusive should succeed on unlocked file: %v", err) + } + + FlockUnlock(f) + }) + + t.Run("flockExclusive returns errDaemonLocked when already locked", func(t *testing.T) { + tmpDir := t.TempDir() + lockPath := filepath.Join(tmpDir, "test.lock") + + if err := os.WriteFile(lockPath, []byte("test"), 0644); err != nil { + t.Fatalf("failed to create lock file: %v", err) + } + + f1, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open lock file: %v", err) + } + defer f1.Close() + + if err := FlockExclusiveBlocking(f1); err != nil { + t.Fatalf("failed to acquire first lock: %v", err) + } + defer FlockUnlock(f1) + + f2, err := os.OpenFile(lockPath, os.O_RDWR, 0644) + if err != nil { + t.Fatalf("failed to open second lock file handle: %v", err) + } + defer f2.Close() + + err = flockExclusive(f2) + if err != errDaemonLocked { + t.Errorf("expected errDaemonLocked, got %v", err) + } + }) +} + +func TestIsProcessRunning(t *testing.T) { + t.Run("current process is running", func(t *testing.T) { + if !isProcessRunning(os.Getpid()) { + t.Error("expected current process to be running") + } + }) + + t.Run("non-existent process is not running", func(t *testing.T) { + if isProcessRunning(99999) { + t.Error("expected non-existent process to not be running") + } + }) + + t.Run("parent process is running", func(t *testing.T) { + ppid := os.Getppid() + if ppid > 0 && !isProcessRunning(ppid) { + t.Error("expected parent process to be running") } }) }