pytest: Show a message when sandbox crashes

When a test hands on a real board there is no way on the console to obtain
any information about why it hung.

With sandbox we can actually find out that it died and get a signal or
exit code. Add this to make it easier to figure out what happened.

So instead of:

test/py/u_boot_spawn.py:171: in expect
    c = os.read(self.fd, 1024).decode(errors='replace')
E   OSError: [Errno 5] Input/output error

We get:

test/py/u_boot_spawn.py:171: in expect
    c = os.read(self.fd, 1024).decode(errors='replace')
E   ValueError: U-Boot exited with signal 11 (Signals.SIGSEGV)

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2021-10-08 09:15:23 -06:00 committed by Tom Rini
parent c3aea68705
commit 35839eda8b
2 changed files with 53 additions and 13 deletions

View file

@ -103,6 +103,14 @@ will be written to `${build_dir}/test-log.html`. This is best viewed in a web
browser, but may be read directly as plain text, perhaps with the aid of the browser, but may be read directly as plain text, perhaps with the aid of the
`html2text` utility. `html2text` utility.
If sandbox crashes (e.g. with a segfault) you will see message like this::
test/py/u_boot_spawn.py:171: in expect
c = os.read(self.fd, 1024).decode(errors='replace')
E ValueError: U-Boot exited with signal 11 (Signals.SIGSEGV)
Controlling output Controlling output
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~

View file

@ -35,6 +35,8 @@ class Spawn(object):
""" """
self.waited = False self.waited = False
self.exit_code = 0
self.exit_info = ''
self.buf = '' self.buf = ''
self.output = '' self.output = ''
self.logfile_read = None self.logfile_read = None
@ -80,6 +82,34 @@ class Spawn(object):
os.kill(self.pid, sig) os.kill(self.pid, sig)
def checkalive(self):
"""Determine whether the child process is still running.
Returns:
tuple:
True if process is alive, else False
0 if process is alive, else exit code of process
string describing what happened ('' or 'status/signal n')
"""
if self.waited:
return False, self.exit_code, self.exit_info
w = os.waitpid(self.pid, os.WNOHANG)
if w[0] == 0:
return True, 0, 'running'
status = w[1]
if os.WIFEXITED(status):
self.exit_code = os.WEXITSTATUS(status)
self.exit_info = 'status %d' % self.exit_code
elif os.WIFSIGNALED(status):
signum = os.WTERMSIG(status)
self.exit_code = -signum
self.exit_info = 'signal %d (%s)' % (signum, signal.Signals(signum))
self.waited = True
return False, self.exit_code, self.exit_info
def isalive(self): def isalive(self):
"""Determine whether the child process is still running. """Determine whether the child process is still running.
@ -89,16 +119,7 @@ class Spawn(object):
Returns: Returns:
Boolean indicating whether process is alive. Boolean indicating whether process is alive.
""" """
return self.checkalive()[0]
if self.waited:
return False
w = os.waitpid(self.pid, os.WNOHANG)
if w[0] == 0:
return True
self.waited = True
return False
def send(self, data): def send(self, data):
"""Send data to the sub-process's stdin. """Send data to the sub-process's stdin.
@ -168,9 +189,20 @@ class Spawn(object):
events = self.poll.poll(poll_maxwait) events = self.poll.poll(poll_maxwait)
if not events: if not events:
raise Timeout() raise Timeout()
c = os.read(self.fd, 1024).decode(errors='replace') try:
if not c: c = os.read(self.fd, 1024).decode(errors='replace')
raise EOFError() except OSError as err:
# With sandbox, try to detect when U-Boot exits when it
# shouldn't and explain why. This is much more friendly than
# just dying with an I/O error
if err.errno == 5: # Input/output error
alive, exit_code, info = self.checkalive()
if alive:
raise
else:
raise ValueError('U-Boot exited with %s' % info)
else:
raise
if self.logfile_read: if self.logfile_read:
self.logfile_read.write(c) self.logfile_read.write(c)
self.buf += c self.buf += c