xref: /openbmc/qemu/scripts/device-crash-test (revision 1721fe75df1cbabf2665a2b76a6e7b5bc0fc036b)
13d004a37SPhilippe Mathieu-Daudé#!/usr/bin/env python3
223ea4f30SEduardo Habkost#
323ea4f30SEduardo Habkost#  Copyright (c) 2017 Red Hat Inc
423ea4f30SEduardo Habkost#
523ea4f30SEduardo Habkost# Author:
623ea4f30SEduardo Habkost#  Eduardo Habkost <ehabkost@redhat.com>
723ea4f30SEduardo Habkost#
823ea4f30SEduardo Habkost# This program is free software; you can redistribute it and/or modify
923ea4f30SEduardo Habkost# it under the terms of the GNU General Public License as published by
1023ea4f30SEduardo Habkost# the Free Software Foundation; either version 2 of the License, or
1123ea4f30SEduardo Habkost# (at your option) any later version.
1223ea4f30SEduardo Habkost#
1323ea4f30SEduardo Habkost# This program is distributed in the hope that it will be useful,
1423ea4f30SEduardo Habkost# but WITHOUT ANY WARRANTY; without even the implied warranty of
1523ea4f30SEduardo Habkost# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1623ea4f30SEduardo Habkost# GNU General Public License for more details.
1723ea4f30SEduardo Habkost#
1823ea4f30SEduardo Habkost# You should have received a copy of the GNU General Public License along
19*19f5891fSSean Wei# with this program; if not, see <https://www.gnu.org/licenses/>.
2023ea4f30SEduardo Habkost
2123ea4f30SEduardo Habkost"""
2223ea4f30SEduardo HabkostRun QEMU with all combinations of -machine and -device types,
2323ea4f30SEduardo Habkostcheck for crashes and unexpected errors.
2423ea4f30SEduardo Habkost"""
2523ea4f30SEduardo Habkost
268f8fd9edSCleber Rosaimport os
2723ea4f30SEduardo Habkostimport sys
2823ea4f30SEduardo Habkostimport glob
2923ea4f30SEduardo Habkostimport logging
3023ea4f30SEduardo Habkostimport traceback
3123ea4f30SEduardo Habkostimport re
3223ea4f30SEduardo Habkostimport random
3323ea4f30SEduardo Habkostimport argparse
3423ea4f30SEduardo Habkostfrom itertools import chain
351d8cf47eSJohn Snowfrom pathlib import Path
3623ea4f30SEduardo Habkost
371d8cf47eSJohn Snowtry:
38abf0bf99SJohn Snow    from qemu.machine import QEMUMachine
3937094b6dSJohn Snow    from qemu.qmp import ConnectError
401d8cf47eSJohn Snowexcept ModuleNotFoundError as exc:
411d8cf47eSJohn Snow    path = Path(__file__).resolve()
421d8cf47eSJohn Snow    print(f"Module '{exc.name}' not found.")
431d8cf47eSJohn Snow    print("  Try 'make check-venv' from your build directory,")
441d8cf47eSJohn Snow    print("  and then one way to run this script is like so:")
45c03f57fdSPaolo Bonzini    print(f'  > $builddir/pyvenv/bin/python3 "{path}"')
461d8cf47eSJohn Snow    sys.exit(1)
4723ea4f30SEduardo Habkost
4823ea4f30SEduardo Habkostlogger = logging.getLogger('device-crash-test')
4923ea4f30SEduardo Habkostdbg = logger.debug
5023ea4f30SEduardo Habkost
5123ea4f30SEduardo Habkost
521a14d4e1SEduardo Habkost# Purposes of the following rule list:
5323ea4f30SEduardo Habkost# * Avoiding verbose log messages when we find known non-fatal
5423ea4f30SEduardo Habkost#   (exitcode=1) errors
5523ea4f30SEduardo Habkost# * Avoiding fatal errors when we find known crashes
5623ea4f30SEduardo Habkost# * Skipping machines/devices that are known not to work out of
5723ea4f30SEduardo Habkost#   the box, when running in --quick mode
5823ea4f30SEduardo Habkost#
591a14d4e1SEduardo Habkost# Keeping the rule list updated is desirable, but not required,
6023ea4f30SEduardo Habkost# because unexpected cases where QEMU exits with exitcode=1 will
6123ea4f30SEduardo Habkost# just trigger a INFO message.
6223ea4f30SEduardo Habkost
631a14d4e1SEduardo Habkost# Valid error rule keys:
6423ea4f30SEduardo Habkost# * accel: regexp, full match only
6523ea4f30SEduardo Habkost# * machine: regexp, full match only
6623ea4f30SEduardo Habkost# * device: regexp, full match only
6723ea4f30SEduardo Habkost# * log: regexp, partial match allowed
6823ea4f30SEduardo Habkost# * exitcode: if not present, defaults to 1. If None, matches any exitcode
6923ea4f30SEduardo Habkost# * warn: if True, matching failures will be logged as warnings
7023ea4f30SEduardo Habkost# * expected: if True, QEMU is expected to always fail every time
7123ea4f30SEduardo Habkost#   when testing the corresponding test case
7223ea4f30SEduardo Habkost# * loglevel: log level of log output when there's a match.
731a14d4e1SEduardo HabkostERROR_RULE_LIST = [
7423ea4f30SEduardo Habkost    # Machines that won't work out of the box:
7523ea4f30SEduardo Habkost    #             MACHINE                         | ERROR MESSAGE
7623ea4f30SEduardo Habkost    {'machine':'niagara', 'expected':True},       # Unable to load a firmware for -M niagara
7723ea4f30SEduardo Habkost    {'machine':'boston', 'expected':True},        # Please provide either a -kernel or -bios argument
7823ea4f30SEduardo Habkost    {'machine':'leon3_generic', 'expected':True}, # Can't read bios image (null)
7923ea4f30SEduardo Habkost
8023ea4f30SEduardo Habkost    # devices that don't work out of the box because they require extra options to "-device DEV":
8123ea4f30SEduardo Habkost    #            DEVICE                                    | ERROR MESSAGE
8223ea4f30SEduardo Habkost    {'device':'.*-(i386|x86_64)-cpu', 'expected':True},    # CPU socket-id is not set
8323ea4f30SEduardo Habkost    {'device':'icp', 'expected':True},                     # icp_realize: required link 'xics' not found: Property '.xics' not found
8423ea4f30SEduardo Habkost    {'device':'ics', 'expected':True},                     # ics_base_realize: required link 'xics' not found: Property '.xics' not found
8523ea4f30SEduardo Habkost    # "-device ide-cd" does work on more recent QEMU versions, so it doesn't have expected=True
8623ea4f30SEduardo Habkost    {'device':'ide-cd'},                                 # No drive specified
8723ea4f30SEduardo Habkost    {'device':'ide-hd', 'expected':True},                  # No drive specified
8823ea4f30SEduardo Habkost    {'device':'ipmi-bmc-extern', 'expected':True},         # IPMI external bmc requires chardev attribute
8923ea4f30SEduardo Habkost    {'device':'isa-debugcon', 'expected':True},            # Can't create serial device, empty char device
9023ea4f30SEduardo Habkost    {'device':'isa-ipmi-bt', 'expected':True},             # IPMI device requires a bmc attribute to be set
9123ea4f30SEduardo Habkost    {'device':'isa-ipmi-kcs', 'expected':True},            # IPMI device requires a bmc attribute to be set
9223ea4f30SEduardo Habkost    {'device':'isa-parallel', 'expected':True},            # Can't create serial device, empty char device
9323ea4f30SEduardo Habkost    {'device':'ivshmem-doorbell', 'expected':True},        # You must specify a 'chardev'
9423ea4f30SEduardo Habkost    {'device':'ivshmem-plain', 'expected':True},           # You must specify a 'memdev'
9523ea4f30SEduardo Habkost    {'device':'loader', 'expected':True},                  # please include valid arguments
9623ea4f30SEduardo Habkost    {'device':'nand', 'expected':True},                    # Unsupported NAND block size 0x1
9723ea4f30SEduardo Habkost    {'device':'nvdimm', 'expected':True},                  # 'memdev' property is not set
9823ea4f30SEduardo Habkost    {'device':'nvme', 'expected':True},                    # Device initialization failed
9923ea4f30SEduardo Habkost    {'device':'pc-dimm', 'expected':True},                 # 'memdev' property is not set
10023ea4f30SEduardo Habkost    {'device':'pci-bridge', 'expected':True},              # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
10123ea4f30SEduardo Habkost    {'device':'pci-bridge-seat', 'expected':True},         # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
10223ea4f30SEduardo Habkost    {'device':'pxb', 'expected':True},                     # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
1034f8db871SBen Widawsky    {'device':'pxb-cxl', 'expected':True},                 # pxb-cxl devices cannot reside on a PCI bus.
10423ea4f30SEduardo Habkost    {'device':'scsi-block', 'expected':True},              # drive property not set
10523ea4f30SEduardo Habkost    {'device':'scsi-generic', 'expected':True},            # drive property not set
10623ea4f30SEduardo Habkost    {'device':'scsi-hd', 'expected':True},                 # drive property not set
10723ea4f30SEduardo Habkost    {'device':'spapr-pci-host-bridge', 'expected':True},   # BUID not specified for PHB
10823ea4f30SEduardo Habkost    {'device':'spapr-rng', 'expected':True},               # spapr-rng needs an RNG backend!
10923ea4f30SEduardo Habkost    {'device':'spapr-vty', 'expected':True},               # chardev property not set
11023ea4f30SEduardo Habkost    {'device':'tpm-tis', 'expected':True},                 # tpm_tis: backend driver with id (null) could not be found
11123ea4f30SEduardo Habkost    {'device':'unimplemented-device', 'expected':True},    # property 'size' not specified or zero
11223ea4f30SEduardo Habkost    {'device':'usb-braille', 'expected':True},             # Property chardev is required
1131ee53067SBandan    {'device':'usb-mtp', 'expected':True},                 # rootdir property must be configured
11423ea4f30SEduardo Habkost    {'device':'usb-redir', 'expected':True},               # Parameter 'chardev' is missing
11523ea4f30SEduardo Habkost    {'device':'usb-serial', 'expected':True},              # Property chardev is required
11623ea4f30SEduardo Habkost    {'device':'usb-storage', 'expected':True},             # drive property not set
11723ea4f30SEduardo Habkost    {'device':'vfio-amd-xgbe', 'expected':True},           # -device vfio-amd-xgbe: vfio error: wrong host device name
11823ea4f30SEduardo Habkost    {'device':'vfio-calxeda-xgmac', 'expected':True},      # -device vfio-calxeda-xgmac: vfio error: wrong host device name
11923ea4f30SEduardo Habkost    {'device':'vfio-pci', 'expected':True},                # No provided host device
12023ea4f30SEduardo Habkost    {'device':'vfio-pci-igd-lpc-bridge', 'expected':True}, # VFIO dummy ISA/LPC bridge must have address 1f.0
12123ea4f30SEduardo Habkost    {'device':'vhost-scsi.*', 'expected':True},            # vhost-scsi: missing wwpn
12223ea4f30SEduardo Habkost    {'device':'vhost-vsock-device', 'expected':True},      # guest-cid property must be greater than 2
12323ea4f30SEduardo Habkost    {'device':'vhost-vsock-pci', 'expected':True},         # guest-cid property must be greater than 2
12423ea4f30SEduardo Habkost    {'device':'virtio-9p-ccw', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
12523ea4f30SEduardo Habkost    {'device':'virtio-9p-device', 'expected':True},        # 9pfs device couldn't find fsdev with the id = NULL
12623ea4f30SEduardo Habkost    {'device':'virtio-9p-pci', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
12723ea4f30SEduardo Habkost    {'device':'virtio-blk-ccw', 'expected':True},          # drive property not set
12823ea4f30SEduardo Habkost    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
12923ea4f30SEduardo Habkost    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
13023ea4f30SEduardo Habkost    {'device':'virtio-blk-pci', 'expected':True},          # drive property not set
13123ea4f30SEduardo Habkost    {'device':'virtio-crypto-ccw', 'expected':True},       # 'cryptodev' parameter expects a valid object
13223ea4f30SEduardo Habkost    {'device':'virtio-crypto-device', 'expected':True},    # 'cryptodev' parameter expects a valid object
13323ea4f30SEduardo Habkost    {'device':'virtio-crypto-pci', 'expected':True},       # 'cryptodev' parameter expects a valid object
13423ea4f30SEduardo Habkost    {'device':'virtio-input-host-device', 'expected':True}, # evdev property is required
13523ea4f30SEduardo Habkost    {'device':'virtio-input-host-pci', 'expected':True},   # evdev property is required
13623ea4f30SEduardo Habkost    {'device':'xen-pvdevice', 'expected':True},            # Device ID invalid, it must always be supplied
13723ea4f30SEduardo Habkost    {'device':'vhost-vsock-ccw', 'expected':True},         # guest-cid property must be greater than 2
13823ea4f30SEduardo Habkost    {'device':'zpci', 'expected':True},                    # target must be defined
13923ea4f30SEduardo Habkost    {'device':'pnv-(occ|icp|lpc)', 'expected':True},       # required link 'xics' not found: Property '.xics' not found
14023ea4f30SEduardo Habkost    {'device':'powernv-cpu-.*', 'expected':True},          # pnv_core_realize: required link 'xics' not found: Property '.xics' not found
14123ea4f30SEduardo Habkost
14223ea4f30SEduardo Habkost    # ioapic devices are already created by pc and will fail:
14323ea4f30SEduardo Habkost    {'machine':'q35|pc.*', 'device':'kvm-ioapic', 'expected':True}, # Only 1 ioapics allowed
14423ea4f30SEduardo Habkost    {'machine':'q35|pc.*', 'device':'ioapic', 'expected':True},     # Only 1 ioapics allowed
14523ea4f30SEduardo Habkost
1462363d5eeSThomas Huth    # "spapr-cpu-core needs a pseries machine"
1472363d5eeSThomas Huth    {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True},
1482363d5eeSThomas Huth
14923ea4f30SEduardo Habkost    # KVM-specific devices shouldn't be tried without accel=kvm:
15023ea4f30SEduardo Habkost    {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
15123ea4f30SEduardo Habkost
15223ea4f30SEduardo Habkost    # xen-specific machines and devices:
15323ea4f30SEduardo Habkost    {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
15423ea4f30SEduardo Habkost    {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
15523ea4f30SEduardo Habkost
15623ea4f30SEduardo Habkost    # this fails on some machine-types, but not all, so they don't have expected=True:
15723ea4f30SEduardo Habkost    {'device':'vmgenid'}, # vmgenid requires DMA write support in fw_cfg, which this machine type does not provide
15823ea4f30SEduardo Habkost
15923ea4f30SEduardo Habkost    # Silence INFO messages for errors that are common on multiple
16023ea4f30SEduardo Habkost    # devices/machines:
16123ea4f30SEduardo Habkost    {'log':r"No '[\w-]+' bus found for device '[\w-]+'"},
16223ea4f30SEduardo Habkost    {'log':r"images* must be given with the 'pflash' parameter"},
16323ea4f30SEduardo Habkost    {'log':r"(Guest|ROM|Flash|Kernel) image must be specified"},
16423ea4f30SEduardo Habkost    {'log':r"[cC]ould not load [\w ]+ (BIOS|bios) '[\w-]+\.bin'"},
16523ea4f30SEduardo Habkost    {'log':r"Couldn't find rom image '[\w-]+\.bin'"},
16623ea4f30SEduardo Habkost    {'log':r"speed mismatch trying to attach usb device"},
16723ea4f30SEduardo Habkost    {'log':r"Can't create a second ISA bus"},
16823ea4f30SEduardo Habkost    {'log':r"duplicate fw_cfg file name"},
16923ea4f30SEduardo Habkost    # sysbus-related error messages: most machines reject most dynamic sysbus devices:
17023ea4f30SEduardo Habkost    {'log':r"Option '-device [\w.,-]+' cannot be handled by this machine"},
17123ea4f30SEduardo Habkost    {'log':r"Device [\w.,-]+ is not supported by this machine yet"},
17223ea4f30SEduardo Habkost    {'log':r"Device [\w.,-]+ can not be dynamically instantiated"},
17323ea4f30SEduardo Habkost    {'log':r"Platform Bus: Can not fit MMIO region of size "},
17423ea4f30SEduardo Habkost    # other more specific errors we will ignore:
17523ea4f30SEduardo Habkost    {'device':'.*-spapr-cpu-core', 'log':r"CPU core type should be"},
17623ea4f30SEduardo Habkost    {'log':r"MSI(-X)? is not supported by interrupt controller"},
17723ea4f30SEduardo Habkost    {'log':r"pxb-pcie? devices cannot reside on a PCIe? bus"},
17823ea4f30SEduardo Habkost    {'log':r"Ignoring smp_cpus value"},
17923ea4f30SEduardo Habkost    {'log':r"sd_init failed: Drive 'sd0' is already in use because it has been automatically connected to another device"},
18023ea4f30SEduardo Habkost    {'log':r"This CPU requires a smaller page size than the system is using"},
18123ea4f30SEduardo Habkost    {'log':r"MSI-X support is mandatory in the S390 architecture"},
18223ea4f30SEduardo Habkost    {'log':r"rom check and register reset failed"},
18323ea4f30SEduardo Habkost    {'log':r"Unable to initialize GIC, CPUState for CPU#0 not valid"},
18423ea4f30SEduardo Habkost    {'log':r"Multiple VT220 operator consoles are not supported"},
18523ea4f30SEduardo Habkost    {'log':r"core 0 already populated"},
18623ea4f30SEduardo Habkost    {'log':r"could not find stage1 bootloader"},
18723ea4f30SEduardo Habkost
18823ea4f30SEduardo Habkost    # other exitcode=1 failures not listed above will just generate INFO messages:
18923ea4f30SEduardo Habkost    {'exitcode':1, 'loglevel':logging.INFO},
19023ea4f30SEduardo Habkost
19123ea4f30SEduardo Habkost    # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
19223ea4f30SEduardo Habkost    {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
19323ea4f30SEduardo Habkost]
19423ea4f30SEduardo Habkost
19523ea4f30SEduardo Habkost
1961a14d4e1SEduardo Habkostdef errorRuleTestCaseMatch(rule, t):
1971a14d4e1SEduardo Habkost    """Check if a test case specification can match a error rule
19823ea4f30SEduardo Habkost
1991a14d4e1SEduardo Habkost    This only checks if a error rule is a candidate match
20023ea4f30SEduardo Habkost    for a given test case, it won't check if the test case
2011a14d4e1SEduardo Habkost    results/output match the rule.  See ruleListResultMatch().
20223ea4f30SEduardo Habkost    """
2031a14d4e1SEduardo Habkost    return (('machine' not in rule or
20423ea4f30SEduardo Habkost             'machine' not in t or
2051a14d4e1SEduardo Habkost             re.match(rule['machine'] + '$', t['machine'])) and
2061a14d4e1SEduardo Habkost            ('accel' not in rule or
20723ea4f30SEduardo Habkost             'accel' not in t or
2081a14d4e1SEduardo Habkost             re.match(rule['accel'] + '$', t['accel'])) and
2091a14d4e1SEduardo Habkost            ('device' not in rule or
21023ea4f30SEduardo Habkost             'device' not in t or
2111a14d4e1SEduardo Habkost             re.match(rule['device'] + '$', t['device'])))
21223ea4f30SEduardo Habkost
21323ea4f30SEduardo Habkost
2141a14d4e1SEduardo Habkostdef ruleListCandidates(t):
21523ea4f30SEduardo Habkost    """Generate the list of candidates that can match a test case"""
2161a14d4e1SEduardo Habkost    for i, rule in enumerate(ERROR_RULE_LIST):
2171a14d4e1SEduardo Habkost        if errorRuleTestCaseMatch(rule, t):
2181a14d4e1SEduardo Habkost            yield (i, rule)
21923ea4f30SEduardo Habkost
22023ea4f30SEduardo Habkost
22123ea4f30SEduardo Habkostdef findExpectedResult(t):
2221a14d4e1SEduardo Habkost    """Check if there's an expected=True error rule for a test case
22323ea4f30SEduardo Habkost
2241a14d4e1SEduardo Habkost    Returns (i, rule) tuple, where i is the index in
2251a14d4e1SEduardo Habkost    ERROR_RULE_LIST and rule is the error rule itself.
22623ea4f30SEduardo Habkost    """
2271a14d4e1SEduardo Habkost    for i, rule in ruleListCandidates(t):
2281a14d4e1SEduardo Habkost        if rule.get('expected'):
2291a14d4e1SEduardo Habkost            return (i, rule)
23023ea4f30SEduardo Habkost
23123ea4f30SEduardo Habkost
2321a14d4e1SEduardo Habkostdef ruleListResultMatch(rule, r):
2331a14d4e1SEduardo Habkost    """Check if test case results/output match a error rule
23423ea4f30SEduardo Habkost
23523ea4f30SEduardo Habkost    It is valid to call this function only if
2361a14d4e1SEduardo Habkost    errorRuleTestCaseMatch() is True for the rule (e.g. on
2371a14d4e1SEduardo Habkost    rules returned by ruleListCandidates())
23823ea4f30SEduardo Habkost    """
2391a14d4e1SEduardo Habkost    assert errorRuleTestCaseMatch(rule, r['testcase'])
2401a14d4e1SEduardo Habkost    return ((rule.get('exitcode', 1) is None or
2411a14d4e1SEduardo Habkost             r['exitcode'] == rule.get('exitcode', 1)) and
2421a14d4e1SEduardo Habkost            ('log' not in rule or
2431a14d4e1SEduardo Habkost             re.search(rule['log'], r['log'], re.MULTILINE)))
24423ea4f30SEduardo Habkost
24523ea4f30SEduardo Habkost
2461a14d4e1SEduardo Habkostdef checkResultRuleList(r):
2471a14d4e1SEduardo Habkost    """Look up error rule for a given test case result
24823ea4f30SEduardo Habkost
2491a14d4e1SEduardo Habkost    Returns (i, rule) tuple, where i is the index in
2501a14d4e1SEduardo Habkost    ERROR_RULE_LIST and rule is the error rule itself.
25123ea4f30SEduardo Habkost    """
2521a14d4e1SEduardo Habkost    for i, rule in ruleListCandidates(r['testcase']):
2531a14d4e1SEduardo Habkost        if ruleListResultMatch(rule, r):
2541a14d4e1SEduardo Habkost            return i, rule
25523ea4f30SEduardo Habkost
25623ea4f30SEduardo Habkost    raise Exception("this should never happen")
25723ea4f30SEduardo Habkost
25823ea4f30SEduardo Habkost
25923ea4f30SEduardo Habkostdef qemuOptsEscape(s):
26023ea4f30SEduardo Habkost    """Escape option value QemuOpts"""
26123ea4f30SEduardo Habkost    return s.replace(",", ",,")
26223ea4f30SEduardo Habkost
26323ea4f30SEduardo Habkost
26423ea4f30SEduardo Habkostdef formatTestCase(t):
26523ea4f30SEduardo Habkost    """Format test case info as "key=value key=value" for prettier logging output"""
26623ea4f30SEduardo Habkost    return ' '.join('%s=%s' % (k, v) for k, v in t.items())
26723ea4f30SEduardo Habkost
26823ea4f30SEduardo Habkost
26923ea4f30SEduardo Habkostdef qomListTypeNames(vm, **kwargs):
27023ea4f30SEduardo Habkost    """Run qom-list-types QMP command, return type names"""
271684750abSVladimir Sementsov-Ogievskiy    types = vm.cmd('qom-list-types', **kwargs)
27223ea4f30SEduardo Habkost    return [t['name'] for t in types]
27323ea4f30SEduardo Habkost
27423ea4f30SEduardo Habkost
27523ea4f30SEduardo Habkostdef infoQDM(vm):
27623ea4f30SEduardo Habkost    """Parse 'info qdm' output"""
27723ea4f30SEduardo Habkost    args = {'command-line': 'info qdm'}
278684750abSVladimir Sementsov-Ogievskiy    devhelp = vm.cmd('human-monitor-command', **args)
27923ea4f30SEduardo Habkost    for l in devhelp.split('\n'):
28023ea4f30SEduardo Habkost        l = l.strip()
28123ea4f30SEduardo Habkost        if l == '' or l.endswith(':'):
28223ea4f30SEduardo Habkost            continue
28323ea4f30SEduardo Habkost        d = {'name': re.search(r'name "([^"]+)"', l).group(1),
28423ea4f30SEduardo Habkost             'no-user': (re.search(', no-user', l) is not None)}
28523ea4f30SEduardo Habkost        yield d
28623ea4f30SEduardo Habkost
28723ea4f30SEduardo Habkost
28823ea4f30SEduardo Habkostclass QemuBinaryInfo(object):
28923ea4f30SEduardo Habkost    def __init__(self, binary, devtype):
29023ea4f30SEduardo Habkost        if devtype is None:
29123ea4f30SEduardo Habkost            devtype = 'device'
29223ea4f30SEduardo Habkost
29323ea4f30SEduardo Habkost        self.binary = binary
29423ea4f30SEduardo Habkost        self._machine_info = {}
29523ea4f30SEduardo Habkost
29623ea4f30SEduardo Habkost        dbg("devtype: %r", devtype)
29723ea4f30SEduardo Habkost        args = ['-S', '-machine', 'none,accel=kvm:tcg']
29823ea4f30SEduardo Habkost        dbg("querying info for QEMU binary: %s", binary)
29923ea4f30SEduardo Habkost        vm = QEMUMachine(binary=binary, args=args)
30023ea4f30SEduardo Habkost        vm.launch()
30123ea4f30SEduardo Habkost        try:
30223ea4f30SEduardo Habkost            self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
30323ea4f30SEduardo Habkost            # there's no way to query DeviceClass::user_creatable using QMP,
30423ea4f30SEduardo Habkost            # so use 'info qdm':
30523ea4f30SEduardo Habkost            self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
306684750abSVladimir Sementsov-Ogievskiy            self.machines = list(m['name'] for m in vm.cmd('query-machines'))
30723ea4f30SEduardo Habkost            self.user_devs = self.alldevs.difference(self.no_user_devs)
308684750abSVladimir Sementsov-Ogievskiy            self.kvm_available = vm.cmd('query-kvm')['enabled']
30923ea4f30SEduardo Habkost        finally:
31023ea4f30SEduardo Habkost            vm.shutdown()
31123ea4f30SEduardo Habkost
31223ea4f30SEduardo Habkost    def machineInfo(self, machine):
31323ea4f30SEduardo Habkost        """Query for information on a specific machine-type
31423ea4f30SEduardo Habkost
31523ea4f30SEduardo Habkost        Results are cached internally, in case the same machine-
31623ea4f30SEduardo Habkost        type is queried multiple times.
31723ea4f30SEduardo Habkost        """
31823ea4f30SEduardo Habkost        if machine in self._machine_info:
31923ea4f30SEduardo Habkost            return self._machine_info[machine]
32023ea4f30SEduardo Habkost
32123ea4f30SEduardo Habkost        mi = {}
32223ea4f30SEduardo Habkost        args = ['-S', '-machine', '%s' % (machine)]
32323ea4f30SEduardo Habkost        dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
32423ea4f30SEduardo Habkost        vm = QEMUMachine(binary=self.binary, args=args)
32523ea4f30SEduardo Habkost        try:
32623ea4f30SEduardo Habkost            vm.launch()
32723ea4f30SEduardo Habkost            mi['runnable'] = True
32847b43acdSJohn Snow        except Exception:
32923ea4f30SEduardo Habkost            dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
33023ea4f30SEduardo Habkost            dbg("log: %r", vm.get_log())
33123ea4f30SEduardo Habkost            mi['runnable'] = False
33223ea4f30SEduardo Habkost
33323ea4f30SEduardo Habkost        vm.shutdown()
33423ea4f30SEduardo Habkost        self._machine_info[machine] = mi
33523ea4f30SEduardo Habkost        return mi
33623ea4f30SEduardo Habkost
33723ea4f30SEduardo Habkost
33823ea4f30SEduardo HabkostBINARY_INFO = {}
33923ea4f30SEduardo Habkost
34023ea4f30SEduardo Habkost
34123ea4f30SEduardo Habkostdef getBinaryInfo(args, binary):
34223ea4f30SEduardo Habkost    if binary not in BINARY_INFO:
34323ea4f30SEduardo Habkost        BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
34423ea4f30SEduardo Habkost    return BINARY_INFO[binary]
34523ea4f30SEduardo Habkost
34623ea4f30SEduardo Habkost
34723ea4f30SEduardo Habkostdef checkOneCase(args, testcase):
34823ea4f30SEduardo Habkost    """Check one specific case
34923ea4f30SEduardo Habkost
35023ea4f30SEduardo Habkost    Returns a dictionary containing failure information on error,
35123ea4f30SEduardo Habkost    or None on success
35223ea4f30SEduardo Habkost    """
35323ea4f30SEduardo Habkost    binary = testcase['binary']
35423ea4f30SEduardo Habkost    accel = testcase['accel']
35523ea4f30SEduardo Habkost    machine = testcase['machine']
35623ea4f30SEduardo Habkost    device = testcase['device']
35723ea4f30SEduardo Habkost
35823ea4f30SEduardo Habkost    dbg("will test: %r", testcase)
35923ea4f30SEduardo Habkost
36023ea4f30SEduardo Habkost    args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
36123ea4f30SEduardo Habkost            '-device', qemuOptsEscape(device)]
36223ea4f30SEduardo Habkost    cmdline = ' '.join([binary] + args)
36323ea4f30SEduardo Habkost    dbg("will launch QEMU: %s", cmdline)
364206439cdSJohn Snow    vm = QEMUMachine(binary=binary, args=args, qmp_timer=15)
36523ea4f30SEduardo Habkost
366c398a241SJohn Snow    exc = None
36723ea4f30SEduardo Habkost    exc_traceback = None
36823ea4f30SEduardo Habkost    try:
36923ea4f30SEduardo Habkost        vm.launch()
370c398a241SJohn Snow    except Exception as this_exc:
371c398a241SJohn Snow        exc = this_exc
37223ea4f30SEduardo Habkost        exc_traceback = traceback.format_exc()
37323ea4f30SEduardo Habkost        dbg("Exception while running test case")
37423ea4f30SEduardo Habkost    finally:
37523ea4f30SEduardo Habkost        vm.shutdown()
37623ea4f30SEduardo Habkost        ec = vm.exitcode()
37723ea4f30SEduardo Habkost        log = vm.get_log()
37823ea4f30SEduardo Habkost
379c398a241SJohn Snow    if exc is not None or ec != 0:
380c398a241SJohn Snow        return {'exc': exc,
381c398a241SJohn Snow                'exc_traceback':exc_traceback,
38223ea4f30SEduardo Habkost                'exitcode':ec,
38323ea4f30SEduardo Habkost                'log':log,
38423ea4f30SEduardo Habkost                'testcase':testcase,
38523ea4f30SEduardo Habkost                'cmdline':cmdline}
38623ea4f30SEduardo Habkost
38723ea4f30SEduardo Habkost
38823ea4f30SEduardo Habkostdef binariesToTest(args, testcase):
38923ea4f30SEduardo Habkost    if args.qemu:
39023ea4f30SEduardo Habkost        r = args.qemu
39123ea4f30SEduardo Habkost    else:
3928a478365SEduardo Habkost        r = [f.path for f in os.scandir('.')
3938a478365SEduardo Habkost             if f.name.startswith('qemu-system-') and
3948a478365SEduardo Habkost                f.is_file() and os.access(f, os.X_OK)]
39523ea4f30SEduardo Habkost    return r
39623ea4f30SEduardo Habkost
39723ea4f30SEduardo Habkost
39823ea4f30SEduardo Habkostdef accelsToTest(args, testcase):
3998b869aa5SThomas Huth    if getBinaryInfo(args, testcase['binary']).kvm_available and not args.tcg_only:
40023ea4f30SEduardo Habkost        yield 'kvm'
40123ea4f30SEduardo Habkost    yield 'tcg'
40223ea4f30SEduardo Habkost
40323ea4f30SEduardo Habkost
40423ea4f30SEduardo Habkostdef machinesToTest(args, testcase):
40523ea4f30SEduardo Habkost    return getBinaryInfo(args, testcase['binary']).machines
40623ea4f30SEduardo Habkost
40723ea4f30SEduardo Habkost
40823ea4f30SEduardo Habkostdef devicesToTest(args, testcase):
40923ea4f30SEduardo Habkost    return getBinaryInfo(args, testcase['binary']).user_devs
41023ea4f30SEduardo Habkost
41123ea4f30SEduardo Habkost
41223ea4f30SEduardo HabkostTESTCASE_VARIABLES = [
41323ea4f30SEduardo Habkost    ('binary', binariesToTest),
41423ea4f30SEduardo Habkost    ('accel', accelsToTest),
41523ea4f30SEduardo Habkost    ('machine', machinesToTest),
41623ea4f30SEduardo Habkost    ('device', devicesToTest),
41723ea4f30SEduardo Habkost]
41823ea4f30SEduardo Habkost
41923ea4f30SEduardo Habkost
42023ea4f30SEduardo Habkostdef genCases1(args, testcases, var, fn):
42123ea4f30SEduardo Habkost    """Generate new testcases for one variable
42223ea4f30SEduardo Habkost
42323ea4f30SEduardo Habkost    If an existing item already has a variable set, don't
42423ea4f30SEduardo Habkost    generate new items and just return it directly. This
42523ea4f30SEduardo Habkost    allows the "-t" command-line option to be used to choose
42623ea4f30SEduardo Habkost    a specific test case.
42723ea4f30SEduardo Habkost    """
42823ea4f30SEduardo Habkost    for testcase in testcases:
42923ea4f30SEduardo Habkost        if var in testcase:
43023ea4f30SEduardo Habkost            yield testcase.copy()
43123ea4f30SEduardo Habkost        else:
43223ea4f30SEduardo Habkost            for i in fn(args, testcase):
43323ea4f30SEduardo Habkost                t = testcase.copy()
43423ea4f30SEduardo Habkost                t[var] = i
43523ea4f30SEduardo Habkost                yield t
43623ea4f30SEduardo Habkost
43723ea4f30SEduardo Habkost
43823ea4f30SEduardo Habkostdef genCases(args, testcase):
43923ea4f30SEduardo Habkost    """Generate test cases for all variables
44023ea4f30SEduardo Habkost    """
44123ea4f30SEduardo Habkost    cases = [testcase.copy()]
44223ea4f30SEduardo Habkost    for var, fn in TESTCASE_VARIABLES:
44323ea4f30SEduardo Habkost        dbg("var: %r, fn: %r", var, fn)
44423ea4f30SEduardo Habkost        cases = genCases1(args, cases, var, fn)
44523ea4f30SEduardo Habkost    return cases
44623ea4f30SEduardo Habkost
44723ea4f30SEduardo Habkost
44823ea4f30SEduardo Habkostdef casesToTest(args, testcase):
44923ea4f30SEduardo Habkost    cases = genCases(args, testcase)
45023ea4f30SEduardo Habkost    if args.random:
45123ea4f30SEduardo Habkost        cases = list(cases)
45223ea4f30SEduardo Habkost        cases = random.sample(cases, min(args.random, len(cases)))
45323ea4f30SEduardo Habkost    if args.debug:
45423ea4f30SEduardo Habkost        cases = list(cases)
45523ea4f30SEduardo Habkost        dbg("%d test cases to test", len(cases))
45623ea4f30SEduardo Habkost    if args.shuffle:
45723ea4f30SEduardo Habkost        cases = list(cases)
45823ea4f30SEduardo Habkost        random.shuffle(cases)
45923ea4f30SEduardo Habkost    return cases
46023ea4f30SEduardo Habkost
46123ea4f30SEduardo Habkost
46223ea4f30SEduardo Habkostdef logFailure(f, level):
46323ea4f30SEduardo Habkost    t = f['testcase']
46423ea4f30SEduardo Habkost    logger.log(level, "failed: %s", formatTestCase(t))
46523ea4f30SEduardo Habkost    logger.log(level, "cmdline: %s", f['cmdline'])
46623ea4f30SEduardo Habkost    for l in f['log'].strip().split('\n'):
46723ea4f30SEduardo Habkost        logger.log(level, "log: %s", l)
46823ea4f30SEduardo Habkost    logger.log(level, "exit code: %r", f['exitcode'])
469c398a241SJohn Snow
470c398a241SJohn Snow    # If the Exception is merely a QMP connect error,
471c398a241SJohn Snow    # reduce the logging level for its traceback to
472c398a241SJohn Snow    # improve visual clarity.
473c398a241SJohn Snow    if isinstance(f.get('exc'), ConnectError):
474c398a241SJohn Snow        logger.log(level, "%s.%s: %s",
475c398a241SJohn Snow                   type(f['exc']).__module__,
476c398a241SJohn Snow                   type(f['exc']).__qualname__,
477c398a241SJohn Snow                   str(f['exc']))
478c398a241SJohn Snow        level = logging.DEBUG
479c398a241SJohn Snow
48023ea4f30SEduardo Habkost    if f['exc_traceback']:
48123ea4f30SEduardo Habkost        logger.log(level, "exception:")
48223ea4f30SEduardo Habkost        for l in f['exc_traceback'].split('\n'):
48323ea4f30SEduardo Habkost            logger.log(level, "  %s", l.rstrip('\n'))
48423ea4f30SEduardo Habkost
48523ea4f30SEduardo Habkost
48623ea4f30SEduardo Habkostdef main():
48723ea4f30SEduardo Habkost    parser = argparse.ArgumentParser(description="QEMU -device crash test")
48823ea4f30SEduardo Habkost    parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
48923ea4f30SEduardo Habkost                        help="Limit test cases to KEY=VALUE",
49023ea4f30SEduardo Habkost                        action='append', dest='testcases', default=[])
49123ea4f30SEduardo Habkost    parser.add_argument('-d', '--debug', action='store_true',
49223ea4f30SEduardo Habkost                        help='debug output')
49323ea4f30SEduardo Habkost    parser.add_argument('-v', '--verbose', action='store_true', default=True,
49423ea4f30SEduardo Habkost                        help='verbose output')
49523ea4f30SEduardo Habkost    parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
49623ea4f30SEduardo Habkost                        help='non-verbose output')
49723ea4f30SEduardo Habkost    parser.add_argument('-r', '--random', type=int, metavar='COUNT',
49823ea4f30SEduardo Habkost                        help='run a random sample of COUNT test cases',
49923ea4f30SEduardo Habkost                        default=0)
50023ea4f30SEduardo Habkost    parser.add_argument('--shuffle', action='store_true',
50123ea4f30SEduardo Habkost                        help='Run test cases in random order')
50223ea4f30SEduardo Habkost    parser.add_argument('--dry-run', action='store_true',
50323ea4f30SEduardo Habkost                        help="Don't run any tests, just generate list")
50423ea4f30SEduardo Habkost    parser.add_argument('-D', '--devtype', metavar='TYPE',
50523ea4f30SEduardo Habkost                        help="Test only device types that implement TYPE")
50623ea4f30SEduardo Habkost    parser.add_argument('-Q', '--quick', action='store_true', default=True,
50723ea4f30SEduardo Habkost                        help="Quick mode: skip test cases that are expected to fail")
50823ea4f30SEduardo Habkost    parser.add_argument('-F', '--full', action='store_false', dest='quick',
50923ea4f30SEduardo Habkost                        help="Full mode: test cases that are expected to fail")
51023ea4f30SEduardo Habkost    parser.add_argument('--strict', action='store_true', dest='strict',
51123ea4f30SEduardo Habkost                        help="Treat all warnings as fatal")
5128b869aa5SThomas Huth    parser.add_argument('--tcg-only', action='store_true', dest='tcg_only',
5138b869aa5SThomas Huth                        help="Only test with TCG accelerator")
51423ea4f30SEduardo Habkost    parser.add_argument('qemu', nargs='*', metavar='QEMU',
51523ea4f30SEduardo Habkost                        help='QEMU binary to run')
51623ea4f30SEduardo Habkost    args = parser.parse_args()
51723ea4f30SEduardo Habkost
51823ea4f30SEduardo Habkost    if args.debug:
51923ea4f30SEduardo Habkost        lvl = logging.DEBUG
52023ea4f30SEduardo Habkost    elif args.verbose:
52123ea4f30SEduardo Habkost        lvl = logging.INFO
52223ea4f30SEduardo Habkost    else:
52323ea4f30SEduardo Habkost        lvl = logging.WARN
52423ea4f30SEduardo Habkost    logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
52523ea4f30SEduardo Habkost
52676f86e78SJohn Snow    if not args.debug:
52776f86e78SJohn Snow        # Async QMP, when in use, is chatty about connection failures.
52876f86e78SJohn Snow        # This script knowingly generates a ton of connection errors.
52976f86e78SJohn Snow        # Silence this logger.
53037094b6dSJohn Snow        logging.getLogger('qemu.qmp.qmp_client').setLevel(logging.CRITICAL)
53176f86e78SJohn Snow
53223ea4f30SEduardo Habkost    fatal_failures = []
53323ea4f30SEduardo Habkost    wl_stats = {}
53423ea4f30SEduardo Habkost    skipped = 0
53523ea4f30SEduardo Habkost    total = 0
53623ea4f30SEduardo Habkost
53723ea4f30SEduardo Habkost    tc = {}
53823ea4f30SEduardo Habkost    dbg("testcases: %r", args.testcases)
53923ea4f30SEduardo Habkost    if args.testcases:
54023ea4f30SEduardo Habkost        for t in chain(*args.testcases):
54123ea4f30SEduardo Habkost            for kv in t.split():
54223ea4f30SEduardo Habkost                k, v = kv.split('=', 1)
54323ea4f30SEduardo Habkost                tc[k] = v
54423ea4f30SEduardo Habkost
54523ea4f30SEduardo Habkost    if len(binariesToTest(args, tc)) == 0:
546f03868bdSEduardo Habkost        print("No QEMU binary found", file=sys.stderr)
54723ea4f30SEduardo Habkost        parser.print_usage(sys.stderr)
54823ea4f30SEduardo Habkost        return 1
54923ea4f30SEduardo Habkost
55023ea4f30SEduardo Habkost    for t in casesToTest(args, tc):
55123ea4f30SEduardo Habkost        logger.info("running test case: %s", formatTestCase(t))
55223ea4f30SEduardo Habkost        total += 1
55323ea4f30SEduardo Habkost
55423ea4f30SEduardo Habkost        expected_match = findExpectedResult(t)
55523ea4f30SEduardo Habkost        if (args.quick and
55623ea4f30SEduardo Habkost                (expected_match or
55723ea4f30SEduardo Habkost                 not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
55823ea4f30SEduardo Habkost            dbg("skipped: %s", formatTestCase(t))
55923ea4f30SEduardo Habkost            skipped += 1
56023ea4f30SEduardo Habkost            continue
56123ea4f30SEduardo Habkost
56223ea4f30SEduardo Habkost        if args.dry_run:
56323ea4f30SEduardo Habkost            continue
56423ea4f30SEduardo Habkost
56523ea4f30SEduardo Habkost        try:
56623ea4f30SEduardo Habkost            f = checkOneCase(args, t)
56723ea4f30SEduardo Habkost        except KeyboardInterrupt:
56823ea4f30SEduardo Habkost            break
56923ea4f30SEduardo Habkost
57023ea4f30SEduardo Habkost        if f:
5711a14d4e1SEduardo Habkost            i, rule = checkResultRuleList(f)
5721a14d4e1SEduardo Habkost            dbg("testcase: %r, rule list match: %r", t, rule)
57323ea4f30SEduardo Habkost            wl_stats.setdefault(i, []).append(f)
5741a14d4e1SEduardo Habkost            level = rule.get('loglevel', logging.DEBUG)
57523ea4f30SEduardo Habkost            logFailure(f, level)
5761a14d4e1SEduardo Habkost            if rule.get('fatal') or (args.strict and level >= logging.WARN):
57723ea4f30SEduardo Habkost                fatal_failures.append(f)
57823ea4f30SEduardo Habkost        else:
57923ea4f30SEduardo Habkost            dbg("success: %s", formatTestCase(t))
58023ea4f30SEduardo Habkost            if expected_match:
58123ea4f30SEduardo Habkost                logger.warn("Didn't fail as expected: %s", formatTestCase(t))
58223ea4f30SEduardo Habkost
58323ea4f30SEduardo Habkost    logger.info("Total: %d test cases", total)
58423ea4f30SEduardo Habkost    if skipped:
58523ea4f30SEduardo Habkost        logger.info("Skipped %d test cases", skipped)
58623ea4f30SEduardo Habkost
58723ea4f30SEduardo Habkost    if args.debug:
5881a14d4e1SEduardo Habkost        stats = sorted([(len(wl_stats.get(i, [])), rule) for i, rule in
5891a14d4e1SEduardo Habkost                         enumerate(ERROR_RULE_LIST)], key=lambda x: x[0])
5901a14d4e1SEduardo Habkost        for count, rule in stats:
5911a14d4e1SEduardo Habkost            dbg("error rule stats: %d: %r", count, rule)
59223ea4f30SEduardo Habkost
59323ea4f30SEduardo Habkost    if fatal_failures:
59423ea4f30SEduardo Habkost        for f in fatal_failures:
59523ea4f30SEduardo Habkost            t = f['testcase']
59623ea4f30SEduardo Habkost            logger.error("Fatal failure: %s", formatTestCase(t))
59723ea4f30SEduardo Habkost        logger.error("Fatal failures on some machine/device combinations")
59823ea4f30SEduardo Habkost        return 1
59923ea4f30SEduardo Habkost
60023ea4f30SEduardo Habkostif __name__ == '__main__':
60123ea4f30SEduardo Habkost    sys.exit(main())
602