xref: /openbmc/qemu/scripts/device-crash-test (revision 8f0a3716)
1#!/usr/bin/env python2.7
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':'gus', 'loglevel':logging.ERROR, 'expected':True},
221    {'exitcode':-11, 'device':'isa-serial', 'loglevel':logging.ERROR, 'expected':True},
222    {'exitcode':-11, 'device':'sb16', 'loglevel':logging.ERROR, 'expected':True},
223    {'exitcode':-11, 'device':'cs4231a', 'loglevel':logging.ERROR, 'expected':True},
224    {'exitcode':-11, 'machine':'isapc', 'device':'.*-iommu', 'loglevel':logging.ERROR, 'expected':True},
225
226    # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
227    {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
228]
229
230
231def whitelistTestCaseMatch(wl, t):
232    """Check if a test case specification can match a whitelist entry
233
234    This only checks if a whitelist entry is a candidate match
235    for a given test case, it won't check if the test case
236    results/output match the entry.  See whitelistResultMatch().
237    """
238    return (('machine' not in wl or
239             'machine' not in t or
240             re.match(wl['machine'] + '$', t['machine'])) and
241            ('accel' not in wl or
242             'accel' not in t or
243             re.match(wl['accel'] + '$', t['accel'])) and
244            ('device' not in wl or
245             'device' not in t or
246             re.match(wl['device'] + '$', t['device'])))
247
248
249def whitelistCandidates(t):
250    """Generate the list of candidates that can match a test case"""
251    for i, wl in enumerate(ERROR_WHITELIST):
252        if whitelistTestCaseMatch(wl, t):
253            yield (i, wl)
254
255
256def findExpectedResult(t):
257    """Check if there's an expected=True whitelist entry for a test case
258
259    Returns (i, wl) tuple, where i is the index in
260    ERROR_WHITELIST and wl is the whitelist entry itself.
261    """
262    for i, wl in whitelistCandidates(t):
263        if wl.get('expected'):
264            return (i, wl)
265
266
267def whitelistResultMatch(wl, r):
268    """Check if test case results/output match a whitelist entry
269
270    It is valid to call this function only if
271    whitelistTestCaseMatch() is True for the entry (e.g. on
272    entries returned by whitelistCandidates())
273    """
274    assert whitelistTestCaseMatch(wl, r['testcase'])
275    return ((wl.get('exitcode', 1) is None or
276             r['exitcode'] == wl.get('exitcode', 1)) and
277            ('log' not in wl or
278             re.search(wl['log'], r['log'], re.MULTILINE)))
279
280
281def checkResultWhitelist(r):
282    """Look up whitelist entry for a given test case result
283
284    Returns (i, wl) tuple, where i is the index in
285    ERROR_WHITELIST and wl is the whitelist entry itself.
286    """
287    for i, wl in whitelistCandidates(r['testcase']):
288        if whitelistResultMatch(wl, r):
289            return i, wl
290
291    raise Exception("this should never happen")
292
293
294def qemuOptsEscape(s):
295    """Escape option value QemuOpts"""
296    return s.replace(",", ",,")
297
298
299def formatTestCase(t):
300    """Format test case info as "key=value key=value" for prettier logging output"""
301    return ' '.join('%s=%s' % (k, v) for k, v in t.items())
302
303
304def qomListTypeNames(vm, **kwargs):
305    """Run qom-list-types QMP command, return type names"""
306    types = vm.command('qom-list-types', **kwargs)
307    return [t['name'] for t in types]
308
309
310def infoQDM(vm):
311    """Parse 'info qdm' output"""
312    args = {'command-line': 'info qdm'}
313    devhelp = vm.command('human-monitor-command', **args)
314    for l in devhelp.split('\n'):
315        l = l.strip()
316        if l == '' or l.endswith(':'):
317            continue
318        d = {'name': re.search(r'name "([^"]+)"', l).group(1),
319             'no-user': (re.search(', no-user', l) is not None)}
320        yield d
321
322
323class QemuBinaryInfo(object):
324    def __init__(self, binary, devtype):
325        if devtype is None:
326            devtype = 'device'
327
328        self.binary = binary
329        self._machine_info = {}
330
331        dbg("devtype: %r", devtype)
332        args = ['-S', '-machine', 'none,accel=kvm:tcg']
333        dbg("querying info for QEMU binary: %s", binary)
334        vm = QEMUMachine(binary=binary, args=args)
335        vm.launch()
336        try:
337            self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
338            # there's no way to query DeviceClass::user_creatable using QMP,
339            # so use 'info qdm':
340            self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
341            self.machines = list(m['name'] for m in vm.command('query-machines'))
342            self.user_devs = self.alldevs.difference(self.no_user_devs)
343            self.kvm_available = vm.command('query-kvm')['enabled']
344        finally:
345            vm.shutdown()
346
347    def machineInfo(self, machine):
348        """Query for information on a specific machine-type
349
350        Results are cached internally, in case the same machine-
351        type is queried multiple times.
352        """
353        if machine in self._machine_info:
354            return self._machine_info[machine]
355
356        mi = {}
357        args = ['-S', '-machine', '%s' % (machine)]
358        dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
359        vm = QEMUMachine(binary=self.binary, args=args)
360        try:
361            vm.launch()
362            mi['runnable'] = True
363        except KeyboardInterrupt:
364            raise
365        except:
366            dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
367            dbg("log: %r", vm.get_log())
368            mi['runnable'] = False
369
370        vm.shutdown()
371        self._machine_info[machine] = mi
372        return mi
373
374
375BINARY_INFO = {}
376
377
378def getBinaryInfo(args, binary):
379    if binary not in BINARY_INFO:
380        BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
381    return BINARY_INFO[binary]
382
383
384def checkOneCase(args, testcase):
385    """Check one specific case
386
387    Returns a dictionary containing failure information on error,
388    or None on success
389    """
390    binary = testcase['binary']
391    accel = testcase['accel']
392    machine = testcase['machine']
393    device = testcase['device']
394
395    dbg("will test: %r", testcase)
396
397    args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
398            '-device', qemuOptsEscape(device)]
399    cmdline = ' '.join([binary] + args)
400    dbg("will launch QEMU: %s", cmdline)
401    vm = QEMUMachine(binary=binary, args=args)
402
403    exc_traceback = None
404    try:
405        vm.launch()
406    except KeyboardInterrupt:
407        raise
408    except:
409        exc_traceback = traceback.format_exc()
410        dbg("Exception while running test case")
411    finally:
412        vm.shutdown()
413        ec = vm.exitcode()
414        log = vm.get_log()
415
416    if exc_traceback is not None or ec != 0:
417        return {'exc_traceback':exc_traceback,
418                'exitcode':ec,
419                'log':log,
420                'testcase':testcase,
421                'cmdline':cmdline}
422
423
424def binariesToTest(args, testcase):
425    if args.qemu:
426        r = args.qemu
427    else:
428        r = glob.glob('./*-softmmu/qemu-system-*')
429    return r
430
431
432def accelsToTest(args, testcase):
433    if getBinaryInfo(args, testcase['binary']).kvm_available:
434        yield 'kvm'
435    yield 'tcg'
436
437
438def machinesToTest(args, testcase):
439    return getBinaryInfo(args, testcase['binary']).machines
440
441
442def devicesToTest(args, testcase):
443    return getBinaryInfo(args, testcase['binary']).user_devs
444
445
446TESTCASE_VARIABLES = [
447    ('binary', binariesToTest),
448    ('accel', accelsToTest),
449    ('machine', machinesToTest),
450    ('device', devicesToTest),
451]
452
453
454def genCases1(args, testcases, var, fn):
455    """Generate new testcases for one variable
456
457    If an existing item already has a variable set, don't
458    generate new items and just return it directly. This
459    allows the "-t" command-line option to be used to choose
460    a specific test case.
461    """
462    for testcase in testcases:
463        if var in testcase:
464            yield testcase.copy()
465        else:
466            for i in fn(args, testcase):
467                t = testcase.copy()
468                t[var] = i
469                yield t
470
471
472def genCases(args, testcase):
473    """Generate test cases for all variables
474    """
475    cases = [testcase.copy()]
476    for var, fn in TESTCASE_VARIABLES:
477        dbg("var: %r, fn: %r", var, fn)
478        cases = genCases1(args, cases, var, fn)
479    return cases
480
481
482def casesToTest(args, testcase):
483    cases = genCases(args, testcase)
484    if args.random:
485        cases = list(cases)
486        cases = random.sample(cases, min(args.random, len(cases)))
487    if args.debug:
488        cases = list(cases)
489        dbg("%d test cases to test", len(cases))
490    if args.shuffle:
491        cases = list(cases)
492        random.shuffle(cases)
493    return cases
494
495
496def logFailure(f, level):
497    t = f['testcase']
498    logger.log(level, "failed: %s", formatTestCase(t))
499    logger.log(level, "cmdline: %s", f['cmdline'])
500    for l in f['log'].strip().split('\n'):
501        logger.log(level, "log: %s", l)
502    logger.log(level, "exit code: %r", f['exitcode'])
503    if f['exc_traceback']:
504        logger.log(level, "exception:")
505        for l in f['exc_traceback'].split('\n'):
506            logger.log(level, "  %s", l.rstrip('\n'))
507
508
509def main():
510    parser = argparse.ArgumentParser(description="QEMU -device crash test")
511    parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
512                        help="Limit test cases to KEY=VALUE",
513                        action='append', dest='testcases', default=[])
514    parser.add_argument('-d', '--debug', action='store_true',
515                        help='debug output')
516    parser.add_argument('-v', '--verbose', action='store_true', default=True,
517                        help='verbose output')
518    parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
519                        help='non-verbose output')
520    parser.add_argument('-r', '--random', type=int, metavar='COUNT',
521                        help='run a random sample of COUNT test cases',
522                        default=0)
523    parser.add_argument('--shuffle', action='store_true',
524                        help='Run test cases in random order')
525    parser.add_argument('--dry-run', action='store_true',
526                        help="Don't run any tests, just generate list")
527    parser.add_argument('-D', '--devtype', metavar='TYPE',
528                        help="Test only device types that implement TYPE")
529    parser.add_argument('-Q', '--quick', action='store_true', default=True,
530                        help="Quick mode: skip test cases that are expected to fail")
531    parser.add_argument('-F', '--full', action='store_false', dest='quick',
532                        help="Full mode: test cases that are expected to fail")
533    parser.add_argument('--strict', action='store_true', dest='strict',
534                        help="Treat all warnings as fatal")
535    parser.add_argument('qemu', nargs='*', metavar='QEMU',
536                        help='QEMU binary to run')
537    args = parser.parse_args()
538
539    if args.debug:
540        lvl = logging.DEBUG
541    elif args.verbose:
542        lvl = logging.INFO
543    else:
544        lvl = logging.WARN
545    logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
546
547    fatal_failures = []
548    wl_stats = {}
549    skipped = 0
550    total = 0
551
552    tc = {}
553    dbg("testcases: %r", args.testcases)
554    if args.testcases:
555        for t in chain(*args.testcases):
556            for kv in t.split():
557                k, v = kv.split('=', 1)
558                tc[k] = v
559
560    if len(binariesToTest(args, tc)) == 0:
561        print >>sys.stderr, "No QEMU binary found"
562        parser.print_usage(sys.stderr)
563        return 1
564
565    for t in casesToTest(args, tc):
566        logger.info("running test case: %s", formatTestCase(t))
567        total += 1
568
569        expected_match = findExpectedResult(t)
570        if (args.quick and
571                (expected_match or
572                 not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
573            dbg("skipped: %s", formatTestCase(t))
574            skipped += 1
575            continue
576
577        if args.dry_run:
578            continue
579
580        try:
581            f = checkOneCase(args, t)
582        except KeyboardInterrupt:
583            break
584
585        if f:
586            i, wl = checkResultWhitelist(f)
587            dbg("testcase: %r, whitelist match: %r", t, wl)
588            wl_stats.setdefault(i, []).append(f)
589            level = wl.get('loglevel', logging.DEBUG)
590            logFailure(f, level)
591            if wl.get('fatal') or (args.strict and level >= logging.WARN):
592                fatal_failures.append(f)
593        else:
594            dbg("success: %s", formatTestCase(t))
595            if expected_match:
596                logger.warn("Didn't fail as expected: %s", formatTestCase(t))
597
598    logger.info("Total: %d test cases", total)
599    if skipped:
600        logger.info("Skipped %d test cases", skipped)
601
602    if args.debug:
603        stats = sorted([(len(wl_stats.get(i, [])), wl) for i, wl in enumerate(ERROR_WHITELIST)])
604        for count, wl in stats:
605            dbg("whitelist entry stats: %d: %r", count, wl)
606
607    if fatal_failures:
608        for f in fatal_failures:
609            t = f['testcase']
610            logger.error("Fatal failure: %s", formatTestCase(t))
611        logger.error("Fatal failures on some machine/device combinations")
612        return 1
613
614if __name__ == '__main__':
615    sys.exit(main())
616