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