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