Python patches for 5.1

- Reduce race conditions on QEMUMachine::shutdown()
 
  1. Remove the "bare except" pattern in the existing shutdown code,
     which can mask problems and make debugging difficult.
  2. Ensure that post-shutdown cleanup is always performed, even when
     graceful termination fails.
  3. Unify cleanup paths such that no matter how the VM is terminated,
     the same functions and steps are always taken to reset the object
     state.
  4. Rewrite shutdown() such that any error encountered when attempting
     a graceful shutdown will be raised as an AbnormalShutdown exception.
     The pythonic idiom is to allow the caller to decide if this is a
     problem or not.
 
 - Modify part of the python/qemu library to comply with:
 
   . mypy --strict
   . pylint
   . flake8
 
 - Script for the TCG Continuous Benchmarking project that uses
   callgrind to dissect QEMU execution into three main phases:
 
   . code generation
   . JIT execution
   . helpers execution
 
 CI jobs results:
 . https://cirrus-ci.com/build/5421349961203712
 . https://gitlab.com/philmd/qemu/-/pipelines/166556001
 . https://travis-ci.org/github/philmd/qemu/builds/708102347
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAl8OGCUACgkQ4+MsLN6t
 wN52ZQ//cB8pRhOeirMzPTamtAePAHhU9kRKSQ4fFzVM/AHxaO3qO3KrMKAv4JnR
 1fwdyAbum0Dv+HrRAXbIVz/lgrlTb6tLqwk20YWlmNUR2CtMmno9fPQzg6LeFWBe
 +qr2/anXWBqRZu5uHzQ3Ly3LxlVXE3qmStxlttNxed9wK9wdhsQ4ijOrExXPmMI5
 IQi9tKijsGvOczcSpIwJET6Px0L/GIYHSFGXfHv4LiJCMQUX4gqY1akr+/2adqlr
 ElcOHlvWG90GHftTC+hq+KbSNmpkwJEnXLHC+HwMaYmQuhH4+U6f9AcrnzYGHjoQ
 84muuYt5CBvT/Q38qO3dUsr42oD8uiDZe2cpD5DmcAHj6U9sza4UwU2Nkd4cNvRD
 OiYRWuHQDzQhwPGbzBym1GRxRlejmPXCpvZ4XqaYODkzhEHi8UVq5AOsNhpTGWVk
 WoDZ3NtnZb9WbU93wDordhWTtHKHOLHNk4CuJLbdRvkW+sg8aFqUObSRvc6RqyU8
 1DXWMMQr9UfCDfMMbp/OCymphn0VrAaixIRIjmYc6nL5mQlyugMwij55jzyugqBH
 rN5bdcsqxAhBTD03lu0lbDn1BYIqbZFXABXeDnJNbxmY/6gD0E1HKWRvIKjxzH+t
 L19QXsOWLoVUpUIjxlOgMlb9QJLPc9m/N3yVOanO0zVrMSBmOKQ=
 =Jp6n
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/philmd-gitlab/tags/python-next-20200714' into staging

Python patches for 5.1

- Reduce race conditions on QEMUMachine::shutdown()

 1. Remove the "bare except" pattern in the existing shutdown code,
    which can mask problems and make debugging difficult.
 2. Ensure that post-shutdown cleanup is always performed, even when
    graceful termination fails.
 3. Unify cleanup paths such that no matter how the VM is terminated,
    the same functions and steps are always taken to reset the object
    state.
 4. Rewrite shutdown() such that any error encountered when attempting
    a graceful shutdown will be raised as an AbnormalShutdown exception.
    The pythonic idiom is to allow the caller to decide if this is a
    problem or not.

- Modify part of the python/qemu library to comply with:

  . mypy --strict
  . pylint
  . flake8

- Script for the TCG Continuous Benchmarking project that uses
  callgrind to dissect QEMU execution into three main phases:

  . code generation
  . JIT execution
  . helpers execution

CI jobs results:
. https://cirrus-ci.com/build/5421349961203712
. https://gitlab.com/philmd/qemu/-/pipelines/166556001
. https://travis-ci.org/github/philmd/qemu/builds/708102347

