This adds an Exception that extends the garden variety 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 if we augment the exceptions we print more information in the default case. Signed-off-by: John Snow <[email protected]> --- 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..7df393df2c3 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 = [] + + if self.stdout: + name = 'output' if self.stderr is None else '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
