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