xref: /openbmc/qemu/scripts/device-crash-test (revision ca89d15f8e42f2e5eac5bd200af38fdbfb32e875)
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
1923ea4f30SEduardo Habkost# with this program; if not, write to the Free Software Foundation, Inc.,
2023ea4f30SEduardo Habkost# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2123ea4f30SEduardo Habkost
2223ea4f30SEduardo Habkost"""
2323ea4f30SEduardo HabkostRun QEMU with all combinations of -machine and -device types,
2423ea4f30SEduardo Habkostcheck for crashes and unexpected errors.
2523ea4f30SEduardo Habkost"""
2623ea4f30SEduardo Habkost
278f8fd9edSCleber Rosaimport os
2823ea4f30SEduardo Habkostimport sys
2923ea4f30SEduardo Habkostimport glob
3023ea4f30SEduardo Habkostimport logging
3123ea4f30SEduardo Habkostimport traceback
3223ea4f30SEduardo Habkostimport re
3323ea4f30SEduardo Habkostimport random
3423ea4f30SEduardo Habkostimport argparse
3523ea4f30SEduardo Habkostfrom itertools import chain
3623ea4f30SEduardo Habkost
378f8fd9edSCleber Rosasys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python'))
38abf0bf99SJohn Snowfrom qemu.machine import QEMUMachine
3923ea4f30SEduardo Habkost
4023ea4f30SEduardo Habkostlogger = logging.getLogger('device-crash-test')
4123ea4f30SEduardo Habkostdbg = logger.debug
4223ea4f30SEduardo Habkost
4323ea4f30SEduardo Habkost
441a14d4e1SEduardo Habkost# Purposes of the following rule list:
4523ea4f30SEduardo Habkost# * Avoiding verbose log messages when we find known non-fatal
4623ea4f30SEduardo Habkost#   (exitcode=1) errors
4723ea4f30SEduardo Habkost# * Avoiding fatal errors when we find known crashes
4823ea4f30SEduardo Habkost# * Skipping machines/devices that are known not to work out of
4923ea4f30SEduardo Habkost#   the box, when running in --quick mode
5023ea4f30SEduardo Habkost#
511a14d4e1SEduardo Habkost# Keeping the rule list updated is desirable, but not required,
5223ea4f30SEduardo Habkost# because unexpected cases where QEMU exits with exitcode=1 will
5323ea4f30SEduardo Habkost# just trigger a INFO message.
5423ea4f30SEduardo Habkost
551a14d4e1SEduardo Habkost# Valid error rule keys:
5623ea4f30SEduardo Habkost# * accel: regexp, full match only
5723ea4f30SEduardo Habkost# * machine: regexp, full match only
5823ea4f30SEduardo Habkost# * device: regexp, full match only
5923ea4f30SEduardo Habkost# * log: regexp, partial match allowed
6023ea4f30SEduardo Habkost# * exitcode: if not present, defaults to 1. If None, matches any exitcode
6123ea4f30SEduardo Habkost# * warn: if True, matching failures will be logged as warnings
6223ea4f30SEduardo Habkost# * expected: if True, QEMU is expected to always fail every time
6323ea4f30SEduardo Habkost#   when testing the corresponding test case
6423ea4f30SEduardo Habkost# * loglevel: log level of log output when there's a match.
651a14d4e1SEduardo HabkostERROR_RULE_LIST = [
6623ea4f30SEduardo Habkost    # Machines that won't work out of the box:
6723ea4f30SEduardo Habkost    #             MACHINE                         | ERROR MESSAGE
6823ea4f30SEduardo Habkost    {'machine':'niagara', 'expected':True},       # Unable to load a firmware for -M niagara
6923ea4f30SEduardo Habkost    {'machine':'boston', 'expected':True},        # Please provide either a -kernel or -bios argument
7023ea4f30SEduardo Habkost    {'machine':'leon3_generic', 'expected':True}, # Can't read bios image (null)
7123ea4f30SEduardo Habkost
7223ea4f30SEduardo Habkost    # devices that don't work out of the box because they require extra options to "-device DEV":
7323ea4f30SEduardo Habkost    #            DEVICE                                    | ERROR MESSAGE
7423ea4f30SEduardo Habkost    {'device':'.*-(i386|x86_64)-cpu', 'expected':True},    # CPU socket-id is not set
7523ea4f30SEduardo Habkost    {'device':'icp', 'expected':True},                     # icp_realize: required link 'xics' not found: Property '.xics' not found
7623ea4f30SEduardo Habkost    {'device':'ics', 'expected':True},                     # ics_base_realize: required link 'xics' not found: Property '.xics' not found
7723ea4f30SEduardo Habkost    # "-device ide-cd" does work on more recent QEMU versions, so it doesn't have expected=True
7823ea4f30SEduardo Habkost    {'device':'ide-cd'},                                 # No drive specified
7923ea4f30SEduardo Habkost    {'device':'ide-hd', 'expected':True},                  # No drive specified
8023ea4f30SEduardo Habkost    {'device':'ipmi-bmc-extern', 'expected':True},         # IPMI external bmc requires chardev attribute
8123ea4f30SEduardo Habkost    {'device':'isa-debugcon', 'expected':True},            # Can't create serial device, empty char device
8223ea4f30SEduardo Habkost    {'device':'isa-ipmi-bt', 'expected':True},             # IPMI device requires a bmc attribute to be set
8323ea4f30SEduardo Habkost    {'device':'isa-ipmi-kcs', 'expected':True},            # IPMI device requires a bmc attribute to be set
8423ea4f30SEduardo Habkost    {'device':'isa-parallel', 'expected':True},            # Can't create serial device, empty char device
8523ea4f30SEduardo Habkost    {'device':'ivshmem-doorbell', 'expected':True},        # You must specify a 'chardev'
8623ea4f30SEduardo Habkost    {'device':'ivshmem-plain', 'expected':True},           # You must specify a 'memdev'
8723ea4f30SEduardo Habkost    {'device':'loader', 'expected':True},                  # please include valid arguments
8823ea4f30SEduardo Habkost    {'device':'nand', 'expected':True},                    # Unsupported NAND block size 0x1
8923ea4f30SEduardo Habkost    {'device':'nvdimm', 'expected':True},                  # 'memdev' property is not set
9023ea4f30SEduardo Habkost    {'device':'nvme', 'expected':True},                    # Device initialization failed
9123ea4f30SEduardo Habkost    {'device':'pc-dimm', 'expected':True},                 # 'memdev' property is not set
9223ea4f30SEduardo Habkost    {'device':'pci-bridge', 'expected':True},              # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
9323ea4f30SEduardo Habkost    {'device':'pci-bridge-seat', 'expected':True},         # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
9423ea4f30SEduardo Habkost    {'device':'pxb', 'expected':True},                     # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
9523ea4f30SEduardo Habkost    {'device':'scsi-block', 'expected':True},              # drive property not set
9623ea4f30SEduardo Habkost    {'device':'scsi-generic', 'expected':True},            # drive property not set
9723ea4f30SEduardo Habkost    {'device':'scsi-hd', 'expected':True},                 # drive property not set
9823ea4f30SEduardo Habkost    {'device':'spapr-pci-host-bridge', 'expected':True},   # BUID not specified for PHB
9923ea4f30SEduardo Habkost    {'device':'spapr-rng', 'expected':True},               # spapr-rng needs an RNG backend!
10023ea4f30SEduardo Habkost    {'device':'spapr-vty', 'expected':True},               # chardev property not set
10123ea4f30SEduardo Habkost    {'device':'tpm-tis', 'expected':True},                 # tpm_tis: backend driver with id (null) could not be found
10223ea4f30SEduardo Habkost    {'device':'unimplemented-device', 'expected':True},    # property 'size' not specified or zero
10323ea4f30SEduardo Habkost    {'device':'usb-braille', 'expected':True},             # Property chardev is required
1041ee53067SBandan    {'device':'usb-mtp', 'expected':True},                 # rootdir property must be configured
10523ea4f30SEduardo Habkost    {'device':'usb-redir', 'expected':True},               # Parameter 'chardev' is missing
10623ea4f30SEduardo Habkost    {'device':'usb-serial', 'expected':True},              # Property chardev is required
10723ea4f30SEduardo Habkost    {'device':'usb-storage', 'expected':True},             # drive property not set
10823ea4f30SEduardo Habkost    {'device':'vfio-amd-xgbe', 'expected':True},           # -device vfio-amd-xgbe: vfio error: wrong host device name
10923ea4f30SEduardo Habkost    {'device':'vfio-calxeda-xgmac', 'expected':True},      # -device vfio-calxeda-xgmac: vfio error: wrong host device name
11023ea4f30SEduardo Habkost    {'device':'vfio-pci', 'expected':True},                # No provided host device
11123ea4f30SEduardo Habkost    {'device':'vfio-pci-igd-lpc-bridge', 'expected':True}, # VFIO dummy ISA/LPC bridge must have address 1f.0
11223ea4f30SEduardo Habkost    {'device':'vhost-scsi.*', 'expected':True},            # vhost-scsi: missing wwpn
11323ea4f30SEduardo Habkost    {'device':'vhost-vsock-device', 'expected':True},      # guest-cid property must be greater than 2
11423ea4f30SEduardo Habkost    {'device':'vhost-vsock-pci', 'expected':True},         # guest-cid property must be greater than 2
11523ea4f30SEduardo Habkost    {'device':'virtio-9p-ccw', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
11623ea4f30SEduardo Habkost    {'device':'virtio-9p-device', 'expected':True},        # 9pfs device couldn't find fsdev with the id = NULL
11723ea4f30SEduardo Habkost    {'device':'virtio-9p-pci', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
11823ea4f30SEduardo Habkost    {'device':'virtio-blk-ccw', 'expected':True},          # drive property not set
11923ea4f30SEduardo Habkost    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
12023ea4f30SEduardo Habkost    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
12123ea4f30SEduardo Habkost    {'device':'virtio-blk-pci', 'expected':True},          # drive property not set
12223ea4f30SEduardo Habkost    {'device':'virtio-crypto-ccw', 'expected':True},       # 'cryptodev' parameter expects a valid object
12323ea4f30SEduardo Habkost    {'device':'virtio-crypto-device', 'expected':True},    # 'cryptodev' parameter expects a valid object
12423ea4f30SEduardo Habkost    {'device':'virtio-crypto-pci', 'expected':True},       # 'cryptodev' parameter expects a valid object
12523ea4f30SEduardo Habkost    {'device':'virtio-input-host-device', 'expected':True}, # evdev property is required
12623ea4f30SEduardo Habkost    {'device':'virtio-input-host-pci', 'expected':True},   # evdev property is required
12723ea4f30SEduardo Habkost    {'device':'xen-pvdevice', 'expected':True},            # Device ID invalid, it must always be supplied
12823ea4f30SEduardo Habkost    {'device':'vhost-vsock-ccw', 'expected':True},         # guest-cid property must be greater than 2
12923ea4f30SEduardo Habkost    {'device':'zpci', 'expected':True},                    # target must be defined
13023ea4f30SEduardo Habkost    {'device':'pnv-(occ|icp|lpc)', 'expected':True},       # required link 'xics' not found: Property '.xics' not found
13123ea4f30SEduardo Habkost    {'device':'powernv-cpu-.*', 'expected':True},          # pnv_core_realize: required link 'xics' not found: Property '.xics' not found
13223ea4f30SEduardo Habkost
13323ea4f30SEduardo Habkost    # ioapic devices are already created by pc and will fail:
13423ea4f30SEduardo Habkost    {'machine':'q35|pc.*', 'device':'kvm-ioapic', 'expected':True}, # Only 1 ioapics allowed
13523ea4f30SEduardo Habkost    {'machine':'q35|pc.*', 'device':'ioapic', 'expected':True},     # Only 1 ioapics allowed
13623ea4f30SEduardo Habkost
1372363d5eeSThomas Huth    # "spapr-cpu-core needs a pseries machine"
1382363d5eeSThomas Huth    {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True},
1392363d5eeSThomas Huth
14023ea4f30SEduardo Habkost    # KVM-specific devices shouldn't be tried without accel=kvm:
14123ea4f30SEduardo Habkost    {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
14223ea4f30SEduardo Habkost
14323ea4f30SEduardo Habkost    # xen-specific machines and devices:
14423ea4f30SEduardo Habkost    {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
14523ea4f30SEduardo Habkost    {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
14623ea4f30SEduardo Habkost
14723ea4f30SEduardo Habkost    # this fails on some machine-types, but not all, so they don't have expected=True:
14823ea4f30SEduardo Habkost    {'device':'vmgenid'}, # vmgenid requires DMA write support in fw_cfg, which this machine type does not provide
14923ea4f30SEduardo Habkost
15023ea4f30SEduardo Habkost    # Silence INFO messages for errors that are common on multiple
15123ea4f30SEduardo Habkost    # devices/machines:
15223ea4f30SEduardo Habkost    {'log':r"No '[\w-]+' bus found for device '[\w-]+'"},
15323ea4f30SEduardo Habkost    {'log':r"images* must be given with the 'pflash' parameter"},
15423ea4f30SEduardo Habkost    {'log':r"(Guest|ROM|Flash|Kernel) image must be specified"},
15523ea4f30SEduardo Habkost    {'log':r"[cC]ould not load [\w ]+ (BIOS|bios) '[\w-]+\.bin'"},
15623ea4f30SEduardo Habkost    {'log':r"Couldn't find rom image '[\w-]+\.bin'"},
15723ea4f30SEduardo Habkost    {'log':r"speed mismatch trying to attach usb device"},
15823ea4f30SEduardo Habkost    {'log':r"Can't create a second ISA bus"},
15923ea4f30SEduardo Habkost    {'log':r"duplicate fw_cfg file name"},
16023ea4f30SEduardo Habkost    # sysbus-related error messages: most machines reject most dynamic sysbus devices:
16123ea4f30SEduardo Habkost    {'log':r"Option '-device [\w.,-]+' cannot be handled by this machine"},
16223ea4f30SEduardo Habkost    {'log':r"Device [\w.,-]+ is not supported by this machine yet"},
16323ea4f30SEduardo Habkost    {'log':r"Device [\w.,-]+ can not be dynamically instantiated"},
16423ea4f30SEduardo Habkost    {'log':r"Platform Bus: Can not fit MMIO region of size "},
16523ea4f30SEduardo Habkost    # other more specific errors we will ignore:
16623ea4f30SEduardo Habkost    {'device':'.*-spapr-cpu-core', 'log':r"CPU core type should be"},
16723ea4f30SEduardo Habkost    {'log':r"MSI(-X)? is not supported by interrupt controller"},
16823ea4f30SEduardo Habkost    {'log':r"pxb-pcie? devices cannot reside on a PCIe? bus"},
16923ea4f30SEduardo Habkost    {'log':r"Ignoring smp_cpus value"},
17023ea4f30SEduardo Habkost    {'log':r"sd_init failed: Drive 'sd0' is already in use because it has been automatically connected to another device"},
17123ea4f30SEduardo Habkost    {'log':r"This CPU requires a smaller page size than the system is using"},
17223ea4f30SEduardo Habkost    {'log':r"MSI-X support is mandatory in the S390 architecture"},
17323ea4f30SEduardo Habkost    {'log':r"rom check and register reset failed"},
17423ea4f30SEduardo Habkost    {'log':r"Unable to initialize GIC, CPUState for CPU#0 not valid"},
17523ea4f30SEduardo Habkost    {'log':r"Multiple VT220 operator consoles are not supported"},
17623ea4f30SEduardo Habkost    {'log':r"core 0 already populated"},
17723ea4f30SEduardo Habkost    {'log':r"could not find stage1 bootloader"},
178*ca89d15fSThomas Huth    {'log':r"No '.*' bus found for device"},
17923ea4f30SEduardo Habkost
18023ea4f30SEduardo Habkost    # other exitcode=1 failures not listed above will just generate INFO messages:
18123ea4f30SEduardo Habkost    {'exitcode':1, 'loglevel':logging.INFO},
18223ea4f30SEduardo Habkost
18323ea4f30SEduardo Habkost    # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
18423ea4f30SEduardo Habkost    {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
18523ea4f30SEduardo Habkost]
18623ea4f30SEduardo Habkost
18723ea4f30SEduardo Habkost
1881a14d4e1SEduardo Habkostdef errorRuleTestCaseMatch(rule, t):
1891a14d4e1SEduardo Habkost    """Check if a test case specification can match a error rule
19023ea4f30SEduardo Habkost
1911a14d4e1SEduardo Habkost    This only checks if a error rule is a candidate match
19223ea4f30SEduardo Habkost    for a given test case, it won't check if the test case
1931a14d4e1SEduardo Habkost    results/output match the rule.  See ruleListResultMatch().
19423ea4f30SEduardo Habkost    """
1951a14d4e1SEduardo Habkost    return (('machine' not in rule or
19623ea4f30SEduardo Habkost             'machine' not in t or
1971a14d4e1SEduardo Habkost             re.match(rule['machine'] + '$', t['machine'])) and
1981a14d4e1SEduardo Habkost            ('accel' not in rule or
19923ea4f30SEduardo Habkost             'accel' not in t or
2001a14d4e1SEduardo Habkost             re.match(rule['accel'] + '$', t['accel'])) and
2011a14d4e1SEduardo Habkost            ('device' not in rule or
20223ea4f30SEduardo Habkost             'device' not in t or
2031a14d4e1SEduardo Habkost             re.match(rule['device'] + '$', t['device'])))
20423ea4f30SEduardo Habkost
20523ea4f30SEduardo Habkost
2061a14d4e1SEduardo Habkostdef ruleListCandidates(t):
20723ea4f30SEduardo Habkost    """Generate the list of candidates that can match a test case"""
2081a14d4e1SEduardo Habkost    for i, rule in enumerate(ERROR_RULE_LIST):
2091a14d4e1SEduardo Habkost        if errorRuleTestCaseMatch(rule, t):
2101a14d4e1SEduardo Habkost            yield (i, rule)
21123ea4f30SEduardo Habkost
21223ea4f30SEduardo Habkost
21323ea4f30SEduardo Habkostdef findExpectedResult(t):
2141a14d4e1SEduardo Habkost    """Check if there's an expected=True error rule for a test case
21523ea4f30SEduardo Habkost
2161a14d4e1SEduardo Habkost    Returns (i, rule) tuple, where i is the index in
2171a14d4e1SEduardo Habkost    ERROR_RULE_LIST and rule is the error rule itself.
21823ea4f30SEduardo Habkost    """
2191a14d4e1SEduardo Habkost    for i, rule in ruleListCandidates(t):
2201a14d4e1SEduardo Habkost        if rule.get('expected'):
2211a14d4e1SEduardo Habkost            return (i, rule)
22223ea4f30SEduardo Habkost
22323ea4f30SEduardo Habkost
2241a14d4e1SEduardo Habkostdef ruleListResultMatch(rule, r):
2251a14d4e1SEduardo Habkost    """Check if test case results/output match a error rule
22623ea4f30SEduardo Habkost
22723ea4f30SEduardo Habkost    It is valid to call this function only if
2281a14d4e1SEduardo Habkost    errorRuleTestCaseMatch() is True for the rule (e.g. on
2291a14d4e1SEduardo Habkost    rules returned by ruleListCandidates())
23023ea4f30SEduardo Habkost    """
2311a14d4e1SEduardo Habkost    assert errorRuleTestCaseMatch(rule, r['testcase'])
2321a14d4e1SEduardo Habkost    return ((rule.get('exitcode', 1) is None or
2331a14d4e1SEduardo Habkost             r['exitcode'] == rule.get('exitcode', 1)) and
2341a14d4e1SEduardo Habkost            ('log' not in rule or
2351a14d4e1SEduardo Habkost             re.search(rule['log'], r['log'], re.MULTILINE)))
23623ea4f30SEduardo Habkost
23723ea4f30SEduardo Habkost
2381a14d4e1SEduardo Habkostdef checkResultRuleList(r):
2391a14d4e1SEduardo Habkost    """Look up error rule for a given test case result
24023ea4f30SEduardo Habkost
2411a14d4e1SEduardo Habkost    Returns (i, rule) tuple, where i is the index in
2421a14d4e1SEduardo Habkost    ERROR_RULE_LIST and rule is the error rule itself.
24323ea4f30SEduardo Habkost    """
2441a14d4e1SEduardo Habkost    for i, rule in ruleListCandidates(r['testcase']):
2451a14d4e1SEduardo Habkost        if ruleListResultMatch(rule, r):
2461a14d4e1SEduardo Habkost            return i, rule
24723ea4f30SEduardo Habkost
24823ea4f30SEduardo Habkost    raise Exception("this should never happen")
24923ea4f30SEduardo Habkost
25023ea4f30SEduardo Habkost
25123ea4f30SEduardo Habkostdef qemuOptsEscape(s):
25223ea4f30SEduardo Habkost    """Escape option value QemuOpts"""
25323ea4f30SEduardo Habkost    return s.replace(",", ",,")
25423ea4f30SEduardo Habkost
25523ea4f30SEduardo Habkost
25623ea4f30SEduardo Habkostdef formatTestCase(t):
25723ea4f30SEduardo Habkost    """Format test case info as "key=value key=value" for prettier logging output"""
25823ea4f30SEduardo Habkost    return ' '.join('%s=%s' % (k, v) for k, v in t.items())
25923ea4f30SEduardo Habkost
26023ea4f30SEduardo Habkost
26123ea4f30SEduardo Habkostdef qomListTypeNames(vm, **kwargs):
26223ea4f30SEduardo Habkost    """Run qom-list-types QMP command, return type names"""
26323ea4f30SEduardo Habkost    types = vm.command('qom-list-types', **kwargs)
26423ea4f30SEduardo Habkost    return [t['name'] for t in types]
26523ea4f30SEduardo Habkost
26623ea4f30SEduardo Habkost
26723ea4f30SEduardo Habkostdef infoQDM(vm):
26823ea4f30SEduardo Habkost    """Parse 'info qdm' output"""
26923ea4f30SEduardo Habkost    args = {'command-line': 'info qdm'}
27023ea4f30SEduardo Habkost    devhelp = vm.command('human-monitor-command', **args)
27123ea4f30SEduardo Habkost    for l in devhelp.split('\n'):
27223ea4f30SEduardo Habkost        l = l.strip()
27323ea4f30SEduardo Habkost        if l == '' or l.endswith(':'):
27423ea4f30SEduardo Habkost            continue
27523ea4f30SEduardo Habkost        d = {'name': re.search(r'name "([^"]+)"', l).group(1),
27623ea4f30SEduardo Habkost             'no-user': (re.search(', no-user', l) is not None)}
27723ea4f30SEduardo Habkost        yield d
27823ea4f30SEduardo Habkost
27923ea4f30SEduardo Habkost
28023ea4f30SEduardo Habkostclass QemuBinaryInfo(object):
28123ea4f30SEduardo Habkost    def __init__(self, binary, devtype):
28223ea4f30SEduardo Habkost        if devtype is None:
28323ea4f30SEduardo Habkost            devtype = 'device'
28423ea4f30SEduardo Habkost
28523ea4f30SEduardo Habkost        self.binary = binary
28623ea4f30SEduardo Habkost        self._machine_info = {}
28723ea4f30SEduardo Habkost
28823ea4f30SEduardo Habkost        dbg("devtype: %r", devtype)
28923ea4f30SEduardo Habkost        args = ['-S', '-machine', 'none,accel=kvm:tcg']
29023ea4f30SEduardo Habkost        dbg("querying info for QEMU binary: %s", binary)
29123ea4f30SEduardo Habkost        vm = QEMUMachine(binary=binary, args=args)
29223ea4f30SEduardo Habkost        vm.launch()
29323ea4f30SEduardo Habkost        try:
29423ea4f30SEduardo Habkost            self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
29523ea4f30SEduardo Habkost            # there's no way to query DeviceClass::user_creatable using QMP,
29623ea4f30SEduardo Habkost            # so use 'info qdm':
29723ea4f30SEduardo Habkost            self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
29823ea4f30SEduardo Habkost            self.machines = list(m['name'] for m in vm.command('query-machines'))
29923ea4f30SEduardo Habkost            self.user_devs = self.alldevs.difference(self.no_user_devs)
30023ea4f30SEduardo Habkost            self.kvm_available = vm.command('query-kvm')['enabled']
30123ea4f30SEduardo Habkost        finally:
30223ea4f30SEduardo Habkost            vm.shutdown()
30323ea4f30SEduardo Habkost
30423ea4f30SEduardo Habkost    def machineInfo(self, machine):
30523ea4f30SEduardo Habkost        """Query for information on a specific machine-type
30623ea4f30SEduardo Habkost
30723ea4f30SEduardo Habkost        Results are cached internally, in case the same machine-
30823ea4f30SEduardo Habkost        type is queried multiple times.
30923ea4f30SEduardo Habkost        """
31023ea4f30SEduardo Habkost        if machine in self._machine_info:
31123ea4f30SEduardo Habkost            return self._machine_info[machine]
31223ea4f30SEduardo Habkost
31323ea4f30SEduardo Habkost        mi = {}
31423ea4f30SEduardo Habkost        args = ['-S', '-machine', '%s' % (machine)]
31523ea4f30SEduardo Habkost        dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
31623ea4f30SEduardo Habkost        vm = QEMUMachine(binary=self.binary, args=args)
31723ea4f30SEduardo Habkost        try:
31823ea4f30SEduardo Habkost            vm.launch()
31923ea4f30SEduardo Habkost            mi['runnable'] = True
32023ea4f30SEduardo Habkost        except KeyboardInterrupt:
32123ea4f30SEduardo Habkost            raise
32223ea4f30SEduardo Habkost        except:
32323ea4f30SEduardo Habkost            dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
32423ea4f30SEduardo Habkost            dbg("log: %r", vm.get_log())
32523ea4f30SEduardo Habkost            mi['runnable'] = False
32623ea4f30SEduardo Habkost
32723ea4f30SEduardo Habkost        vm.shutdown()
32823ea4f30SEduardo Habkost        self._machine_info[machine] = mi
32923ea4f30SEduardo Habkost        return mi
33023ea4f30SEduardo Habkost
33123ea4f30SEduardo Habkost
33223ea4f30SEduardo HabkostBINARY_INFO = {}
33323ea4f30SEduardo Habkost
33423ea4f30SEduardo Habkost
33523ea4f30SEduardo Habkostdef getBinaryInfo(args, binary):
33623ea4f30SEduardo Habkost    if binary not in BINARY_INFO:
33723ea4f30SEduardo Habkost        BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
33823ea4f30SEduardo Habkost    return BINARY_INFO[binary]
33923ea4f30SEduardo Habkost
34023ea4f30SEduardo Habkost
34123ea4f30SEduardo Habkostdef checkOneCase(args, testcase):
34223ea4f30SEduardo Habkost    """Check one specific case
34323ea4f30SEduardo Habkost
34423ea4f30SEduardo Habkost    Returns a dictionary containing failure information on error,
34523ea4f30SEduardo Habkost    or None on success
34623ea4f30SEduardo Habkost    """
34723ea4f30SEduardo Habkost    binary = testcase['binary']
34823ea4f30SEduardo Habkost    accel = testcase['accel']
34923ea4f30SEduardo Habkost    machine = testcase['machine']
35023ea4f30SEduardo Habkost    device = testcase['device']
35123ea4f30SEduardo Habkost
35223ea4f30SEduardo Habkost    dbg("will test: %r", testcase)
35323ea4f30SEduardo Habkost
35423ea4f30SEduardo Habkost    args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
35523ea4f30SEduardo Habkost            '-device', qemuOptsEscape(device)]
35623ea4f30SEduardo Habkost    cmdline = ' '.join([binary] + args)
35723ea4f30SEduardo Habkost    dbg("will launch QEMU: %s", cmdline)
35823ea4f30SEduardo Habkost    vm = QEMUMachine(binary=binary, args=args)
35923ea4f30SEduardo Habkost
36023ea4f30SEduardo Habkost    exc_traceback = None
36123ea4f30SEduardo Habkost    try:
36223ea4f30SEduardo Habkost        vm.launch()
36323ea4f30SEduardo Habkost    except KeyboardInterrupt:
36423ea4f30SEduardo Habkost        raise
36523ea4f30SEduardo Habkost    except:
36623ea4f30SEduardo Habkost        exc_traceback = traceback.format_exc()
36723ea4f30SEduardo Habkost        dbg("Exception while running test case")
36823ea4f30SEduardo Habkost    finally:
36923ea4f30SEduardo Habkost        vm.shutdown()
37023ea4f30SEduardo Habkost        ec = vm.exitcode()
37123ea4f30SEduardo Habkost        log = vm.get_log()
37223ea4f30SEduardo Habkost
37323ea4f30SEduardo Habkost    if exc_traceback is not None or ec != 0:
37423ea4f30SEduardo Habkost        return {'exc_traceback':exc_traceback,
37523ea4f30SEduardo Habkost                'exitcode':ec,
37623ea4f30SEduardo Habkost                'log':log,
37723ea4f30SEduardo Habkost                'testcase':testcase,
37823ea4f30SEduardo Habkost                'cmdline':cmdline}
37923ea4f30SEduardo Habkost
38023ea4f30SEduardo Habkost
38123ea4f30SEduardo Habkostdef binariesToTest(args, testcase):
38223ea4f30SEduardo Habkost    if args.qemu:
38323ea4f30SEduardo Habkost        r = args.qemu
38423ea4f30SEduardo Habkost    else:
3858a478365SEduardo Habkost        r = [f.path for f in os.scandir('.')
3868a478365SEduardo Habkost             if f.name.startswith('qemu-system-') and
3878a478365SEduardo Habkost                f.is_file() and os.access(f, os.X_OK)]
38823ea4f30SEduardo Habkost    return r
38923ea4f30SEduardo Habkost
39023ea4f30SEduardo Habkost
39123ea4f30SEduardo Habkostdef accelsToTest(args, testcase):
39223ea4f30SEduardo Habkost    if getBinaryInfo(args, testcase['binary']).kvm_available:
39323ea4f30SEduardo Habkost        yield 'kvm'
39423ea4f30SEduardo Habkost    yield 'tcg'
39523ea4f30SEduardo Habkost
39623ea4f30SEduardo Habkost
39723ea4f30SEduardo Habkostdef machinesToTest(args, testcase):
39823ea4f30SEduardo Habkost    return getBinaryInfo(args, testcase['binary']).machines
39923ea4f30SEduardo Habkost
40023ea4f30SEduardo Habkost
40123ea4f30SEduardo Habkostdef devicesToTest(args, testcase):
40223ea4f30SEduardo Habkost    return getBinaryInfo(args, testcase['binary']).user_devs
40323ea4f30SEduardo Habkost
40423ea4f30SEduardo Habkost
40523ea4f30SEduardo HabkostTESTCASE_VARIABLES = [
40623ea4f30SEduardo Habkost    ('binary', binariesToTest),
40723ea4f30SEduardo Habkost    ('accel', accelsToTest),
40823ea4f30SEduardo Habkost    ('machine', machinesToTest),
40923ea4f30SEduardo Habkost    ('device', devicesToTest),
41023ea4f30SEduardo Habkost]
41123ea4f30SEduardo Habkost
41223ea4f30SEduardo Habkost
41323ea4f30SEduardo Habkostdef genCases1(args, testcases, var, fn):
41423ea4f30SEduardo Habkost    """Generate new testcases for one variable
41523ea4f30SEduardo Habkost
41623ea4f30SEduardo Habkost    If an existing item already has a variable set, don't
41723ea4f30SEduardo Habkost    generate new items and just return it directly. This
41823ea4f30SEduardo Habkost    allows the "-t" command-line option to be used to choose
41923ea4f30SEduardo Habkost    a specific test case.
42023ea4f30SEduardo Habkost    """
42123ea4f30SEduardo Habkost    for testcase in testcases:
42223ea4f30SEduardo Habkost        if var in testcase:
42323ea4f30SEduardo Habkost            yield testcase.copy()
42423ea4f30SEduardo Habkost        else:
42523ea4f30SEduardo Habkost            for i in fn(args, testcase):
42623ea4f30SEduardo Habkost                t = testcase.copy()
42723ea4f30SEduardo Habkost                t[var] = i
42823ea4f30SEduardo Habkost                yield t
42923ea4f30SEduardo Habkost
43023ea4f30SEduardo Habkost
43123ea4f30SEduardo Habkostdef genCases(args, testcase):
43223ea4f30SEduardo Habkost    """Generate test cases for all variables
43323ea4f30SEduardo Habkost    """
43423ea4f30SEduardo Habkost    cases = [testcase.copy()]
43523ea4f30SEduardo Habkost    for var, fn in TESTCASE_VARIABLES:
43623ea4f30SEduardo Habkost        dbg("var: %r, fn: %r", var, fn)
43723ea4f30SEduardo Habkost        cases = genCases1(args, cases, var, fn)
43823ea4f30SEduardo Habkost    return cases
43923ea4f30SEduardo Habkost
44023ea4f30SEduardo Habkost
44123ea4f30SEduardo Habkostdef casesToTest(args, testcase):
44223ea4f30SEduardo Habkost    cases = genCases(args, testcase)
44323ea4f30SEduardo Habkost    if args.random:
44423ea4f30SEduardo Habkost        cases = list(cases)
44523ea4f30SEduardo Habkost        cases = random.sample(cases, min(args.random, len(cases)))
44623ea4f30SEduardo Habkost    if args.debug:
44723ea4f30SEduardo Habkost        cases = list(cases)
44823ea4f30SEduardo Habkost        dbg("%d test cases to test", len(cases))
44923ea4f30SEduardo Habkost    if args.shuffle:
45023ea4f30SEduardo Habkost        cases = list(cases)
45123ea4f30SEduardo Habkost        random.shuffle(cases)
45223ea4f30SEduardo Habkost    return cases
45323ea4f30SEduardo Habkost
45423ea4f30SEduardo Habkost
45523ea4f30SEduardo Habkostdef logFailure(f, level):
45623ea4f30SEduardo Habkost    t = f['testcase']
45723ea4f30SEduardo Habkost    logger.log(level, "failed: %s", formatTestCase(t))
45823ea4f30SEduardo Habkost    logger.log(level, "cmdline: %s", f['cmdline'])
45923ea4f30SEduardo Habkost    for l in f['log'].strip().split('\n'):
46023ea4f30SEduardo Habkost        logger.log(level, "log: %s", l)
46123ea4f30SEduardo Habkost    logger.log(level, "exit code: %r", f['exitcode'])
46223ea4f30SEduardo Habkost    if f['exc_traceback']:
46323ea4f30SEduardo Habkost        logger.log(level, "exception:")
46423ea4f30SEduardo Habkost        for l in f['exc_traceback'].split('\n'):
46523ea4f30SEduardo Habkost            logger.log(level, "  %s", l.rstrip('\n'))
46623ea4f30SEduardo Habkost
46723ea4f30SEduardo Habkost
46823ea4f30SEduardo Habkostdef main():
46923ea4f30SEduardo Habkost    parser = argparse.ArgumentParser(description="QEMU -device crash test")
47023ea4f30SEduardo Habkost    parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
47123ea4f30SEduardo Habkost                        help="Limit test cases to KEY=VALUE",
47223ea4f30SEduardo Habkost                        action='append', dest='testcases', default=[])
47323ea4f30SEduardo Habkost    parser.add_argument('-d', '--debug', action='store_true',
47423ea4f30SEduardo Habkost                        help='debug output')
47523ea4f30SEduardo Habkost    parser.add_argument('-v', '--verbose', action='store_true', default=True,
47623ea4f30SEduardo Habkost                        help='verbose output')
47723ea4f30SEduardo Habkost    parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
47823ea4f30SEduardo Habkost                        help='non-verbose output')
47923ea4f30SEduardo Habkost    parser.add_argument('-r', '--random', type=int, metavar='COUNT',
48023ea4f30SEduardo Habkost                        help='run a random sample of COUNT test cases',
48123ea4f30SEduardo Habkost                        default=0)
48223ea4f30SEduardo Habkost    parser.add_argument('--shuffle', action='store_true',
48323ea4f30SEduardo Habkost                        help='Run test cases in random order')
48423ea4f30SEduardo Habkost    parser.add_argument('--dry-run', action='store_true',
48523ea4f30SEduardo Habkost                        help="Don't run any tests, just generate list")
48623ea4f30SEduardo Habkost    parser.add_argument('-D', '--devtype', metavar='TYPE',
48723ea4f30SEduardo Habkost                        help="Test only device types that implement TYPE")
48823ea4f30SEduardo Habkost    parser.add_argument('-Q', '--quick', action='store_true', default=True,
48923ea4f30SEduardo Habkost                        help="Quick mode: skip test cases that are expected to fail")
49023ea4f30SEduardo Habkost    parser.add_argument('-F', '--full', action='store_false', dest='quick',
49123ea4f30SEduardo Habkost                        help="Full mode: test cases that are expected to fail")
49223ea4f30SEduardo Habkost    parser.add_argument('--strict', action='store_true', dest='strict',
49323ea4f30SEduardo Habkost                        help="Treat all warnings as fatal")
49423ea4f30SEduardo Habkost    parser.add_argument('qemu', nargs='*', metavar='QEMU',
49523ea4f30SEduardo Habkost                        help='QEMU binary to run')
49623ea4f30SEduardo Habkost    args = parser.parse_args()
49723ea4f30SEduardo Habkost
49823ea4f30SEduardo Habkost    if args.debug:
49923ea4f30SEduardo Habkost        lvl = logging.DEBUG
50023ea4f30SEduardo Habkost    elif args.verbose:
50123ea4f30SEduardo Habkost        lvl = logging.INFO
50223ea4f30SEduardo Habkost    else:
50323ea4f30SEduardo Habkost        lvl = logging.WARN
50423ea4f30SEduardo Habkost    logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
50523ea4f30SEduardo Habkost
50623ea4f30SEduardo Habkost    fatal_failures = []
50723ea4f30SEduardo Habkost    wl_stats = {}
50823ea4f30SEduardo Habkost    skipped = 0
50923ea4f30SEduardo Habkost    total = 0
51023ea4f30SEduardo Habkost
51123ea4f30SEduardo Habkost    tc = {}
51223ea4f30SEduardo Habkost    dbg("testcases: %r", args.testcases)
51323ea4f30SEduardo Habkost    if args.testcases:
51423ea4f30SEduardo Habkost        for t in chain(*args.testcases):
51523ea4f30SEduardo Habkost            for kv in t.split():
51623ea4f30SEduardo Habkost                k, v = kv.split('=', 1)
51723ea4f30SEduardo Habkost                tc[k] = v
51823ea4f30SEduardo Habkost
51923ea4f30SEduardo Habkost    if len(binariesToTest(args, tc)) == 0:
520f03868bdSEduardo Habkost        print("No QEMU binary found", file=sys.stderr)
52123ea4f30SEduardo Habkost        parser.print_usage(sys.stderr)
52223ea4f30SEduardo Habkost        return 1
52323ea4f30SEduardo Habkost
52423ea4f30SEduardo Habkost    for t in casesToTest(args, tc):
52523ea4f30SEduardo Habkost        logger.info("running test case: %s", formatTestCase(t))
52623ea4f30SEduardo Habkost        total += 1
52723ea4f30SEduardo Habkost
52823ea4f30SEduardo Habkost        expected_match = findExpectedResult(t)
52923ea4f30SEduardo Habkost        if (args.quick and
53023ea4f30SEduardo Habkost                (expected_match or
53123ea4f30SEduardo Habkost                 not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
53223ea4f30SEduardo Habkost            dbg("skipped: %s", formatTestCase(t))
53323ea4f30SEduardo Habkost            skipped += 1
53423ea4f30SEduardo Habkost            continue
53523ea4f30SEduardo Habkost
53623ea4f30SEduardo Habkost        if args.dry_run:
53723ea4f30SEduardo Habkost            continue
53823ea4f30SEduardo Habkost
53923ea4f30SEduardo Habkost        try:
54023ea4f30SEduardo Habkost            f = checkOneCase(args, t)
54123ea4f30SEduardo Habkost        except KeyboardInterrupt:
54223ea4f30SEduardo Habkost            break
54323ea4f30SEduardo Habkost
54423ea4f30SEduardo Habkost        if f:
5451a14d4e1SEduardo Habkost            i, rule = checkResultRuleList(f)
5461a14d4e1SEduardo Habkost            dbg("testcase: %r, rule list match: %r", t, rule)
54723ea4f30SEduardo Habkost            wl_stats.setdefault(i, []).append(f)
5481a14d4e1SEduardo Habkost            level = rule.get('loglevel', logging.DEBUG)
54923ea4f30SEduardo Habkost            logFailure(f, level)
5501a14d4e1SEduardo Habkost            if rule.get('fatal') or (args.strict and level >= logging.WARN):
55123ea4f30SEduardo Habkost                fatal_failures.append(f)
55223ea4f30SEduardo Habkost        else:
55323ea4f30SEduardo Habkost            dbg("success: %s", formatTestCase(t))
55423ea4f30SEduardo Habkost            if expected_match:
55523ea4f30SEduardo Habkost                logger.warn("Didn't fail as expected: %s", formatTestCase(t))
55623ea4f30SEduardo Habkost
55723ea4f30SEduardo Habkost    logger.info("Total: %d test cases", total)
55823ea4f30SEduardo Habkost    if skipped:
55923ea4f30SEduardo Habkost        logger.info("Skipped %d test cases", skipped)
56023ea4f30SEduardo Habkost
56123ea4f30SEduardo Habkost    if args.debug:
5621a14d4e1SEduardo Habkost        stats = sorted([(len(wl_stats.get(i, [])), rule) for i, rule in
5631a14d4e1SEduardo Habkost                         enumerate(ERROR_RULE_LIST)], key=lambda x: x[0])
5641a14d4e1SEduardo Habkost        for count, rule in stats:
5651a14d4e1SEduardo Habkost            dbg("error rule stats: %d: %r", count, rule)
56623ea4f30SEduardo Habkost
56723ea4f30SEduardo Habkost    if fatal_failures:
56823ea4f30SEduardo Habkost        for f in fatal_failures:
56923ea4f30SEduardo Habkost            t = f['testcase']
57023ea4f30SEduardo Habkost            logger.error("Fatal failure: %s", formatTestCase(t))
57123ea4f30SEduardo Habkost        logger.error("Fatal failures on some machine/device combinations")
57223ea4f30SEduardo Habkost        return 1
57323ea4f30SEduardo Habkost
57423ea4f30SEduardo Habkostif __name__ == '__main__':
57523ea4f30SEduardo Habkost    sys.exit(main())
576