417 lines
9.8 KiB
Go
417 lines
9.8 KiB
Go
package bus
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPidManagerBasics(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a custom pidManager for testing
|
|
testPidManager := &pidManager{
|
|
path: filepath.Join(tempDir, PidName),
|
|
}
|
|
|
|
t.Run("create and remove PID file", func(t *testing.T) {
|
|
// Create PID file
|
|
err := testPidManager.create()
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
|
|
// Check file exists and contains current PID
|
|
pidData, err := os.ReadFile(testPidManager.path)
|
|
if err != nil {
|
|
t.Fatalf("failed to read PID file: %v", err)
|
|
}
|
|
|
|
expectedPid := strconv.Itoa(os.Getpid())
|
|
if string(pidData) != expectedPid {
|
|
t.Errorf("PID file contains %q, expected %q", string(pidData), expectedPid)
|
|
}
|
|
|
|
// Remove PID file
|
|
err = testPidManager.remove()
|
|
if err != nil {
|
|
t.Fatalf("remove failed: %v", err)
|
|
}
|
|
|
|
// Check file no longer exists
|
|
if _, err := os.Stat(testPidManager.path); !os.IsNotExist(err) {
|
|
t.Error("PID file should not exist after removal")
|
|
}
|
|
})
|
|
|
|
t.Run("checkExisting with no PID file", func(t *testing.T) {
|
|
err := testPidManager.checkExisting()
|
|
if err != nil {
|
|
t.Errorf("checkExisting should not error when no PID file exists: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("checkExisting with current process", func(t *testing.T) {
|
|
// Create PID file with current process
|
|
err := testPidManager.create()
|
|
if err != nil {
|
|
t.Fatalf("create failed: %v", err)
|
|
}
|
|
defer testPidManager.remove()
|
|
|
|
// Check should fail because process is running
|
|
err = testPidManager.checkExisting()
|
|
if err == nil {
|
|
t.Error("checkExisting should fail when process is running")
|
|
}
|
|
})
|
|
|
|
t.Run("checkExisting with stale PID file", func(t *testing.T) {
|
|
// Create PID file with non-existent PID
|
|
stalePid := "99999"
|
|
err := os.WriteFile(testPidManager.path, []byte(stalePid), 0o600)
|
|
if err != nil {
|
|
t.Fatalf("failed to write stale PID file: %v", err)
|
|
}
|
|
|
|
// Check should succeed and remove stale file
|
|
err = testPidManager.checkExisting()
|
|
if err != nil {
|
|
t.Errorf("checkExisting should succeed with stale PID: %v", err)
|
|
}
|
|
|
|
// File should be removed
|
|
if _, err := os.Stat(testPidManager.path); !os.IsNotExist(err) {
|
|
t.Error("stale PID file should be removed")
|
|
}
|
|
})
|
|
|
|
t.Run("checkExisting with invalid PID file", func(t *testing.T) {
|
|
// Create PID file with invalid content
|
|
err := os.WriteFile(testPidManager.path, []byte("invalid"), 0o600)
|
|
if err != nil {
|
|
t.Fatalf("failed to write invalid PID file: %v", err)
|
|
}
|
|
|
|
// Check should succeed and remove invalid file
|
|
err = testPidManager.checkExisting()
|
|
if err != nil {
|
|
t.Errorf("checkExisting should succeed with invalid PID: %v", err)
|
|
}
|
|
|
|
// File should be removed
|
|
if _, err := os.Stat(testPidManager.path); !os.IsNotExist(err) {
|
|
t.Error("invalid PID file should be removed")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIsProcessAlive(t *testing.T) {
|
|
pm := &pidManager{}
|
|
|
|
t.Run("current process", func(t *testing.T) {
|
|
if !pm.isProcessAlive(os.Getpid()) {
|
|
t.Error("current process should be alive")
|
|
}
|
|
})
|
|
|
|
t.Run("non-existent process", func(t *testing.T) {
|
|
// Use a PID that's very unlikely to exist
|
|
if pm.isProcessAlive(99999) {
|
|
t.Error("non-existent process should not be alive")
|
|
}
|
|
})
|
|
|
|
t.Run("init process", func(t *testing.T) {
|
|
// PID 1 should always exist on Unix systems, but we might not have permission to signal it
|
|
alive := pm.isProcessAlive(1)
|
|
// Don't fail the test if we can't signal PID 1 due to permissions
|
|
// This is expected behavior in containers or restricted environments
|
|
_ = alive
|
|
})
|
|
}
|
|
|
|
func TestSocketManagerBasics(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a custom socketManager for testing
|
|
testSocketManager := &socketManager{
|
|
path: filepath.Join(tempDir, SockName),
|
|
}
|
|
|
|
t.Run("listen and dial", func(t *testing.T) {
|
|
// Start listening
|
|
listener, err := testSocketManager.listen()
|
|
if err != nil {
|
|
t.Fatalf("listen failed: %v", err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
// Accept connections in background
|
|
connCh := make(chan error, 1)
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
connCh <- err
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
// Echo back what we receive
|
|
buf := make([]byte, 1024)
|
|
n, err := conn.Read(buf)
|
|
if err != nil {
|
|
connCh <- err
|
|
return
|
|
}
|
|
|
|
_, err = conn.Write(buf[:n])
|
|
connCh <- err
|
|
}()
|
|
|
|
// Give listener time to start
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Dial and send message
|
|
conn, err := testSocketManager.dial()
|
|
if err != nil {
|
|
t.Fatalf("dial failed: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
testMsg := "hello"
|
|
_, err = conn.Write([]byte(testMsg))
|
|
if err != nil {
|
|
t.Fatalf("write failed: %v", err)
|
|
}
|
|
|
|
// Read echo
|
|
buf := make([]byte, 1024)
|
|
n, err := conn.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("read failed: %v", err)
|
|
}
|
|
|
|
if string(buf[:n]) != testMsg {
|
|
t.Errorf("got %q, expected %q", string(buf[:n]), testMsg)
|
|
}
|
|
|
|
// Check background goroutine
|
|
if err := <-connCh; err != nil {
|
|
t.Errorf("background connection error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("dial without listener", func(t *testing.T) {
|
|
_, err := testSocketManager.dial()
|
|
if err == nil {
|
|
t.Error("dial should fail when no listener exists")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSendCommandIntegration(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
|
|
// Create a custom socketManager for testing
|
|
testSocketManager := &socketManager{
|
|
path: filepath.Join(tempDir, SockName),
|
|
}
|
|
|
|
t.Run("successful command with mock server", func(t *testing.T) {
|
|
// Start a mock server
|
|
listener, err := testSocketManager.listen()
|
|
if err != nil {
|
|
t.Fatalf("listen failed: %v", err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
// Handle connections in background
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func(c net.Conn) {
|
|
defer c.Close()
|
|
|
|
buf := make([]byte, 2)
|
|
n, err := c.Read(buf)
|
|
if err != nil || n != 2 {
|
|
return
|
|
}
|
|
|
|
cmd := buf[0]
|
|
switch cmd {
|
|
case 't':
|
|
fmt.Fprint(c, "OK toggled\n")
|
|
case 's':
|
|
fmt.Fprint(c, "STATUS status=idle\n")
|
|
case 'v':
|
|
fmt.Fprintf(c, "STATUS proto=%s\n", ProtoVer)
|
|
case 'q':
|
|
fmt.Fprint(c, "OK quitting\n")
|
|
default:
|
|
fmt.Fprintf(c, "ERR unknown=%q\n", cmd)
|
|
}
|
|
}(conn)
|
|
}
|
|
}()
|
|
|
|
// Give server time to start
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Test different commands by manually creating connections
|
|
tests := []struct {
|
|
cmd byte
|
|
expected string
|
|
}{
|
|
{'t', "OK toggled\n"},
|
|
{'s', "STATUS status=idle\n"},
|
|
{'v', fmt.Sprintf("STATUS proto=%s\n", ProtoVer)},
|
|
{'q', "OK quitting\n"},
|
|
{'x', "ERR unknown='x'\n"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
conn, err := testSocketManager.dial()
|
|
if err != nil {
|
|
t.Errorf("dial failed for command %c: %v", tt.cmd, err)
|
|
continue
|
|
}
|
|
|
|
_, err = conn.Write([]byte{tt.cmd, '\n'})
|
|
if err != nil {
|
|
conn.Close()
|
|
t.Errorf("write failed for command %c: %v", tt.cmd, err)
|
|
continue
|
|
}
|
|
|
|
buf := make([]byte, 1024)
|
|
n, err := conn.Read(buf)
|
|
conn.Close()
|
|
|
|
if err != nil {
|
|
t.Errorf("read failed for command %c: %v", tt.cmd, err)
|
|
continue
|
|
}
|
|
|
|
resp := string(buf[:n])
|
|
if resp != tt.expected {
|
|
t.Errorf("command %c: got %q, expected %q", tt.cmd, resp, tt.expected)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPathFunctions(t *testing.T) {
|
|
t.Run("SockPath", func(t *testing.T) {
|
|
path, err := SockPath()
|
|
if err != nil {
|
|
t.Fatalf("SockPath failed: %v", err)
|
|
}
|
|
|
|
if !filepath.IsAbs(path) {
|
|
t.Error("SockPath should return absolute path")
|
|
}
|
|
|
|
if filepath.Base(path) != SockName {
|
|
t.Errorf("SockPath should end with %s, got %s", SockName, filepath.Base(path))
|
|
}
|
|
})
|
|
|
|
t.Run("getSockPath", func(t *testing.T) {
|
|
path, err := getSockPath()
|
|
if err != nil {
|
|
t.Fatalf("getSockPath failed: %v", err)
|
|
}
|
|
|
|
if !filepath.IsAbs(path) {
|
|
t.Error("getSockPath should return absolute path")
|
|
}
|
|
|
|
if filepath.Base(path) != SockName {
|
|
t.Errorf("getSockPath should end with %s, got %s", SockName, filepath.Base(path))
|
|
}
|
|
})
|
|
|
|
t.Run("getPidPath", func(t *testing.T) {
|
|
path, err := getPidPath()
|
|
if err != nil {
|
|
t.Fatalf("getPidPath failed: %v", err)
|
|
}
|
|
|
|
if !filepath.IsAbs(path) {
|
|
t.Error("getPidPath should return absolute path")
|
|
}
|
|
|
|
if filepath.Base(path) != PidName {
|
|
t.Errorf("getPidPath should end with %s, got %s", PidName, filepath.Base(path))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestConstants(t *testing.T) {
|
|
if SockName == "" {
|
|
t.Error("SockName should not be empty")
|
|
}
|
|
if PidName == "" {
|
|
t.Error("PidName should not be empty")
|
|
}
|
|
if ProtoVer == "" {
|
|
t.Error("ProtoVer should not be empty")
|
|
}
|
|
}
|
|
|
|
// Test the public API functions with temporary directories
|
|
func TestPublicAPIWithTempDirs(t *testing.T) {
|
|
// We can't override the internal functions, but we can test the public API
|
|
// and clean up any files we create
|
|
|
|
t.Run("CheckExistingDaemon with no daemon", func(t *testing.T) {
|
|
// This should succeed when no daemon is running
|
|
// Clean up any existing PID file first
|
|
pidPath, _ := getPidPath()
|
|
os.Remove(pidPath)
|
|
|
|
err := CheckExistingDaemon()
|
|
if err != nil {
|
|
t.Errorf("CheckExistingDaemon should succeed when no daemon running: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("CreatePidFile and RemovePidFile", func(t *testing.T) {
|
|
// Clean up first
|
|
pidPath, _ := getPidPath()
|
|
os.Remove(pidPath)
|
|
|
|
err := CreatePidFile()
|
|
if err != nil {
|
|
t.Fatalf("CreatePidFile failed: %v", err)
|
|
}
|
|
|
|
// Check file exists
|
|
if _, err := os.Stat(pidPath); os.IsNotExist(err) {
|
|
t.Error("PID file should exist after CreatePidFile")
|
|
}
|
|
|
|
err = RemovePidFile()
|
|
if err != nil {
|
|
t.Fatalf("RemovePidFile failed: %v", err)
|
|
}
|
|
|
|
// Check file is removed
|
|
if _, err := os.Stat(pidPath); !os.IsNotExist(err) {
|
|
t.Error("PID file should not exist after RemovePidFile")
|
|
}
|
|
})
|
|
}
|