i386: expose "TCGTCGTCGTCG" in the 0x40000000 CPUID leaf
[qemu/ar7.git] / scripts / device-crash-test
blobe77b693eb27d8980624c593b615a619d8558043f
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     # KVM-specific devices shouldn't be tried without accel=kvm:
164     {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
165     {'accel':'(?!kvm).*', 'device':'kvm-pci-assign', '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':'allwinner-a10', 'log':"Unsupported NIC model:"},
191     {'device':'.*-spapr-cpu-core', 'log':r"CPU core type should be"},
192     {'log':r"MSI(-X)? is not supported by interrupt controller"},
193     {'log':r"pxb-pcie? devices cannot reside on a PCIe? bus"},
194     {'log':r"Ignoring smp_cpus value"},
195     {'log':r"sd_init failed: Drive 'sd0' is already in use because it has been automatically connected to another device"},
196     {'log':r"This CPU requires a smaller page size than the system is using"},
197     {'log':r"MSI-X support is mandatory in the S390 architecture"},
198     {'log':r"rom check and register reset failed"},
199     {'log':r"Unable to initialize GIC, CPUState for CPU#0 not valid"},
200     {'log':r"Multiple VT220 operator consoles are not supported"},
201     {'log':r"core 0 already populated"},
202     {'log':r"could not find stage1 bootloader"},
204     # other exitcode=1 failures not listed above will just generate INFO messages:
205     {'exitcode':1, 'loglevel':logging.INFO},
207     # KNOWN CRASHES:
208     # Known crashes will generate error messages, but won't be fatal.
209     # Those entries must be removed once we fix the crashes.
210     {'exitcode':-6, 'log':r"Device 'serial0' is in use", 'loglevel':logging.ERROR},
211     {'exitcode':-6, 'log':r"spapr_rtas_register: Assertion .*rtas_table\[token\]\.name.* failed", 'loglevel':logging.ERROR},
212     {'exitcode':-6, 'log':r"qemu_net_client_setup: Assertion `!peer->peer' failed", 'loglevel':logging.ERROR},
213     {'exitcode':-6, 'log':r'RAMBlock "[\w.-]+" already registered', 'loglevel':logging.ERROR},
214     {'exitcode':-6, 'log':r"find_ram_offset: Assertion `size != 0' failed.", 'loglevel':logging.ERROR},
215     {'exitcode':-6, 'log':r"puv3_load_kernel: Assertion `kernel_filename != NULL' failed", 'loglevel':logging.ERROR},
216     {'exitcode':-6, 'log':r"add_cpreg_to_hashtable: code should not be reached", 'loglevel':logging.ERROR},
217     {'exitcode':-6, 'log':r"qemu_alloc_display: Assertion `surface->image != NULL' failed", 'loglevel':logging.ERROR},
218     {'exitcode':-6, 'log':r"Unexpected error in error_set_from_qdev_prop_error", 'loglevel':logging.ERROR},
219     {'exitcode':-6, 'log':r"Object .* is not an instance of type spapr-machine", 'loglevel':logging.ERROR},
220     {'exitcode':-6, 'log':r"Object .* is not an instance of type generic-pc-machine", 'loglevel':logging.ERROR},
221     {'exitcode':-6, 'log':r"Object .* is not an instance of type e500-ccsr", 'loglevel':logging.ERROR},
222     {'exitcode':-6, 'log':r"vmstate_register_with_alias_id: Assertion `!se->compat \|\| se->instance_id == 0' failed", 'loglevel':logging.ERROR},
223     {'exitcode':-11, 'device':'stm32f205-soc', 'loglevel':logging.ERROR, 'expected':True},
224     {'exitcode':-11, 'device':'xlnx,zynqmp', 'loglevel':logging.ERROR, 'expected':True},
225     {'exitcode':-11, 'device':'mips-cps', 'loglevel':logging.ERROR, 'expected':True},
226     {'exitcode':-11, 'device':'gus', 'loglevel':logging.ERROR, 'expected':True},
227     {'exitcode':-11, 'device':'a9mpcore_priv', 'loglevel':logging.ERROR, 'expected':True},
228     {'exitcode':-11, 'device':'a15mpcore_priv', 'loglevel':logging.ERROR, 'expected':True},
229     {'exitcode':-11, 'device':'isa-serial', 'loglevel':logging.ERROR, 'expected':True},
230     {'exitcode':-11, 'device':'sb16', 'loglevel':logging.ERROR, 'expected':True},
231     {'exitcode':-11, 'device':'cs4231a', 'loglevel':logging.ERROR, 'expected':True},
232     {'exitcode':-11, 'device':'arm-gicv3', 'loglevel':logging.ERROR, 'expected':True},
233     {'exitcode':-11, 'machine':'isapc', 'device':'.*-iommu', 'loglevel':logging.ERROR, 'expected':True},
235     # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
236     {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
240 def whitelistTestCaseMatch(wl, t):
241     """Check if a test case specification can match a whitelist entry
243     This only checks if a whitelist entry is a candidate match
244     for a given test case, it won't check if the test case
245     results/output match the entry.  See whitelistResultMatch().
246     """
247     return (('machine' not in wl or
248              'machine' not in t or
249              re.match(wl['machine'] + '$', t['machine'])) and
250             ('accel' not in wl or
251              'accel' not in t or
252              re.match(wl['accel'] + '$', t['accel'])) and
253             ('device' not in wl or
254              'device' not in t or
255              re.match(wl['device'] + '$', t['device'])))
258 def whitelistCandidates(t):
259     """Generate the list of candidates that can match a test case"""
260     for i, wl in enumerate(ERROR_WHITELIST):
261         if whitelistTestCaseMatch(wl, t):
262             yield (i, wl)
265 def findExpectedResult(t):
266     """Check if there's an expected=True whitelist entry for a test case
268     Returns (i, wl) tuple, where i is the index in
269     ERROR_WHITELIST and wl is the whitelist entry itself.
270     """
271     for i, wl in whitelistCandidates(t):
272         if wl.get('expected'):
273             return (i, wl)
276 def whitelistResultMatch(wl, r):
277     """Check if test case results/output match a whitelist entry
279     It is valid to call this function only if
280     whitelistTestCaseMatch() is True for the entry (e.g. on
281     entries returned by whitelistCandidates())
282     """
283     assert whitelistTestCaseMatch(wl, r['testcase'])
284     return ((wl.get('exitcode', 1) is None or
285              r['exitcode'] == wl.get('exitcode', 1)) and
286             ('log' not in wl or
287              re.search(wl['log'], r['log'], re.MULTILINE)))
290 def checkResultWhitelist(r):
291     """Look up whitelist entry for a given test case result
293     Returns (i, wl) tuple, where i is the index in
294     ERROR_WHITELIST and wl is the whitelist entry itself.
295     """
296     for i, wl in whitelistCandidates(r['testcase']):
297         if whitelistResultMatch(wl, r):
298             return i, wl
300     raise Exception("this should never happen")
303 def qemuOptsEscape(s):
304     """Escape option value QemuOpts"""
305     return s.replace(",", ",,")
308 def formatTestCase(t):
309     """Format test case info as "key=value key=value" for prettier logging output"""
310     return ' '.join('%s=%s' % (k, v) for k, v in t.items())
313 def qomListTypeNames(vm, **kwargs):
314     """Run qom-list-types QMP command, return type names"""
315     types = vm.command('qom-list-types', **kwargs)
316     return [t['name'] for t in types]
319 def infoQDM(vm):
320     """Parse 'info qdm' output"""
321     args = {'command-line': 'info qdm'}
322     devhelp = vm.command('human-monitor-command', **args)
323     for l in devhelp.split('\n'):
324         l = l.strip()
325         if l == '' or l.endswith(':'):
326             continue
327         d = {'name': re.search(r'name "([^"]+)"', l).group(1),
328              'no-user': (re.search(', no-user', l) is not None)}
329         yield d
332 class QemuBinaryInfo(object):
333     def __init__(self, binary, devtype):
334         if devtype is None:
335             devtype = 'device'
337         self.binary = binary
338         self._machine_info = {}
340         dbg("devtype: %r", devtype)
341         args = ['-S', '-machine', 'none,accel=kvm:tcg']
342         dbg("querying info for QEMU binary: %s", binary)
343         vm = QEMUMachine(binary=binary, args=args)
344         vm.launch()
345         try:
346             self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
347             # there's no way to query DeviceClass::user_creatable using QMP,
348             # so use 'info qdm':
349             self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
350             self.machines = list(m['name'] for m in vm.command('query-machines'))
351             self.user_devs = self.alldevs.difference(self.no_user_devs)
352             self.kvm_available = vm.command('query-kvm')['enabled']
353         finally:
354             vm.shutdown()
356     def machineInfo(self, machine):
357         """Query for information on a specific machine-type
359         Results are cached internally, in case the same machine-
360         type is queried multiple times.
361         """
362         if machine in self._machine_info:
363             return self._machine_info[machine]
365         mi = {}
366         args = ['-S', '-machine', '%s' % (machine)]
367         dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
368         vm = QEMUMachine(binary=self.binary, args=args)
369         try:
370             vm.launch()
371             mi['runnable'] = True
372         except KeyboardInterrupt:
373             raise
374         except:
375             dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
376             dbg("log: %r", vm.get_log())
377             mi['runnable'] = False
379         vm.shutdown()
380         self._machine_info[machine] = mi
381         return mi
384 BINARY_INFO = {}
387 def getBinaryInfo(args, binary):
388     if binary not in BINARY_INFO:
389         BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
390     return BINARY_INFO[binary]
393 def checkOneCase(args, testcase):
394     """Check one specific case
396     Returns a dictionary containing failure information on error,
397     or None on success
398     """
399     binary = testcase['binary']
400     accel = testcase['accel']
401     machine = testcase['machine']
402     device = testcase['device']
404     dbg("will test: %r", testcase)
406     args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
407             '-device', qemuOptsEscape(device)]
408     cmdline = ' '.join([binary] + args)
409     dbg("will launch QEMU: %s", cmdline)
410     vm = QEMUMachine(binary=binary, args=args)
412     exc_traceback = None
413     try:
414         vm.launch()
415     except KeyboardInterrupt:
416         raise
417     except:
418         exc_traceback = traceback.format_exc()
419         dbg("Exception while running test case")
420     finally:
421         vm.shutdown()
422         ec = vm.exitcode()
423         log = vm.get_log()
425     if exc_traceback is not None or ec != 0:
426         return {'exc_traceback':exc_traceback,
427                 'exitcode':ec,
428                 'log':log,
429                 'testcase':testcase,
430                 'cmdline':cmdline}
433 def binariesToTest(args, testcase):
434     if args.qemu:
435         r = args.qemu
436     else:
437         r = glob.glob('./*-softmmu/qemu-system-*')
438     return r
441 def accelsToTest(args, testcase):
442     if getBinaryInfo(args, testcase['binary']).kvm_available:
443         yield 'kvm'
444     yield 'tcg'
447 def machinesToTest(args, testcase):
448     return getBinaryInfo(args, testcase['binary']).machines
451 def devicesToTest(args, testcase):
452     return getBinaryInfo(args, testcase['binary']).user_devs
455 TESTCASE_VARIABLES = [
456     ('binary', binariesToTest),
457     ('accel', accelsToTest),
458     ('machine', machinesToTest),
459     ('device', devicesToTest),
463 def genCases1(args, testcases, var, fn):
464     """Generate new testcases for one variable
466     If an existing item already has a variable set, don't
467     generate new items and just return it directly. This
468     allows the "-t" command-line option to be used to choose
469     a specific test case.
470     """
471     for testcase in testcases:
472         if var in testcase:
473             yield testcase.copy()
474         else:
475             for i in fn(args, testcase):
476                 t = testcase.copy()
477                 t[var] = i
478                 yield t
481 def genCases(args, testcase):
482     """Generate test cases for all variables
483     """
484     cases = [testcase.copy()]
485     for var, fn in TESTCASE_VARIABLES:
486         dbg("var: %r, fn: %r", var, fn)
487         cases = genCases1(args, cases, var, fn)
488     return cases
491 def casesToTest(args, testcase):
492     cases = genCases(args, testcase)
493     if args.random:
494         cases = list(cases)
495         cases = random.sample(cases, min(args.random, len(cases)))
496     if args.debug:
497         cases = list(cases)
498         dbg("%d test cases to test", len(cases))
499     if args.shuffle:
500         cases = list(cases)
501         random.shuffle(cases)
502     return cases
505 def logFailure(f, level):
506     t = f['testcase']
507     logger.log(level, "failed: %s", formatTestCase(t))
508     logger.log(level, "cmdline: %s", f['cmdline'])
509     for l in f['log'].strip().split('\n'):
510         logger.log(level, "log: %s", l)
511     logger.log(level, "exit code: %r", f['exitcode'])
512     if f['exc_traceback']:
513         logger.log(level, "exception:")
514         for l in f['exc_traceback'].split('\n'):
515             logger.log(level, "  %s", l.rstrip('\n'))
518 def main():
519     parser = argparse.ArgumentParser(description="QEMU -device crash test")
520     parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
521                         help="Limit test cases to KEY=VALUE",
522                         action='append', dest='testcases', default=[])
523     parser.add_argument('-d', '--debug', action='store_true',
524                         help='debug output')
525     parser.add_argument('-v', '--verbose', action='store_true', default=True,
526                         help='verbose output')
527     parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
528                         help='non-verbose output')
529     parser.add_argument('-r', '--random', type=int, metavar='COUNT',
530                         help='run a random sample of COUNT test cases',
531                         default=0)
532     parser.add_argument('--shuffle', action='store_true',
533                         help='Run test cases in random order')
534     parser.add_argument('--dry-run', action='store_true',
535                         help="Don't run any tests, just generate list")
536     parser.add_argument('-D', '--devtype', metavar='TYPE',
537                         help="Test only device types that implement TYPE")
538     parser.add_argument('-Q', '--quick', action='store_true', default=True,
539                         help="Quick mode: skip test cases that are expected to fail")
540     parser.add_argument('-F', '--full', action='store_false', dest='quick',
541                         help="Full mode: test cases that are expected to fail")
542     parser.add_argument('--strict', action='store_true', dest='strict',
543                         help="Treat all warnings as fatal")
544     parser.add_argument('qemu', nargs='*', metavar='QEMU',
545                         help='QEMU binary to run')
546     args = parser.parse_args()
548     if args.debug:
549         lvl = logging.DEBUG
550     elif args.verbose:
551         lvl = logging.INFO
552     else:
553         lvl = logging.WARN
554     logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
556     fatal_failures = []
557     wl_stats = {}
558     skipped = 0
559     total = 0
561     tc = {}
562     dbg("testcases: %r", args.testcases)
563     if args.testcases:
564         for t in chain(*args.testcases):
565             for kv in t.split():
566                 k, v = kv.split('=', 1)
567                 tc[k] = v
569     if len(binariesToTest(args, tc)) == 0:
570         print >>sys.stderr, "No QEMU binary found"
571         parser.print_usage(sys.stderr)
572         return 1
574     for t in casesToTest(args, tc):
575         logger.info("running test case: %s", formatTestCase(t))
576         total += 1
578         expected_match = findExpectedResult(t)
579         if (args.quick and
580                 (expected_match or
581                  not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
582             dbg("skipped: %s", formatTestCase(t))
583             skipped += 1
584             continue
586         if args.dry_run:
587             continue
589         try:
590             f = checkOneCase(args, t)
591         except KeyboardInterrupt:
592             break
594         if f:
595             i, wl = checkResultWhitelist(f)
596             dbg("testcase: %r, whitelist match: %r", t, wl)
597             wl_stats.setdefault(i, []).append(f)
598             level = wl.get('loglevel', logging.DEBUG)
599             logFailure(f, level)
600             if wl.get('fatal') or (args.strict and level >= logging.WARN):
601                 fatal_failures.append(f)
602         else:
603             dbg("success: %s", formatTestCase(t))
604             if expected_match:
605                 logger.warn("Didn't fail as expected: %s", formatTestCase(t))
607     logger.info("Total: %d test cases", total)
608     if skipped:
609         logger.info("Skipped %d test cases", skipped)
611     if args.debug:
612         stats = sorted([(len(wl_stats.get(i, [])), wl) for i, wl in enumerate(ERROR_WHITELIST)])
613         for count, wl in stats:
614             dbg("whitelist entry stats: %d: %r", count, wl)
616     if fatal_failures:
617         for f in fatal_failures:
618             t = f['testcase']
619             logger.error("Fatal failure: %s", formatTestCase(t))
620         logger.error("Fatal failures on some machine/device combinations")
621         return 1
623 if __name__ == '__main__':
624     sys.exit(main())