bkg() failures are currently quite hard to debug and spot. Often we have code along the lines of: with bkg("./cmd_rx_something -p PORT"): wait_port_listen(PORT) cmd("./cmd_tx_something", host=remote) When wait_port_listen() fails we don't get to see the exit status of bkg(). Even tho very often it's a failure in the bkg() command that's actually to blame. Try not to interfere with the bkg() command error checking. With: with bkg("false", exit_wait=True): time.sleep(0.01) # let the 'false' cmd run raise Exception("bla") Before: .. stack trace .. # Exception| Exception: bla After: .. stack trace .. # Exception| Exception: bla # Exception| # Exception| During handling of the above exception, another exception occurred: .. stack trace .. # Exception| lib.py.utils.CmdExitFailure: Command failed: false # Exception| STDOUT: b'' # Exception| STDERR: b'' Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib/py/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index 85884f3e827b..8fa1c2fabfc2 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -159,8 +159,11 @@ import time 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) -- 2.53.0 Reuse repr(cmd) instead of manually formatting a similar string. Before: # Exception| lib.py.utils.CmdExitFailure: Command failed: false # Exception| STDOUT: b'' # Exception| STDERR: b'' After: # Exception| lib.py.utils.CmdExitFailure: Command failed # Exception| CMD: false # Exception| EXIT: 1 Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib/py/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index 8fa1c2fabfc2..52d98ca139ff 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -11,7 +11,7 @@ import time class CmdExitFailure(Exception): def __init__(self, msg, cmd_obj): - super().__init__(msg) + super().__init__(msg + "\n" + repr(cmd_obj)) self.cmd = cmd_obj @@ -98,8 +98,7 @@ import time 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): -- 2.53.0 Gal recently complained: When [ksft_wait failure] happens, the test fails with a cryptic message: # Exception| Exception: Did not receive ready message Let's try to include the stdout/stderr of the command we tried to start. E.g. for cmd("false", ksft_wait=True): # Exception| lib.py.utils.CmdInitFailure: Did not receive ready message # Exception| CMD: false # Exception| EXIT: 1 We need to factor out _process_terminate() otherwise the exit path may try to write to already disconnected self.ksft_term_fd. Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib/py/utils.py | 30 ++++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index 52d98ca139ff..09535346c13d 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -9,7 +9,15 @@ import subprocess import time +class CmdInitFailure(Exception): + """ Command failed to start. Only raised by bkg(). """ + def __init__(self, msg, cmd_obj): + 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 @@ import time 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,6 +100,17 @@ import time 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] -- 2.53.0