Merge branch 'selftests-net-py-improve-bkg-error-reporting'

Jakub Kicinski says:

====================
selftests: net: py: improve bkg() error reporting

bkg() is a helper for running commands in the background.
When init or body of a with() block fails check if the bkg()
process already exited and report its status (including stdout/
/stderr). This significantly improves debugability.
====================

Link: https://patch.msgid.link/20260223202633.4126087-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-02-24 18:25:32 -08:00
commit 7235555e9a

View File

@ -9,9 +9,17 @@ import subprocess
import time
class CmdExitFailure(Exception):
class CmdInitFailure(Exception):
""" Command failed to start. Only raised by bkg(). """
def __init__(self, msg, cmd_obj):
super().__init__(msg)
super().__init__(msg + "\n" + repr(cmd_obj))
self.cmd = cmd_obj
class CmdExitFailure(Exception):
""" Command failed (returned non-zero exit code). """
def __init__(self, msg, cmd_obj):
super().__init__(msg + "\n" + repr(cmd_obj))
self.cmd = cmd_obj
@ -76,16 +84,13 @@ class cmd:
msg = fd_read_timeout(rfd, ksft_wait)
os.close(rfd)
if not msg:
raise Exception("Did not receive ready message")
terminate = self.proc.poll() is None
self._process_terminate(terminate=terminate, timeout=1)
raise CmdInitFailure("Did not receive ready message", self)
if not background:
self.process(terminate=False, fail=fail, timeout=timeout)
def process(self, terminate=True, fail=None, timeout=5):
if fail is None:
fail = not terminate
if self.ksft_term_fd:
os.write(self.ksft_term_fd, b"1")
def _process_terminate(self, terminate, timeout):
if terminate:
self.proc.terminate()
stdout, stderr = self.proc.communicate(timeout)
@ -95,11 +100,21 @@ class cmd:
self.proc.stderr.close()
self.ret = self.proc.returncode
return stdout, stderr
def process(self, terminate=True, fail=None, timeout=5):
if fail is None:
fail = not terminate
if self.ksft_term_fd:
os.write(self.ksft_term_fd, b"1")
stdout, stderr = self._process_terminate(terminate=terminate,
timeout=timeout)
if self.proc.returncode != 0 and fail:
if len(stderr) > 0 and stderr[-1] == "\n":
stderr = stderr[:-1]
raise CmdExitFailure("Command failed: %s\nSTDOUT: %s\nSTDERR: %s" %
(self.proc.args, stdout, stderr), self)
raise CmdExitFailure("Command failed", self)
def __repr__(self):
def str_fmt(name, s):
@ -159,8 +174,11 @@ class bkg(cmd):
return self
def __exit__(self, ex_type, ex_value, ex_tb):
# Force termination on exception
terminate = self.terminate or (self._exit_wait and ex_type is not None)
terminate = self.terminate
# Force termination on exception, but only if bkg() didn't already exit
# since forcing termination silences failures with fail=None
if self.proc.poll() is None:
terminate = terminate or (self._exit_wait and ex_type is not None)
return self.process(terminate=terminate, fail=self.check_fail)