diff --git a/changelogs/fragments/ansible-test-probe-error-handling.yml b/changelogs/fragments/ansible-test-probe-error-handling.yml new file mode 100644 index 00000000000..bf4301cc48b --- /dev/null +++ b/changelogs/fragments/ansible-test-probe-error-handling.yml @@ -0,0 +1,3 @@ +minor_changes: + - ansible-test - Improve container runtime probe error handling. + When unexpected probe output is encountered, an error with more useful debugging information is provided. diff --git a/test/lib/ansible_test/_internal/docker_util.py b/test/lib/ansible_test/_internal/docker_util.py index 97c022c1de7..32c4cea2493 100644 --- a/test/lib/ansible_test/_internal/docker_util.py +++ b/test/lib/ansible_test/_internal/docker_util.py @@ -20,6 +20,8 @@ from .util import ( SubprocessError, cache, OutputStream, + InternalError, + format_command_output, ) from .util_common import ( @@ -300,7 +302,7 @@ def detect_host_properties(args: CommonConfig) -> ContainerHostProperties: options = ['--volume', '/sys/fs/cgroup:/probe:ro'] cmd = ['sh', '-c', ' && echo "-" && '.join(multi_line_commands)] - stdout = run_utility_container(args, 'ansible-test-probe', cmd, options)[0] + stdout, stderr = run_utility_container(args, 'ansible-test-probe', cmd, options) if args.explain: return ContainerHostProperties( @@ -313,6 +315,12 @@ def detect_host_properties(args: CommonConfig) -> ContainerHostProperties: blocks = stdout.split('\n-\n') + if len(blocks) != len(multi_line_commands): + message = f'Unexpected probe output. Expected {len(multi_line_commands)} blocks but found {len(blocks)}.\n' + message += format_command_output(stdout, stderr) + + raise InternalError(message.strip()) + values = blocks[0].split('\n') audit_parts = values[0].split(' ', 1) diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py index 903cbcc50aa..6609373fe94 100644 --- a/test/lib/ansible_test/_internal/util.py +++ b/test/lib/ansible_test/_internal/util.py @@ -930,14 +930,7 @@ class SubprocessError(ApplicationError): error_callback: t.Optional[c.Callable[[SubprocessError], None]] = None, ) -> None: message = 'Command "%s" returned exit status %s.\n' % (shlex.join(cmd), status) - - if stderr: - message += '>>> Standard Error\n' - message += '%s%s\n' % (stderr.strip(), Display.clear) - - if stdout: - message += '>>> Standard Output\n' - message += '%s%s\n' % (stdout.strip(), Display.clear) + message += format_command_output(stdout, stderr) self.cmd = cmd self.message = message @@ -981,6 +974,21 @@ class HostConnectionError(ApplicationError): self._callback() +def format_command_output(stdout: str, stderr: str) -> str: + """Return a formatted string containing the given stdout and stderr (if any).""" + message = '' + + if stderr := stderr.strip(): + message += '>>> Standard Error\n' + message += f'{stderr}{Display.clear}\n' + + if stdout := stdout.strip(): + message += '>>> Standard Output\n' + message += f'{stdout}{Display.clear}\n' + + return message + + def retry(func: t.Callable[..., TValue], ex_type: t.Type[BaseException] = SubprocessError, sleep: int = 10, attempts: int = 10, warn: bool = True) -> TValue: """Retry the specified function on failure.""" for dummy in range(1, attempts):