]> git.proxmox.com Git - mirror_qemu.git/blob - scripts/device-crash-test
Merge remote-tracking branch 'remotes/berrange/tags/pull-qio-2017-07-18-1' into staging
[mirror_qemu.git] / scripts / device-crash-test
1 #!/usr/bin/env python2.7
2 #
3 # Copyright (c) 2017 Red Hat Inc
4 #
5 # Author:
6 # Eduardo Habkost <ehabkost@redhat.com>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with this program; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22 """
23 Run QEMU with all combinations of -machine and -device types,
24 check for crashes and unexpected errors.
25 """
26
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
36
37 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts'))
38 from qemu import QEMUMachine
39
40 logger = logging.getLogger('device-crash-test')
41 dbg = logger.debug
42
43
44 # Purposes of the following whitelist:
45 # * Avoiding verbose log messages when we find known non-fatal
46 # (exitcode=1) errors
47 # * Avoiding fatal errors when we find known crashes
48 # * Skipping machines/devices that are known not to work out of
49 # the box, when running in --quick mode
50 #
51 # Keeping the whitelist updated is desirable, but not required,
52 # because unexpected cases where QEMU exits with exitcode=1 will
53 # just trigger a INFO message.
54
55 # Valid whitelist entry keys:
56 # * accel: regexp, full match only
57 # * machine: regexp, full match only
58 # * device: regexp, full match only
59 # * log: regexp, partial match allowed
60 # * exitcode: if not present, defaults to 1. If None, matches any exitcode
61 # * warn: if True, matching failures will be logged as warnings
62 # * expected: if True, QEMU is expected to always fail every time
63 # when testing the corresponding test case
64 # * loglevel: log level of log output when there's a match.
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)
71
72 # devices that don't work out of the box because they require extra options to "-device DEV":
73 # DEVICE | ERROR MESSAGE
74 {'device':'.*-(i386|x86_64)-cpu', 'expected':True}, # CPU socket-id is not set
75 {'device':'ARM,bitband-memory', 'expected':True}, # source-memory property not set
76 {'device':'arm.cortex-a9-global-timer', 'expected':True}, # a9_gtimer_realize: num-cpu must be between 1 and 4
77 {'device':'arm_mptimer', 'expected':True}, # num-cpu must be between 1 and 4
78 {'device':'armv7m', 'expected':True}, # memory property was not set
79 {'device':'aspeed.scu', 'expected':True}, # Unknown silicon revision: 0x0
80 {'device':'aspeed.sdmc', 'expected':True}, # Unknown silicon revision: 0x0
81 {'device':'bcm2835-dma', 'expected':True}, # bcm2835_dma_realize: required dma-mr link not found: Property '.dma-mr' not found
82 {'device':'bcm2835-fb', 'expected':True}, # bcm2835_fb_realize: required vcram-base property not set
83 {'device':'bcm2835-mbox', 'expected':True}, # bcm2835_mbox_realize: required mbox-mr link not found: Property '.mbox-mr' not found
84 {'device':'bcm2835-peripherals', 'expected':True}, # bcm2835_peripherals_realize: required ram link not found: Property '.ram' not found
85 {'device':'bcm2835-property', 'expected':True}, # bcm2835_property_realize: required fb link not found: Property '.fb' not found
86 {'device':'bcm2835_gpio', 'expected':True}, # bcm2835_gpio_realize: required sdhci link not found: Property '.sdbus-sdhci' not found
87 {'device':'bcm2836', 'expected':True}, # bcm2836_realize: required ram link not found: Property '.ram' not found
88 {'device':'cfi.pflash01', 'expected':True}, # attribute "sector-length" not specified or zero.
89 {'device':'cfi.pflash02', 'expected':True}, # attribute "sector-length" not specified or zero.
90 {'device':'icp', 'expected':True}, # icp_realize: required link 'xics' not found: Property '.xics' not found
91 {'device':'ics', 'expected':True}, # ics_base_realize: required link 'xics' not found: Property '.xics' not found
92 # "-device ide-cd" does work on more recent QEMU versions, so it doesn't have expected=True
93 {'device':'ide-cd'}, # No drive specified
94 {'device':'ide-drive', 'expected':True}, # No drive specified
95 {'device':'ide-hd', 'expected':True}, # No drive specified
96 {'device':'ipmi-bmc-extern', 'expected':True}, # IPMI external bmc requires chardev attribute
97 {'device':'isa-debugcon', 'expected':True}, # Can't create serial device, empty char device
98 {'device':'isa-ipmi-bt', 'expected':True}, # IPMI device requires a bmc attribute to be set
99 {'device':'isa-ipmi-kcs', 'expected':True}, # IPMI device requires a bmc attribute to be set
100 {'device':'isa-parallel', 'expected':True}, # Can't create serial device, empty char device
101 {'device':'isa-serial', 'expected':True}, # Can't create serial device, empty char device
102 {'device':'ivshmem', 'expected':True}, # You must specify either 'shm' or 'chardev'
103 {'device':'ivshmem-doorbell', 'expected':True}, # You must specify a 'chardev'
104 {'device':'ivshmem-plain', 'expected':True}, # You must specify a 'memdev'
105 {'device':'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
158
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
162
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},
166
167 # xen-specific machines and devices:
168 {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
169 {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
170
171 # this fails on some machine-types, but not all, so they don't have expected=True:
172 {'device':'vmgenid'}, # vmgenid requires DMA write support in fw_cfg, which this machine type does not provide
173
174 # Silence INFO messages for errors that are common on multiple
175 # devices/machines:
176 {'log':r"No '[\w-]+' bus found for device '[\w-]+'"},
177 {'log':r"images* must be given with the 'pflash' parameter"},
178 {'log':r"(Guest|ROM|Flash|Kernel) image must be specified"},
179 {'log':r"[cC]ould not load [\w ]+ (BIOS|bios) '[\w-]+\.bin'"},
180 {'log':r"Couldn't find rom image '[\w-]+\.bin'"},
181 {'log':r"speed mismatch trying to attach usb device"},
182 {'log':r"Can't create a second ISA bus"},
183 {'log':r"duplicate fw_cfg file name"},
184 # sysbus-related error messages: most machines reject most dynamic sysbus devices:
185 {'log':r"Option '-device [\w.,-]+' cannot be handled by this machine"},
186 {'log':r"Device [\w.,-]+ is not supported by this machine yet"},
187 {'log':r"Device [\w.,-]+ can not be dynamically instantiated"},
188 {'log':r"Platform Bus: Can not fit MMIO region of size "},
189 # other more specific errors we will ignore:
190 {'device':'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"},
203
204 # other exitcode=1 failures not listed above will just generate INFO messages:
205 {'exitcode':1, 'loglevel':logging.INFO},
206
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},
234
235 # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
236 {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
237 ]
238
239
240 def whitelistTestCaseMatch(wl, t):
241 """Check if a test case specification can match a whitelist entry
242
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'])))
256
257
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)
263
264
265 def findExpectedResult(t):
266 """Check if there's an expected=True whitelist entry for a test case
267
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)
274
275
276 def whitelistResultMatch(wl, r):
277 """Check if test case results/output match a whitelist entry
278
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)))
288
289
290 def checkResultWhitelist(r):
291 """Look up whitelist entry for a given test case result
292
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
299
300 raise Exception("this should never happen")
301
302
303 def qemuOptsEscape(s):
304 """Escape option value QemuOpts"""
305 return s.replace(",", ",,")
306
307
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())
311
312
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]
317
318
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
330
331
332 class QemuBinaryInfo(object):
333 def __init__(self, binary, devtype):
334 if devtype is None:
335 devtype = 'device'
336
337 self.binary = binary
338 self._machine_info = {}
339
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()
355
356 def machineInfo(self, machine):
357 """Query for information on a specific machine-type
358
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]
364
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
378
379 vm.shutdown()
380 self._machine_info[machine] = mi
381 return mi
382
383
384 BINARY_INFO = {}
385
386
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]
391
392
393 def checkOneCase(args, testcase):
394 """Check one specific case
395
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']
403
404 dbg("will test: %r", testcase)
405
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)
411
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()
424
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}
431
432
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
439
440
441 def accelsToTest(args, testcase):
442 if getBinaryInfo(args, testcase['binary']).kvm_available:
443 yield 'kvm'
444 yield 'tcg'
445
446
447 def machinesToTest(args, testcase):
448 return getBinaryInfo(args, testcase['binary']).machines
449
450
451 def devicesToTest(args, testcase):
452 return getBinaryInfo(args, testcase['binary']).user_devs
453
454
455 TESTCASE_VARIABLES = [
456 ('binary', binariesToTest),
457 ('accel', accelsToTest),
458 ('machine', machinesToTest),
459 ('device', devicesToTest),
460 ]
461
462
463 def genCases1(args, testcases, var, fn):
464 """Generate new testcases for one variable
465
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
479
480
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
489
490
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
503
504
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'))
516
517
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()
547
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')
555
556 fatal_failures = []
557 wl_stats = {}
558 skipped = 0
559 total = 0
560
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
568
569 if len(binariesToTest(args, tc)) == 0:
570 print >>sys.stderr, "No QEMU binary found"
571 parser.print_usage(sys.stderr)
572 return 1
573
574 for t in casesToTest(args, tc):
575 logger.info("running test case: %s", formatTestCase(t))
576 total += 1
577
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
585
586 if args.dry_run:
587 continue
588
589 try:
590 f = checkOneCase(args, t)
591 except KeyboardInterrupt:
592 break
593
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))
606
607 logger.info("Total: %d test cases", total)
608 if skipped:
609 logger.info("Skipped %d test cases", skipped)
610
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)
615
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
622
623 if __name__ == '__main__':
624 sys.exit(main())