# gpg: Signature made Tue 14 Jul 2020 21:40:05 BST
# gpg:                using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE
# gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [full]
# Primary key fingerprint: FAAB E75E 1291 7221 DCFD  6BB2 E3E3 2C2C DEAD C0DE

* remotes/philmd-gitlab/tags/python-next-20200714:
  python/qmp.py: add QMPProtocolError
  python/qmp.py: add casts to JSON deserialization
  python/qmp.py: Do not return None from cmd_obj
  python/qmp.py: re-absorb MonitorResponseError
  iotests.py: use qemu.qmp type aliases
  python/qmp.py: Define common types
  python/machine.py: change default wait timeout to 3 seconds
  python/machine.py: re-add sigkill warning suppression
  python/machine.py: split shutdown into hard and soft flavors
  tests/acceptance: Don't test reboot on cubieboard
  tests/acceptance: wait() instead of shutdown() where appropriate
  python/machine.py: Make wait() call shutdown()
  python/machine.py: Add a configurable timeout to shutdown()
  python/machine.py: Prohibit multiple shutdown() calls
  python/machine.py: Perform early cleanup for wait() calls, too
  python/machine.py: Add _early_cleanup hook
  python/machine.py: Close QMP socket in cleanup
  python/machine.py: consolidate _post_shutdown()
  scripts/performance: Add dissect.py script

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-07-15 13:04:27 +01:00
commit 673205379f
7 changed files with 369 additions and 72 deletions

View File

