qemu/scripts/device-crash-test
<<
>>
Prefs
   1#!/usr/bin/env python
   2#
   3#  Copyright (c) 2017 Red Hat Inc
   4#
   5# Author:
   6#  Eduardo Habkost <ehabkost@redhat.com>
   7#
   8# This program is free software; you can redistribute it and/or modify
   9# it under the terms of the GNU General Public License as published by
  10# the Free Software Foundation; either version 2 of the License, or
  11# (at your option) any later version.
  12#
  13# This program is distributed in the hope that it will be useful,
  14# but WITHOUT ANY WARRANTY; without even the implied warranty of
  15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16# GNU General Public License for more details.
  17#
  18# You should have received a copy of the GNU General Public License along
  19# with this program; if not, write to the Free Software Foundation, Inc.,
  20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21
  22"""
  23Run QEMU with all combinations of -machine and -device types,
  24check for crashes and unexpected errors.
  25"""
  26from __future__ import print_function
  27
  28import sys
  29import os
  30import glob
  31import logging
  32import traceback
  33import re
  34import random
  35import argparse
  36from itertools import chain
  37
  38sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts'))
  39from qemu import QEMUMachine
  40
  41logger = logging.getLogger('device-crash-test')
  42dbg = logger.debug
  43
  44
  45# Purposes of the following whitelist:
  46# * Avoiding verbose log messages when we find known non-fatal
  47#   (exitcode=1) errors
  48# * Avoiding fatal errors when we find known crashes
  49# * Skipping machines/devices that are known not to work out of
  50#   the box, when running in --quick mode
  51#
  52# Keeping the whitelist updated is desirable, but not required,
  53# because unexpected cases where QEMU exits with exitcode=1 will
  54# just trigger a INFO message.
  55
  56# Valid whitelist entry keys:
  57# * accel: regexp, full match only
  58# * machine: regexp, full match only
  59# * device: regexp, full match only
  60# * log: regexp, partial match allowed
  61# * exitcode: if not present, defaults to 1. If None, matches any exitcode
  62# * warn: if True, matching failures will be logged as warnings
  63# * expected: if True, QEMU is expected to always fail every time
  64#   when testing the corresponding test case
  65# * loglevel: log level of log output when there's a match.
  66ERROR_WHITELIST = [
  67    # Machines that won't work out of the box:
  68    #             MACHINE                         | ERROR MESSAGE
  69    {'machine':'niagara', 'expected':True},       # Unable to load a firmware for -M niagara
  70    {'machine':'boston', 'expected':True},        # Please provide either a -kernel or -bios argument
  71    {'machine':'leon3_generic', 'expected':True}, # Can't read bios image (null)
  72
  73    # devices that don't work out of the box because they require extra options to "-device DEV":
  74    #            DEVICE                                    | ERROR MESSAGE
  75    {'device':'.*-(i386|x86_64)-cpu', 'expected':True},    # CPU socket-id is not set
  76    {'device':'ARM,bitband-memory', 'expected':True},      # source-memory property not set
  77    {'device':'arm.cortex-a9-global-timer', 'expected':True}, # a9_gtimer_realize: num-cpu must be between 1 and 4
  78    {'device':'arm_mptimer', 'expected':True},             # num-cpu must be between 1 and 4
  79    {'device':'armv7m', 'expected':True},                  # memory property was not set
  80    {'device':'aspeed.scu', 'expected':True},              # Unknown silicon revision: 0x0
  81    {'device':'aspeed.sdmc', 'expected':True},             # Unknown silicon revision: 0x0
  82    {'device':'bcm2835-dma', 'expected':True},             # bcm2835_dma_realize: required dma-mr link not found: Property '.dma-mr' not found
  83    {'device':'bcm2835-fb', 'expected':True},              # bcm2835_fb_realize: required vcram-base property not set
  84    {'device':'bcm2835-mbox', 'expected':True},            # bcm2835_mbox_realize: required mbox-mr link not found: Property '.mbox-mr' not found
  85    {'device':'bcm2835-peripherals', 'expected':True},     # bcm2835_peripherals_realize: required ram link not found: Property '.ram' not found
  86    {'device':'bcm2835-property', 'expected':True},        # bcm2835_property_realize: required fb link not found: Property '.fb' not found
  87    {'device':'bcm2835_gpio', 'expected':True},            # bcm2835_gpio_realize: required sdhci link not found: Property '.sdbus-sdhci' not found
  88    {'device':'bcm2836', 'expected':True},                 # bcm2836_realize: required ram link not found: Property '.ram' not found
  89    {'device':'cfi.pflash01', 'expected':True},            # attribute "sector-length" not specified or zero.
  90    {'device':'cfi.pflash02', 'expected':True},            # attribute "sector-length" not specified or zero.
  91    {'device':'icp', 'expected':True},                     # icp_realize: required link 'xics' not found: Property '.xics' not found
  92    {'device':'ics', 'expected':True},                     # ics_base_realize: required link 'xics' not found: Property '.xics' not found
  93    # "-device ide-cd" does work on more recent QEMU versions, so it doesn't have expected=True
  94    {'device':'ide-cd'},                                 # No drive specified
  95    {'device':'ide-drive', 'expected':True},               # No drive specified
  96    {'device':'ide-hd', 'expected':True},                  # No drive specified
  97    {'device':'ipmi-bmc-extern', 'expected':True},         # IPMI external bmc requires chardev attribute
  98    {'device':'isa-debugcon', 'expected':True},            # Can't create serial device, empty char device
  99    {'device':'isa-ipmi-bt', 'expected':True},             # IPMI device requires a bmc attribute to be set
 100    {'device':'isa-ipmi-kcs', 'expected':True},            # IPMI device requires a bmc attribute to be set
 101    {'device':'isa-parallel', 'expected':True},            # Can't create serial device, empty char device
 102    {'device':'isa-serial', 'expected':True},              # Can't create serial device, empty char device
 103    {'device':'ivshmem', 'expected':True},                 # You must specify either 'shm' or 'chardev'
 104    {'device':'ivshmem-doorbell', 'expected':True},        # You must specify a 'chardev'
 105    {'device':'ivshmem-plain', 'expected':True},           # You must specify a 'memdev'
 106    {'device':'loader', 'expected':True},                  # please include valid arguments
 107    {'device':'nand', 'expected':True},                    # Unsupported NAND block size 0x1
 108    {'device':'nvdimm', 'expected':True},                  # 'memdev' property is not set
 109    {'device':'nvme', 'expected':True},                    # Device initialization failed
 110    {'device':'pc-dimm', 'expected':True},                 # 'memdev' property is not set
 111    {'device':'pci-bridge', 'expected':True},              # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
 112    {'device':'pci-bridge-seat', 'expected':True},         # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
 113    {'device':'pci-serial', 'expected':True},              # Can't create serial device, empty char device
 114    {'device':'pci-serial-2x', 'expected':True},           # Can't create serial device, empty char device
 115    {'device':'pci-serial-4x', 'expected':True},           # Can't create serial device, empty char device
 116    {'device':'pxa2xx-dma', 'expected':True},              # channels value invalid
 117    {'device':'pxb', 'expected':True},                     # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
 118    {'device':'scsi-block', 'expected':True},              # drive property not set
 119    {'device':'scsi-disk', 'expected':True},               # drive property not set
 120    {'device':'scsi-generic', 'expected':True},            # drive property not set
 121    {'device':'scsi-hd', 'expected':True},                 # drive property not set
 122    {'device':'spapr-pci-host-bridge', 'expected':True},   # BUID not specified for PHB
 123    {'device':'spapr-rng', 'expected':True},               # spapr-rng needs an RNG backend!
 124    {'device':'spapr-vty', 'expected':True},               # chardev property not set
 125    {'device':'tpm-tis', 'expected':True},                 # tpm_tis: backend driver with id (null) could not be found
 126    {'device':'unimplemented-device', 'expected':True},    # property 'size' not specified or zero
 127    {'device':'usb-braille', 'expected':True},             # Property chardev is required
 128    {'device':'usb-mtp', 'expected':True},                 # x-root property must be configured
 129    {'device':'usb-redir', 'expected':True},               # Parameter 'chardev' is missing
 130    {'device':'usb-serial', 'expected':True},              # Property chardev is required
 131    {'device':'usb-storage', 'expected':True},             # drive property not set
 132    {'device':'vfio-amd-xgbe', 'expected':True},           # -device vfio-amd-xgbe: vfio error: wrong host device name
 133    {'device':'vfio-calxeda-xgmac', 'expected':True},      # -device vfio-calxeda-xgmac: vfio error: wrong host device name
 134    {'device':'vfio-pci', 'expected':True},                # No provided host device
 135    {'device':'vfio-pci-igd-lpc-bridge', 'expected':True}, # VFIO dummy ISA/LPC bridge must have address 1f.0
 136    {'device':'vhost-scsi.*', 'expected':True},            # vhost-scsi: missing wwpn
 137    {'device':'vhost-vsock-device', 'expected':True},      # guest-cid property must be greater than 2
 138    {'device':'vhost-vsock-pci', 'expected':True},         # guest-cid property must be greater than 2
 139    {'device':'virtio-9p-ccw', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
 140    {'device':'virtio-9p-device', 'expected':True},        # 9pfs device couldn't find fsdev with the id = NULL
 141    {'device':'virtio-9p-pci', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
 142    {'device':'virtio-blk-ccw', 'expected':True},          # drive property not set
 143    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
 144    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
 145    {'device':'virtio-blk-pci', 'expected':True},          # drive property not set
 146    {'device':'virtio-crypto-ccw', 'expected':True},       # 'cryptodev' parameter expects a valid object
 147    {'device':'virtio-crypto-device', 'expected':True},    # 'cryptodev' parameter expects a valid object
 148    {'device':'virtio-crypto-pci', 'expected':True},       # 'cryptodev' parameter expects a valid object
 149    {'device':'virtio-input-host-device', 'expected':True}, # evdev property is required
 150    {'device':'virtio-input-host-pci', 'expected':True},   # evdev property is required
 151    {'device':'xen-pvdevice', 'expected':True},            # Device ID invalid, it must always be supplied
 152    {'device':'vhost-vsock-ccw', 'expected':True},         # guest-cid property must be greater than 2
 153    {'device':'ALTR.timer', 'expected':True},              # "clock-frequency" property must be provided
 154    {'device':'zpci', 'expected':True},                    # target must be defined
 155    {'device':'pnv-(occ|icp|lpc)', 'expected':True},       # required link 'xics' not found: Property '.xics' not found
 156    {'device':'powernv-cpu-.*', 'expected':True},          # pnv_core_realize: required link 'xics' not found: Property '.xics' not found
 157
 158    # ioapic devices are already created by pc and will fail:
 159    {'machine':'q35|pc.*', 'device':'kvm-ioapic', 'expected':True}, # Only 1 ioapics allowed
 160    {'machine':'q35|pc.*', 'device':'ioapic', 'expected':True},     # Only 1 ioapics allowed
 161
 162    # "spapr-cpu-core needs a pseries machine"
 163    {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True},
 164
 165    # KVM-specific devices shouldn't be tried without accel=kvm:
 166    {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
 167
 168    # xen-specific machines and devices:
 169    {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
 170    {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
 171
 172    # this fails on some machine-types, but not all, so they don't have expected=True:
 173    {'device':'vmgenid'}, # vmgenid requires DMA write support in fw_cfg, which this machine type does not provide
 174
 175    # Silence INFO messages for errors that are common on multiple
 176    # devices/machines:
 177    {'log':r"No '[\w-]+' bus found for device '[\w-]+'"},
 178    {'log':r"images* must be given with the 'pflash' parameter"},
 179    {'log':r"(Guest|ROM|Flash|Kernel) image must be specified"},
 180    {'log':r"[cC]ould not load [\w ]+ (BIOS|bios) '[\w-]+\.bin'"},
 181    {'log':r"Couldn't find rom image '[\w-]+\.bin'"},
 182    {'log':r"speed mismatch trying to attach usb device"},
 183    {'log':r"Can't create a second ISA bus"},
 184    {'log':r"duplicate fw_cfg file name"},
 185    # sysbus-related error messages: most machines reject most dynamic sysbus devices:
 186    {'log':r"Option '-device [\w.,-]+' cannot be handled by this machine"},
 187    {'log':r"Device [\w.,-]+ is not supported by this machine yet"},
 188    {'log':r"Device [\w.,-]+ can not be dynamically instantiated"},
 189    {'log':r"Platform Bus: Can not fit MMIO region of size "},
 190    # other more specific errors we will ignore:
 191    {'device':'.*-spapr-cpu-core', 'log':r"CPU core type should be"},
 192    {'log':r"MSI(-X)? is not supported by interrupt controller"},
 193    {'log':r"pxb-pcie? devices cannot reside on a PCIe? bus"},
 194    {'log':r"Ignoring smp_cpus value"},
 195    {'log':r"sd_init failed: Drive 'sd0' is already in use because it has been automatically connected to another device"},
 196    {'log':r"This CPU requires a smaller page size than the system is using"},
 197    {'log':r"MSI-X support is mandatory in the S390 architecture"},
 198    {'log':r"rom check and register reset failed"},
 199    {'log':r"Unable to initialize GIC, CPUState for CPU#0 not valid"},
 200    {'log':r"Multiple VT220 operator consoles are not supported"},
 201    {'log':r"core 0 already populated"},
 202    {'log':r"could not find stage1 bootloader"},
 203
 204    # other exitcode=1 failures not listed above will just generate INFO messages:
 205    {'exitcode':1, 'loglevel':logging.INFO},
 206
 207    # KNOWN CRASHES:
 208    # Known crashes will generate error messages, but won't be fatal.
 209    # Those entries must be removed once we fix the crashes.
 210    {'exitcode':-6, 'log':r"Device 'serial0' is in use", 'loglevel':logging.ERROR},
 211    {'exitcode':-6, 'log':r"qemu_net_client_setup: Assertion `!peer->peer' failed", 'loglevel':logging.ERROR},
 212    {'exitcode':-6, 'log':r'RAMBlock "[\w.-]+" already registered', 'loglevel':logging.ERROR},
 213    {'exitcode':-6, 'log':r"find_ram_offset: Assertion `size != 0' failed.", 'loglevel':logging.ERROR},
 214    {'exitcode':-6, 'log':r"add_cpreg_to_hashtable: code should not be reached", 'loglevel':logging.ERROR},
 215    {'exitcode':-6, 'log':r"qemu_alloc_display: Assertion `surface->image != NULL' failed", 'loglevel':logging.ERROR},
 216    {'exitcode':-6, 'log':r"Unexpected error in error_set_from_qdev_prop_error", 'loglevel':logging.ERROR},
 217    {'exitcode':-6, 'log':r"Object .* is not an instance of type spapr-machine", 'loglevel':logging.ERROR},
 218    {'exitcode':-6, 'log':r"Object .* is not an instance of type generic-pc-machine", 'loglevel':logging.ERROR},
 219    {'exitcode':-6, 'log':r"Object .* is not an instance of type e500-ccsr", 'loglevel':logging.ERROR},
 220    {'exitcode':-6, 'log':r"vmstate_register_with_alias_id: Assertion `!se->compat \|\| se->instance_id == 0' failed", 'loglevel':logging.ERROR},
 221    {'exitcode':-11, 'device':'isa-serial', 'loglevel':logging.ERROR, 'expected':True},
 222
 223    # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
 224    {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
 225]
 226
 227
 228def whitelistTestCaseMatch(wl, t):
 229    """Check if a test case specification can match a whitelist entry
 230
 231    This only checks if a whitelist entry is a candidate match
 232    for a given test case, it won't check if the test case
 233    results/output match the entry.  See whitelistResultMatch().
 234    """
 235    return (('machine' not in wl or
 236             'machine' not in t or
 237             re.match(wl['machine'] + '$', t['machine'])) and
 238            ('accel' not in wl or
 239             'accel' not in t or
 240             re.match(wl['accel'] + '$', t['accel'])) and
 241            ('device' not in wl or
 242             'device' not in t or
 243             re.match(wl['device'] + '$', t['device'])))
 244
 245
 246def whitelistCandidates(t):
 247    """Generate the list of candidates that can match a test case"""
 248    for i, wl in enumerate(ERROR_WHITELIST):
 249        if whitelistTestCaseMatch(wl, t):
 250            yield (i, wl)
 251
 252
 253def findExpectedResult(t):
 254    """Check if there's an expected=True whitelist entry for a test case
 255
 256    Returns (i, wl) tuple, where i is the index in
 257    ERROR_WHITELIST and wl is the whitelist entry itself.
 258    """
 259    for i, wl in whitelistCandidates(t):
 260        if wl.get('expected'):
 261            return (i, wl)
 262
 263
 264def whitelistResultMatch(wl, r):
 265    """Check if test case results/output match a whitelist entry
 266
 267    It is valid to call this function only if
 268    whitelistTestCaseMatch() is True for the entry (e.g. on
 269    entries returned by whitelistCandidates())
 270    """
 271    assert whitelistTestCaseMatch(wl, r['testcase'])
 272    return ((wl.get('exitcode', 1) is None or
 273             r['exitcode'] == wl.get('exitcode', 1)) and
 274            ('log' not in wl or
 275             re.search(wl['log'], r['log'], re.MULTILINE)))
 276
 277
 278def checkResultWhitelist(r):
 279    """Look up whitelist entry for a given test case result
 280
 281    Returns (i, wl) tuple, where i is the index in
 282    ERROR_WHITELIST and wl is the whitelist entry itself.
 283    """
 284    for i, wl in whitelistCandidates(r['testcase']):
 285        if whitelistResultMatch(wl, r):
 286            return i, wl
 287
 288    raise Exception("this should never happen")
 289
 290
 291def qemuOptsEscape(s):
 292    """Escape option value QemuOpts"""
 293    return s.replace(",", ",,")
 294
 295
 296def formatTestCase(t):
 297    """Format test case info as "key=value key=value" for prettier logging output"""
 298    return ' '.join('%s=%s' % (k, v) for k, v in t.items())
 299
 300
 301def qomListTypeNames(vm, **kwargs):
 302    """Run qom-list-types QMP command, return type names"""
 303    types = vm.command('qom-list-types', **kwargs)
 304    return [t['name'] for t in types]
 305
 306
 307def infoQDM(vm):
 308    """Parse 'info qdm' output"""
 309    args = {'command-line': 'info qdm'}
 310    devhelp = vm.command('human-monitor-command', **args)
 311    for l in devhelp.split('\n'):
 312        l = l.strip()
 313        if l == '' or l.endswith(':'):
 314            continue
 315        d = {'name': re.search(r'name "([^"]+)"', l).group(1),
 316             'no-user': (re.search(', no-user', l) is not None)}
 317        yield d
 318
 319
 320class QemuBinaryInfo(object):
 321    def __init__(self, binary, devtype):
 322        if devtype is None:
 323            devtype = 'device'
 324
 325        self.binary = binary
 326        self._machine_info = {}
 327
 328        dbg("devtype: %r", devtype)
 329        args = ['-S', '-machine', 'none,accel=kvm:tcg']
 330        dbg("querying info for QEMU binary: %s", binary)
 331        vm = QEMUMachine(binary=binary, args=args)
 332        vm.launch()
 333        try:
 334            self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
 335            # there's no way to query DeviceClass::user_creatable using QMP,
 336            # so use 'info qdm':
 337            self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
 338            self.machines = list(m['name'] for m in vm.command('query-machines'))
 339            self.user_devs = self.alldevs.difference(self.no_user_devs)
 340            self.kvm_available = vm.command('query-kvm')['enabled']
 341        finally:
 342            vm.shutdown()
 343
 344    def machineInfo(self, machine):
 345        """Query for information on a specific machine-type
 346
 347        Results are cached internally, in case the same machine-
 348        type is queried multiple times.
 349        """
 350        if machine in self._machine_info:
 351            return self._machine_info[machine]
 352
 353        mi = {}
 354        args = ['-S', '-machine', '%s' % (machine)]
 355        dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
 356        vm = QEMUMachine(binary=self.binary, args=args)
 357        try:
 358            vm.launch()
 359            mi['runnable'] = True
 360        except KeyboardInterrupt:
 361            raise
 362        except:
 363            dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
 364            dbg("log: %r", vm.get_log())
 365            mi['runnable'] = False
 366
 367        vm.shutdown()
 368        self._machine_info[machine] = mi
 369        return mi
 370
 371
 372BINARY_INFO = {}
 373
 374
 375def getBinaryInfo(args, binary):
 376    if binary not in BINARY_INFO:
 377        BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
 378    return BINARY_INFO[binary]
 379
 380
 381def checkOneCase(args, testcase):
 382    """Check one specific case
 383
 384    Returns a dictionary containing failure information on error,
 385    or None on success
 386    """
 387    binary = testcase['binary']
 388    accel = testcase['accel']
 389    machine = testcase['machine']
 390    device = testcase['device']
 391
 392    dbg("will test: %r", testcase)
 393
 394    args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
 395            '-device', qemuOptsEscape(device)]
 396    cmdline = ' '.join([binary] + args)
 397    dbg("will launch QEMU: %s", cmdline)
 398    vm = QEMUMachine(binary=binary, args=args)
 399
 400    exc_traceback = None
 401    try:
 402        vm.launch()
 403    except KeyboardInterrupt:
 404        raise
 405    except:
 406        exc_traceback = traceback.format_exc()
 407        dbg("Exception while running test case")
 408    finally:
 409        vm.shutdown()
 410        ec = vm.exitcode()
 411        log = vm.get_log()
 412
 413    if exc_traceback is not None or ec != 0:
 414        return {'exc_traceback':exc_traceback,
 415                'exitcode':ec,
 416                'log':log,
 417                'testcase':testcase,
 418                'cmdline':cmdline}
 419
 420
 421def binariesToTest(args, testcase):
 422    if args.qemu:
 423        r = args.qemu
 424    else:
 425        r = glob.glob('./*-softmmu/qemu-system-*')
 426    return r
 427
 428
 429def accelsToTest(args, testcase):
 430    if getBinaryInfo(args, testcase['binary']).kvm_available:
 431        yield 'kvm'
 432    yield 'tcg'
 433
 434
 435def machinesToTest(args, testcase):
 436    return getBinaryInfo(args, testcase['binary']).machines
 437
 438
 439def devicesToTest(args, testcase):
 440    return getBinaryInfo(args, testcase['binary']).user_devs
 441
 442
 443TESTCASE_VARIABLES = [
 444    ('binary', binariesToTest),
 445    ('accel', accelsToTest),
 446    ('machine', machinesToTest),
 447    ('device', devicesToTest),
 448]
 449
 450
 451def genCases1(args, testcases, var, fn):
 452    """Generate new testcases for one variable
 453
 454    If an existing item already has a variable set, don't
 455    generate new items and just return it directly. This
 456    allows the "-t" command-line option to be used to choose
 457    a specific test case.
 458    """
 459    for testcase in testcases:
 460        if var in testcase:
 461            yield testcase.copy()
 462        else:
 463            for i in fn(args, testcase):
 464                t = testcase.copy()
 465                t[var] = i
 466                yield t
 467
 468
 469def genCases(args, testcase):
 470    """Generate test cases for all variables
 471    """
 472    cases = [testcase.copy()]
 473    for var, fn in TESTCASE_VARIABLES:
 474        dbg("var: %r, fn: %r", var, fn)
 475        cases = genCases1(args, cases, var, fn)
 476    return cases
 477
 478
 479def casesToTest(args, testcase):
 480    cases = genCases(args, testcase)
 481    if args.random:
 482        cases = list(cases)
 483        cases = random.sample(cases, min(args.random, len(cases)))
 484    if args.debug:
 485        cases = list(cases)
 486        dbg("%d test cases to test", len(cases))
 487    if args.shuffle:
 488        cases = list(cases)
 489        random.shuffle(cases)
 490    return cases
 491
 492
 493def logFailure(f, level):
 494    t = f['testcase']
 495    logger.log(level, "failed: %s", formatTestCase(t))
 496    logger.log(level, "cmdline: %s", f['cmdline'])
 497    for l in f['log'].strip().split('\n'):
 498        logger.log(level, "log: %s", l)
 499    logger.log(level, "exit code: %r", f['exitcode'])
 500    if f['exc_traceback']:
 501        logger.log(level, "exception:")
 502        for l in f['exc_traceback'].split('\n'):
 503            logger.log(level, "  %s", l.rstrip('\n'))
 504
 505
 506def main():
 507    parser = argparse.ArgumentParser(description="QEMU -device crash test")
 508    parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
 509                        help="Limit test cases to KEY=VALUE",
 510                        action='append', dest='testcases', default=[])
 511    parser.add_argument('-d', '--debug', action='store_true',
 512                        help='debug output')
 513    parser.add_argument('-v', '--verbose', action='store_true', default=True,
 514                        help='verbose output')
 515    parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
 516                        help='non-verbose output')
 517    parser.add_argument('-r', '--random', type=int, metavar='COUNT',
 518                        help='run a random sample of COUNT test cases',
 519                        default=0)
 520    parser.add_argument('--shuffle', action='store_true',
 521                        help='Run test cases in random order')
 522    parser.add_argument('--dry-run', action='store_true',
 523                        help="Don't run any tests, just generate list")
 524    parser.add_argument('-D', '--devtype', metavar='TYPE',
 525                        help="Test only device types that implement TYPE")
 526    parser.add_argument('-Q', '--quick', action='store_true', default=True,
 527                        help="Quick mode: skip test cases that are expected to fail")
 528    parser.add_argument('-F', '--full', action='store_false', dest='quick',
 529                        help="Full mode: test cases that are expected to fail")
 530    parser.add_argument('--strict', action='store_true', dest='strict',
 531                        help="Treat all warnings as fatal")
 532    parser.add_argument('qemu', nargs='*', metavar='QEMU',
 533                        help='QEMU binary to run')
 534    args = parser.parse_args()
 535
 536    if args.debug:
 537        lvl = logging.DEBUG
 538    elif args.verbose:
 539        lvl = logging.INFO
 540    else:
 541        lvl = logging.WARN
 542    logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
 543
 544    fatal_failures = []
 545    wl_stats = {}
 546    skipped = 0
 547    total = 0
 548
 549    tc = {}
 550    dbg("testcases: %r", args.testcases)
 551    if args.testcases:
 552        for t in chain(*args.testcases):
 553            for kv in t.split():
 554                k, v = kv.split('=', 1)
 555                tc[k] = v
 556
 557    if len(binariesToTest(args, tc)) == 0:
 558        print("No QEMU binary found", file=sys.stderr)
 559        parser.print_usage(sys.stderr)
 560        return 1
 561
 562    for t in casesToTest(args, tc):
 563        logger.info("running test case: %s", formatTestCase(t))
 564        total += 1
 565
 566        expected_match = findExpectedResult(t)
 567        if (args.quick and
 568                (expected_match or
 569                 not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
 570            dbg("skipped: %s", formatTestCase(t))
 571            skipped += 1
 572            continue
 573
 574        if args.dry_run:
 575            continue
 576
 577        try:
 578            f = checkOneCase(args, t)
 579        except KeyboardInterrupt:
 580            break
 581
 582        if f:
 583            i, wl = checkResultWhitelist(f)
 584            dbg("testcase: %r, whitelist match: %r", t, wl)
 585            wl_stats.setdefault(i, []).append(f)
 586            level = wl.get('loglevel', logging.DEBUG)
 587            logFailure(f, level)
 588            if wl.get('fatal') or (args.strict and level >= logging.WARN):
 589                fatal_failures.append(f)
 590        else:
 591            dbg("success: %s", formatTestCase(t))
 592            if expected_match:
 593                logger.warn("Didn't fail as expected: %s", formatTestCase(t))
 594
 595    logger.info("Total: %d test cases", total)
 596    if skipped:
 597        logger.info("Skipped %d test cases", skipped)
 598
 599    if args.debug:
 600        stats = sorted([(len(wl_stats.get(i, [])), wl) for i, wl in enumerate(ERROR_WHITELIST)])
 601        for count, wl in stats:
 602            dbg("whitelist entry stats: %d: %r", count, wl)
 603
 604    if fatal_failures:
 605        for f in fatal_failures:
 606            t = f['testcase']
 607            logger.error("Fatal failure: %s", formatTestCase(t))
 608        logger.error("Fatal failures on some machine/device combinations")
 609        return 1
 610
 611if __name__ == '__main__':
 612    sys.exit(main())
 613