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':-11, 'device':'isa-serial', 'loglevel':logging.ERROR, 'expected':True}, 221 {'exitcode':-11, 'device':'mioe3680_pci', 'loglevel':logging.ERROR, 'expected':True}, 222 {'exitcode':-11, 'device':'pcm3680_pci', 'loglevel':logging.ERROR, 'expected':True}, 223 {'exitcode':-11, 'device':'kvaser_pci', 'loglevel':logging.ERROR, 'expected':True}, 224 225 # everything else (including SIGABRT and SIGSEGV) will be a fatal error: 226 {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL}, 227] 228 229 230def whitelistTestCaseMatch(wl, t): 231 """Check if a test case specification can match a whitelist entry 232 233 This only checks if a whitelist entry is a candidate match 234 for a given test case, it won't check if the test case 235 results/output match the entry. See whitelistResultMatch(). 236 """ 237 return (('machine' not in wl or 238 'machine' not in t or 239 re.match(wl['machine'] + '$', t['machine'])) and 240 ('accel' not in wl or 241 'accel' not in t or 242 re.match(wl['accel'] + '$', t['accel'])) and 243 ('device' not in wl or 244 'device' not in t or 245 re.match(wl['device'] + '$', t['device']))) 246 247 248def whitelistCandidates(t): 249 """Generate the list of candidates that can match a test case""" 250 for i, wl in enumerate(ERROR_WHITELIST): 251 if whitelistTestCaseMatch(wl, t): 252 yield (i, wl) 253 254 255def findExpectedResult(t): 256 """Check if there's an expected=True whitelist entry for a test case 257 258 Returns (i, wl) tuple, where i is the index in 259 ERROR_WHITELIST and wl is the whitelist entry itself. 260 """ 261 for i, wl in whitelistCandidates(t): 262 if wl.get('expected'): 263 return (i, wl) 264 265 266def whitelistResultMatch(wl, r): 267 """Check if test case results/output match a whitelist entry 268 269 It is valid to call this function only if 270 whitelistTestCaseMatch() is True for the entry (e.g. on 271 entries returned by whitelistCandidates()) 272 """ 273 assert whitelistTestCaseMatch(wl, r['testcase']) 274 return ((wl.get('exitcode', 1) is None or 275 r['exitcode'] == wl.get('exitcode', 1)) and 276 ('log' not in wl or 277 re.search(wl['log'], r['log'], re.MULTILINE))) 278 279 280def checkResultWhitelist(r): 281 """Look up whitelist entry for a given test case result 282 283 Returns (i, wl) tuple, where i is the index in 284 ERROR_WHITELIST and wl is the whitelist entry itself. 285 """ 286 for i, wl in whitelistCandidates(r['testcase']): 287 if whitelistResultMatch(wl, r): 288 return i, wl 289 290 raise Exception("this should never happen") 291 292 293def qemuOptsEscape(s): 294 """Escape option value QemuOpts""" 295 return s.replace(",", ",,") 296 297 298def formatTestCase(t): 299 """Format test case info as "key=value key=value" for prettier logging output""" 300 return ' '.join('%s=%s' % (k, v) for k, v in t.items()) 301 302 303def qomListTypeNames(vm, **kwargs): 304 """Run qom-list-types QMP command, return type names""" 305 types = vm.command('qom-list-types', **kwargs) 306 return [t['name'] for t in types] 307 308 309def infoQDM(vm): 310 """Parse 'info qdm' output""" 311 args = {'command-line': 'info qdm'} 312 devhelp = vm.command('human-monitor-command', **args) 313 for l in devhelp.split('\n'): 314 l = l.strip() 315 if l == '' or l.endswith(':'): 316 continue 317 d = {'name': re.search(r'name "([^"]+)"', l).group(1), 318 'no-user': (re.search(', no-user', l) is not None)} 319 yield d 320 321 322class QemuBinaryInfo(object): 323 def __init__(self, binary, devtype): 324 if devtype is None: 325 devtype = 'device' 326 327 self.binary = binary 328 self._machine_info = {} 329 330 dbg("devtype: %r", devtype) 331 args = ['-S', '-machine', 'none,accel=kvm:tcg'] 332 dbg("querying info for QEMU binary: %s", binary) 333 vm = QEMUMachine(binary=binary, args=args) 334 vm.launch() 335 try: 336 self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False)) 337 # there's no way to query DeviceClass::user_creatable using QMP, 338 # so use 'info qdm': 339 self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']]) 340 self.machines = list(m['name'] for m in vm.command('query-machines')) 341 self.user_devs = self.alldevs.difference(self.no_user_devs) 342 self.kvm_available = vm.command('query-kvm')['enabled'] 343 finally: 344 vm.shutdown() 345 346 def machineInfo(self, machine): 347 """Query for information on a specific machine-type 348 349 Results are cached internally, in case the same machine- 350 type is queried multiple times. 351 """ 352 if machine in self._machine_info: 353 return self._machine_info[machine] 354 355 mi = {} 356 args = ['-S', '-machine', '%s' % (machine)] 357 dbg("querying machine info for binary=%s machine=%s", self.binary, machine) 358 vm = QEMUMachine(binary=self.binary, args=args) 359 try: 360 vm.launch() 361 mi['runnable'] = True 362 except KeyboardInterrupt: 363 raise 364 except: 365 dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info()) 366 dbg("log: %r", vm.get_log()) 367 mi['runnable'] = False 368 369 vm.shutdown() 370 self._machine_info[machine] = mi 371 return mi 372 373 374BINARY_INFO = {} 375 376 377def getBinaryInfo(args, binary): 378 if binary not in BINARY_INFO: 379 BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype) 380 return BINARY_INFO[binary] 381 382 383def checkOneCase(args, testcase): 384 """Check one specific case 385 386 Returns a dictionary containing failure information on error, 387 or None on success 388 """ 389 binary = testcase['binary'] 390 accel = testcase['accel'] 391 machine = testcase['machine'] 392 device = testcase['device'] 393 394 dbg("will test: %r", testcase) 395 396 args = ['-S', '-machine', '%s,accel=%s' % (machine, accel), 397 '-device', qemuOptsEscape(device)] 398 cmdline = ' '.join([binary] + args) 399 dbg("will launch QEMU: %s", cmdline) 400 vm = QEMUMachine(binary=binary, args=args) 401 402 exc_traceback = None 403 try: 404 vm.launch() 405 except KeyboardInterrupt: 406 raise 407 except: 408 exc_traceback = traceback.format_exc() 409 dbg("Exception while running test case") 410 finally: 411 vm.shutdown() 412 ec = vm.exitcode() 413 log = vm.get_log() 414 415 if exc_traceback is not None or ec != 0: 416 return {'exc_traceback':exc_traceback, 417 'exitcode':ec, 418 'log':log, 419 'testcase':testcase, 420 'cmdline':cmdline} 421 422 423def binariesToTest(args, testcase): 424 if args.qemu: 425 r = args.qemu 426 else: 427 r = glob.glob('./*-softmmu/qemu-system-*') 428 return r 429 430 431def accelsToTest(args, testcase): 432 if getBinaryInfo(args, testcase['binary']).kvm_available: 433 yield 'kvm' 434 yield 'tcg' 435 436 437def machinesToTest(args, testcase): 438 return getBinaryInfo(args, testcase['binary']).machines 439 440 441def devicesToTest(args, testcase): 442 return getBinaryInfo(args, testcase['binary']).user_devs 443 444 445TESTCASE_VARIABLES = [ 446 ('binary', binariesToTest), 447 ('accel', accelsToTest), 448 ('machine', machinesToTest), 449 ('device', devicesToTest), 450] 451 452 453def genCases1(args, testcases, var, fn): 454 """Generate new testcases for one variable 455 456 If an existing item already has a variable set, don't 457 generate new items and just return it directly. This 458 allows the "-t" command-line option to be used to choose 459 a specific test case. 460 """ 461 for testcase in testcases: 462 if var in testcase: 463 yield testcase.copy() 464 else: 465 for i in fn(args, testcase): 466 t = testcase.copy() 467 t[var] = i 468 yield t 469 470 471def genCases(args, testcase): 472 """Generate test cases for all variables 473 """ 474 cases = [testcase.copy()] 475 for var, fn in TESTCASE_VARIABLES: 476 dbg("var: %r, fn: %r", var, fn) 477 cases = genCases1(args, cases, var, fn) 478 return cases 479 480 481def casesToTest(args, testcase): 482 cases = genCases(args, testcase) 483 if args.random: 484 cases = list(cases) 485 cases = random.sample(cases, min(args.random, len(cases))) 486 if args.debug: 487 cases = list(cases) 488 dbg("%d test cases to test", len(cases)) 489 if args.shuffle: 490 cases = list(cases) 491 random.shuffle(cases) 492 return cases 493 494 495def logFailure(f, level): 496 t = f['testcase'] 497 logger.log(level, "failed: %s", formatTestCase(t)) 498 logger.log(level, "cmdline: %s", f['cmdline']) 499 for l in f['log'].strip().split('\n'): 500 logger.log(level, "log: %s", l) 501 logger.log(level, "exit code: %r", f['exitcode']) 502 if f['exc_traceback']: 503 logger.log(level, "exception:") 504 for l in f['exc_traceback'].split('\n'): 505 logger.log(level, " %s", l.rstrip('\n')) 506 507 508def main(): 509 parser = argparse.ArgumentParser(description="QEMU -device crash test") 510 parser.add_argument('-t', metavar='KEY=VALUE', nargs='*', 511 help="Limit test cases to KEY=VALUE", 512 action='append', dest='testcases', default=[]) 513 parser.add_argument('-d', '--debug', action='store_true', 514 help='debug output') 515 parser.add_argument('-v', '--verbose', action='store_true', default=True, 516 help='verbose output') 517 parser.add_argument('-q', '--quiet', dest='verbose', action='store_false', 518 help='non-verbose output') 519 parser.add_argument('-r', '--random', type=int, metavar='COUNT', 520 help='run a random sample of COUNT test cases', 521 default=0) 522 parser.add_argument('--shuffle', action='store_true', 523 help='Run test cases in random order') 524 parser.add_argument('--dry-run', action='store_true', 525 help="Don't run any tests, just generate list") 526 parser.add_argument('-D', '--devtype', metavar='TYPE', 527 help="Test only device types that implement TYPE") 528 parser.add_argument('-Q', '--quick', action='store_true', default=True, 529 help="Quick mode: skip test cases that are expected to fail") 530 parser.add_argument('-F', '--full', action='store_false', dest='quick', 531 help="Full mode: test cases that are expected to fail") 532 parser.add_argument('--strict', action='store_true', dest='strict', 533 help="Treat all warnings as fatal") 534 parser.add_argument('qemu', nargs='*', metavar='QEMU', 535 help='QEMU binary to run') 536 args = parser.parse_args() 537 538 if args.debug: 539 lvl = logging.DEBUG 540 elif args.verbose: 541 lvl = logging.INFO 542 else: 543 lvl = logging.WARN 544 logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s') 545 546 fatal_failures = [] 547 wl_stats = {} 548 skipped = 0 549 total = 0 550 551 tc = {} 552 dbg("testcases: %r", args.testcases) 553 if args.testcases: 554 for t in chain(*args.testcases): 555 for kv in t.split(): 556 k, v = kv.split('=', 1) 557 tc[k] = v 558 559 if len(binariesToTest(args, tc)) == 0: 560 print >>sys.stderr, "No QEMU binary found" 561 parser.print_usage(sys.stderr) 562 return 1 563 564 for t in casesToTest(args, tc): 565 logger.info("running test case: %s", formatTestCase(t)) 566 total += 1 567 568 expected_match = findExpectedResult(t) 569 if (args.quick and 570 (expected_match or 571 not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])): 572 dbg("skipped: %s", formatTestCase(t)) 573 skipped += 1 574 continue 575 576 if args.dry_run: 577 continue 578 579 try: 580 f = checkOneCase(args, t) 581 except KeyboardInterrupt: 582 break 583 584 if f: 585 i, wl = checkResultWhitelist(f) 586 dbg("testcase: %r, whitelist match: %r", t, wl) 587 wl_stats.setdefault(i, []).append(f) 588 level = wl.get('loglevel', logging.DEBUG) 589 logFailure(f, level) 590 if wl.get('fatal') or (args.strict and level >= logging.WARN): 591 fatal_failures.append(f) 592 else: 593 dbg("success: %s", formatTestCase(t)) 594 if expected_match: 595 logger.warn("Didn't fail as expected: %s", formatTestCase(t)) 596 597 logger.info("Total: %d test cases", total) 598 if skipped: 599 logger.info("Skipped %d test cases", skipped) 600 601 if args.debug: 602 stats = sorted([(len(wl_stats.get(i, [])), wl) for i, wl in enumerate(ERROR_WHITELIST)]) 603 for count, wl in stats: 604 dbg("whitelist entry stats: %d: %r", count, wl) 605 606 if fatal_failures: 607 for f in fatal_failures: 608 t = f['testcase'] 609 logger.error("Fatal failure: %s", formatTestCase(t)) 610 logger.error("Fatal failures on some machine/device combinations") 611 return 1 612 613if __name__ == '__main__': 614 sys.exit(main()) 615