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