maintainers: Add myself as a OpenBSD maintainer
[qemu.git] / scripts / device-crash-test
blob7417177ebb448f96620827e0d385f2d573a5136d
1 #!/usr/bin/env python2.7
3 #  Copyright (c) 2017 Red Hat Inc
5 # Author:
6 #  Eduardo Habkost <ehabkost@redhat.com>
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.
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.
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.
22 """
23 Run QEMU with all combinations of -machine and -device types,
24 check for crashes and unexpected errors.
25 """
27 import sys
28 import os
29 import glob
30 import logging
31 import traceback
32 import re
33 import random
34 import argparse
35 from itertools import chain
37 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts'))
38 from qemu import QEMUMachine
40 logger = logging.getLogger('device-crash-test')
41 dbg = logger.debug
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
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.
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.
65 ERROR_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)
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
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
161     # "spapr-cpu-core needs a pseries machine"
162     {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True},
164     # KVM-specific devices shouldn't be tried without accel=kvm:
165     {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
167     # xen-specific machines and devices:
168     {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
169     {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
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
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"},
203     # other exitcode=1 failures not listed above will just generate INFO messages:
204     {'exitcode':1, 'loglevel':logging.INFO},
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},
226     # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
227     {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
231 def whitelistTestCaseMatch(wl, t):
232     """Check if a test case specification can match a whitelist entry
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'])))
249 def 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)
256 def findExpectedResult(t):
257     """Check if there's an expected=True whitelist entry for a test case
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)
267 def whitelistResultMatch(wl, r):
268     """Check if test case results/output match a whitelist entry
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)))
281 def checkResultWhitelist(r):
282     """Look up whitelist entry for a given test case result
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
291     raise Exception("this should never happen")
294 def qemuOptsEscape(s):
295     """Escape option value QemuOpts"""
296     return s.replace(",", ",,")
299 def 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())
304 def 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]
310 def 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
323 class QemuBinaryInfo(object):
324     def __init__(self, binary, devtype):
325         if devtype is None:
326             devtype = 'device'
328         self.binary = binary
329         self._machine_info = {}
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()
347     def machineInfo(self, machine):
348         """Query for information on a specific machine-type
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]
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
370         vm.shutdown()
371         self._machine_info[machine] = mi
372         return mi
375 BINARY_INFO = {}
378 def getBinaryInfo(args, binary):
379     if binary not in BINARY_INFO:
380         BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
381     return BINARY_INFO[binary]
384 def checkOneCase(args, testcase):
385     """Check one specific case
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']
395     dbg("will test: %r", testcase)
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)
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()
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}
424 def binariesToTest(args, testcase):
425     if args.qemu:
426         r = args.qemu
427     else:
428         r = glob.glob('./*-softmmu/qemu-system-*')
429     return r
432 def accelsToTest(args, testcase):
433     if getBinaryInfo(args, testcase['binary']).kvm_available:
434         yield 'kvm'
435     yield 'tcg'
438 def machinesToTest(args, testcase):
439     return getBinaryInfo(args, testcase['binary']).machines
442 def devicesToTest(args, testcase):
443     return getBinaryInfo(args, testcase['binary']).user_devs
446 TESTCASE_VARIABLES = [
447     ('binary', binariesToTest),
448     ('accel', accelsToTest),
449     ('machine', machinesToTest),
450     ('device', devicesToTest),
454 def genCases1(args, testcases, var, fn):
455     """Generate new testcases for one variable
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
472 def 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
482 def 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
496 def 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'))
509 def 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()
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')
547     fatal_failures = []
548     wl_stats = {}
549     skipped = 0
550     total = 0
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
560     if len(binariesToTest(args, tc)) == 0:
561         print >>sys.stderr, "No QEMU binary found"
562         parser.print_usage(sys.stderr)
563         return 1
565     for t in casesToTest(args, tc):
566         logger.info("running test case: %s", formatTestCase(t))
567         total += 1
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
577         if args.dry_run:
578             continue
580         try:
581             f = checkOneCase(args, t)
582         except KeyboardInterrupt:
583             break
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))
598     logger.info("Total: %d test cases", total)
599     if skipped:
600         logger.info("Skipped %d test cases", skipped)
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)
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
614 if __name__ == '__main__':
615     sys.exit(main())