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