xref: /openbmc/qemu/scripts/device-crash-test (revision 0dacec87)
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