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
This commit is contained in:
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user