This adds an Exception that extends the Python built-in subprocess.CalledProcessError. When this exception is raised, it will still be caught when selecting for the stdlib variant.
The difference is that the str() method of this Exception also adds the stdout/stderr logs. In effect, if this exception goes unhandled, Python will print the output in a nice, highlighted box to the terminal so that it's easy to spot. This should save some headache from having to re-run test suites with debugging enabled by augmenting the exceptions to print more information in the default case. Signed-off-by: John Snow <js...@redhat.com> --- tests/qemu-iotests/iotests.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 6ba65eb1ffe..5617f991da7 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -30,6 +30,7 @@ import struct import subprocess import sys +import textwrap import time from typing import (Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, TextIO, Tuple, Type, TypeVar) @@ -39,6 +40,7 @@ from qemu.machine import qtest from qemu.qmp import QMPMessage +from qemu.utils import enboxify # Use this logger for logging messages directly from the iotests module logger = logging.getLogger('qemu.iotests') @@ -117,6 +119,38 @@ sample_img_dir = os.environ['SAMPLE_IMG_DIR'] +class VerboseProcessError(subprocess.CalledProcessError): + """ + The same as CalledProcessError, but more verbose. + + This is useful for debugging failed calls during test executions. + The return code, signal (if any), and terminal output will be displayed + on unhandled exceptions. + """ + def summary(self) -> str: + return super().__str__() + + def __str__(self) -> str: + lmargin = ' ' + width = shutil.get_terminal_size()[0] - len(lmargin) + sections = [] + + name = 'output' if self.stderr is None else 'stdout' + if self.stdout: + sections.append(enboxify(self.stdout, width, name)) + else: + sections.append(f"{name}: N/A") + + if self.stderr: + sections.append(enboxify(self.stderr, width, 'stderr')) + elif self.stderr is not None: + sections.append("stderr: N/A") + + return os.linesep.join(( + self.summary(), + textwrap.indent(os.linesep.join(sections), prefix=lmargin), + )) + @contextmanager def change_log_level( logger_name: str, level: int = logging.CRITICAL) -> Iterator[None]: -- 2.34.1