@ -22,6 +22,7 @@ import logging
import os
import subprocess
import shutil
import signal
import socket
import tempfile
from typing import Optional, Type
@ -49,17 +50,10 @@ class QEMUMachineAddDeviceError(QEMUMachineError):
"""
class MonitorResponseError(qmp.QMPError):
class AbnormalShutdown(QEMUMachineError):
"""
Represents erroneous QMP monitor reply
Exception raised when a graceful shutdown was requested, but not performed.
"""
def __init__(self, reply):
try:
desc = reply["error"]["desc"]
except KeyError:
desc = reply
super().__init__(desc)
self.reply = reply
class QEMUMachine:
@ -127,6 +121,7 @@ class QEMUMachine:
self._console_address = None
self._console_socket = None
self._remove_files = []
self._user_killed = False
self._console_log_path = console_log
if self._console_log_path:
# In order to log the console, buffering needs to be enabled.
@ -294,6 +289,19 @@ class QEMUMachine:
self._qmp.accept()
def _post_shutdown(self):
"""
Called to cleanup the VM instance after the process has exited.
May also be called after a failed launch.
"""
# Comprehensive reset for the failed launch case:
self._early_cleanup()
if self._qmp:
self._qmp.close()
self._qmp = None
self._load_io_log()
if self._qemu_log_file is not None:
self._qemu_log_file.close()
self._qemu_log_file = None
@ -307,6 +315,19 @@ class QEMUMachine:
while len(self._remove_files) > 0:
self._remove_if_exists(self._remove_files.pop())
exitcode = self.exitcode()
if (exitcode is not None and exitcode < 0
and not (self._user_killed and exitcode == -signal.SIGKILL)):
msg = 'qemu received signal %i; command: "%s"'
if self._qemu_full_args:
command = ' '.join(self._qemu_full_args)
else:
command = ''
LOG.warning(msg, -int(exitcode), command)
self._user_killed = False
self._launched = False
def launch(self):
"""
Launch the VM and make sure we cleanup and expose the
@ -322,7 +343,7 @@ class QEMUMachine:
self._launch()
self._launched = True
except:
self.shutdown()
self._post_shutdown()
LOG.debug('Error launching VM')
if self._qemu_full_args:
@ -348,19 +369,12 @@ class QEMUMachine:
close_fds=False)
self._post_launch()
def wait(self):
def _early_cleanup(self) -> None:
"""
Wait for the VM to power off
"""
self._popen.wait()
if self._qmp:
self._qmp.close()
self._load_io_log()
self._post_shutdown()
Perform any cleanup that needs to happen before the VM exits.
def shutdown(self, has_quit=False, hard=False):
"""
Terminate the VM and clean up
May be invoked by both soft and hard shutdown in failover scenarios.
Called additionally by _post_shutdown for comprehensive cleanup.
"""
# If we keep the console socket open, we may deadlock waiting
# for QEMU to exit, while QEMU is waiting for the socket to
@ -369,37 +383,105 @@ class QEMUMachine:
self._console_socket.close()
self._console_socket = None
if self.is_running():
def _hard_shutdown(self) -> None:
"""
Perform early cleanup, kill the VM, and wait for it to terminate.
:raise subprocess.Timeout: When timeout is exceeds 60 seconds
waiting for the QEMU process to terminate.
"""
self._early_cleanup()
self._popen.kill()
self._popen.wait(timeout=60)
def _soft_shutdown(self, has_quit: bool = False,
timeout: Optional[int] = 3) -> None:
"""
Perform early cleanup, attempt to gracefully shut down the VM, and wait
for it to terminate.
:param has_quit: When True, don't attempt to issue 'quit' QMP command
:param timeout: Optional timeout in seconds for graceful shutdown.
Default 3 seconds, A value of None is an infinite wait.
:raise ConnectionReset: On QMP communication errors
:raise subprocess.TimeoutExpired: When timeout is exceeded waiting for
the QEMU process to terminate.
"""
self._early_cleanup()
if self._qmp is not None:
if not has_quit:
# Might raise ConnectionReset
self._qmp.cmd('quit')
# May raise subprocess.TimeoutExpired
self._popen.wait(timeout=timeout)
def _do_shutdown(self, has_quit: bool = False,
timeout: Optional[int] = 3) -> None:
"""
Attempt to shutdown the VM gracefully; fallback to a hard shutdown.
:param has_quit: When True, don't attempt to issue 'quit' QMP command
:param timeout: Optional timeout in seconds for graceful shutdown.
Default 3 seconds, A value of None is an infinite wait.
:raise AbnormalShutdown: When the VM could not be shut down gracefully.
The inner exception will likely be ConnectionReset or
subprocess.TimeoutExpired. In rare cases, non-graceful termination
may result in its own exceptions, likely subprocess.TimeoutExpired.
"""
try:
self._soft_shutdown(has_quit, timeout)
except Exception as exc:
self._hard_shutdown()
raise AbnormalShutdown("Could not perform graceful shutdown") \
from exc
def shutdown(self, has_quit: bool = False,
hard: bool = False,
timeout: Optional[int] = 3) -> None:
"""
Terminate the VM (gracefully if possible) and perform cleanup.
Cleanup will always be performed.
If the VM has not yet been launched, or shutdown(), wait(), or kill()
have already been called, this method does nothing.
:param has_quit: When true, do not attempt to issue 'quit' QMP command.
:param hard: When true, do not attempt graceful shutdown, and
suppress the SIGKILL warning log message.
:param timeout: Optional timeout in seconds for graceful shutdown.
Default 3 seconds, A value of None is an infinite wait.
"""
if not self._launched:
return
try:
if hard:
self._popen.kill()
elif self._qmp:
try:
if not has_quit:
self._qmp.cmd('quit')
self._qmp.close()
self._popen.wait(timeout=3)
except:
self._popen.kill()
self._popen.wait()
self._load_io_log()
self._post_shutdown()
exitcode = self.exitcode()
if exitcode is not None and exitcode < 0 and \
not (exitcode == -9 and hard):
msg = 'qemu received signal %i: %s'
if self._qemu_full_args:
command = ' '.join(self._qemu_full_args)
self._user_killed = True
self._hard_shutdown()
else:
command = ''
LOG.warning(msg, -int(exitcode), command)
self._launched = False
self._do_shutdown(has_quit, timeout=timeout)
finally:
self._post_shutdown()
def kill(self):
"""
Terminate the VM forcefully, wait for it to exit, and perform cleanup.
"""
self.shutdown(hard=True)
def wait(self, timeout: Optional[int] = 3) -> None:
"""
Wait for the VM to power off and perform post-shutdown cleanup.
:param timeout: Optional timeout in seconds.
Default 3 seconds, A value of None is an infinite wait.
"""
self.shutdown(has_quit=True, timeout=timeout)
def set_qmp_monitor(self, enabled=True):
"""
Set the QMP monitor.
@ -438,7 +520,7 @@ class QEMUMachine:
if reply is None:
raise qmp.QMPError("Monitor is closed")
if "error" in reply:
raise MonitorResponseError(reply)
raise qmp.QMPResponseError(reply)
return reply["return"]
def get_qmp_event(self, wait=False):

View File

@ -12,13 +12,32 @@ import errno
import socket
import logging
from typing import (
Any,
cast,
Dict,
Optional,
TextIO,
Type,
Tuple,
Union,
)
from types import TracebackType
# QMPMessage is a QMP Message of any kind.
# e.g. {'yee': 'haw'}
#
# QMPReturnValue is the inner value of return values only.
# {'return': {}} is the QMPMessage,
# {} is the QMPReturnValue.
QMPMessage = Dict[str, Any]
QMPReturnValue = Dict[str, Any]
InternetAddrT = Tuple[str, str]
UnixAddrT = str
SocketAddrT = Union[InternetAddrT, UnixAddrT]
class QMPError(Exception):
"""
QMP base exception
@ -43,6 +62,25 @@ class QMPTimeoutError(QMPError):
"""
class QMPProtocolError(QMPError):
"""
QMP protocol error; unexpected response
"""
class QMPResponseError(QMPError):
"""
Represents erroneous QMP monitor reply
"""
def __init__(self, reply: QMPMessage):
try:
desc = reply['error']['desc']
except KeyError:
desc = reply
super().__init__(desc)
self.reply = reply
class QEMUMonitorProtocol:
"""
Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and then
@ -99,7 +137,10 @@ class QEMUMonitorProtocol:
data = self.__sockfile.readline()
if not data:
return None
resp = json.loads(data)
# By definition, any JSON received from QMP is a QMPMessage,
# and we are asserting only at static analysis time that it
# has a particular shape.
resp: QMPMessage = json.loads(data)
if 'event' in resp:
self.logger.debug("<<< %s", resp)
self.__events.append(resp)
@ -194,22 +235,18 @@ class QEMUMonitorProtocol:
self.__sockfile = self.__sock.makefile(mode='r')
return self.__negotiate_capabilities()
def cmd_obj(self, qmp_cmd):
def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
"""
Send a QMP command to the QMP Monitor.
@param qmp_cmd: QMP command to be sent as a Python dict
@return QMP response as a Python dict or None if the connection has
been closed
@return QMP response as a Python dict
"""
self.logger.debug(">>> %s", qmp_cmd)
try:
self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
except OSError as err:
if err.errno == errno.EPIPE:
return None
raise err
self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
resp = self.__json_read()
if resp is None:
raise QMPConnectError("Unexpected empty reply from server")
self.logger.debug("<<< %s", resp)
return resp
@ -233,9 +270,13 @@ class QEMUMonitorProtocol:
Build and send a QMP command to the monitor, report errors if any
"""
ret = self.cmd(cmd, kwds)
if "error" in ret:
raise Exception(ret['error']['desc'])
return ret['return']
if 'error' in ret:
raise QMPResponseError(ret)
if 'return' not in ret:
raise QMPProtocolError(
"'return' key not found in QMP response '{}'".format(str(ret))
)
return cast(QMPReturnValue, ret['return'])
def pull_event(self, wait=False):
"""

