arm: drop intermediate cpu_model -> cpu type parsing and use cpu type directly
[qemu/kevin.git] / scripts / device-crash-test
blob043b24a4aa8c6b8391d8b4b2706d6c5aea1ee5d1
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':'kvm-pci-assign', 'expected':True},          # no host device specified
106     {'device':'loader', 'expected':True},                  # please include valid arguments
107     {'device':'nand', 'expected':True},                    # Unsupported NAND block size 0x1
108     {'device':'nvdimm', 'expected':True},                  # 'memdev' property is not set
109     {'device':'nvme', 'expected':True},                    # Device initialization failed
110     {'device':'pc-dimm', 'expected':True},                 # 'memdev' property is not set
111     {'device':'pci-bridge', 'expected':True},              # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
112     {'device':'pci-bridge-seat', 'expected':True},         # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
113     {'device':'pci-serial', 'expected':True},              # Can't create serial device, empty char device
114     {'device':'pci-serial-2x', 'expected':True},           # Can't create serial device, empty char device
115     {'device':'pci-serial-4x', 'expected':True},           # Can't create serial device, empty char device
116     {'device':'pxa2xx-dma', 'expected':True},              # channels value invalid
117     {'device':'pxb', 'expected':True},                     # Bridge chassis not specified. Each bridge is required to be assigned a unique chassis id > 0.
118     {'device':'scsi-block', 'expected':True},              # drive property not set
119     {'device':'scsi-disk', 'expected':True},               # drive property not set
120     {'device':'scsi-generic', 'expected':True},            # drive property not set
121     {'device':'scsi-hd', 'expected':True},                 # drive property not set
122     {'device':'spapr-pci-host-bridge', 'expected':True},   # BUID not specified for PHB
123     {'device':'spapr-pci-vfio-host-bridge', 'expected':True}, # BUID not specified for PHB
124     {'device':'spapr-rng', 'expected':True},               # spapr-rng needs an RNG backend!
125     {'device':'spapr-vty', 'expected':True},               # chardev property not set
126     {'device':'tpm-tis', 'expected':True},                 # tpm_tis: backend driver with id (null) could not be found
127     {'device':'unimplemented-device', 'expected':True},    # property 'size' not specified or zero
128     {'device':'usb-braille', 'expected':True},             # Property chardev is required
129     {'device':'usb-mtp', 'expected':True},                 # x-root property must be configured
130     {'device':'usb-redir', 'expected':True},               # Parameter 'chardev' is missing
131     {'device':'usb-serial', 'expected':True},              # Property chardev is required
132     {'device':'usb-storage', 'expected':True},             # drive property not set
133     {'device':'vfio-amd-xgbe', 'expected':True},           # -device vfio-amd-xgbe: vfio error: wrong host device name
134     {'device':'vfio-calxeda-xgmac', 'expected':True},      # -device vfio-calxeda-xgmac: vfio error: wrong host device name
135     {'device':'vfio-pci', 'expected':True},                # No provided host device
136     {'device':'vfio-pci-igd-lpc-bridge', 'expected':True}, # VFIO dummy ISA/LPC bridge must have address 1f.0
137     {'device':'vhost-scsi.*', 'expected':True},            # vhost-scsi: missing wwpn
138     {'device':'vhost-vsock-device', 'expected':True},      # guest-cid property must be greater than 2
139     {'device':'vhost-vsock-pci', 'expected':True},         # guest-cid property must be greater than 2
140     {'device':'virtio-9p-ccw', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
141     {'device':'virtio-9p-device', 'expected':True},        # 9pfs device couldn't find fsdev with the id = NULL
142     {'device':'virtio-9p-pci', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
143     {'device':'virtio-blk-ccw', 'expected':True},          # drive property not set
144     {'device':'virtio-blk-device', 'expected':True},       # drive property not set
145     {'device':'virtio-blk-device', 'expected':True},       # drive property not set
146     {'device':'virtio-blk-pci', 'expected':True},          # drive property not set
147     {'device':'virtio-crypto-ccw', 'expected':True},       # 'cryptodev' parameter expects a valid object
148     {'device':'virtio-crypto-device', 'expected':True},    # 'cryptodev' parameter expects a valid object
149     {'device':'virtio-crypto-pci', 'expected':True},       # 'cryptodev' parameter expects a valid object
150     {'device':'virtio-input-host-device', 'expected':True}, # evdev property is required
151     {'device':'virtio-input-host-pci', 'expected':True},   # evdev property is required
152     {'device':'xen-pvdevice', 'expected':True},            # Device ID invalid, it must always be supplied
153     {'device':'vhost-vsock-ccw', 'expected':True},         # guest-cid property must be greater than 2
154     {'device':'ALTR.timer', 'expected':True},              # "clock-frequency" property must be provided
155     {'device':'zpci', 'expected':True},                    # target must be defined
156     {'device':'pnv-(occ|icp|lpc)', 'expected':True},       # required link 'xics' not found: Property '.xics' not found
157     {'device':'powernv-cpu-.*', 'expected':True},          # pnv_core_realize: required link 'xics' not found: Property '.xics' not found
159     # ioapic devices are already created by pc and will fail:
160     {'machine':'q35|pc.*', 'device':'kvm-ioapic', 'expected':True}, # Only 1 ioapics allowed
161     {'machine':'q35|pc.*', 'device':'ioapic', 'expected':True},     # Only 1 ioapics allowed
163     # "spapr-cpu-core needs a pseries machine"
164     {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True},
166     # KVM-specific devices shouldn't be tried without accel=kvm:
167     {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
168     {'accel':'(?!kvm).*', 'device':'kvm-pci-assign', 'expected':True},
170     # xen-specific machines and devices:
171     {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
172     {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
174     # this fails on some machine-types, but not all, so they don't have expected=True:
175     {'device':'vmgenid'}, # vmgenid requires DMA write support in fw_cfg, which this machine type does not provide
177     # Silence INFO messages for errors that are common on multiple
178     # devices/machines:
179     {'log':r"No '[\w-]+' bus found for device '[\w-]+'"},
180     {'log':r"images* must be given with the 'pflash' parameter"},
181     {'log':r"(Guest|ROM|Flash|Kernel) image must be specified"},
182     {'log':r"[cC]ould not load [\w ]+ (BIOS|bios) '[\w-]+\.bin'"},
183     {'log':r"Couldn't find rom image '[\w-]+\.bin'"},
184     {'log':r"speed mismatch trying to attach usb device"},
185     {'log':r"Can't create a second ISA bus"},
186     {'log':r"duplicate fw_cfg file name"},
187     # sysbus-related error messages: most machines reject most dynamic sysbus devices:
188     {'log':r"Option '-device [\w.,-]+' cannot be handled by this machine"},
189     {'log':r"Device [\w.,-]+ is not supported by this machine yet"},
190     {'log':r"Device [\w.,-]+ can not be dynamically instantiated"},
191     {'log':r"Platform Bus: Can not fit MMIO region of size "},
192     # other more specific errors we will ignore:
193     {'device':'.*-spapr-cpu-core', 'log':r"CPU core type should be"},
194     {'log':r"MSI(-X)? is not supported by interrupt controller"},
195     {'log':r"pxb-pcie? devices cannot reside on a PCIe? bus"},
196     {'log':r"Ignoring smp_cpus value"},
197     {'log':r"sd_init failed: Drive 'sd0' is already in use because it has been automatically connected to another device"},
198     {'log':r"This CPU requires a smaller page size than the system is using"},
199     {'log':r"MSI-X support is mandatory in the S390 architecture"},
200     {'log':r"rom check and register reset failed"},
201     {'log':r"Unable to initialize GIC, CPUState for CPU#0 not valid"},
202     {'log':r"Multiple VT220 operator consoles are not supported"},
203     {'log':r"core 0 already populated"},
204     {'log':r"could not find stage1 bootloader"},
206     # other exitcode=1 failures not listed above will just generate INFO messages:
207     {'exitcode':1, 'loglevel':logging.INFO},
209     # KNOWN CRASHES:
210     # Known crashes will generate error messages, but won't be fatal.
211     # Those entries must be removed once we fix the crashes.
212     {'exitcode':-6, 'log':r"Device 'serial0' is in use", 'loglevel':logging.ERROR},
213     {'exitcode':-6, 'log':r"spapr_rtas_register: Assertion .*rtas_table\[token\]\.name.* failed", 'loglevel':logging.ERROR},
214     {'exitcode':-6, 'log':r"qemu_net_client_setup: Assertion `!peer->peer' failed", 'loglevel':logging.ERROR},
215     {'exitcode':-6, 'log':r'RAMBlock "[\w.-]+" already registered', 'loglevel':logging.ERROR},
216     {'exitcode':-6, 'log':r"find_ram_offset: Assertion `size != 0' failed.", 'loglevel':logging.ERROR},
217     {'exitcode':-6, 'log':r"puv3_load_kernel: Assertion `kernel_filename != NULL' failed", 'loglevel':logging.ERROR},
218     {'exitcode':-6, 'log':r"add_cpreg_to_hashtable: code should not be reached", 'loglevel':logging.ERROR},
219     {'exitcode':-6, 'log':r"qemu_alloc_display: Assertion `surface->image != NULL' failed", 'loglevel':logging.ERROR},
220     {'exitcode':-6, 'log':r"Unexpected error in error_set_from_qdev_prop_error", 'loglevel':logging.ERROR},
221     {'exitcode':-6, 'log':r"Object .* is not an instance of type spapr-machine", 'loglevel':logging.ERROR},
222     {'exitcode':-6, 'log':r"Object .* is not an instance of type generic-pc-machine", 'loglevel':logging.ERROR},
223     {'exitcode':-6, 'log':r"Object .* is not an instance of type e500-ccsr", 'loglevel':logging.ERROR},
224     {'exitcode':-6, 'log':r"vmstate_register_with_alias_id: Assertion `!se->compat \|\| se->instance_id == 0' failed", 'loglevel':logging.ERROR},
225     {'exitcode':-11, 'device':'stm32f205-soc', 'loglevel':logging.ERROR, 'expected':True},
226     {'exitcode':-11, 'device':'xlnx,zynqmp', 'loglevel':logging.ERROR, 'expected':True},
227     {'exitcode':-11, 'device':'mips-cps', 'loglevel':logging.ERROR, 'expected':True},
228     {'exitcode':-11, 'device':'gus', 'loglevel':logging.ERROR, 'expected':True},
229     {'exitcode':-11, 'device':'a9mpcore_priv', 'loglevel':logging.ERROR, 'expected':True},
230     {'exitcode':-11, 'device':'a15mpcore_priv', 'loglevel':logging.ERROR, 'expected':True},
231     {'exitcode':-11, 'device':'isa-serial', 'loglevel':logging.ERROR, 'expected':True},
232     {'exitcode':-11, 'device':'sb16', 'loglevel':logging.ERROR, 'expected':True},
233     {'exitcode':-11, 'device':'cs4231a', 'loglevel':logging.ERROR, 'expected':True},
234     {'exitcode':-11, 'device':'arm-gicv3', 'loglevel':logging.ERROR, 'expected':True},
235     {'exitcode':-11, 'machine':'isapc', 'device':'.*-iommu', 'loglevel':logging.ERROR, 'expected':True},
237     # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
238     {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
242 def whitelistTestCaseMatch(wl, t):
243     """Check if a test case specification can match a whitelist entry
245     This only checks if a whitelist entry is a candidate match
246     for a given test case, it won't check if the test case
247     results/output match the entry.  See whitelistResultMatch().
248     """
249     return (('machine' not in wl or
250              'machine' not in t or
251              re.match(wl['machine'] + '$', t['machine'])) and
252             ('accel' not in wl or
253              'accel' not in t or
254              re.match(wl['accel'] + '$', t['accel'])) and
255             ('device' not in wl or
256              'device' not in t or
257              re.match(wl['device'] + '$', t['device'])))
260 def whitelistCandidates(t):
261     """Generate the list of candidates that can match a test case"""
262     for i, wl in enumerate(ERROR_WHITELIST):
263         if whitelistTestCaseMatch(wl, t):
264             yield (i, wl)
267 def findExpectedResult(t):
268     """Check if there's an expected=True whitelist entry for a test case
270     Returns (i, wl) tuple, where i is the index in
271     ERROR_WHITELIST and wl is the whitelist entry itself.
272     """
273     for i, wl in whitelistCandidates(t):
274         if wl.get('expected'):
275             return (i, wl)
278 def whitelistResultMatch(wl, r):
279     """Check if test case results/output match a whitelist entry
281     It is valid to call this function only if
282     whitelistTestCaseMatch() is True for the entry (e.g. on
283     entries returned by whitelistCandidates())
284     """
285     assert whitelistTestCaseMatch(wl, r['testcase'])
286     return ((wl.get('exitcode', 1) is None or
287              r['exitcode'] == wl.get('exitcode', 1)) and
288             ('log' not in wl or
289              re.search(wl['log'], r['log'], re.MULTILINE)))
292 def checkResultWhitelist(r):
293     """Look up whitelist entry for a given test case result
295     Returns (i, wl) tuple, where i is the index in
296     ERROR_WHITELIST and wl is the whitelist entry itself.
297     """
298     for i, wl in whitelistCandidates(r['testcase']):
299         if whitelistResultMatch(wl, r):
300             return i, wl
302     raise Exception("this should never happen")
305 def qemuOptsEscape(s):
306     """Escape option value QemuOpts"""
307     return s.replace(",", ",,")
310 def formatTestCase(t):
311     """Format test case info as "key=value key=value" for prettier logging output"""
312     return ' '.join('%s=%s' % (k, v) for k, v in t.items())
315 def qomListTypeNames(vm, **kwargs):
316     """Run qom-list-types QMP command, return type names"""
317     types = vm.command('qom-list-types', **kwargs)
318     return [t['name'] for t in types]
321 def infoQDM(vm):
322     """Parse 'info qdm' output"""
323     args = {'command-line': 'info qdm'}
324     devhelp = vm.command('human-monitor-command', **args)
325     for l in devhelp.split('\n'):
326         l = l.strip()
327         if l == '' or l.endswith(':'):
328             continue
329         d = {'name': re.search(r'name "([^"]+)"', l).group(1),
330              'no-user': (re.search(', no-user', l) is not None)}
331         yield d
334 class QemuBinaryInfo(object):
335     def __init__(self, binary, devtype):
336         if devtype is None:
337             devtype = 'device'
339         self.binary = binary
340         self._machine_info = {}
342         dbg("devtype: %r", devtype)
343         args = ['-S', '-machine', 'none,accel=kvm:tcg']
344         dbg("querying info for QEMU binary: %s", binary)
345         vm = QEMUMachine(binary=binary, args=args)
346         vm.launch()
347         try:
348             self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
349             # there's no way to query DeviceClass::user_creatable using QMP,
350             # so use 'info qdm':
351             self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
352             self.machines = list(m['name'] for m in vm.command('query-machines'))
353             self.user_devs = self.alldevs.difference(self.no_user_devs)
354             self.kvm_available = vm.command('query-kvm')['enabled']
355         finally:
356             vm.shutdown()
358     def machineInfo(self, machine):
359         """Query for information on a specific machine-type
361         Results are cached internally, in case the same machine-
362         type is queried multiple times.
363         """
364         if machine in self._machine_info:
365             return self._machine_info[machine]
367         mi = {}
368         args = ['-S', '-machine', '%s' % (machine)]
369         dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
370         vm = QEMUMachine(binary=self.binary, args=args)
371         try:
372             vm.launch()
373             mi['runnable'] = True
374         except KeyboardInterrupt:
375             raise
376         except:
377             dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
378             dbg("log: %r", vm.get_log())
379             mi['runnable'] = False
381         vm.shutdown()
382         self._machine_info[machine] = mi
383         return mi
386 BINARY_INFO = {}
389 def getBinaryInfo(args, binary):
390     if binary not in BINARY_INFO:
391         BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
392     return BINARY_INFO[binary]
395 def checkOneCase(args, testcase):
396     """Check one specific case
398     Returns a dictionary containing failure information on error,
399     or None on success
400     """
401     binary = testcase['binary']
402     accel = testcase['accel']
403     machine = testcase['machine']
404     device = testcase['device']
406     dbg("will test: %r", testcase)
408     args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
409             '-device', qemuOptsEscape(device)]
410     cmdline = ' '.join([binary] + args)
411     dbg("will launch QEMU: %s", cmdline)
412     vm = QEMUMachine(binary=binary, args=args)
414     exc_traceback = None
415     try:
416         vm.launch()
417     except KeyboardInterrupt:
418         raise
419     except:
420         exc_traceback = traceback.format_exc()
421         dbg("Exception while running test case")
422     finally:
423         vm.shutdown()
424         ec = vm.exitcode()
425         log = vm.get_log()
427     if exc_traceback is not None or ec != 0:
428         return {'exc_traceback':exc_traceback,
429                 'exitcode':ec,
430                 'log':log,
431                 'testcase':testcase,
432                 'cmdline':cmdline}
435 def binariesToTest(args, testcase):
436     if args.qemu:
437         r = args.qemu
438     else:
439         r = glob.glob('./*-softmmu/qemu-system-*')
440     return r
443 def accelsToTest(args, testcase):
444     if getBinaryInfo(args, testcase['binary']).kvm_available:
445         yield 'kvm'
446     yield 'tcg'
449 def machinesToTest(args, testcase):
450     return getBinaryInfo(args, testcase['binary']).machines
453 def devicesToTest(args, testcase):
454     return getBinaryInfo(args, testcase['binary']).user_devs
457 TESTCASE_VARIABLES = [
458     ('binary', binariesToTest),
459     ('accel', accelsToTest),
460     ('machine', machinesToTest),
461     ('device', devicesToTest),
465 def genCases1(args, testcases, var, fn):
466     """Generate new testcases for one variable
468     If an existing item already has a variable set, don't
469     generate new items and just return it directly. This
470     allows the "-t" command-line option to be used to choose
471     a specific test case.
472     """
473     for testcase in testcases:
474         if var in testcase:
475             yield testcase.copy()
476         else:
477             for i in fn(args, testcase):
478                 t = testcase.copy()
479                 t[var] = i
480                 yield t
483 def genCases(args, testcase):
484     """Generate test cases for all variables
485     """
486     cases = [testcase.copy()]
487     for var, fn in TESTCASE_VARIABLES:
488         dbg("var: %r, fn: %r", var, fn)
489         cases = genCases1(args, cases, var, fn)
490     return cases
493 def casesToTest(args, testcase):
494     cases = genCases(args, testcase)
495     if args.random:
496         cases = list(cases)
497         cases = random.sample(cases, min(args.random, len(cases)))
498     if args.debug:
499         cases = list(cases)
500         dbg("%d test cases to test", len(cases))
501     if args.shuffle:
502         cases = list(cases)
503         random.shuffle(cases)
504     return cases
507 def logFailure(f, level):
508     t = f['testcase']
509     logger.log(level, "failed: %s", formatTestCase(t))
510     logger.log(level, "cmdline: %s", f['cmdline'])
511     for l in f['log'].strip().split('\n'):
512         logger.log(level, "log: %s", l)
513     logger.log(level, "exit code: %r", f['exitcode'])
514     if f['exc_traceback']:
515         logger.log(level, "exception:")
516         for l in f['exc_traceback'].split('\n'):
517             logger.log(level, "  %s", l.rstrip('\n'))
520 def main():
521     parser = argparse.ArgumentParser(description="QEMU -device crash test")
522     parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
523                         help="Limit test cases to KEY=VALUE",
524                         action='append', dest='testcases', default=[])
525     parser.add_argument('-d', '--debug', action='store_true',
526                         help='debug output')
527     parser.add_argument('-v', '--verbose', action='store_true', default=True,
528                         help='verbose output')
529     parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
530                         help='non-verbose output')
531     parser.add_argument('-r', '--random', type=int, metavar='COUNT',
532                         help='run a random sample of COUNT test cases',
533                         default=0)
534     parser.add_argument('--shuffle', action='store_true',
535                         help='Run test cases in random order')
536     parser.add_argument('--dry-run', action='store_true',
537                         help="Don't run any tests, just generate list")
538     parser.add_argument('-D', '--devtype', metavar='TYPE',
539                         help="Test only device types that implement TYPE")
540     parser.add_argument('-Q', '--quick', action='store_true', default=True,
541                         help="Quick mode: skip test cases that are expected to fail")
542     parser.add_argument('-F', '--full', action='store_false', dest='quick',
543                         help="Full mode: test cases that are expected to fail")
544     parser.add_argument('--strict', action='store_true', dest='strict',
545                         help="Treat all warnings as fatal")
546     parser.add_argument('qemu', nargs='*', metavar='QEMU',
547                         help='QEMU binary to run')
548     args = parser.parse_args()
550     if args.debug:
551         lvl = logging.DEBUG
552     elif args.verbose:
553         lvl = logging.INFO
554     else:
555         lvl = logging.WARN
556     logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
558     fatal_failures = []
559     wl_stats = {}
560     skipped = 0
561     total = 0
563     tc = {}
564     dbg("testcases: %r", args.testcases)
565     if args.testcases:
566         for t in chain(*args.testcases):
567             for kv in t.split():
568                 k, v = kv.split('=', 1)
569                 tc[k] = v
571     if len(binariesToTest(args, tc)) == 0:
572         print >>sys.stderr, "No QEMU binary found"
573         parser.print_usage(sys.stderr)
574         return 1
576     for t in casesToTest(args, tc):
577         logger.info("running test case: %s", formatTestCase(t))
578         total += 1
580         expected_match = findExpectedResult(t)
581         if (args.quick and
582                 (expected_match or
583                  not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
584             dbg("skipped: %s", formatTestCase(t))
585             skipped += 1
586             continue
588         if args.dry_run:
589             continue
591         try:
592             f = checkOneCase(args, t)
593         except KeyboardInterrupt:
594             break
596         if f:
597             i, wl = checkResultWhitelist(f)
598             dbg("testcase: %r, whitelist match: %r", t, wl)
599             wl_stats.setdefault(i, []).append(f)
600             level = wl.get('loglevel', logging.DEBUG)
601             logFailure(f, level)
602             if wl.get('fatal') or (args.strict and level >= logging.WARN):
603                 fatal_failures.append(f)
604         else:
605             dbg("success: %s", formatTestCase(t))
606             if expected_match:
607                 logger.warn("Didn't fail as expected: %s", formatTestCase(t))
609     logger.info("Total: %d test cases", total)
610     if skipped:
611         logger.info("Skipped %d test cases", skipped)
613     if args.debug:
614         stats = sorted([(len(wl_stats.get(i, [])), wl) for i, wl in enumerate(ERROR_WHITELIST)])
615         for count, wl in stats:
616             dbg("whitelist entry stats: %d: %r", count, wl)
618     if fatal_failures:
619         for f in fatal_failures:
620             t = f['testcase']
621             logger.error("Fatal failure: %s", formatTestCase(t))
622         logger.error("Fatal failures on some machine/device combinations")
623         return 1
625 if __name__ == '__main__':
626     sys.exit(main())