Skip to content

Commit acc2e68

Browse files
committed
pyboot: the boot program prints full stack trace + catches exit status
1 parent ebb3466 commit acc2e68

File tree

5 files changed

+97
-33
lines changed

5 files changed

+97
-33
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ indent_style = tab
1818

1919
[*.py]
2020
indent_size = 4
21+
22+
[boot]
23+
indent_size = 4

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ endif(rapidjson)
148148
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etc/service.cmake DESTINATION includeos)
149149
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etc/library.cmake DESTINATION includeos)
150150
install(DIRECTORY vmrunner DESTINATION includeos/)
151-
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/etc/boot DESTINATION includeos/bin)
151+
install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/etc/boot DESTINATION includeos/bin)
152152
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/i686-elf-toolchain.cmake DESTINATION includeos)
153153
install(DIRECTORY seed/ DESTINATION includeos/seed)
154154

etc/boot

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import argparse
88
sys.path.append(os.environ['INCLUDEOS_PREFIX']+"/includeos")
99

1010
from vmrunner import vmrunner
11+
from vmrunner.prettify import color
1112

1213
# Argparse
1314
parser = argparse.ArgumentParser(
@@ -21,15 +22,20 @@ parser.add_argument('vmargs', nargs='*', help="Arguments to pass on to the VM s
2122
args = parser.parse_args()
2223

2324

24-
print "Args to pass to VM: ",args.vmargs
25+
nametag = "<boot>"
2526

26-
print "Found ",len(vmrunner.vms),"VM desription files"
27+
print color.INFO(nametag), "Args to pass to VM: ", args.vmargs
2728

29+
if len(vmrunner.vms) < 1:
30+
print color.FAIL("No vm description files found - nothing to boot")
31+
exit(-1)
32+
33+
print color.INFO(nametag), len(vmrunner.vms), "VM initialized. Commencing build- and boot..."
2834

2935
vm = vmrunner.vms[0]
3036

3137
if (args.clean):
32-
print ">>> Cleaning old build"
38+
print color.INFO(nametag), "Cleaning build"
3339
vm.clean()
3440

35-
vm.cmake().boot(kernel_args=" ".join(args.vmargs))
41+
vm.cmake().boot(timeout = None, kernel_args=" ".join(args.vmargs))

vmrunner/prettify.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ def WARNING(string):
5252
def FAIL(string):
5353
return "\n" + color.C_FAILED + "[ FAIL ] " + string.rstrip() + color.C_ENDC + "\n"
5454

55+
@staticmethod
56+
def EXIT_ERROR(tag, string):
57+
return "\n" + color.C_FAILED + "[ " + tag + " ] " + string.rstrip() + color.C_ENDC + "\n"
58+
5559
@staticmethod
5660
def FAIL_INLINE():
5761
return '[ ' + color.C_WHITE_ON_RED + " FAIL " + color.C_ENDC + ' ]'

vmrunner/vmrunner.py

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,23 @@
2727

2828
package_path = os.path.dirname(os.path.realpath(__file__))
2929

30+
# The end-of-transmission character
31+
EOT = chr(4)
32+
3033
# Exit codes used by this program
3134
exit_codes = {"SUCCESS" : 0,
3235
"PROGRAM_FAILURE" : 1,
3336
"TIMEOUT" : 66,
34-
"VM_FAIL" : 67,
37+
"VM_PANIC" : 67,
3538
"OUTSIDE_FAIL" : 68,
3639
"BUILD_FAIL" : 69,
37-
"ABORT" : 70 }
40+
"ABORT" : 70,
41+
"VM_EOT" : 71 }
3842

3943
def get_exit_code_name (exit_code):
4044
for name, code in exit_codes.iteritems():
4145
if code == exit_code: return name
42-
return "UNKNOWN"
46+
return "UNKNOWN ERROR"
4347

4448
# We want to catch the exceptions from callbacks, but still tell the test writer what went wrong
4549
def print_exception():
@@ -200,11 +204,13 @@ def boot(self, multiboot, kernel_args):
200204
try:
201205
self._proc = self.start_process(command)
202206
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
204208
raise e
205209

206210
def stop(self):
207211

212+
signal = "-SIGTERM"
213+
208214
# Don't try to kill twice
209215
if self._stopped:
210216
self.wait()
@@ -215,20 +221,23 @@ def stop(self):
215221
if self._proc and self._proc.poll() == None :
216222

217223
if not self._sudo:
224+
print color.INFO(self._nametag),"Stopping child process (no sudo required)"
218225
self._proc.terminate()
219-
220226
else:
221227
# Find and terminate all child processes, since parent is "sudo"
222228
parent = psutil.Process(self._proc.pid)
223229
children = parent.children()
224230

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+
226233
for child in children:
227234
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)])
229239

230240
# Wait for termination (avoids the need to reset the terminal etc.)
231-
print color.INFO(self._nametag),"process signalled"
232241
self.wait()
233242

234243
return self
@@ -237,11 +246,24 @@ def wait(self):
237246
if (self._proc): self._proc.wait()
238247
return self
239248

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+
240261
def readline(self):
241262
if self._proc.poll():
242263
raise Exception("Process completed")
243264
return self._proc.stdout.readline()
244265

