1//go:build linux
2
3package platform
4
5import (
6 "fmt"
7 "os"
8 "path/filepath"
9 "reflect"
10 "strconv"
11 "testing"
12)
13
14func TestProcessCwdRejectsInvalidPID(t *testing.T) {
15 if _, err := processCwd("/proc", 0); err == nil {
16 t.Fatal("processCwd accepted invalid pid")
17 }
18}
19
20func TestProcessArgv(t *testing.T) {
21 argv, err := ProcessArgv(os.Getpid())
22 if err != nil {
23 t.Fatalf("ProcessArgv current process: %v", err)
24 }
25 if len(argv) == 0 {
26 t.Fatal("ProcessArgv returned empty argv")
27 }
28}
29
30func TestProcessArgvRejectsEmptyCmdline(t *testing.T) {
31 path := filepath.Join(t.TempDir(), "cmdline")
32 if err := os.WriteFile(path, []byte("\x00\x00"), 0o600); err != nil {
33 t.Fatal(err)
34 }
35 if argv, err := processArgv(path, 123); err == nil || argv != nil {
36 t.Fatalf("processArgv empty = %#v, %v; want nil error", argv, err)
37 }
38}
39
40func TestSelectProcessGroupPID(t *testing.T) {
41 tests := []struct {
42 name string
43 recs []processRecord
44 pgid int
45 shell int
46 wantPID int
47 wantOK bool
48 }{
49 {
50 name: "selects other member",
51 recs: []processRecord{{pid: 10, pgrp: 7}, {pid: 12, pgrp: 7}, {pid: 20, pgrp: 9}},
52 pgid: 7,
53 shell: 10,
54 wantPID: 12,
55 wantOK: true,
56 },
57 {
58 name: "bare shell has no candidate",
59 recs: []processRecord{{pid: 10, pgrp: 7}, {pid: 20, pgrp: 9}},
60 pgid: 7,
61 shell: 10,
62 wantPID: 0,
63 wantOK: false,
64 },
65 }
66
67 for _, tt := range tests {
68 t.Run(tt.name, func(t *testing.T) {
69 pid, ok := selectProcessGroupPID(tt.recs, tt.pgid, tt.shell)
70 if ok != tt.wantOK || pid != tt.wantPID {
71 t.Fatalf("selectProcessGroupPID = %d, %v; want %d, %v", pid, ok, tt.wantPID, tt.wantOK)
72 }
73 })
74 }
75}
76
77func TestProcessInspectorUsesConfiguredRoot(t *testing.T) {
78 root := t.TempDir()
79 writeProcStat(t, root, 10, 7)
80 pidDir := filepath.Join(root, "10")
81 cwdTarget := t.TempDir()
82 if err := os.Symlink(cwdTarget, filepath.Join(pidDir, "cwd")); err != nil {
83 t.Fatal(err)
84 }
85 if err := os.WriteFile(filepath.Join(pidDir, "comm"), []byte("custom\n"), 0o600); err != nil {
86 t.Fatal(err)
87 }
88 if err := os.WriteFile(filepath.Join(pidDir, "cmdline"), []byte("custom\x00arg\x00"), 0o600); err != nil {
89 t.Fatal(err)
90 }
91 ins := newProcessInspector(root)
92
93 cwd, err := ins.Cwd(10)
94 if err != nil || cwd != cwdTarget {
95 t.Fatalf("Cwd = %q, %v; want %q, nil", cwd, err, cwdTarget)
96 }
97 comm, err := ins.Comm(10)
98 if err != nil || comm != "custom" {
99 t.Fatalf("Comm = %q, %v; want custom, nil", comm, err)
100 }
101 argv, err := ins.Argv(10)
102 if err != nil || !reflect.DeepEqual(argv, []string{"custom", "arg"}) {
103 t.Fatalf("Argv = %#v, %v; want custom argv, nil", argv, err)
104 }
105}
106
107func TestProcessInspectorCachesProcessRecordsBriefly(t *testing.T) {
108 root := t.TempDir()
109 writeProcStat(t, root, 10, 7)
110 ins := newProcessInspector(root)
111
112 first, err := ins.processRecords()
113 if err != nil {
114 t.Fatal(err)
115 }
116 writeProcStat(t, root, 12, 7)
117 second, err := ins.processRecords()
118 if err != nil {
119 t.Fatal(err)
120 }
121 if len(first) != 1 || len(second) != 1 {
122 t.Fatalf("cached processRecords lengths = %d, %d; want 1, 1", len(first), len(second))
123 }
124}
125
126func writeProcStat(t *testing.T, root string, pid int, pgrp int) {
127 t.Helper()
128 dir := filepath.Join(root, strconv.Itoa(pid))
129 if err := os.MkdirAll(dir, 0o700); err != nil {
130 t.Fatal(err)
131 }
132 stat := fmt.Sprintf("%d (cmd) S 1 %d 1 0 0", pid, pgrp)
133 if err := os.WriteFile(filepath.Join(dir, "stat"), []byte(stat), 0o600); err != nil {
134 t.Fatal(err)
135 }
136}
137
138func TestParseStatPgrp(t *testing.T) {
139 got, err := parseStatPgrp("123 (cmd with spaces) S 1 456 456 0")
140 if err != nil || got != 456 {
141 t.Fatalf("parseStatPgrp = %d, %v; want 456, nil", got, err)
142 }
143}
144
145func TestParseKernProcArgs2(t *testing.T) {
146 tests := []struct {
147 name string
148 data []byte
149 want []string
150 wantErr bool
151 }{
152 {
153 name: "argv after executable and padding",
154 data: append([]byte{2, 0, 0, 0}, []byte("/bin/tool\x00\x00tool\x00arg\x00ENV=value\x00")...),
155 want: []string{"tool", "arg"},
156 },
157 {
158 name: "preserves empty argument",
159 data: append([]byte{3, 0, 0, 0}, []byte("/bin/tool\x00tool\x00\x00arg\x00")...),
160 want: []string{"tool", "", "arg"},
161 },
162 {
163 name: "truncated argc",
164 data: []byte{1, 0, 0},
165 wantErr: true,
166 },
167 {
168 name: "unterminated executable path",
169 data: append([]byte{1, 0, 0, 0}, []byte("/bin/tool")...),
170 wantErr: true,
171 },
172 {
173 name: "truncated argv",
174 data: append([]byte{1, 0, 0, 0}, []byte("/bin/tool\x00tool")...),
175 wantErr: true,
176 },
177 {
178 name: "negative argc",
179 data: append([]byte{0xff, 0xff, 0xff, 0xff}, []byte("/bin/tool\x00tool\x00")...),
180 wantErr: true,
181 },
182 {
183 name: "argc exceeds remaining data",
184 data: append([]byte{0xff, 0xff, 0xff, 0x7f}, []byte("/bin/tool\x00tool\x00")...),
185 wantErr: true,
186 },
187 }
188
189 for _, tt := range tests {
190 t.Run(tt.name, func(t *testing.T) {
191 got, err := parseKernProcArgs2(tt.data)
192 if (err != nil) != tt.wantErr {
193 t.Fatalf("parseKernProcArgs2 error = %v, wantErr %v", err, tt.wantErr)
194 }
195 if !reflect.DeepEqual(got, tt.want) {
196 t.Fatalf("parseKernProcArgs2 = %#v; want %#v", got, tt.want)
197 }
198 })
199 }
200}
201
202func TestProcessArgvFileParsing(t *testing.T) {
203 tests := []struct {
204 name string
205 data []byte
206 want []string
207 }{
208 {
209 name: "splits nul-delimited argv",
210 data: []byte("cmd\x00two words\x00"),
211 want: []string{"cmd", "two words"},
212 },
213 {
214 name: "preserves empty argument",
215 data: []byte("cmd\x00\x00arg\x00"),
216 want: []string{"cmd", "", "arg"},
217 },
218 }
219
220 for _, tt := range tests {
221 t.Run(tt.name, func(t *testing.T) {
222 path := filepath.Join(t.TempDir(), "cmdline")
223 if err := os.WriteFile(path, tt.data, 0o600); err != nil {
224 t.Fatal(err)
225 }
226 got, err := processArgv(path, 123)
227 if err != nil {
228 t.Fatal(err)
229 }
230 if !reflect.DeepEqual(got, tt.want) {
231 t.Fatalf("processArgv = %#v; want %#v", got, tt.want)
232 }
233 })
234 }
235}