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