266+
245267
def writeline(self, line):
246268
if self._proc.poll():
247269
raise Exception("Process completed")
@@ -255,25 +277,25 @@ class vm:
255277

256278
def __init__(self, config, hyper = qemu):
257279
self._exit_status = 0
280+
self._exit_msg = ""
258281
self._config = config
259282
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
261284
self._on_timeout = self.timeout
262285
self._on_output = {
263286
"PANIC" : self._on_panic,
264287
"SUCCESS" : self._on_success }
265288
assert(issubclass(hyper, hypervisor))
266289
self._hyper = hyper(config)
290+
self._timeout_after = None
267291
self._timer = None
268292
self._on_exit_success = lambda : None
269293
self._on_exit = lambda : None
270294
self._root = os.getcwd()
271295

272296
def stop(self):
273297
self._hyper.stop().wait()
274-
print color.INFO(nametag),"VM stopped"
275298
if self._timer:
276-
print color.INFO(nametag),"Cancelling timer"
277299
self._timer.cancel()
278300
return self
279301

@@ -289,7 +311,7 @@ def poll(self):
289311
def exit(self, status, msg):
290312
self._exit_status = status
291313
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),")"
293315
print color.INFO(nametag),"Calling on_exit"
294316
# Change back to test source
295317
os.chdir(self._root)
@@ -301,20 +323,32 @@ def exit(self, status, msg):
301323
return self._on_exit_success()
302324

303325
# Print fail message and exit with appropriate code
304-
print color.FAIL(msg)
326+
print color.EXIT_ERROR(get_exit_code_name(status), msg)
305327
sys.exit(status)
306328

307329
# Default timeout event
308330
def timeout(self):
309-
print color.INFO("timeout"), "VM timed out"
331+
print color.INFO("<timeout>"), "VM timed out"
310332

311333
# Note: we have to stop the VM since the main thread is blocking on vm.readline
312334
#self.exit(exit_codes["TIMEOUT"], nametag + " Test timed out")
313335
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"
315337
self._hyper.stop().wait()
316-
print color.INFO("timeout"), "Timer thread finished"
317338

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
318352
def on_output(self, output, callback):
319353
self._on_output[ output ] = callback
320354

@@ -333,23 +367,23 @@ def on_exit_success(self, callback):
333367
def on_exit(self, callback):
334368
self._on_exit = callback
335369

370+
# Read a line from the VM's standard out
336371
def readline(self):
337372
return self._hyper.readline()
338373

374+
# Write a line to VM stdout
339375
def writeline(self, line):
340376
return self._hyper.writeline(line)
341377

378+
# Make using GNU Make
342379
def make(self, params = []):
343380
print color.INFO(nametag), "Building with 'make' (params=" + str(params) + ")"
344381
make = ["make"]
345382
make.extend(params)
346383
cmd(make)
347384
return self
348385

349-
def clean(self):
350-
print color.INFO(nametag), "Cleaning cmake build folder"
351-
subprocess.call(["rm","-rf","build"])
352-
386+
# Call cmake
353387
def cmake(self, args = []):
354388
print color.INFO(nametag), "Building with cmake (%s)" % args
355389
# install dir:
@@ -379,10 +413,17 @@ def cmake(self, args = []):
379413
print "Excetption while building: ", e
380414
self.exit(exit_codes["BUILD_FAIL"], "building with cmake failed")
381415

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.
382422
def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunner"):
383423

384424
# This might be a reboot
385425
self._exit_status = None
426+
self._timeout_after = timeout
386427

387428
# Start the timeout thread
388429
if (timeout):
@@ -406,11 +447,23 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
406447
except Exception as e:
407448
print color.WARNING("Exception thrown while waiting for vm output")
408449
break
450+
409451
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+
411463
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+
414467
for pattern, func in self._on_output.iteritems():
415468
if re.search(pattern, line):
416469
try:
@@ -421,7 +474,7 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
421474
res = False
422475
self.stop()
423476

424-
#NOTE: It can be 'None' without problem
477+
# NOTE: It can be 'None' without problem
425478
if res == False:
426479
self._exit_status = exit_codes["OUTSIDE_FAIL"]
427480
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
433486
self.stop()
434487

435488
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)
437490
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()
439492
return self
440493

441494

@@ -452,15 +505,13 @@ def boot(self, timeout = 60, multiboot = True, kernel_args = "booted with vmrunn
452505
vms = []
453506

454507
if validate_vm.valid_vms:
455-
print
456508
print color.INFO(nametag), "Loaded VM specification(s) from JSON"
457509
for spec in validate_vm.valid_vms:
458510
print color.INFO(nametag), "Found VM spec: "
459511
print color.DATA(spec.__str__())
460512
vms.append(vm(spec))
461513

462514
else:
463-
print
464515
print color.WARNING(nametag), "No VM specification JSON found, trying default: ", default_spec
465516
vms.append(vm(default_spec))
466517

0 commit comments

Comments
 (0)