zts: add a debug option to get full test output

The test runner accumulates output from individual tests, then writes it
to the log at the end. If a test hangs or crashes the system half way
through, we get no insight into how it got to where it did.

This adds a -D option for "debug". When set, all test output is written
to stdout.

Sponsored-by: Klara, Inc.
Sponsored-by: Wasabi Technology, Inc.
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Akash B <akash-b@hpe.com>
Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
Closes #16096
This commit is contained in:
Rob N 2024-04-17 02:13:01 +10:00 committed by Tony Hutter
parent f14a62ebbe
commit ad8c8c1e31
2 changed files with 25 additions and 8 deletions

View File

@ -32,6 +32,7 @@ SCRIPT_COMMON=${SCRIPT_COMMON:-${0%/*}/common.sh}
PROG=zfs-tests.sh PROG=zfs-tests.sh
VERBOSE="no" VERBOSE="no"
QUIET="" QUIET=""
DEBUG=""
CLEANUP="yes" CLEANUP="yes"
CLEANUPALL="no" CLEANUPALL="no"
KMSG="" KMSG=""
@ -313,6 +314,7 @@ OPTIONS:
-h Show this message -h Show this message
-v Verbose zfs-tests.sh output -v Verbose zfs-tests.sh output
-q Quiet test-runner output -q Quiet test-runner output
-D Debug; show all test output immediately (noisy)
-x Remove all testpools, dm, lo, and files (unsafe) -x Remove all testpools, dm, lo, and files (unsafe)
-k Disable cleanup after test failure -k Disable cleanup after test failure
-K Log test names to /dev/kmsg -K Log test names to /dev/kmsg
@ -351,7 +353,7 @@ $0 -x
EOF EOF
} }
while getopts 'hvqxkKfScRmn:d:s:r:?t:T:u:I:' OPTION; do while getopts 'hvqxkKfScRmn:d:Ds:r:?t:T:u:I:' OPTION; do
case $OPTION in case $OPTION in
h) h)
usage usage
@ -397,6 +399,9 @@ while getopts 'hvqxkKfScRmn:d:s:r:?t:T:u:I:' OPTION; do
d) d)
FILEDIR="$OPTARG" FILEDIR="$OPTARG"
;; ;;
D)
DEBUG="yes"
;;
I) I)
ITERATIONS="$OPTARG" ITERATIONS="$OPTARG"
if [ "$ITERATIONS" -le 0 ]; then if [ "$ITERATIONS" -le 0 ]; then
@ -691,6 +696,7 @@ REPORT_FILE=$(mktemp_file zts-report)
# #
msg "${TEST_RUNNER}" \ msg "${TEST_RUNNER}" \
"${QUIET:+-q}" \ "${QUIET:+-q}" \
"${DEBUG:+-D}" \
"${KMEMLEAK:+-m}" \ "${KMEMLEAK:+-m}" \
"${KMSG:+-K}" \ "${KMSG:+-K}" \
"-c \"${RUNFILES}\"" \ "-c \"${RUNFILES}\"" \
@ -700,6 +706,7 @@ msg "${TEST_RUNNER}" \
{ PATH=$STF_PATH \ { PATH=$STF_PATH \
${TEST_RUNNER} \ ${TEST_RUNNER} \
${QUIET:+-q} \ ${QUIET:+-q} \
${DEBUG:+-D} \
${KMEMLEAK:+-m} \ ${KMEMLEAK:+-m} \
${KMSG:+-K} \ ${KMSG:+-K} \
-c "${RUNFILES}" \ -c "${RUNFILES}" \
@ -726,6 +733,7 @@ if [ "$RESULT" -eq "2" ] && [ -n "$RERUN" ]; then
{ PATH=$STF_PATH \ { PATH=$STF_PATH \
${TEST_RUNNER} \ ${TEST_RUNNER} \
${QUIET:+-q} \ ${QUIET:+-q} \
${DEBUG:+-D} \
${KMEMLEAK:+-m} \ ${KMEMLEAK:+-m} \
-c "${RUNFILES}" \ -c "${RUNFILES}" \
-T "${TAGS}" \ -T "${TAGS}" \

View File

@ -113,8 +113,9 @@ class Output(object):
This class is a slightly modified version of the 'Stream' class found This class is a slightly modified version of the 'Stream' class found
here: http://goo.gl/aSGfv here: http://goo.gl/aSGfv
""" """
def __init__(self, stream): def __init__(self, stream, debug=False):
self.stream = stream self.stream = stream
self.debug = debug
self._buf = b'' self._buf = b''
self.lines = [] self.lines = []
@ -140,6 +141,8 @@ class Output(object):
buf = os.read(fd, 4096) buf = os.read(fd, 4096)
if not buf: if not buf:
return None return None
if self.debug:
os.write(sys.stderr.fileno(), buf)
if b'\n' not in buf: if b'\n' not in buf:
self._buf += buf self._buf += buf
return [] return []
@ -238,14 +241,14 @@ User: %s
ret = '%s -E -u %s %s' % (SUDO, user, cmd) ret = '%s -E -u %s %s' % (SUDO, user, cmd)
return ret.split(' ') 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 Read from stdout/stderr as data becomes available, until the
process is no longer running. Return the lines from the stdout and process is no longer running. Return the lines from the stdout and
stderr Output objects. stderr Output objects.
""" """
out = Output(proc.stdout) out = Output(proc.stdout, debug)
err = Output(proc.stderr) err = Output(proc.stderr, debug)
res = [] res = []
while proc.returncode is None: while proc.returncode is None:
proc.poll() proc.poll()
@ -308,7 +311,10 @@ User: %s
try: try:
t.start() 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: if kmemleak:
cmd = f'{SUDO} sh -c "echo scan > {KMEMLEAK_FILE}"' cmd = f'{SUDO} sh -c "echo scan > {KMEMLEAK_FILE}"'
@ -624,7 +630,7 @@ Tags: %s
class TestRun(object): class TestRun(object):
props = ['quiet', 'outputdir'] props = ['quiet', 'outputdir', 'debug']
def __init__(self, options): def __init__(self, options):
self.tests = {} self.tests = {}
@ -644,7 +650,8 @@ class TestRun(object):
('post_user', ''), ('post_user', ''),
('failsafe', ''), ('failsafe', ''),
('failsafe_user', ''), ('failsafe_user', ''),
('tags', []) ('tags', []),
('debug', False)
] ]
def __str__(self): def __str__(self):
@ -1067,6 +1074,8 @@ def parse_args():
help='Specify tests to run via config files.') help='Specify tests to run via config files.')
parser.add_option('-d', action='store_true', default=False, dest='dryrun', parser.add_option('-d', action='store_true', default=False, dest='dryrun',
help='Dry run. Print tests, but take no other action.') 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, parser.add_option('-l', action='callback', callback=options_cb,
default=None, dest='logfile', metavar='logfile', default=None, dest='logfile', metavar='logfile',
type='string', type='string',