2 QEMU development and testing utilities
4 This package provides a small handful of utilities for performing
5 various tasks not directly related to the launching of a VM.
8 # Copyright (C) 2021 Red Hat Inc.
14 # This work is licensed under the terms of the GNU GPL, version 2. See
15 # the COPYING file in the top-level directory.
21 from subprocess import CalledProcessError
23 from typing import Optional
25 # pylint: disable=import-error
26 from .accel import kvm_available, list_accel, tcg_available
30 'VerboseProcessError',
32 'get_info_usernet_hostfwd_port',
39 def get_info_usernet_hostfwd_port(info_usernet_output: str) -> Optional[int]:
41 Returns the port given to the hostfwd parameter via info usernet
43 :param info_usernet_output: output generated by hmp command "info usernet"
44 :return: the port number allocated by the hostfwd option
46 for line in info_usernet_output.split('\r\n'):
47 regex = r'TCP.HOST_FORWARD.*127\.0\.0\.1\s+(\d+)\s+10\.'
48 match = re.search(regex, line)
54 # pylint: disable=too-many-arguments
55 def add_visual_margin(
57 width: Optional[int] = None,
58 name: Optional[str] = None,
60 upper_left: str = '┏',
61 lower_left: str = '┗',
62 horizontal: str = '━',
66 Decorate and wrap some text with a visual decoration around it.
68 This function assumes that the text decoration characters are single
69 characters that display using a single monospace column.
71 ┏━ Example ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
72 ┃ This is what this function looks like with text content that's
73 ┃ wrapped to 66 characters. The right-hand margin is left open to
74 ┃ accommodate the occasional unicode character that might make
75 ┃ predicting the total "visual" width of a line difficult. This
76 ┃ provides a visual distinction that's good-enough, though.
77 ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79 :param content: The text to wrap and decorate.
81 The number of columns to use, including for the decoration
82 itself. The default (None) uses the available width of the
83 current terminal, or a fallback of 72 lines. A negative number
84 subtracts a fixed-width from the default size. The default obeys
85 the COLUMNS environment variable, if set.
86 :param name: A label to apply to the upper-left of the box.
87 :param padding: How many columns of padding to apply inside.
88 :param upper_left: Upper-left single-width text decoration character.
89 :param lower_left: Lower-left single-width text decoration character.
90 :param horizontal: Horizontal single-width text decoration character.
91 :param vertical: Vertical single-width text decoration character.
93 if width is None or width < 0:
94 avail = shutil.get_terminal_size(fallback=(72, 24))[0]
98 _width = avail + width
102 prefix = vertical + (' ' * padding)
104 def _bar(name: Optional[str], top: bool = True) -> str:
105 ret = upper_left if top else lower_left
107 ret += f"{horizontal} {name} "
109 filler_len = _width - len(ret)
110 ret += f"{horizontal * filler_len}"
113 def _wrap(line: str) -> str:
114 return os.linesep.join(
116 line, width=_width - padding, initial_indent=prefix,
117 subsequent_indent=prefix, replace_whitespace=False,
118 drop_whitespace=True, break_on_hyphens=False)
121 return os.linesep.join((
122 _bar(name, top=True),
123 os.linesep.join(_wrap(line) for line in content.splitlines()),
124 _bar(None, top=False),
128 class VerboseProcessError(CalledProcessError):
130 The same as CalledProcessError, but more verbose.
132 This is useful for debugging failed calls during test executions.
133 The return code, signal (if any), and terminal output will be displayed
134 on unhandled exceptions.
136 def summary(self) -> str:
137 """Return the normal CalledProcessError str() output."""
138 return super().__str__()
140 def __str__(self) -> str:
142 width = -len(lmargin)
145 # Does self.stdout contain both stdout and stderr?
146 has_combined_output = self.stderr is None
148 name = 'output' if has_combined_output else 'stdout'
150 sections.append(add_visual_margin(self.stdout, width, name))
152 sections.append(f"{name}: N/A")
155 sections.append(add_visual_margin(self.stderr, width, 'stderr'))
156 elif not has_combined_output:
157 sections.append("stderr: N/A")
159 return os.linesep.join((
161 textwrap.indent(os.linesep.join(sections), prefix=lmargin),