166
scripts/performance/dissect.py Executable file
View File

@ -0,0 +1,166 @@
#!/usr/bin/env python3
# Print the percentage of instructions spent in each phase of QEMU
# execution.
#
# Syntax:
# dissect.py [-h] -- <qemu executable> [<qemu executable options>] \
# <target executable> [<target executable options>]
#
# [-h] - Print the script arguments help message.
#
# Example of usage:
# dissect.py -- qemu-arm coulomb_double-arm
#
# This file is a part of the project "TCG Continuous Benchmarking".
#
# Copyright (C) 2020 Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
# Copyright (C) 2020 Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import argparse
import os
import subprocess
import sys
import tempfile
def get_JIT_line(callgrind_data):
"""
Search for the first instance of the JIT call in
the callgrind_annotate output when ran using --tree=caller
This is equivalent to the self number of instructions of JIT.
Parameters:
callgrind_data (list): callgrind_annotate output
Returns:
(int): Line number
"""
line = -1
for i in range(len(callgrind_data)):
if callgrind_data[i].strip('\n') and \
callgrind_data[i].split()[-1] == "[???]":
line = i
break
if line == -1:
sys.exit("Couldn't locate the JIT call ... Exiting.")
return line
def main():
# Parse the command line arguments
parser = argparse.ArgumentParser(
usage='dissect.py [-h] -- '
'<qemu executable> [<qemu executable options>] '
'<target executable> [<target executable options>]')
parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS)
args = parser.parse_args()
# Extract the needed variables from the args
command = args.command
# Insure that valgrind is installed
check_valgrind = subprocess.run(
["which", "valgrind"], stdout=subprocess.DEVNULL)
if check_valgrind.returncode:
sys.exit("Please install valgrind before running the script.")
# Save all intermediate files in a temporary directory
with tempfile.TemporaryDirectory() as tmpdirname:
# callgrind output file path
data_path = os.path.join(tmpdirname, "callgrind.data")
# callgrind_annotate output file path
annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")
# Run callgrind
callgrind = subprocess.run((["valgrind",
"--tool=callgrind",
"--callgrind-out-file=" + data_path]
+ command),
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE)
if callgrind.returncode:
sys.exit(callgrind.stderr.decode("utf-8"))
# Save callgrind_annotate output
with open(annotate_out_path, "w") as output:
callgrind_annotate = subprocess.run(
["callgrind_annotate", data_path, "--tree=caller"],
stdout=output,
stderr=subprocess.PIPE)
if callgrind_annotate.returncode:
sys.exit(callgrind_annotate.stderr.decode("utf-8"))
# Read the callgrind_annotate output to callgrind_data[]
callgrind_data = []
with open(annotate_out_path, 'r') as data:
callgrind_data = data.readlines()
# Line number with the total number of instructions
total_instructions_line_number = 20
# Get the total number of instructions
total_instructions_line_data = \
callgrind_data[total_instructions_line_number]
total_instructions = total_instructions_line_data.split()[0]
total_instructions = int(total_instructions.replace(',', ''))
# Line number with the JIT self number of instructions
JIT_self_instructions_line_number = get_JIT_line(callgrind_data)
# Get the JIT self number of instructions
JIT_self_instructions_line_data = \
callgrind_data[JIT_self_instructions_line_number]
JIT_self_instructions = JIT_self_instructions_line_data.split()[0]
JIT_self_instructions = int(JIT_self_instructions.replace(',', ''))
# Line number with the JIT self + inclusive number of instructions
# It's the line above the first JIT call when running with --tree=caller
JIT_total_instructions_line_number = JIT_self_instructions_line_number-1
# Get the JIT self + inclusive number of instructions
JIT_total_instructions_line_data = \
callgrind_data[JIT_total_instructions_line_number]
JIT_total_instructions = JIT_total_instructions_line_data.split()[0]
JIT_total_instructions = int(JIT_total_instructions.replace(',', ''))
# Calculate number of instructions in helpers and code generation
helpers_instructions = JIT_total_instructions-JIT_self_instructions
code_generation_instructions = total_instructions-JIT_total_instructions
# Print results (Insert commas in large numbers)
# Print total number of instructions
print('{:<20}{:>20}\n'.
format("Total Instructions:",
format(total_instructions, ',')))
# Print code generation instructions and percentage
print('{:<20}{:>20}\t{:>6.3f}%'.
format("Code Generation:",
format(code_generation_instructions, ","),
(code_generation_instructions / total_instructions) * 100))
# Print JIT instructions and percentage
print('{:<20}{:>20}\t{:>6.3f}%'.
format("JIT Execution:",
format(JIT_self_instructions, ","),
(JIT_self_instructions / total_instructions) * 100))
# Print helpers instructions and percentage
print('{:<20}{:>20}\t{:>6.3f}%'.
format("Helpers:",
format(helpers_instructions, ","),
(helpers_instructions/total_instructions)*100))
if __name__ == "__main__":
main()

