aboutsummaryrefslogtreecommitdiff
path: root/tests/test-runner/bin/test-runner.py.in
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-runner/bin/test-runner.py.in')
-rwxr-xr-xtests/test-runner/bin/test-runner.py.in97
1 files changed, 56 insertions, 41 deletions
diff --git a/tests/test-runner/bin/test-runner.py.in b/tests/test-runner/bin/test-runner.py.in
index cb453b266f3c..65247f4f06fc 100755
--- a/tests/test-runner/bin/test-runner.py.in
+++ b/tests/test-runner/bin/test-runner.py.in
@@ -47,25 +47,25 @@ LOG_OUT = 'LOG_OUT'
LOG_ERR = 'LOG_ERR'
LOG_FILE_OBJ = None
+try:
+ from time import monotonic as monotonic_time
+except ImportError:
+ class timespec(ctypes.Structure):
+ _fields_ = [
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long)
+ ]
-class timespec(ctypes.Structure):
- _fields_ = [
- ('tv_sec', ctypes.c_long),
- ('tv_nsec', ctypes.c_long)
- ]
-
-
-librt = ctypes.CDLL('librt.so.1', use_errno=True)
-clock_gettime = librt.clock_gettime
-clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
-
+ librt = ctypes.CDLL('librt.so.1', use_errno=True)
+ clock_gettime = librt.clock_gettime
+ clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
-def monotonic_time():
- t = timespec()
- if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t)) != 0:
- errno_ = ctypes.get_errno()
- raise OSError(errno_, os.strerror(errno_))
- return t.tv_sec + t.tv_nsec * 1e-9
+ def monotonic_time():
+ t = timespec()
+ if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(t)) != 0:
+ errno_ = ctypes.get_errno()
+ raise OSError(errno_, os.strerror(errno_))
+ return t.tv_sec + t.tv_nsec * 1e-9
class Result(object):
@@ -113,8 +113,9 @@ class Output(object):
This class is a slightly modified version of the 'Stream' class found
here: http://goo.gl/aSGfv
"""
- def __init__(self, stream):
+ def __init__(self, stream, debug=False):
self.stream = stream
+ self.debug = debug
self._buf = b''
self.lines = []
@@ -140,6 +141,8 @@ class Output(object):
buf = os.read(fd, 4096)
if not buf:
return None
+ if self.debug:
+ os.write(sys.stderr.fileno(), buf)
if b'\n' not in buf:
self._buf += buf
return []
@@ -181,7 +184,7 @@ Timeout: %d
User: %s
''' % (self.pathname, self.identifier, self.outputdir, self.timeout, self.user)
- def kill_cmd(self, proc, keyboard_interrupt=False):
+ def kill_cmd(self, proc, options, kmemleak, keyboard_interrupt=False):
"""
Kill a running command due to timeout, or ^C from the keyboard. If
sudo is required, this user was verified previously.
@@ -211,7 +214,7 @@ User: %s
if int(self.timeout) > runtime:
self.killed = False
self.reran = False
- self.run(False)
+ self.run(options, dryrun=False, kmemleak=kmemleak)
self.reran = True
def update_cmd_privs(self, cmd, user):
@@ -238,14 +241,14 @@ User: %s
ret = '%s -E -u %s %s' % (SUDO, user, cmd)
return ret.split(' ')
- def collect_output(self, proc):
+ def collect_output(self, proc, debug=False):
"""
Read from stdout/stderr as data becomes available, until the
process is no longer running. Return the lines from the stdout and
stderr Output objects.
"""
- out = Output(proc.stdout)
- err = Output(proc.stderr)
+ out = Output(proc.stdout, debug)
+ err = Output(proc.stderr, debug)
res = []
while proc.returncode is None:
proc.poll()
@@ -257,15 +260,19 @@ User: %s
return out.lines, err.lines
- def run(self, dryrun, kmemleak, kmsg):
+ def run(self, options, dryrun=None, kmemleak=None):
"""
This is the main function that runs each individual test.
Determine whether or not the command requires sudo, and modify it
if needed. Run the command, and update the result object.
"""
+ if dryrun is None:
+ dryrun = options.dryrun
if dryrun is True:
print(self)
return
+ if kmemleak is None:
+ kmemleak = options.kmemleak
privcmd = self.update_cmd_privs(self.pathname, self.user)
try:
@@ -280,7 +287,7 @@ User: %s
Log each test we run to /dev/kmsg (on Linux), so if there's a kernel
warning we'll be able to match it up to a particular test.
"""
- if kmsg is True and exists("/dev/kmsg"):
+ if options.kmsg is True and exists("/dev/kmsg"):
try:
kp = Popen([SUDO, "sh", "-c",
f"echo ZTS run {self.pathname} > /dev/kmsg"])
@@ -297,12 +304,17 @@ User: %s
proc = Popen(privcmd, stdout=PIPE, stderr=PIPE)
# Allow a special timeout value of 0 to mean infinity
if int(self.timeout) == 0:
- self.timeout = sys.maxsize
- t = Timer(int(self.timeout), self.kill_cmd, [proc])
+ self.timeout = sys.maxsize / (10 ** 9)
+ t = Timer(
+ int(self.timeout), self.kill_cmd, [proc, options, kmemleak]
+ )
try:
t.start()
- self.result.stdout, self.result.stderr = self.collect_output(proc)
+
+ out, err = self.collect_output(proc, options.debug)
+ self.result.stdout = out
+ self.result.stderr = err
if kmemleak:
cmd = f'{SUDO} sh -c "echo scan > {KMEMLEAK_FILE}"'
@@ -310,7 +322,7 @@ User: %s
cmd = f'{SUDO} cat {KMEMLEAK_FILE}'
self.result.kmemleak = check_output(cmd, shell=True)
except KeyboardInterrupt:
- self.kill_cmd(proc, True)
+ self.kill_cmd(proc, options, kmemleak, True)
fail('\nRun terminated at user request.')
finally:
t.cancel()
@@ -450,7 +462,7 @@ Tags: %s
return True
- def run(self, options):
+ def run(self, options, dryrun=None, kmemleak=None):
"""
Create Cmd instances for the pre/post/failsafe scripts. If the pre
script doesn't pass, skip this Test. Run the post script regardless.
@@ -472,14 +484,14 @@ Tags: %s
cont = True
if len(pretest.pathname):
- pretest.run(options.dryrun, False, options.kmsg)
+ pretest.run(options, kmemleak=False)
cont = pretest.result.result == 'PASS'
pretest.log(options)
if cont:
- test.run(options.dryrun, options.kmemleak, options.kmsg)
+ test.run(options, kmemleak=kmemleak)
if test.result.result == 'KILLED' and len(failsafe.pathname):
- failsafe.run(options.dryrun, False, options.kmsg)
+ failsafe.run(options, kmemleak=False)
failsafe.log(options, suppress_console=True)
else:
test.skip()
@@ -487,7 +499,7 @@ Tags: %s
test.log(options)
if len(posttest.pathname):
- posttest.run(options.dryrun, False, options.kmsg)
+ posttest.run(options, kmemleak=False)
posttest.log(options)
@@ -571,7 +583,7 @@ Tags: %s
return len(self.tests) != 0
- def run(self, options):
+ def run(self, options, dryrun=None, kmemleak=None):
"""
Create Cmd instances for the pre/post/failsafe scripts. If the pre
script doesn't pass, skip all the tests in this TestGroup. Run the
@@ -590,7 +602,7 @@ Tags: %s
cont = True
if len(pretest.pathname):
- pretest.run(options.dryrun, False, options.kmsg)
+ pretest.run(options, dryrun=dryrun, kmemleak=False)
cont = pretest.result.result == 'PASS'
pretest.log(options)
@@ -603,9 +615,9 @@ Tags: %s
failsafe = Cmd(self.failsafe, outputdir=odir, timeout=self.timeout,
user=self.failsafe_user, identifier=self.identifier)
if cont:
- test.run(options.dryrun, options.kmemleak, options.kmsg)
+ test.run(options, dryrun=dryrun, kmemleak=kmemleak)
if test.result.result == 'KILLED' and len(failsafe.pathname):
- failsafe.run(options.dryrun, False, options.kmsg)
+ failsafe.run(options, dryrun=dryrun, kmemleak=False)
failsafe.log(options, suppress_console=True)
else:
test.skip()
@@ -613,12 +625,12 @@ Tags: %s
test.log(options)
if len(posttest.pathname):
- posttest.run(options.dryrun, False, options.kmsg)
+ posttest.run(options, dryrun=dryrun, kmemleak=False)
posttest.log(options)
class TestRun(object):
- props = ['quiet', 'outputdir']
+ props = ['quiet', 'outputdir', 'debug']
def __init__(self, options):
self.tests = {}
@@ -638,7 +650,8 @@ class TestRun(object):
('post_user', ''),
('failsafe', ''),
('failsafe_user', ''),
- ('tags', [])
+ ('tags', []),
+ ('debug', False)
]
def __str__(self):
@@ -1061,6 +1074,8 @@ def parse_args():
help='Specify tests to run via config files.')
parser.add_option('-d', action='store_true', default=False, dest='dryrun',
help='Dry run. Print tests, but take no other action.')
+ parser.add_option('-D', action='store_true', default=False, dest='debug',
+ help='Write all test output to stdout as it arrives.')
parser.add_option('-l', action='callback', callback=options_cb,
default=None, dest='logfile', metavar='logfile',
type='string',