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