1//go:build darwin && arm64
2
3package platform
4
5import (
6 "errors"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strings"
11 "syscall"
12 "testing"
13 "unsafe"
14)
15
16func TestDarwinKinfoProcLayout(t *testing.T) {
17 tests := []struct {
18 name string
19 got uintptr
20 want uintptr
21 }{
22 {"Proc.Pid", unsafe.Offsetof(kinfoProc{}.Proc) + unsafe.Offsetof(externProc{}.Pid), 40},
23 {"Proc.Comm", unsafe.Offsetof(kinfoProc{}.Proc) + unsafe.Offsetof(externProc{}.Comm), 243},
24 {"Eproc.Pgid", unsafe.Offsetof(kinfoProc{}.Eproc) + unsafe.Offsetof(eproc{}.Pgid), 564},
25 {"kinfoProc size", unsafe.Sizeof(kinfoProc{}), darwinKinfoProcSize},
26 }
27 for _, tt := range tests {
28 t.Run(tt.name, func(t *testing.T) {
29 if tt.got != tt.want {
30 t.Fatalf("%s = %d; want %d", tt.name, tt.got, tt.want)
31 }
32 })
33 }
34}
35
36func TestDarwinProcessInspectorCurrentProcess(t *testing.T) {
37 inspector := NewProcessInspector()
38 pid := os.Getpid()
39
40 cwd, err := inspector.Cwd(pid)
41 if err != nil {
42 t.Fatalf("Cwd(%d): %v", pid, err)
43 }
44 cwdInfo, err := os.Stat(cwd)
45 if err != nil {
46 t.Fatalf("stat Cwd(%d) result %q: %v", pid, cwd, err)
47 }
48 currentDirInfo, err := os.Stat(".")
49 if err != nil {
50 t.Fatal(err)
51 }
52 if !os.SameFile(cwdInfo, currentDirInfo) {
53 t.Fatalf("Cwd(%d) = %q; does not identify the current directory", pid, cwd)
54 }
55
56 comm, err := inspector.Comm(pid)
57 if err != nil {
58 t.Fatalf("Comm(%d): %v", pid, err)
59 }
60 wantComm := filepath.Base(os.Args[0])
61 if len(wantComm) > len(externProc{}.Comm)-1 {
62 wantComm = wantComm[:len(externProc{}.Comm)-1]
63 }
64 if comm != wantComm {
65 t.Fatalf("Comm(%d) = %q; want %q", pid, comm, wantComm)
66 }
67
68 argv, err := inspector.Argv(pid)
69 if err != nil {
70 t.Fatalf("Argv(%d): %v", pid, err)
71 }
72 if len(argv) == 0 || argv[0] != os.Args[0] {
73 t.Fatalf("Argv(%d) = %#v; want argv[0] %q", pid, argv, os.Args[0])
74 }
75
76 stdinReader, stdinWriter, err := os.Pipe()
77 if err != nil {
78 t.Fatal(err)
79 }
80 cmd := exec.Command("/bin/cat")
81 cmd.Stdin = stdinReader
82 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
83 if err := cmd.Start(); err != nil {
84 _ = stdinReader.Close()
85 _ = stdinWriter.Close()
86 t.Fatal(err)
87 }
88 t.Cleanup(func() {
89 _ = stdinReader.Close()
90 _ = stdinWriter.Close()
91 _ = cmd.Process.Kill()
92 _ = cmd.Wait()
93 })
94 groupArgv, err := inspector.GroupArgv(cmd.Process.Pid, -1)
95 if err != nil {
96 t.Fatalf("GroupArgv(child group): %v", err)
97 }
98 if len(groupArgv) != 1 || groupArgv[0] != "/bin/cat" {
99 t.Fatalf("GroupArgv(child group) = %#v; want [/bin/cat]", groupArgv)
100 }
101}
102
103func TestDarwinPIDRejectsUnrepresentableValues(t *testing.T) {
104 for _, pid := range []int{0, -1, maxDarwinPID + 1} {
105 if _, err := ProcessCwd(pid); err == nil {
106 t.Errorf("ProcessCwd(%d) succeeded; want invalid PID error", pid)
107 }
108 if _, err := ProcessComm(pid); err == nil {
109 t.Errorf("ProcessComm(%d) succeeded; want invalid PID error", pid)
110 }
111 if _, err := ProcessArgv(pid); err == nil {
112 t.Errorf("ProcessArgv(%d) succeeded; want invalid PID error", pid)
113 }
114 }
115}
116
117func TestRawSysctlRetriesAfterENOMEM(t *testing.T) {
118 var sizeCalls, readCalls int
119 data, err := rawSysctlWith([]int32{1, 2}, func(_ []int32, old *byte, oldlen *uintptr) error {
120 if old == nil {
121 sizeCalls++
122 *oldlen = 1
123 return nil
124 }
125 readCalls++
126 if readCalls == 1 {
127 return syscall.ENOMEM
128 }
129 *old = 'x'
130 *oldlen = 1
131 return nil
132 })
133 if err != nil {
134 t.Fatal(err)
135 }
136 if string(data) != "x" || sizeCalls != 2 || readCalls != 2 {
137 t.Fatalf("rawSysctl calls = size %d, read %d, data %q; want 2, 2, x", sizeCalls, readCalls, data)
138 }
139}
140
141func TestRawSysctlStopsRetryingAfterLimit(t *testing.T) {
142 var sizeCalls, readCalls int
143 _, err := rawSysctlWith([]int32{1, 2}, func(_ []int32, old *byte, oldlen *uintptr) error {
144 if old == nil {
145 sizeCalls++
146 *oldlen = 1
147 return nil
148 }
149 readCalls++
150 return syscall.ENOMEM
151 })
152 if !errors.Is(err, syscall.ENOMEM) || !strings.Contains(err.Error(), "result grew after") {
153 t.Fatalf("rawSysctl exhaustion error = %v; want contextual ENOMEM", err)
154 }
155 wantCalls := rawSysctlMaxRetries + 1
156 if sizeCalls != wantCalls || readCalls != wantCalls {
157 t.Fatalf("rawSysctl calls = size %d, read %d; want %d each", sizeCalls, readCalls, wantCalls)
158 }
159}