test-runner: pass kmemleak and kmsg to Cmd.run
test-runner.py orchestrates all of the ZTS executions. The `Cmd` object manages these process, and its `run` method specifically invokes these possibly long-running processes, possibly retrying in the event of a timeout. Since its inception, memory leak detection using the kmemleak infrastructure [1], and kernel logging [2] have been added to this run mechanism. However, the callback to cull a process beyond its timeout threshold, `kill_cmd`, has evaded modernization by both of these changes. As a result, this function fails to properly invoke `run`, leading to an untrapped exception and unreported test failure. This patch extends `kill_cmd` to receive these kernel devices through the `options` parameter, and regularizes all the `.run` calls from `Cmd`, and its subclasses, to accept that parameter. [1] Commita69765ea5b
[2] Commitfc2c0256c5
Reviewed-by: John Wren Kennedy <john.kennedy@delphix.com> Signed-off-by: Antonio Russo <aerusso@aerusso.net> Closes #14849
This commit is contained in:
parent
ee7b71dbc9
commit
e0d5007bcf
|
@ -181,7 +181,7 @@ Timeout: %d
|
||||||
User: %s
|
User: %s
|
||||||
''' % (self.pathname, self.identifier, self.outputdir, self.timeout, self.user)
|
''' % (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
|
Kill a running command due to timeout, or ^C from the keyboard. If
|
||||||
sudo is required, this user was verified previously.
|
sudo is required, this user was verified previously.
|
||||||
|
@ -211,7 +211,7 @@ User: %s
|
||||||
if int(self.timeout) > runtime:
|
if int(self.timeout) > runtime:
|
||||||
self.killed = False
|
self.killed = False
|
||||||
self.reran = False
|
self.reran = False
|
||||||
self.run(False)
|
self.run(options, dryrun=False, kmemleak=kmemleak)
|
||||||
self.reran = True
|
self.reran = True
|
||||||
|
|
||||||
def update_cmd_privs(self, cmd, user):
|
def update_cmd_privs(self, cmd, user):
|
||||||
|
@ -257,15 +257,19 @@ User: %s
|
||||||
|
|
||||||
return out.lines, err.lines
|
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.
|
This is the main function that runs each individual test.
|
||||||
Determine whether or not the command requires sudo, and modify it
|
Determine whether or not the command requires sudo, and modify it
|
||||||
if needed. Run the command, and update the result object.
|
if needed. Run the command, and update the result object.
|
||||||
"""
|
"""
|
||||||
|
if dryrun is None:
|
||||||
|
dryrun = options.dryrun
|
||||||
if dryrun is True:
|
if dryrun is True:
|
||||||
print(self)
|
print(self)
|
||||||
return
|
return
|
||||||
|
if kmemleak is None:
|
||||||
|
kmemleak = options.kmemleak
|
||||||
|
|
||||||
privcmd = self.update_cmd_privs(self.pathname, self.user)
|
privcmd = self.update_cmd_privs(self.pathname, self.user)
|
||||||
try:
|
try:
|
||||||
|
@ -280,7 +284,7 @@ User: %s
|
||||||
Log each test we run to /dev/kmsg (on Linux), so if there's a kernel
|
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.
|
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:
|
try:
|
||||||
kp = Popen([SUDO, "sh", "-c",
|
kp = Popen([SUDO, "sh", "-c",
|
||||||
f"echo ZTS run {self.pathname} > /dev/kmsg"])
|
f"echo ZTS run {self.pathname} > /dev/kmsg"])
|
||||||
|
@ -298,7 +302,9 @@ User: %s
|
||||||
# Allow a special timeout value of 0 to mean infinity
|
# Allow a special timeout value of 0 to mean infinity
|
||||||
if int(self.timeout) == 0:
|
if int(self.timeout) == 0:
|
||||||
self.timeout = sys.maxsize / (10 ** 9)
|
self.timeout = sys.maxsize / (10 ** 9)
|
||||||
t = Timer(int(self.timeout), self.kill_cmd, [proc])
|
t = Timer(
|
||||||
|
int(self.timeout), self.kill_cmd, [proc, options, kmemleak]
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
t.start()
|
t.start()
|
||||||
|
@ -310,7 +316,7 @@ User: %s
|
||||||
cmd = f'{SUDO} cat {KMEMLEAK_FILE}'
|
cmd = f'{SUDO} cat {KMEMLEAK_FILE}'
|
||||||
self.result.kmemleak = check_output(cmd, shell=True)
|
self.result.kmemleak = check_output(cmd, shell=True)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.kill_cmd(proc, True)
|
self.kill_cmd(proc, options, kmemleak, True)
|
||||||
fail('\nRun terminated at user request.')
|
fail('\nRun terminated at user request.')
|
||||||
finally:
|
finally:
|
||||||
t.cancel()
|
t.cancel()
|
||||||
|
@ -450,7 +456,7 @@ Tags: %s
|
||||||
|
|
||||||
return True
|
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
|
Create Cmd instances for the pre/post/failsafe scripts. If the pre
|
||||||
script doesn't pass, skip this Test. Run the post script regardless.
|
script doesn't pass, skip this Test. Run the post script regardless.
|
||||||
|
@ -472,14 +478,14 @@ Tags: %s
|
||||||
|
|
||||||
cont = True
|
cont = True
|
||||||
if len(pretest.pathname):
|
if len(pretest.pathname):
|
||||||
pretest.run(options.dryrun, False, options.kmsg)
|
pretest.run(options, kmemleak=False)
|
||||||
cont = pretest.result.result == 'PASS'
|
cont = pretest.result.result == 'PASS'
|
||||||
pretest.log(options)
|
pretest.log(options)
|
||||||
|
|
||||||
if cont:
|
if cont:
|
||||||
test.run(options.dryrun, options.kmemleak, options.kmsg)
|
test.run(options, kmemleak=kmemleak)
|
||||||
if test.result.result == 'KILLED' and len(failsafe.pathname):
|
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)
|
failsafe.log(options, suppress_console=True)
|
||||||
else:
|
else:
|
||||||
test.skip()
|
test.skip()
|
||||||
|
@ -487,7 +493,7 @@ Tags: %s
|
||||||
test.log(options)
|
test.log(options)
|
||||||
|
|
||||||
if len(posttest.pathname):
|
if len(posttest.pathname):
|
||||||
posttest.run(options.dryrun, False, options.kmsg)
|
posttest.run(options, kmemleak=False)
|
||||||
posttest.log(options)
|
posttest.log(options)
|
||||||
|
|
||||||
|
|
||||||
|
@ -571,7 +577,7 @@ Tags: %s
|
||||||
|
|
||||||
return len(self.tests) != 0
|
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
|
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
|
script doesn't pass, skip all the tests in this TestGroup. Run the
|
||||||
|
@ -590,7 +596,7 @@ Tags: %s
|
||||||
|
|
||||||
cont = True
|
cont = True
|
||||||
if len(pretest.pathname):
|
if len(pretest.pathname):
|
||||||
pretest.run(options.dryrun, False, options.kmsg)
|
pretest.run(options, dryrun=dryrun, kmemleak=False)
|
||||||
cont = pretest.result.result == 'PASS'
|
cont = pretest.result.result == 'PASS'
|
||||||
pretest.log(options)
|
pretest.log(options)
|
||||||
|
|
||||||
|
@ -603,9 +609,9 @@ Tags: %s
|
||||||
failsafe = Cmd(self.failsafe, outputdir=odir, timeout=self.timeout,
|
failsafe = Cmd(self.failsafe, outputdir=odir, timeout=self.timeout,
|
||||||
user=self.failsafe_user, identifier=self.identifier)
|
user=self.failsafe_user, identifier=self.identifier)
|
||||||
if cont:
|
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):
|
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)
|
failsafe.log(options, suppress_console=True)
|
||||||
else:
|
else:
|
||||||
test.skip()
|
test.skip()
|
||||||
|
@ -613,7 +619,7 @@ Tags: %s
|
||||||
test.log(options)
|
test.log(options)
|
||||||
|
|
||||||
if len(posttest.pathname):
|
if len(posttest.pathname):
|
||||||
posttest.run(options.dryrun, False, options.kmsg)
|
posttest.run(options, dryrun=dryrun, kmemleak=False)
|
||||||
posttest.log(options)
|
posttest.log(options)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue