1
2
3 package jobobject
4
5 import (
6 "context"
7 "errors"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "syscall"
12 "testing"
13 "time"
14
15 "golang.org/x/sys/windows"
16 )
17
18 func TestJobNilOptions(t *testing.T) {
19 _, err := Create(context.Background(), nil)
20 if err != nil {
21 t.Fatal(err)
22 }
23 }
24
25 func TestJobCreateAndOpen(t *testing.T) {
26 var (
27 ctx = context.Background()
28 options = &Options{Name: "test"}
29 )
30 jobCreate, err := Create(ctx, options)
31 if err != nil {
32 t.Fatal(err)
33 }
34 defer jobCreate.Close()
35
36 jobOpen, err := Open(ctx, options)
37 if err != nil {
38 t.Fatal(err)
39 }
40 defer jobOpen.Close()
41 }
42
43 func TestSiloCreateAndOpen(t *testing.T) {
44 var (
45 ctx = context.Background()
46 options = &Options{
47 Name: "test",
48 Silo: true,
49 }
50 )
51 jobCreate, err := Create(ctx, options)
52 if err != nil {
53 t.Fatal(err)
54 }
55 defer jobCreate.Close()
56
57 jobOpen, err := Open(ctx, options)
58 if err != nil {
59 t.Fatal(err)
60 }
61 defer jobOpen.Close()
62
63 if !jobOpen.isSilo() {
64 t.Fatal("job is supposed to be a silo")
65 }
66 }
67
68 func TestJobStats(t *testing.T) {
69 var (
70 ctx = context.Background()
71 options = &Options{
72 Name: "test",
73 EnableIOTracking: true,
74 }
75 )
76 job, err := Create(ctx, options)
77 if err != nil {
78 t.Fatal(err)
79 }
80 defer job.Close()
81
82 _, err = createProcsAndAssign(1, job)
83 if err != nil {
84 t.Fatal(err)
85 }
86
87 _, err = job.QueryMemoryStats()
88 if err != nil {
89 t.Fatal(err)
90 }
91
92 _, err = job.QueryProcessorStats()
93 if err != nil {
94 t.Fatal(err)
95 }
96
97 _, err = job.QueryStorageStats()
98 if err != nil {
99 t.Fatal(err)
100 }
101
102 if err := job.Terminate(1); err != nil {
103 t.Fatal(err)
104 }
105 }
106
107 func TestIOTracking(t *testing.T) {
108 var (
109 ctx = context.Background()
110 options = &Options{
111 Name: "test",
112 }
113 )
114 job, err := Create(ctx, options)
115 if err != nil {
116 t.Fatal(err)
117 }
118 defer job.Close()
119
120 _, err = createProcsAndAssign(1, job)
121 if err != nil {
122 t.Fatal(err)
123 }
124
125 _, err = job.QueryStorageStats()
126
127 if err != nil && !errors.Is(err, windows.ERROR_NOT_FOUND) {
128 t.Fatal(err)
129 }
130
131
132 if err := job.SetIOTracking(); err != nil {
133 t.Fatal(err)
134 }
135
136 _, err = job.QueryStorageStats()
137 if err != nil {
138 t.Fatal(err)
139 }
140
141 if err := job.Terminate(1); err != nil {
142 t.Fatal(err)
143 }
144 }
145
146 func createProcsAndAssign(num int, job *JobObject) (_ []*exec.Cmd, err error) {
147 var procs []*exec.Cmd
148
149 defer func() {
150 if err != nil {
151 for _, proc := range procs {
152 _ = proc.Process.Kill()
153 }
154 }
155 }()
156
157 for i := 0; i < num; i++ {
158 cmd := exec.Command("ping", "-t", "127.0.0.1")
159 cmd.SysProcAttr = &syscall.SysProcAttr{
160 CreationFlags: windows.CREATE_NEW_PROCESS_GROUP,
161 }
162
163 if err := cmd.Start(); err != nil {
164 return nil, err
165 }
166
167 if err := job.Assign(uint32(cmd.Process.Pid)); err != nil {
168 return nil, err
169 }
170 procs = append(procs, cmd)
171 }
172 return procs, nil
173 }
174
175 func TestSetTerminateOnLastHandleClose(t *testing.T) {
176 job, err := Create(context.Background(), nil)
177 if err != nil {
178 t.Fatal(err)
179 }
180 defer job.Close()
181
182 if err := job.SetTerminateOnLastHandleClose(); err != nil {
183 t.Fatal(err)
184 }
185
186 procs, err := createProcsAndAssign(1, job)
187 if err != nil {
188 t.Fatal(err)
189 }
190
191 errCh := make(chan error)
192 go func() {
193 if err := job.Close(); err != nil {
194 errCh <- err
195 }
196 if err := procs[0].Wait(); err != nil {
197 errCh <- err
198 }
199
200
201 if !procs[0].ProcessState.Exited() {
202 errCh <- errors.New("process should have exited after closing job handle")
203 }
204 close(errCh)
205 }()
206
207 select {
208 case err := <-errCh:
209 if err != nil {
210 t.Fatal(err)
211 }
212 case <-time.After(time.Second * 10):
213 _ = procs[0].Process.Kill()
214 t.Fatal("process didn't complete wait within timeout")
215 }
216 }
217
218 func TestSetMultipleExtendedLimits(t *testing.T) {
219
220
221 job, err := Create(context.Background(), nil)
222 if err != nil {
223 t.Fatal(err)
224 }
225 defer job.Close()
226
227
228 memLimitInMB := uint64(10 * 1024 * 1204)
229 if err := job.SetMemoryLimit(memLimitInMB); err != nil {
230 t.Fatal(err)
231 }
232
233 if err := job.SetTerminateOnLastHandleClose(); err != nil {
234 t.Fatal(err)
235 }
236
237 eli, err := job.getExtendedInformation()
238 if err != nil {
239 t.Fatal(err)
240 }
241
242 if !isFlagSet(windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, eli.BasicLimitInformation.LimitFlags) {
243 t.Fatal("the job does not have cpu rate control enabled")
244 }
245
246 if !isFlagSet(windows.JOB_OBJECT_LIMIT_JOB_MEMORY, eli.BasicLimitInformation.LimitFlags) {
247 t.Fatal("the job does not have cpu rate control enabled")
248 }
249
250 if eli.JobMemoryLimit != uintptr(memLimitInMB) {
251 t.Fatal("job memory limit not persisted")
252 }
253 }
254
255 func TestNoMoreProcessesMessageKill(t *testing.T) {
256
257
258 options := &Options{
259 Notifications: true,
260 }
261 job, err := Create(context.Background(), options)
262 if err != nil {
263 t.Fatal(err)
264 }
265 defer job.Close()
266
267 if err := job.SetTerminateOnLastHandleClose(); err != nil {
268 t.Fatal(err)
269 }
270
271 procs, err := createProcsAndAssign(2, job)
272 if err != nil {
273 t.Fatal(err)
274 }
275
276 errCh := make(chan error)
277 go func() {
278 for _, proc := range procs {
279 if err := proc.Process.Kill(); err != nil {
280 errCh <- err
281 }
282 }
283
284 for {
285 notif, err := job.PollNotification()
286 if err != nil {
287 errCh <- err
288 }
289
290 switch notif.(type) {
291 case MsgAllProcessesExited:
292 close(errCh)
293 return
294 case MsgUnimplemented:
295 default:
296 }
297 }
298 }()
299
300 select {
301 case err := <-errCh:
302 if err != nil {
303 t.Fatal(err)
304 }
305 case <-time.After(time.Second * 10):
306 t.Fatal("didn't receive no more processes message within timeout")
307 }
308 }
309
310 func TestNoMoreProcessesMessageTerminate(t *testing.T) {
311
312
313 options := &Options{
314 Notifications: true,
315 }
316 job, err := Create(context.Background(), options)
317 if err != nil {
318 t.Fatal(err)
319 }
320 defer job.Close()
321
322 if err := job.SetTerminateOnLastHandleClose(); err != nil {
323 t.Fatal(err)
324 }
325
326 _, err = createProcsAndAssign(2, job)
327 if err != nil {
328 t.Fatal(err)
329 }
330
331 errCh := make(chan error)
332 go func() {
333 if err := job.Terminate(1); err != nil {
334 errCh <- err
335 }
336
337 for {
338 notif, err := job.PollNotification()
339 if err != nil {
340 errCh <- err
341 }
342
343 switch notif.(type) {
344 case MsgAllProcessesExited:
345 close(errCh)
346 return
347 case MsgUnimplemented:
348 default:
349 }
350 }
351 }()
352
353 select {
354 case err := <-errCh:
355 if err != nil {
356 t.Fatal(err)
357 }
358 case <-time.After(time.Second * 10):
359 t.Fatal("didn't receive no more processes message within timeout")
360 }
361 }
362
363 func TestVerifyPidCount(t *testing.T) {
364
365
366 job, err := Create(context.Background(), nil)
367 if err != nil {
368 t.Fatal(err)
369 }
370 defer job.Close()
371
372 numProcs := 2
373 _, err = createProcsAndAssign(numProcs, job)
374 if err != nil {
375 t.Fatal(err)
376 }
377
378 pids, err := job.Pids()
379 if err != nil {
380 t.Fatal(err)
381 }
382
383 if len(pids) != numProcs {
384 t.Fatalf("expected %d processes in the job, got: %d", numProcs, len(pids))
385 }
386
387 if err := job.Terminate(1); err != nil {
388 t.Fatal(err)
389 }
390 }
391
392 func TestSilo(t *testing.T) {
393
394 options := &Options{
395 Silo: true,
396 }
397 job, err := Create(context.Background(), options)
398 if err != nil {
399 t.Fatal(err)
400 }
401 defer job.Close()
402 }
403
404 func TestSiloFileBinding(t *testing.T) {
405
406
407 if _, err := os.Stat(`C:\windows\system32\bindfltapi.dll`); err != nil {
408 t.Skip("Bindflt not present on RS5 or lower, skipping.")
409 }
410
411 options := &Options{
412 Silo: true,
413 }
414 job, err := Create(context.Background(), options)
415 if err != nil {
416 t.Fatal(err)
417 }
418 defer job.Close()
419
420 target := t.TempDir()
421 hostPath := filepath.Join(target, "bind-test.txt")
422 f, err := os.Create(hostPath)
423 if err != nil {
424 t.Fatal(err)
425 }
426 defer f.Close()
427
428 root := t.TempDir()
429 siloPath := filepath.Join(root, "silo-path.txt")
430 if err := job.ApplyFileBinding(siloPath, hostPath, false); err != nil {
431 t.Fatal(err)
432 }
433
434
435 if _, err := os.Stat(siloPath); err == nil {
436 t.Fatalf("expected to not be able to see %q on the host", siloPath)
437 }
438
439
440
441
442
443 cmd := exec.Command("cmd", "/c", "ping", "localhost", "&&", "dir", siloPath)
444 if err := cmd.Start(); err != nil {
445 t.Fatal(err)
446 }
447
448 if err := job.Assign(uint32(cmd.Process.Pid)); err != nil {
449 t.Fatal(err)
450 }
451
452
453
454 if err := cmd.Wait(); err != nil {
455 t.Fatal(err)
456 }
457 }
458
View as plain text