View File

@ -25,7 +25,10 @@ import json
from graphviz import Digraph
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python'))
from qemu.machine import MonitorResponseError
from qemu.qmp import (
QEMUMonitorProtocol,
QMPResponseError,
)
def perm(arr):
@ -102,7 +105,7 @@ class LibvirtGuest():
reply = json.loads(subprocess.check_output(ar))
if 'error' in reply:
raise MonitorResponseError(reply)
raise QMPResponseError(reply)
return reply['return']

View File

@ -207,6 +207,8 @@ class BootLinuxConsole(LinuxKernelTest):
'Debian')
exec_command_and_wait_for_pattern(self, 'reboot',
'reboot: Restarting system')
# Wait for VM to shut down gracefully
self.vm.wait()
@skipUnless(os.getenv('AVOCADO_ALLOW_UNTRUSTED_CODE'), 'untrusted code')
def test_mips64el_malta_5KEc_cpio(self):
@ -247,6 +249,8 @@ class BootLinuxConsole(LinuxKernelTest):
'3.19.3.mtoman.20150408')
exec_command_and_wait_for_pattern(self, 'reboot',
'reboot: Restarting system')
# Wait for VM to shut down gracefully
self.vm.wait()
def do_test_mips_malta32el_nanomips(self, kernel_url, kernel_hash):
kernel_path_xz = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
@ -520,8 +524,7 @@ class BootLinuxConsole(LinuxKernelTest):
'Allwinner sun4i/sun5i')
exec_command_and_wait_for_pattern(self, 'cat /proc/iomem',
'system-control@1c00000')
exec_command_and_wait_for_pattern(self, 'reboot',
'reboot: Restarting system')
# cubieboard's reboot is not functioning; omit reboot test.
def test_arm_cubieboard_sata(self):
"""
@ -564,8 +567,7 @@ class BootLinuxConsole(LinuxKernelTest):
'Allwinner sun4i/sun5i')
exec_command_and_wait_for_pattern(self, 'cat /proc/partitions',
'sda')
exec_command_and_wait_for_pattern(self, 'reboot',
'reboot: Restarting system')
# cubieboard's reboot is not functioning; omit reboot test.
def test_arm_orangepi(self):
"""
@ -631,6 +633,8 @@ class BootLinuxConsole(LinuxKernelTest):
'system-control@1c00000')
exec_command_and_wait_for_pattern(self, 'reboot',
'reboot: Restarting system')
# Wait for VM to shut down gracefully
self.vm.wait()
def test_arm_orangepi_sd(self):
"""
@ -680,6 +684,8 @@ class BootLinuxConsole(LinuxKernelTest):
'3 packets transmitted, 3 packets received, 0% packet loss')
exec_command_and_wait_for_pattern(self, 'reboot',
'reboot: Restarting system')
# Wait for VM to shut down gracefully
self.vm.wait()
@skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited')
@skipUnless(P7ZIP_AVAILABLE, '7z not installed')

View File

@ -212,6 +212,8 @@ class LinuxSSH(Test):
self.run_common_commands(wordsize)
self.shutdown_via_ssh()
# Wait for VM to shut down gracefully
self.vm.wait()
def test_mips_malta32eb_kernel3_2_0(self):
"""

View File

@ -35,13 +35,10 @@ import unittest
# pylint: disable=import-error, wrong-import-position
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu import qtest
from qemu.qmp import QMPMessage
assert sys.version_info >= (3, 6)
# Type Aliases
QMPResponse = Dict[str, Any]
# Use this logger for logging messages directly from the iotests module
logger = logging.getLogger('qemu.iotests')
logger.addHandler(logging.NullHandler())
@ -561,7 +558,7 @@ class VM(qtest.QEMUQtestMachine):
self._args.append(addr)
return self
def hmp(self, command_line: str, use_log: bool = False) -> QMPResponse:
def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage:
cmd = 'human-monitor-command'
kwargs = {'command-line': command_line}
if use_log:
@ -582,7 +579,7 @@ class VM(qtest.QEMUQtestMachine):
self.hmp(f'qemu-io {drive} "remove_break bp_{drive}"')
def hmp_qemu_io(self, drive: str, cmd: str,
use_log: bool = False) -> QMPResponse:
use_log: bool = False) -> QMPMessage:
"""Write to a given drive using an HMP command"""
return self.hmp(f'qemu-io {drive} "{cmd}"', use_log=use_log)