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""" 23Run QEMU with all combinations of -machine and -device types, 24check for crashes and unexpected errors. 25""" 26 27import sys 28import os 29import glob 30import logging 31import traceback 32import re 33import random 34import argparse 35from itertools import chain 36 37sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts')) 38from qemu import QEMUMachine 39 40logger = logging.getLogger('device-crash-test') 41dbg = logger.debug 42 43 44# Purposes of the following whitelist: 45# * Avoiding verbose log messages when we find known non-fatal 46# (exitcode=1) errors 47# * Avoiding fatal errors when we find known crashes 48# * Skipping machines/devices that are known not to work out of 49# the box, when running in --quick mode 50# 51# Keeping the whitelist updated is desirable, but not required, 52# because unexpected cases where QEMU exits with exitcode=1 will 53# just trigger a INFO message. 54 55# Valid whitelist entry keys: 56# * accel: regexp, full match only 57# * machine: regexp, full match only 58# * device: regexp, full match only 59# * log: regexp, partial match allowed 60# * exitcode: if not present, defaults to 1. If None, matches any exitcode 61# * warn: if True, matching failures will be logged as warnings 62# * expected: if True, QEMU is expected to always fail every time 63# when testing the corresponding test case 64# * loglevel: log level of log output when there's a match. 65ERROR_WHITELIST = [ 66 # Machines that won't work out of the box: 67 # MACHINE | ERROR MESSAGE 68 {'machine':'niagara', 'expected':True}, # Unable to load a firmware for -M niagara 69 {'machine':'boston', 'expected':True}, # Please provide either a -kernel or -bios argument 70 {'machine':'leon3_generic', 'expected':True}, # Can't read bios image (null) 71 72 # devices that don't work out of the box because they require extra options to "-device DEV": 73 # DEVICE | ERROR MESSAGE 74 {'device':'.*-(i386|x86_64)-cpu', 'expected':True}, # CPU socket-id is not set 75 {'device':'ARM,bitband-memory', 'expected':True}, # source-memory property not set 76 {'device':'arm.cortex-a9-global-timer', 'expected':True}, # a9_gtimer_realize: num-cpu must be between 1 and 4 77 {'device':'arm_mptimer', 'expected':True}, # num-cpu must be between 1 and 4 78 {'device':'armv7m', 'expected':True}, # memory property was not set 79 {'device':'aspeed.scu', 'expected':True}, # Unknown silicon revision: 0x0 80 {'device':'aspeed.sdmc', 'expected':True}, # Unknown silicon revision: 0x0 81 {'device':'bcm2835-dma', 'expected':True}, # bcm2835_dma_realize: required dma-mr link not found: Property '.dma-mr' not found 82 {'device':'bcm2835-fb', 'expected':True}, # bcm2835_fb_realize: required vcram-base property not set 83 {'device':'bcm2835-mbox', 'expected':True}, # bcm2835_mbox_realize: required mbox-mr link not found: Property '.mbox-mr' not found 84 {'device':'bcm2835-peripherals', 'expected':True}, # bcm2835_peripherals_realize: required ram link not found: Property '.ram' not found 85 {'device':'bcm2835-property', 'expected':True}, # bcm2835_property_realize: required fb link not found: Property '.fb' not found 86 {'device':'bcm2835_gpio', 'expected':True}, # bcm2835_gpio_realize: required sdhci link not found: Property '.sdbus-sdhci' not found 87 {'device':'bcm2836', 'expected':True}, # bcm2836_realize: required ram link not found: Property '.ram' not found 88 {'device':'cfi.pflash01', 'expected':True}, # attribute "sector-length" not specified or zero. 89 {'device':'cfi.pflash02', 'expected':True}, # attribute "sector-length" not specified or zero. 90 {'device':'icp', 'expected':True}, # icp_realize: required link 'xics' not found: Property '.xics' not found 91 {'device':'ics', 'expected':True}, # ics_base_realize: required link 'xics' not found: Property '.xics' not found 92 # "-device ide-cd" does work on more recent QEMU versions, so it doesn't have expected=True 93 {'device':'ide-cd'}, # No drive specified 94 {'device':'ide-drive', 'expected':True}, # No drive specified 95 {'device':'ide-hd', 'expected':True}, # No drive specified 96 {'device':'ipmi-bmc-extern', 'expected':True}, # IPMI external bmc requires chardev attribute 97 {'device':'isa-debugcon', 'expected':True}, # Can't create serial device, empty char device 98 {'device':'isa-ipmi-bt', 'expected':True}, # IPMI device requires a bmc attribute to be set 99 {'device':'isa-ipmi-kcs', 'expected':True}, # IPMI device requires a bmc attribute to be set 100 {'device':'isa-parallel', 'expected':True}, # Can't create serial device, empty char device 101 {'device':'isa-serial', 'expected':True}, # Can't create serial device, empty char device 102 {'device':'ivshmem', 'expected':True}, # You must specify either 'shm' or 'chardev' 103 {'device':'ivshmem-doorbell', 'expected':True}, # You must specify a 'chardev' 104 {'device':'ivshmem-plain', 'expected':True}, # You must specify a 'memdev' 105 {'device':'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 # "spapr-cpu-core needs a pseries machine" 164 {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True}, 165 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}, 169 170 # xen-specific machines and devices: 171 {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True}, 172 {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True}, 173 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 176 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"}, 205 206 # other exitcode=1 failures not listed above will just generate INFO messages: 207 {'exitcode':1, 'loglevel':logging.INFO}, 208 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}, 236 237 # everything else (including SIGABRT and SIGSEGV) will be a fatal error: 238 {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL}, 239] 240 241 242def whitelistTestCaseMatch(wl, t): 243 """Check if a test case specification can match a whitelist entry 244 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']))) 258 259 260def 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) 265 266 267def findExpectedResult(t): 268 """Check if there's an expected=True whitelist entry for a test case 269 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) 276 277 278def whitelistResultMatch(wl, r): 279 """Check if test case results/output match a whitelist entry 280 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))) 290 291 292def checkResultWhitelist(r): 293 """Look up whitelist entry for a given test case result 294 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 301 302 raise Exception("this should never happen") 303 304 305def qemuOptsEscape(s): 306 """Escape option value QemuOpts""" 307 return s.replace(",", ",,") 308 309 310def 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()) 313 314 315def 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] 319 320 321def 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 332 333 334class QemuBinaryInfo(object): 335 def __init__(self, binary, devtype): 336 if devtype is None: 337 devtype = 'device' 338 339 self.binary = binary 340 self._machine_info = {} 341 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() 357 358 def machineInfo(self, machine): 359 """Query for information on a specific machine-type 360 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] 366 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 380 381 vm.shutdown() 382 self._machine_info[machine] = mi 383 return mi 384 385 386BINARY_INFO = {} 387 388 389def getBinaryInfo(args, binary): 390 if binary not in BINARY_INFO: 391 BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype) 392 return BINARY_INFO[binary] 393 394 395def checkOneCase(args, testcase): 396 """Check one specific case 397 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'] 405 406 dbg("will test: %r", testcase) 407 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) 413 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() 426 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} 433 434 435def binariesToTest(args, testcase): 436 if args.qemu: 437 r = args.qemu 438 else: 439 r = glob.glob('./*-softmmu/qemu-system-*') 440 return r 441 442 443def accelsToTest(args, testcase): 444 if getBinaryInfo(args, testcase['binary']).kvm_available: 445 yield 'kvm' 446 yield 'tcg' 447 448 449def machinesToTest(args, testcase): 450 return getBinaryInfo(args, testcase['binary']).machines 451 452 453def devicesToTest(args, testcase): 454 return getBinaryInfo(args, testcase['binary']).user_devs 455 456 457TESTCASE_VARIABLES = [ 458 ('binary', binariesToTest), 459 ('accel', accelsToTest), 460 ('machine', machinesToTest), 461 ('device', devicesToTest), 462] 463 464 465def genCases1(args, testcases, var, fn): 466 """Generate new testcases for one variable 467 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 481 482 483def 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 491 492 493def 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 505 506 507def 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')) 518 519 520def 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() 549 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') 557 558 fatal_failures = [] 559 wl_stats = {} 560 skipped = 0 561 total = 0 562 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 570 571 if len(binariesToTest(args, tc)) == 0: 572 print >>sys.stderr, "No QEMU binary found" 573 parser.print_usage(sys.stderr) 574 return 1 575 576 for t in casesToTest(args, tc): 577 logger.info("running test case: %s", formatTestCase(t)) 578 total += 1 579 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 587 588 if args.dry_run: 589 continue 590 591 try: 592 f = checkOneCase(args, t) 593 except KeyboardInterrupt: 594 break 595 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)) 608 609 logger.info("Total: %d test cases", total) 610 if skipped: 611 logger.info("Skipped %d test cases", skipped) 612 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) 617 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 624 625if __name__ == '__main__': 626 sys.exit(main()) 627