2016-01-15 18:15:24 +00:00
|
|
|
# SPDX-License-Identifier: GPL-2.0
|
2018-05-06 21:58:06 +00:00
|
|
|
# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
# Logic to spawn a sub-process and interact with its stdio.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import pty
|
|
|
|
import signal
|
|
|
|
import select
|
|
|
|
import time
|
|
|
|
|
|
|
|
class Timeout(Exception):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""An exception sub-class that indicates that a timeout occurred."""
|
2016-01-15 18:15:24 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
class Spawn(object):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Represents the stdio of a freshly created sub-process. Commands may be
|
2016-01-15 18:15:24 +00:00
|
|
|
sent to the process, and responses waited for.
|
2016-07-04 17:58:39 +00:00
|
|
|
|
|
|
|
Members:
|
|
|
|
output: accumulated output from expect()
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
2016-01-28 06:57:53 +00:00
|
|
|
def __init__(self, args, cwd=None):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Spawn (fork/exec) the sub-process.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
Args:
|
2016-01-28 06:57:53 +00:00
|
|
|
args: array of processs arguments. argv[0] is the command to
|
|
|
|
execute.
|
|
|
|
cwd: the directory to run the process in, or None for no change.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Nothing.
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
self.waited = False
|
|
|
|
self.buf = ''
|
2016-07-04 17:58:39 +00:00
|
|
|
self.output = ''
|
2016-01-15 18:15:24 +00:00
|
|
|
self.logfile_read = None
|
|
|
|
self.before = ''
|
|
|
|
self.after = ''
|
|
|
|
self.timeout = None
|
test/py: strip VT100 codes from match buffer
Prior to this patch, any VT100 codes emitted by U-Boot are considered part
of a command's output, which often causes tests to fail. For example,
test_env_echo_exists executes printenv, and then considers any text on a
line before an = sign as a valid U-Boot environment variable name. This
includes any VT100 codes emitted. When the test later attempts to use that
variable, the name would be invalid since it includes the VT100 codes.
Solve this by stripping VT100 codes from the match buffer, so they are
never seen by higher level test code.
The codes are still logged unmodified, so that users can expect U-Boot's
exact output without interference. This does clutter the log file a bit.
However, it allows users to see exactly what U-Boot emitted rather than a
modified version, which hopefully is better for debugging. It's also much
simpler to implement, since logging happens as soon as text is received,
and so stripping the VT100 codes from the log would require handling
reception and stripping of partial VT100 codes.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
2016-07-06 16:34:30 +00:00
|
|
|
# http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
|
|
|
|
# Note that re.I doesn't seem to work with this regex (or perhaps the
|
|
|
|
# version of Python in Ubuntu 14.04), hence the inclusion of a-z inside
|
|
|
|
# [] instead.
|
|
|
|
self.re_vt100 = re.compile('(\x1b\[|\x9b)[^@-_a-z]*[@-_a-z]|\x1b[@-_a-z]')
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
(self.pid, self.fd) = pty.fork()
|
|
|
|
if self.pid == 0:
|
|
|
|
try:
|
|
|
|
# For some reason, SIGHUP is set to SIG_IGN at this point when
|
|
|
|
# run under "go" (www.go.cd). Perhaps this happens under any
|
|
|
|
# background (non-interactive) system?
|
|
|
|
signal.signal(signal.SIGHUP, signal.SIG_DFL)
|
2016-01-28 06:57:53 +00:00
|
|
|
if cwd:
|
|
|
|
os.chdir(cwd)
|
2016-01-15 18:15:24 +00:00
|
|
|
os.execvp(args[0], args)
|
|
|
|
except:
|
2017-09-14 21:34:43 +00:00
|
|
|
print('CHILD EXECEPTION:')
|
2016-01-15 18:15:24 +00:00
|
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
|
|
finally:
|
|
|
|
os._exit(255)
|
|
|
|
|
2016-02-10 23:54:37 +00:00
|
|
|
try:
|
|
|
|
self.poll = select.poll()
|
|
|
|
self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
|
|
|
|
except:
|
|
|
|
self.close()
|
|
|
|
raise
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
def kill(self, sig):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Send unix signal "sig" to the child process.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
Args:
|
|
|
|
sig: The signal number to send.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Nothing.
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
os.kill(self.pid, sig)
|
|
|
|
|
|
|
|
def isalive(self):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Determine whether the child process is still running.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
Args:
|
|
|
|
None.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Boolean indicating whether process is alive.
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
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):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Send data to the sub-process's stdin.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
Args:
|
|
|
|
data: The data to send to the process.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Nothing.
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
os.write(self.fd, data)
|
|
|
|
|
|
|
|
def expect(self, patterns):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Wait for the sub-process to emit specific data.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
This function waits for the process to emit one pattern from the
|
|
|
|
supplied list of patterns, or for a timeout to occur.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
patterns: A list of strings or regex objects that we expect to
|
|
|
|
see in the sub-process' stdout.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The index within the patterns array of the pattern the process
|
|
|
|
emitted.
|
|
|
|
|
|
|
|
Notable exceptions:
|
|
|
|
Timeout, if the process did not emit any of the patterns within
|
|
|
|
the expected time.
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
for pi in xrange(len(patterns)):
|
|
|
|
if type(patterns[pi]) == type(''):
|
|
|
|
patterns[pi] = re.compile(patterns[pi])
|
|
|
|
|
2016-01-22 19:30:07 +00:00
|
|
|
tstart_s = time.time()
|
2016-01-15 18:15:24 +00:00
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
earliest_m = None
|
|
|
|
earliest_pi = None
|
|
|
|
for pi in xrange(len(patterns)):
|
|
|
|
pattern = patterns[pi]
|
|
|
|
m = pattern.search(self.buf)
|
|
|
|
if not m:
|
|
|
|
continue
|
2016-01-28 06:57:47 +00:00
|
|
|
if earliest_m and m.start() >= earliest_m.start():
|
2016-01-15 18:15:24 +00:00
|
|
|
continue
|
|
|
|
earliest_m = m
|
|
|
|
earliest_pi = pi
|
|
|
|
if earliest_m:
|
|
|
|
pos = earliest_m.start()
|
2016-02-06 01:04:42 +00:00
|
|
|
posafter = earliest_m.end()
|
2016-01-15 18:15:24 +00:00
|
|
|
self.before = self.buf[:pos]
|
|
|
|
self.after = self.buf[pos:posafter]
|
2016-07-04 17:58:39 +00:00
|
|
|
self.output += self.buf[:posafter]
|
2016-01-15 18:15:24 +00:00
|
|
|
self.buf = self.buf[posafter:]
|
|
|
|
return earliest_pi
|
2016-01-22 19:30:07 +00:00
|
|
|
tnow_s = time.time()
|
test/py: support running sandbox under gdbserver
Implement command--line option --gdbserver COMM, which does two things:
a) Run the sandbox process under gdbserver, using COMM as gdbserver's
communication channel.
b) Disables all timeouts, so that if U-Boot is halted under the debugger,
tests don't fail. If the user gives up in the middle of a debugging
session, they can simply CTRL-C the test script to abort it.
This allows easy debugging of test failures without having to manually
re-create the failure conditions. Usage is:
Window 1:
./test/py/test.py --bd sandbox --gdbserver localhost:1234
Window 2:
gdb ./build-sandbox/u-boot -ex 'target remote localhost:1234'
When using this option, it likely makes sense to use pytest's -k option
to limit the set of tests that are executed.
Simply running U-Boot directly under gdb (rather than gdbserver) was
also considered. However, this was rejected because:
a) gdb's output would then be processed by the test script, and likely
confuse it causing false failures.
b) pytest by default hides stdout from tests, which would prevent the
user from interacting with gdb.
While gdb can be told to redirect the debugee's stdio to a separate
PTY, this would appear to leave gdb's stdio directed at the test
scripts and the debugee's stdio directed elsewhere, which is the
opposite of the desired effect. Perhaps some complicated PTY muxing
and process hierarchy could invert this. However, the current scheme
is simple to implement and use, so it doesn't seem worth complicating
matters.
c) Using gdbserver allows arbitrary debuggers to be used, even those with
a GUI. If the test scripts invoked the debugger themselves, they'd have
to know how to execute arbitary applications. While the user could hide
this all in a wrapper script, this feels like extra complication.
An interesting future idea might be a --gdb-screen option, which could
spawn both U-Boot and gdb separately, and spawn the screen into a newly
created window under screen. Similar options could be envisaged for
creating a new xterm/... too.
--gdbserver currently only supports sandbox, and not real hardware.
That's primarily because the test hooks are responsible for all aspects of
hardware control, so there's nothing for the test scripts themselves can
do to enable gdbserver on real hardware. We might consider introducing a
separate --disable-timeouts option to support use of debuggers on real
hardware, and having --gdbserver imply that option.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
2016-02-04 23:11:50 +00:00
|
|
|
if self.timeout:
|
|
|
|
tdelta_ms = (tnow_s - tstart_s) * 1000
|
|
|
|
poll_maxwait = self.timeout - tdelta_ms
|
|
|
|
if tdelta_ms > self.timeout:
|
|
|
|
raise Timeout()
|
|
|
|
else:
|
|
|
|
poll_maxwait = None
|
|
|
|
events = self.poll.poll(poll_maxwait)
|
2016-01-15 18:15:24 +00:00
|
|
|
if not events:
|
|
|
|
raise Timeout()
|
|
|
|
c = os.read(self.fd, 1024)
|
|
|
|
if not c:
|
|
|
|
raise EOFError()
|
|
|
|
if self.logfile_read:
|
|
|
|
self.logfile_read.write(c)
|
|
|
|
self.buf += c
|
test/py: strip VT100 codes from match buffer
Prior to this patch, any VT100 codes emitted by U-Boot are considered part
of a command's output, which often causes tests to fail. For example,
test_env_echo_exists executes printenv, and then considers any text on a
line before an = sign as a valid U-Boot environment variable name. This
includes any VT100 codes emitted. When the test later attempts to use that
variable, the name would be invalid since it includes the VT100 codes.
Solve this by stripping VT100 codes from the match buffer, so they are
never seen by higher level test code.
The codes are still logged unmodified, so that users can expect U-Boot's
exact output without interference. This does clutter the log file a bit.
However, it allows users to see exactly what U-Boot emitted rather than a
modified version, which hopefully is better for debugging. It's also much
simpler to implement, since logging happens as soon as text is received,
and so stripping the VT100 codes from the log would require handling
reception and stripping of partial VT100 codes.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
2016-07-06 16:34:30 +00:00
|
|
|
# count=0 is supposed to be the default, which indicates
|
|
|
|
# unlimited substitutions, but in practice the version of
|
|
|
|
# Python in Ubuntu 14.04 appears to default to count=2!
|
|
|
|
self.buf = self.re_vt100.sub('', self.buf, count=1000000)
|
2016-01-15 18:15:24 +00:00
|
|
|
finally:
|
|
|
|
if self.logfile_read:
|
|
|
|
self.logfile_read.flush()
|
|
|
|
|
|
|
|
def close(self):
|
2016-01-26 20:41:30 +00:00
|
|
|
"""Close the stdio connection to the sub-process.
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
This also waits a reasonable time for the sub-process to stop running.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
None.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Nothing.
|
2016-01-26 20:41:30 +00:00
|
|
|
"""
|
2016-01-15 18:15:24 +00:00
|
|
|
|
|
|
|
os.close(self.fd)
|
|
|
|
for i in xrange(100):
|
|
|
|
if not self.isalive():
|
|
|
|
break
|
|
|
|
time.sleep(0.1)
|
2016-07-04 17:58:39 +00:00
|
|
|
|
|
|
|
def get_expect_output(self):
|
|
|
|
"""Return the output read by expect()
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The output processed by expect(), as a string.
|
|
|
|
"""
|
|
|
|
return self.output
|