1
2
3
4
5 package cgroup_test
6
7 import (
8 "fmt"
9 "internal/runtime/cgroup"
10 "io"
11 "strconv"
12 "strings"
13 "testing"
14 )
15
16 const _PATH_MAX = 4096
17
18 func TestParseV1Number(t *testing.T) {
19 tests := []struct {
20 name string
21 contents string
22 want int64
23 wantErr bool
24 }{
25 {
26 name: "disabled",
27 contents: "-1\n",
28 want: -1,
29 },
30 {
31 name: "500000",
32 contents: "500000\n",
33 want: 500000,
34 },
35 {
36 name: "MaxInt64",
37 contents: "9223372036854775807\n",
38 want: 9223372036854775807,
39 },
40 {
41 name: "missing-newline",
42 contents: "500000",
43 wantErr: true,
44 },
45 {
46 name: "not-a-number",
47 contents: "123max\n",
48 wantErr: true,
49 },
50 {
51 name: "v2",
52 contents: "1000 5000\n",
53 wantErr: true,
54 },
55 }
56
57 for _, tc := range tests {
58 t.Run(tc.name, func(t *testing.T) {
59 got, err := cgroup.ParseV1Number([]byte(tc.contents))
60 if tc.wantErr {
61 if err == nil {
62 t.Fatalf("parseV1Number got err nil want non-nil")
63 }
64 return
65 }
66 if err != nil {
67 t.Fatalf("parseV1Number got err %v want nil", err)
68 }
69
70 if got != tc.want {
71 t.Errorf("parseV1Number got %d want %d", got, tc.want)
72 }
73 })
74 }
75 }
76
77 func TestParseV2Limit(t *testing.T) {
78 tests := []struct {
79 name string
80 contents string
81 want float64
82 wantOK bool
83 wantErr bool
84 }{
85 {
86 name: "disabled",
87 contents: "max 100000\n",
88 wantOK: false,
89 },
90 {
91 name: "5",
92 contents: "500000 100000\n",
93 want: 5,
94 wantOK: true,
95 },
96 {
97 name: "0.5",
98 contents: "50000 100000\n",
99 want: 0.5,
100 wantOK: true,
101 },
102 {
103 name: "2.5",
104 contents: "250000 100000\n",
105 want: 2.5,
106 wantOK: true,
107 },
108 {
109 name: "MaxInt64",
110 contents: "9223372036854775807 9223372036854775807\n",
111 want: 1,
112 wantOK: true,
113 },
114 {
115 name: "missing-newline",
116 contents: "500000 100000",
117 wantErr: true,
118 },
119 {
120 name: "v1",
121 contents: "500000\n",
122 wantErr: true,
123 },
124 {
125 name: "quota-not-a-number",
126 contents: "500000us 100000\n",
127 wantErr: true,
128 },
129 {
130 name: "period-not-a-number",
131 contents: "500000 100000us\n",
132 wantErr: true,
133 },
134 }
135
136 for _, tc := range tests {
137 t.Run(tc.name, func(t *testing.T) {
138 got, gotOK, err := cgroup.ParseV2Limit([]byte(tc.contents))
139 if tc.wantErr {
140 if err == nil {
141 t.Fatalf("parseV1Limit got err nil want non-nil")
142 }
143 return
144 }
145 if err != nil {
146 t.Fatalf("parseV2Limit got err %v want nil", err)
147 }
148
149 if gotOK != tc.wantOK {
150 t.Errorf("parseV2Limit got ok %v want %v", gotOK, tc.wantOK)
151 }
152
153 if tc.wantOK && got != tc.want {
154 t.Errorf("parseV2Limit got %f want %f", got, tc.want)
155 }
156 })
157 }
158 }
159
160 func TestParseCPURelativePath(t *testing.T) {
161 tests := []struct {
162 name string
163 contents string
164 want string
165 wantVer cgroup.Version
166 wantErr bool
167 }{
168 {
169 name: "empty",
170 contents: "",
171 wantErr: true,
172 },
173 {
174 name: "v1",
175 contents: `2:cpu,cpuacct:/a/b/cpu
176 1:blkio:/a/b/blkio
177 `,
178 want: "/a/b/cpu",
179 wantVer: cgroup.V1,
180 },
181 {
182 name: "v2",
183 contents: "0::/a/b/c\n",
184 want: "/a/b/c",
185 wantVer: cgroup.V2,
186 },
187 {
188 name: "mixed",
189 contents: `2:cpu,cpuacct:/a/b/cpu
190 1:blkio:/a/b/blkio
191 0::/a/b/v2
192 `,
193 want: "/a/b/cpu",
194 wantVer: cgroup.V1,
195 },
196 }
197
198 for _, tc := range tests {
199 t.Run(tc.name, func(t *testing.T) {
200 r := strings.NewReader(tc.contents)
201 read := func(fd int, b []byte) (int, uintptr) {
202 n, err := r.Read(b)
203 if err != nil && err != io.EOF {
204 const dummyErrno = 42
205 return n, dummyErrno
206 }
207 return n, 0
208 }
209
210 var got [cgroup.PathSize]byte
211 var scratch [cgroup.ParseSize]byte
212 n, gotVer, err := cgroup.ParseCPURelativePath(0, read, got[:], scratch[:])
213 if (err != nil) != tc.wantErr {
214 t.Fatalf("parseCPURelativePath got err %v want %v", err, tc.wantErr)
215 }
216
217 if gotVer != tc.wantVer {
218 t.Errorf("parseCPURelativePath got cgroup version %d want %d", gotVer, tc.wantVer)
219 }
220
221 if string(got[:n]) != tc.want {
222 t.Errorf("parseCPURelativePath got %q want %q", string(got[:n]), tc.want)
223 }
224 })
225 }
226 }
227
228 func TestContainsCPU(t *testing.T) {
229 tests := []struct {
230 in string
231 want bool
232 }{
233 {
234 in: "",
235 want: false,
236 },
237 {
238 in: ",",
239 want: false,
240 },
241 {
242 in: "cpu",
243 want: true,
244 },
245 {
246 in: "memory,cpu",
247 want: true,
248 },
249 {
250 in: "cpu,memory",
251 want: true,
252 },
253 {
254 in: "memory,cpu,block",
255 want: true,
256 },
257 {
258 in: "memory,cpuacct,block",
259 want: false,
260 },
261 }
262
263 for _, tc := range tests {
264 t.Run(tc.in, func(t *testing.T) {
265 got := cgroup.ContainsCPU([]byte(tc.in))
266 if got != tc.want {
267 t.Errorf("containsCPU(%q) got %v want %v", tc.in, got, tc.want)
268 }
269 })
270 }
271 }
272
273 func TestParseCPUMount(t *testing.T) {
274
275
276
277 const lowerPath = "/so/many/overlay/layers"
278 overlayLongLowerDir := lowerPath
279 for i := 0; len(overlayLongLowerDir) < cgroup.ScratchSize; i++ {
280 overlayLongLowerDir += fmt.Sprintf(":%s%d", lowerPath, i)
281 }
282
283 tests := []struct {
284 name string
285 contents string
286 want string
287 wantErr bool
288 }{
289 {
290 name: "empty",
291 contents: "",
292 wantErr: true,
293 },
294 {
295 name: "v1",
296 contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw
297 20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw
298 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw
299 49 22 0:37 / /sys/fs/cgroup/memory rw - cgroup cgroup rw,memory
300 54 22 0:38 / /sys/fs/cgroup/io rw - cgroup cgroup rw,io
301 56 22 0:40 / /sys/fs/cgroup/cpu rw - cgroup cgroup rw,cpu,cpuacct
302 58 22 0:42 / /sys/fs/cgroup/net rw - cgroup cgroup rw,net
303 59 22 0:43 / /sys/fs/cgroup/cpuset rw - cgroup cgroup rw,cpuset
304 `,
305 want: "/sys/fs/cgroup/cpu",
306 },
307 {
308 name: "v2",
309 contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw
310 20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw
311 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw
312 25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw
313 `,
314 want: "/sys/fs/cgroup",
315 },
316 {
317 name: "mixed",
318 contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw
319 20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw
320 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw
321 25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw
322 49 22 0:37 / /sys/fs/cgroup/memory rw - cgroup cgroup rw,memory
323 54 22 0:38 / /sys/fs/cgroup/io rw - cgroup cgroup rw,io
324 56 22 0:40 / /sys/fs/cgroup/cpu rw - cgroup cgroup rw,cpu,cpuacct
325 58 22 0:42 / /sys/fs/cgroup/net rw - cgroup cgroup rw,net
326 59 22 0:43 / /sys/fs/cgroup/cpuset rw - cgroup cgroup rw,cpuset
327 `,
328 want: "/sys/fs/cgroup/cpu",
329 },
330 {
331 name: "v2-escaped",
332 contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw
333 20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw
334 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw
335 25 21 0:22 / /sys/fs/cgroup/tab\011tab rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw
336 `,
337 want: `/sys/fs/cgroup/tab tab`,
338 },
339 {
340
341 name: "v2-longline",
342 contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw
343 20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw
344 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw
345 262 31 0:72 / /tmp/overlay2/0143e063b02f4801de9c847ad1c5ddc21fd2ead00653064d0c72ea967b248870/merged rw,relatime shared:729 - overlay overlay rw,lowerdir=` + overlayLongLowerDir + `,upperdir=/tmp/diff,workdir=/tmp/work
346 25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw
347 `,
348 want: "/sys/fs/cgroup",
349 },
350 }
351
352 for _, tc := range tests {
353 t.Run(tc.name, func(t *testing.T) {
354 r := strings.NewReader(tc.contents)
355 read := func(fd int, b []byte) (int, uintptr) {
356 n, err := r.Read(b)
357 if err != nil && err != io.EOF {
358 const dummyErrno = 42
359 return n, dummyErrno
360 }
361 return n, 0
362 }
363
364 var got [cgroup.PathSize]byte
365 var scratch [cgroup.ParseSize]byte
366 n, err := cgroup.ParseCPUMount(0, read, got[:], scratch[:])
367 if (err != nil) != tc.wantErr {
368 t.Fatalf("parseCPUMount got err %v want %v", err, tc.wantErr)
369 }
370
371 if string(got[:n]) != tc.want {
372 t.Errorf("parseCPUMount got %q want %q", string(got[:n]), tc.want)
373 }
374 })
375 }
376 }
377
378
379
380
381
382 func escapePath(s string) string {
383 out := make([]rune, 0, len(s))
384 for _, c := range s {
385 switch c {
386 case '\\', ' ', '\t', '\n':
387 out = append(out, '\\')
388 cs := strconv.FormatInt(int64(c), 8)
389 if len(cs) <= 2 {
390 out = append(out, '0')
391 }
392 if len(cs) <= 1 {
393 out = append(out, '0')
394 }
395 for _, csc := range cs {
396 out = append(out, csc)
397 }
398 default:
399 out = append(out, c)
400 }
401 }
402 return string(out)
403 }
404
405 func TestEscapePath(t *testing.T) {
406 tests := []struct {
407 name string
408 unescaped string
409 escaped string
410 }{
411 {
412 name: "boring",
413 unescaped: `/a/b/c`,
414 escaped: `/a/b/c`,
415 },
416 {
417 name: "space",
418 unescaped: `/a/b b/c`,
419 escaped: `/a/b\040b/c`,
420 },
421 {
422 name: "tab",
423 unescaped: `/a/b b/c`,
424 escaped: `/a/b\011b/c`,
425 },
426 {
427 name: "newline",
428 unescaped: `/a/b
429 b/c`,
430 escaped: `/a/b\012b/c`,
431 },
432 {
433 name: "slash",
434 unescaped: `/a/b\b/c`,
435 escaped: `/a/b\134b/c`,
436 },
437 {
438 name: "beginning",
439 unescaped: `\b/c`,
440 escaped: `\134b/c`,
441 },
442 {
443 name: "ending",
444 unescaped: `/a/\`,
445 escaped: `/a/\134`,
446 },
447 }
448
449 t.Run("escapePath", func(t *testing.T) {
450 for _, tc := range tests {
451 t.Run(tc.name, func(t *testing.T) {
452 got := escapePath(tc.unescaped)
453 if got != tc.escaped {
454 t.Errorf("escapePath got %q want %q", got, tc.escaped)
455 }
456 })
457 }
458 })
459
460 t.Run("unescapePath", func(t *testing.T) {
461 for _, tc := range tests {
462 t.Run(tc.name, func(t *testing.T) {
463 in := []byte(tc.escaped)
464 out := make([]byte, len(in))
465 n, err := cgroup.UnescapePath(out, in)
466 if err != nil {
467 t.Errorf("unescapePath got err %v want nil", err)
468 }
469 got := string(out[:n])
470 if got != tc.unescaped {
471 t.Errorf("unescapePath got %q want %q", got, tc.escaped)
472 }
473 })
474 }
475 })
476 }
477
View as plain text