243 lines
6.6 KiB
Go
243 lines
6.6 KiB
Go
//go:build darwin
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildPlist_KeepAliveDoesNotRestartOnCleanExit(t *testing.T) {
|
|
cfg := Config{
|
|
BinaryPath: "/opt/cc-connect/cc-connect",
|
|
WorkDir: "/tmp/wd",
|
|
LogFile: "/tmp/log",
|
|
LogMaxSize: 10485760,
|
|
EnvPATH: "/usr/bin",
|
|
}
|
|
xml := buildPlist(cfg)
|
|
if !strings.Contains(xml, "<key>SuccessfulExit</key>") {
|
|
t.Fatal("plist should use KeepAlive dict with SuccessfulExit so exit 0 does not respawn")
|
|
}
|
|
// Boolean KeepAlive causes launchd to restart after every exit, including SIGTERM shutdown.
|
|
if strings.Contains(xml, "<key>KeepAlive</key>\n\t<true/>") {
|
|
t.Fatal("plist must not use boolean KeepAlive true")
|
|
}
|
|
if !strings.Contains(xml, "<key>LimitLoadToSessionType</key>") ||
|
|
!strings.Contains(xml, "<string>Aqua</string>") ||
|
|
!strings.Contains(xml, "<string>Background</string>") {
|
|
t.Fatal("plist should allow both Aqua and Background sessions")
|
|
}
|
|
}
|
|
|
|
func TestPreferredLaunchdDomainFallsBackToUserWhenGUIDomainUnavailable(t *testing.T) {
|
|
orig := runLaunchctl
|
|
t.Cleanup(func() { runLaunchctl = orig })
|
|
|
|
guiDomain := launchdGUIDomain()
|
|
userDomain := launchdUserDomain()
|
|
runLaunchctl = func(args ...string) (string, error) {
|
|
if len(args) >= 2 && args[0] == "print" && args[1] == guiDomain {
|
|
return "Bootstrap failed: 125: Domain does not support specified action", fmt.Errorf("exit status 125")
|
|
}
|
|
if len(args) >= 2 && args[0] == "print" && args[1] == userDomain {
|
|
return "subsystem", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
if got := preferredLaunchdDomain(); got != userDomain {
|
|
t.Fatalf("preferredLaunchdDomain() = %q, want %q", got, userDomain)
|
|
}
|
|
}
|
|
|
|
func TestLaunchdStatusUsesUserDomainWhenGUIDomainUnavailable(t *testing.T) {
|
|
orig := runLaunchctl
|
|
t.Cleanup(func() { runLaunchctl = orig })
|
|
|
|
guiDomain := launchdGUIDomain()
|
|
userDomain := launchdUserDomain()
|
|
guiTarget := launchdTarget(guiDomain)
|
|
userTarget := launchdTarget(userDomain)
|
|
runLaunchctl = func(args ...string) (string, error) {
|
|
if len(args) < 2 || args[0] != "print" {
|
|
return "", nil
|
|
}
|
|
switch args[1] {
|
|
case guiDomain, guiTarget:
|
|
return "Bootstrap failed: 125: Domain does not support specified action", fmt.Errorf("exit status 125")
|
|
case userDomain:
|
|
return "subsystem", nil
|
|
case userTarget:
|
|
return "pid = 4321\nstate = running", nil
|
|
default:
|
|
return "", fmt.Errorf("unexpected target %q", args[1])
|
|
}
|
|
}
|
|
|
|
mgr := &launchdManager{}
|
|
st, err := mgr.Status()
|
|
if err != nil {
|
|
t.Fatalf("Status() error = %v", err)
|
|
}
|
|
if !st.Running {
|
|
t.Fatal("Status().Running = false, want true")
|
|
}
|
|
if st.PID != 4321 {
|
|
t.Fatalf("Status().PID = %d, want 4321", st.PID)
|
|
}
|
|
}
|
|
|
|
func TestRestartPrefersGUIDomainWhenAvailable(t *testing.T) {
|
|
orig := runLaunchctl
|
|
t.Cleanup(func() { runLaunchctl = orig })
|
|
|
|
dir := t.TempDir()
|
|
origHome := os.Getenv("HOME")
|
|
t.Setenv("HOME", dir)
|
|
if origHome != "" {
|
|
t.Cleanup(func() { _ = os.Setenv("HOME", origHome) })
|
|
}
|
|
plistPath := launchdPlistPath()
|
|
if err := os.MkdirAll(filepath.Dir(plistPath), 0755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
if err := os.WriteFile(plistPath, []byte("plist"), 0644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
guiDomain := launchdGUIDomain()
|
|
userDomain := launchdUserDomain()
|
|
guiTarget := launchdTarget(guiDomain)
|
|
userTarget := launchdTarget(userDomain)
|
|
|
|
var calls []string
|
|
runLaunchctl = func(args ...string) (string, error) {
|
|
calls = append(calls, strings.Join(args, " "))
|
|
if len(args) < 2 {
|
|
return "", nil
|
|
}
|
|
switch args[0] {
|
|
case "print":
|
|
switch args[1] {
|
|
case guiDomain:
|
|
return "subsystem", nil
|
|
case guiTarget:
|
|
return "Bootstrap failed: 113: Could not find service", fmt.Errorf("exit status 113")
|
|
case userTarget:
|
|
return "pid = 4321\nstate = running", nil
|
|
default:
|
|
return "", fmt.Errorf("unexpected print target %q", args[1])
|
|
}
|
|
case "bootout":
|
|
return "", nil
|
|
case "bootstrap":
|
|
if args[1] != guiDomain {
|
|
t.Fatalf("bootstrap domain = %q, want %q", args[1], guiDomain)
|
|
}
|
|
return "", nil
|
|
case "kickstart":
|
|
if args[len(args)-1] != guiTarget {
|
|
t.Fatalf("kickstart target = %q, want %q", args[len(args)-1], guiTarget)
|
|
}
|
|
return "", nil
|
|
default:
|
|
return "", nil
|
|
}
|
|
}
|
|
|
|
mgr := &launchdManager{}
|
|
if err := mgr.Restart(); err != nil {
|
|
t.Fatalf("Restart() error = %v", err)
|
|
}
|
|
|
|
if !containsCall(calls, "bootstrap "+guiDomain+" "+plistPath) {
|
|
t.Fatalf("expected bootstrap to gui domain, calls = %#v", calls)
|
|
}
|
|
if !containsCall(calls, "kickstart -kp "+guiTarget) {
|
|
t.Fatalf("expected kickstart to gui target, calls = %#v", calls)
|
|
}
|
|
}
|
|
|
|
func TestRestartKeepsUserDomainWhenGUIDomainUnavailable(t *testing.T) {
|
|
orig := runLaunchctl
|
|
t.Cleanup(func() { runLaunchctl = orig })
|
|
|
|
dir := t.TempDir()
|
|
origHome := os.Getenv("HOME")
|
|
t.Setenv("HOME", dir)
|
|
if origHome != "" {
|
|
t.Cleanup(func() { _ = os.Setenv("HOME", origHome) })
|
|
}
|
|
plistPath := launchdPlistPath()
|
|
if err := os.MkdirAll(filepath.Dir(plistPath), 0755); err != nil {
|
|
t.Fatalf("MkdirAll() error = %v", err)
|
|
}
|
|
if err := os.WriteFile(plistPath, []byte("plist"), 0644); err != nil {
|
|
t.Fatalf("WriteFile() error = %v", err)
|
|
}
|
|
|
|
guiDomain := launchdGUIDomain()
|
|
userDomain := launchdUserDomain()
|
|
userTarget := launchdTarget(userDomain)
|
|
|
|
var calls []string
|
|
runLaunchctl = func(args ...string) (string, error) {
|
|
calls = append(calls, strings.Join(args, " "))
|
|
if len(args) < 2 {
|
|
return "", nil
|
|
}
|
|
switch args[0] {
|
|
case "print":
|
|
switch args[1] {
|
|
case guiDomain:
|
|
return "Bootstrap failed: 125: Domain does not support specified action", fmt.Errorf("exit status 125")
|
|
case userDomain:
|
|
return "subsystem", nil
|
|
case userTarget:
|
|
return "pid = 4321\nstate = running", nil
|
|
default:
|
|
return "", fmt.Errorf("unexpected print target %q", args[1])
|
|
}
|
|
case "bootout":
|
|
return "", nil
|
|
case "bootstrap":
|
|
if args[1] != userDomain {
|
|
t.Fatalf("bootstrap domain = %q, want %q", args[1], userDomain)
|
|
}
|
|
return "", nil
|
|
case "kickstart":
|
|
if args[len(args)-1] != userTarget {
|
|
t.Fatalf("kickstart target = %q, want %q", args[len(args)-1], userTarget)
|
|
}
|
|
return "", nil
|
|
default:
|
|
return "", nil
|
|
}
|
|
}
|
|
|
|
mgr := &launchdManager{}
|
|
if err := mgr.Restart(); err != nil {
|
|
t.Fatalf("Restart() error = %v", err)
|
|
}
|
|
|
|
if !containsCall(calls, "bootstrap "+userDomain+" "+plistPath) {
|
|
t.Fatalf("expected bootstrap to user domain, calls = %#v", calls)
|
|
}
|
|
if !containsCall(calls, "kickstart -kp "+userTarget) {
|
|
t.Fatalf("expected kickstart to user target, calls = %#v", calls)
|
|
}
|
|
}
|
|
|
|
func containsCall(calls []string, want string) bool {
|
|
for _, call := range calls {
|
|
if call == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|