27
27
28
28
package_path = os .path .dirname (os .path .realpath (__file__ ))
29
29
30
+ # The end-of-transmission character
31
+ EOT = chr (4 )
32
+
30
33
# Exit codes used by this program
31
34
exit_codes = {"SUCCESS" : 0 ,
32
35
"PROGRAM_FAILURE" : 1 ,
33
36
"TIMEOUT" : 66 ,
34
- "VM_FAIL " : 67 ,
37
+ "VM_PANIC " : 67 ,
35
38
"OUTSIDE_FAIL" : 68 ,
36
39
"BUILD_FAIL" : 69 ,
37
- "ABORT" : 70 }
40
+ "ABORT" : 70 ,
41
+ "VM_EOT" : 71 }
38
42
39
43
def get_exit_code_name (exit_code ):
40
44
for name , code in exit_codes .iteritems ():
41
45
if code == exit_code : return name
42
- return "UNKNOWN"
46
+ return "UNKNOWN ERROR "
43
47
44
48
# We want to catch the exceptions from callbacks, but still tell the test writer what went wrong
45
49
def print_exception ():
@@ -200,11 +204,13 @@ def boot(self, multiboot, kernel_args):
200
204
try :
201
205
self ._proc = self .start_process (command )
202
206
except Exception as e :
203
- print color .INFO (self ._nametag ),"Starting subprocess threw exception:" ,e
207
+ print color .INFO (self ._nametag ),"Starting subprocess threw exception:" , e
204
208
raise e
205
209
206
210
def stop (self ):
207
211
212
+ signal = "-SIGTERM"
213
+
208
214
# Don't try to kill twice
209
215
if self ._stopped :
210
216
self .wait ()
@@ -215,20 +221,23 @@ def stop(self):
215
221
if self ._proc and self ._proc .poll () == None :
216
222
217
223
if not self ._sudo :
224
+ print color .INFO (self ._nametag ),"Stopping child process (no sudo required)"
218
225
self ._proc .terminate ()
219
-
220
226
else :
221
227
# Find and terminate all child processes, since parent is "sudo"
222
228
parent = psutil .Process (self ._proc .pid )
223
229
children = parent .children ()
224
230
225
- print color .INFO (self ._nametag ),"Stopping" , self ._config ["image" ], "PID" ,self ._proc .pid
231
+ print color .INFO (self ._nametag ),"Stopping" , self ._config ["image" ], "PID" ,self ._proc .pid , "with" , signal
232
+
226
233
for child in children :
227
234
print color .INFO (self ._nametag )," + child process " , child .pid
228
- subprocess .check_output (["sudo" , "kill" , "-SIGTERM" , str (child .pid )])
235
+
236
+ # The process might have gotten an exit status by now so check again to avoid negative exit
237
+ if (not self ._proc .poll ()):
238
+ subprocess .call (["sudo" , "kill" , signal , str (child .pid )])
229
239
230
240
# Wait for termination (avoids the need to reset the terminal etc.)
231
- print color .INFO (self ._nametag ),"process signalled"
232
241
self .wait ()
233
242
234
243
return self
@@ -237,11 +246,24 @@ def wait(self):
237
246
if (self ._proc ): self ._proc .wait ()
238
247
return self
239
248
249
+ def read_until_EOT (self ):
250
+ chars = ""
251
+
252
+ while (not self ._proc .poll ()):
253
+ char = self ._proc .stdout .read (1 )
254
+ if char == chr (4 ):
255
+ return chars
256
+ chars += char
257
+
258
+ return chars
259
+
260
+
240
261
def readline (self ):
241
262
if self ._proc .poll ():
242
263
raise Exception ("Process completed" )
243
264
return self ._proc .stdout .readline ()
244
265
266
+
245
267
def writeline (self , line ):
246
268
if self ._proc .poll ():
247
269
raise Exception ("Process completed" )
@@ -255,25 +277,25 @@ class vm:
255
277
256
278
def __init__ (self , config , hyper = qemu ):
257
279
self ._exit_status = 0
280
+ self ._exit_msg = ""
258
281
self ._config = config
259
282
self ._on_success = lambda (line ) : self .exit (exit_codes ["SUCCESS" ], nametag + " All tests passed" )
260
- self ._on_panic = lambda ( line ) : self .exit ( exit_codes [ "VM_FAIL" ], nametag + self . _hyper . readline ())
283
+ self ._on_panic = self .panic
261
284
self ._on_timeout = self .timeout
262
285
self ._on_output = {
263
286
"PANIC" : self ._on_panic ,
264
287
"SUCCESS" : self ._on_success }
265
288
assert (issubclass (hyper , hypervisor ))
266
289
self ._hyper = hyper (config )
290
+ self ._timeout_after = None
267
291
self ._timer = None
268
292
self ._on_exit_success = lambda : None
269
293
self ._on_exit = lambda : None
270
294
self ._root = os .getcwd ()
271
295
272
296
def stop (self ):
273
297
self ._hyper .stop ().wait ()
274
- print color .INFO (nametag ),"VM stopped"
275
298
if self ._timer :
276
- print color .INFO (nametag ),"Cancelling timer"
277
299
self ._timer .cancel ()
278
300
return self
279
301
@@ -289,7 +311,7 @@ def poll(self):
289
311
def exit (self , status , msg ):
290
312
self ._exit_status = status
291
313
self .stop ()
292
- print color .INFO (nametag ),"Exited with status" , self ._exit_status , "(" ,get_exit_code_name (self ._exit_status ),")"
314
+ print color .INFO (nametag ),"Exit called with status" , self ._exit_status , "(" ,get_exit_code_name (self ._exit_status ),")"
293
315
print color .INFO (nametag ),"Calling on_exit"
294
316
# Change back to test source
295
317
os .chdir (self ._root )
@@ -301,20 +323,32 @@ def exit(self, status, msg):
301
323
return self ._on_exit_success ()
302
324
303
325
# Print fail message and exit with appropriate code
304
- print color .FAIL ( msg )
326
+ print color .EXIT_ERROR ( get_exit_code_name ( status ), msg )
305
327
sys .exit (status )
306
328
307
329
# Default timeout event
308
330
def timeout (self ):
309
- print color .INFO ("timeout" ), "VM timed out"
331
+ print color .INFO ("< timeout> " ), "VM timed out"
310
332
311
333
# Note: we have to stop the VM since the main thread is blocking on vm.readline
312
334
#self.exit(exit_codes["TIMEOUT"], nametag + " Test timed out")
313
335
self ._exit_status = exit_codes ["TIMEOUT" ]
314
- print color . INFO ( "timeout" ), "stopping subprocess "
336
+ self . _exit_msg = "vmrunner timed out after " + str ( self . _timeout_after ) + " seconds "
315
337
self ._hyper .stop ().wait ()
316
- print color .INFO ("timeout" ), "Timer thread finished"
317
338
339
+ # Default panic event
340
+ def panic (self , panic_line ):
341
+ panic_reason = self ._hyper .readline ()
342
+ print color .INFO (nametag ), "VM signalled PANIC. Reading until EOT (" , hex (ord (EOT )), ")"
343
+ print color .VM (panic_reason ),
344
+ remaining_output = self ._hyper .read_until_EOT ()
345
+ for line in remaining_output .split ("\n " ):
346
+ print color .VM (line )
347
+
348
+ self .exit (exit_codes ["VM_PANIC" ], panic_reason )
349
+
350
+
351
+ # Events - subscribable
318
352
def on_output (self , output , callback ):
319
353
self ._on_output [ output ] = callback
320
354
@@ -333,23 +367,23 @@ def on_exit_success(self, callback):
333
367
def on_exit (self , callback ):
334
368
self ._on_exit = callback
335
369
370
+ # Read a line from the VM's standard out
336
371
def readline (self ):
337
372
return self ._hyper .readline ()
338
373
374
+ # Write a line to VM stdout
339
375
def writeline (self , line ):
340
376
return self ._hyper .writeline (line )
341
377
378
+ # Make using GNU Make
342
379
def make (self , params = []):
343
380
print color .INFO (nametag ), "Building with 'make' (params=" + str (params ) + ")"
344
381
make = ["make" ]
345
382
make .extend (params )
346
383
cmd (make )
347
384
return self
348
385
349
- def clean (self ):
350
- print color .INFO (nametag ), "Cleaning cmake build folder"
351
- subprocess .call (["rm" ,"-rf" ,"build" ])
352
-
386
+ # Call cmake
353
387
def cmake (self , args = []):
354
388
print color .INFO (nametag ), "Building with cmake (%s)" % args
355
389
# install dir:
@@ -379,10 +413,17 @@ def cmake(self, args = []):
379
413
print "Excetption while building: " , e
380
414
self .exit (exit_codes ["BUILD_FAIL" ], "building with cmake failed" )
381
415
416
+ # Clean cmake build folder
417
+ def clean (self ):
418
+ print color .INFO (nametag ), "Cleaning cmake build folder"
419
+ subprocess .call (["rm" ,"-rf" ,"build" ])
420
+
421
+ # Boot the VM and start reading output. This is the main event loop.
382
422
def boot (self , timeout = 60 , multiboot = True , kernel_args = "booted with vmrunner" ):
383
423
384
424
# This might be a reboot
385
425
self ._exit_status = None
426
+ self ._timeout_after = timeout
386
427
387
428
# Start the timeout thread
388
429
if (timeout ):
@@ -406,11 +447,23 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
406
447
except Exception as e :
407
448
print color .WARNING ("Exception thrown while waiting for vm output" )
408
449
break
450
+
409
451
if line :
410
- print color .VM (line .rstrip ())
452
+ # Special case for end-of-transmission
453
+ if line == EOT :
454
+ if not self ._exit_status : self ._exit_status = exit_codes ["VM_EOT" ]
455
+ break
456
+ if line .startswith (" [ Kernel ] service exited with status" ):
457
+ self ._exit_status = int (line .split (" " )[- 1 ].rstrip ())
458
+ self ._exit_msg = "Service exited"
459
+ break
460
+ else :
461
+ print color .VM (line .rstrip ())
462
+
411
463
else :
412
- print color .INFO (nametag ), "VM output reached EOF"
413
- # Look for event-triggers
464
+ pass
465
+ # TODO: Add event-trigger for EOF?
466
+
414
467
for pattern , func in self ._on_output .iteritems ():
415
468
if re .search (pattern , line ):
416
469
try :
@@ -421,7 +474,7 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
421
474
res = False
422
475
self .stop ()
423
476
424
- #NOTE: It can be 'None' without problem
477
+ # NOTE: It can be 'None' without problem
425
478
if res == False :
426
479
self ._exit_status = exit_codes ["OUTSIDE_FAIL" ]
427
480
self .exit (self ._exit_status , " Event-triggered test failed" )
@@ -433,9 +486,9 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
433
486
self .stop ()
434
487
435
488
if self ._exit_status :
436
- self .exit (self ._exit_status , get_exit_code_name ( self ._exit_status ) )
489
+ self .exit (self ._exit_status , self ._exit_msg )
437
490
else :
438
- print color .INFO (nametag ), " Subprocess finished, exit status" , self ._hyper .poll ()
491
+ print color .INFO (nametag ), "VM process exited with status" , self ._hyper .poll ()
439
492
return self
440
493
441
494
@@ -452,15 +505,13 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
452
505
vms = []
453
506
454
507
if validate_vm .valid_vms :
455
- print
456
508
print color .INFO (nametag ), "Loaded VM specification(s) from JSON"
457
509
for spec in validate_vm .valid_vms :
458
510
print color .INFO (nametag ), "Found VM spec: "
459
511
print color .DATA (spec .__str__ ())
460
512
vms .append (vm (spec ))
461
513
462
514
else :
463
- print
464
515
print color .WARNING (nametag ), "No VM specification JSON found, trying default: " , default_spec
465
516
vms .append (vm (default_spec ))
466
517
0 commit comments