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