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