xref: /openbmc/qemu/scripts/device-crash-test (revision 7f709ce7)
1#!/usr/bin/env python2.7
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-pci-vfio-host-bridge', 'expected':True}, # BUID not specified for PHB
123    {'device':'spapr-rng', 'expected':True},               # spapr-rng needs an RNG backend!
124    {'device':'spapr-vty', 'expected':True},               # chardev property not set
125    {'device':'tpm-tis', 'expected':True},                 # tpm_tis: backend driver with id (null) could not be found
126    {'device':'unimplemented-device', 'expected':True},    # property 'size' not specified or zero
127    {'device':'usb-braille', 'expected':True},             # Property chardev is required
128    {'device':'usb-mtp', 'expected':True},                 # x-root property must be configured
129    {'device':'usb-redir', 'expected':True},               # Parameter 'chardev' is missing
130    {'device':'usb-serial', 'expected':True},              # Property chardev is required
131    {'device':'usb-storage', 'expected':True},             # drive property not set
132    {'device':'vfio-amd-xgbe', 'expected':True},           # -device vfio-amd-xgbe: vfio error: wrong host device name
133    {'device':'vfio-calxeda-xgmac', 'expected':True},      # -device vfio-calxeda-xgmac: vfio error: wrong host device name
134    {'device':'vfio-pci', 'expected':True},                # No provided host device
135    {'device':'vfio-pci-igd-lpc-bridge', 'expected':True}, # VFIO dummy ISA/LPC bridge must have address 1f.0
136    {'device':'vhost-scsi.*', 'expected':True},            # vhost-scsi: missing wwpn
137    {'device':'vhost-vsock-device', 'expected':True},      # guest-cid property must be greater than 2
138    {'device':'vhost-vsock-pci', 'expected':True},         # guest-cid property must be greater than 2
139    {'device':'virtio-9p-ccw', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
140    {'device':'virtio-9p-device', 'expected':True},        # 9pfs device couldn't find fsdev with the id = NULL
141    {'device':'virtio-9p-pci', 'expected':True},           # 9pfs device couldn't find fsdev with the id = NULL
142    {'device':'virtio-blk-ccw', 'expected':True},          # drive property not set
143    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
144    {'device':'virtio-blk-device', 'expected':True},       # drive property not set
145    {'device':'virtio-blk-pci', 'expected':True},          # drive property not set
146    {'device':'virtio-crypto-ccw', 'expected':True},       # 'cryptodev' parameter expects a valid object
147    {'device':'virtio-crypto-device', 'expected':True},    # 'cryptodev' parameter expects a valid object
148    {'device':'virtio-crypto-pci', 'expected':True},       # 'cryptodev' parameter expects a valid object
149    {'device':'virtio-input-host-device', 'expected':True}, # evdev property is required
150    {'device':'virtio-input-host-pci', 'expected':True},   # evdev property is required
151    {'device':'xen-pvdevice', 'expected':True},            # Device ID invalid, it must always be supplied
152    {'device':'vhost-vsock-ccw', 'expected':True},         # guest-cid property must be greater than 2
153    {'device':'ALTR.timer', 'expected':True},              # "clock-frequency" property must be provided
154    {'device':'zpci', 'expected':True},                    # target must be defined
155    {'device':'pnv-(occ|icp|lpc)', 'expected':True},       # required link 'xics' not found: Property '.xics' not found
156    {'device':'powernv-cpu-.*', 'expected':True},          # pnv_core_realize: required link 'xics' not found: Property '.xics' not found
157
158    # ioapic devices are already created by pc and will fail:
159    {'machine':'q35|pc.*', 'device':'kvm-ioapic', 'expected':True}, # Only 1 ioapics allowed
160    {'machine':'q35|pc.*', 'device':'ioapic', 'expected':True},     # Only 1 ioapics allowed
161
162    # "spapr-cpu-core needs a pseries machine"
163    {'machine':'(?!pseries).*', 'device':'.*-spapr-cpu-core', 'expected':True},
164
165    # KVM-specific devices shouldn't be tried without accel=kvm:
166    {'accel':'(?!kvm).*', 'device':'kvmclock', 'expected':True},
167
168    # xen-specific machines and devices:
169    {'accel':'(?!xen).*', 'machine':'xen.*', 'expected':True},
170    {'accel':'(?!xen).*', 'device':'xen-.*', 'expected':True},
171
172    # this fails on some machine-types, but not all, so they don't have expected=True:
173    {'device':'vmgenid'}, # vmgenid requires DMA write support in fw_cfg, which this machine type does not provide
174
175    # Silence INFO messages for errors that are common on multiple
176    # devices/machines:
177    {'log':r"No '[\w-]+' bus found for device '[\w-]+'"},
178    {'log':r"images* must be given with the 'pflash' parameter"},
179    {'log':r"(Guest|ROM|Flash|Kernel) image must be specified"},
180    {'log':r"[cC]ould not load [\w ]+ (BIOS|bios) '[\w-]+\.bin'"},
181    {'log':r"Couldn't find rom image '[\w-]+\.bin'"},
182    {'log':r"speed mismatch trying to attach usb device"},
183    {'log':r"Can't create a second ISA bus"},
184    {'log':r"duplicate fw_cfg file name"},
185    # sysbus-related error messages: most machines reject most dynamic sysbus devices:
186    {'log':r"Option '-device [\w.,-]+' cannot be handled by this machine"},
187    {'log':r"Device [\w.,-]+ is not supported by this machine yet"},
188    {'log':r"Device [\w.,-]+ can not be dynamically instantiated"},
189    {'log':r"Platform Bus: Can not fit MMIO region of size "},
190    # other more specific errors we will ignore:
191    {'device':'.*-spapr-cpu-core', 'log':r"CPU core type should be"},
192    {'log':r"MSI(-X)? is not supported by interrupt controller"},
193    {'log':r"pxb-pcie? devices cannot reside on a PCIe? bus"},
194    {'log':r"Ignoring smp_cpus value"},
195    {'log':r"sd_init failed: Drive 'sd0' is already in use because it has been automatically connected to another device"},
196    {'log':r"This CPU requires a smaller page size than the system is using"},
197    {'log':r"MSI-X support is mandatory in the S390 architecture"},
198    {'log':r"rom check and register reset failed"},
199    {'log':r"Unable to initialize GIC, CPUState for CPU#0 not valid"},
200    {'log':r"Multiple VT220 operator consoles are not supported"},
201    {'log':r"core 0 already populated"},
202    {'log':r"could not find stage1 bootloader"},
203
204    # other exitcode=1 failures not listed above will just generate INFO messages:
205    {'exitcode':1, 'loglevel':logging.INFO},
206
207    # KNOWN CRASHES:
208    # Known crashes will generate error messages, but won't be fatal.
209    # Those entries must be removed once we fix the crashes.
210    {'exitcode':-6, 'log':r"Device 'serial0' is in use", 'loglevel':logging.ERROR},
211    {'exitcode':-6, 'log':r"spapr_rtas_register: Assertion .*rtas_table\[token\]\.name.* failed", 'loglevel':logging.ERROR},
212    {'exitcode':-6, 'log':r"qemu_net_client_setup: Assertion `!peer->peer' failed", 'loglevel':logging.ERROR},
213    {'exitcode':-6, 'log':r'RAMBlock "[\w.-]+" already registered', 'loglevel':logging.ERROR},
214    {'exitcode':-6, 'log':r"find_ram_offset: Assertion `size != 0' failed.", 'loglevel':logging.ERROR},
215    {'exitcode':-6, 'log':r"puv3_load_kernel: Assertion `kernel_filename != NULL' failed", 'loglevel':logging.ERROR},
216    {'exitcode':-6, 'log':r"add_cpreg_to_hashtable: code should not be reached", 'loglevel':logging.ERROR},
217    {'exitcode':-6, 'log':r"qemu_alloc_display: Assertion `surface->image != NULL' failed", 'loglevel':logging.ERROR},
218    {'exitcode':-6, 'log':r"Unexpected error in error_set_from_qdev_prop_error", 'loglevel':logging.ERROR},
219    {'exitcode':-6, 'log':r"Object .* is not an instance of type spapr-machine", 'loglevel':logging.ERROR},
220    {'exitcode':-6, 'log':r"Object .* is not an instance of type generic-pc-machine", 'loglevel':logging.ERROR},
221    {'exitcode':-6, 'log':r"Object .* is not an instance of type e500-ccsr", 'loglevel':logging.ERROR},
222    {'exitcode':-6, 'log':r"vmstate_register_with_alias_id: Assertion `!se->compat \|\| se->instance_id == 0' failed", 'loglevel':logging.ERROR},
223    {'exitcode':-11, 'device':'stm32f205-soc', 'loglevel':logging.ERROR, 'expected':True},
224    {'exitcode':-11, 'device':'xlnx,zynqmp', 'loglevel':logging.ERROR, 'expected':True},
225    {'exitcode':-11, 'device':'mips-cps', 'loglevel':logging.ERROR, 'expected':True},
226    {'exitcode':-11, 'device':'gus', 'loglevel':logging.ERROR, 'expected':True},
227    {'exitcode':-11, 'device':'a9mpcore_priv', 'loglevel':logging.ERROR, 'expected':True},
228    {'exitcode':-11, 'device':'a15mpcore_priv', 'loglevel':logging.ERROR, 'expected':True},
229    {'exitcode':-11, 'device':'isa-serial', 'loglevel':logging.ERROR, 'expected':True},
230    {'exitcode':-11, 'device':'sb16', 'loglevel':logging.ERROR, 'expected':True},
231    {'exitcode':-11, 'device':'cs4231a', 'loglevel':logging.ERROR, 'expected':True},
232    {'exitcode':-11, 'device':'arm-gicv3', 'loglevel':logging.ERROR, 'expected':True},
233    {'exitcode':-11, 'machine':'isapc', 'device':'.*-iommu', 'loglevel':logging.ERROR, 'expected':True},
234
235    # everything else (including SIGABRT and SIGSEGV) will be a fatal error:
236    {'exitcode':None, 'fatal':True, 'loglevel':logging.FATAL},
237]
238
239
240def whitelistTestCaseMatch(wl, t):
241    """Check if a test case specification can match a whitelist entry
242
243    This only checks if a whitelist entry is a candidate match
244    for a given test case, it won't check if the test case
245    results/output match the entry.  See whitelistResultMatch().
246    """
247    return (('machine' not in wl or
248             'machine' not in t or
249             re.match(wl['machine'] + '$', t['machine'])) and
250            ('accel' not in wl or
251             'accel' not in t or
252             re.match(wl['accel'] + '$', t['accel'])) and
253            ('device' not in wl or
254             'device' not in t or
255             re.match(wl['device'] + '$', t['device'])))
256
257
258def whitelistCandidates(t):
259    """Generate the list of candidates that can match a test case"""
260    for i, wl in enumerate(ERROR_WHITELIST):
261        if whitelistTestCaseMatch(wl, t):
262            yield (i, wl)
263
264
265def findExpectedResult(t):
266    """Check if there's an expected=True whitelist entry for a test case
267
268    Returns (i, wl) tuple, where i is the index in
269    ERROR_WHITELIST and wl is the whitelist entry itself.
270    """
271    for i, wl in whitelistCandidates(t):
272        if wl.get('expected'):
273            return (i, wl)
274
275
276def whitelistResultMatch(wl, r):
277    """Check if test case results/output match a whitelist entry
278
279    It is valid to call this function only if
280    whitelistTestCaseMatch() is True for the entry (e.g. on
281    entries returned by whitelistCandidates())
282    """
283    assert whitelistTestCaseMatch(wl, r['testcase'])
284    return ((wl.get('exitcode', 1) is None or
285             r['exitcode'] == wl.get('exitcode', 1)) and
286            ('log' not in wl or
287             re.search(wl['log'], r['log'], re.MULTILINE)))
288
289
290def checkResultWhitelist(r):
291    """Look up whitelist entry for a given test case result
292
293    Returns (i, wl) tuple, where i is the index in
294    ERROR_WHITELIST and wl is the whitelist entry itself.
295    """
296    for i, wl in whitelistCandidates(r['testcase']):
297        if whitelistResultMatch(wl, r):
298            return i, wl
299
300    raise Exception("this should never happen")
301
302
303def qemuOptsEscape(s):
304    """Escape option value QemuOpts"""
305    return s.replace(",", ",,")
306
307
308def formatTestCase(t):
309    """Format test case info as "key=value key=value" for prettier logging output"""
310    return ' '.join('%s=%s' % (k, v) for k, v in t.items())
311
312
313def qomListTypeNames(vm, **kwargs):
314    """Run qom-list-types QMP command, return type names"""
315    types = vm.command('qom-list-types', **kwargs)
316    return [t['name'] for t in types]
317
318
319def infoQDM(vm):
320    """Parse 'info qdm' output"""
321    args = {'command-line': 'info qdm'}
322    devhelp = vm.command('human-monitor-command', **args)
323    for l in devhelp.split('\n'):
324        l = l.strip()
325        if l == '' or l.endswith(':'):
326            continue
327        d = {'name': re.search(r'name "([^"]+)"', l).group(1),
328             'no-user': (re.search(', no-user', l) is not None)}
329        yield d
330
331
332class QemuBinaryInfo(object):
333    def __init__(self, binary, devtype):
334        if devtype is None:
335            devtype = 'device'
336
337        self.binary = binary
338        self._machine_info = {}
339
340        dbg("devtype: %r", devtype)
341        args = ['-S', '-machine', 'none,accel=kvm:tcg']
342        dbg("querying info for QEMU binary: %s", binary)
343        vm = QEMUMachine(binary=binary, args=args)
344        vm.launch()
345        try:
346            self.alldevs = set(qomListTypeNames(vm, implements=devtype, abstract=False))
347            # there's no way to query DeviceClass::user_creatable using QMP,
348            # so use 'info qdm':
349            self.no_user_devs = set([d['name'] for d in infoQDM(vm, ) if d['no-user']])
350            self.machines = list(m['name'] for m in vm.command('query-machines'))
351            self.user_devs = self.alldevs.difference(self.no_user_devs)
352            self.kvm_available = vm.command('query-kvm')['enabled']
353        finally:
354            vm.shutdown()
355
356    def machineInfo(self, machine):
357        """Query for information on a specific machine-type
358
359        Results are cached internally, in case the same machine-
360        type is queried multiple times.
361        """
362        if machine in self._machine_info:
363            return self._machine_info[machine]
364
365        mi = {}
366        args = ['-S', '-machine', '%s' % (machine)]
367        dbg("querying machine info for binary=%s machine=%s", self.binary, machine)
368        vm = QEMUMachine(binary=self.binary, args=args)
369        try:
370            vm.launch()
371            mi['runnable'] = True
372        except KeyboardInterrupt:
373            raise
374        except:
375            dbg("exception trying to run binary=%s machine=%s", self.binary, machine, exc_info=sys.exc_info())
376            dbg("log: %r", vm.get_log())
377            mi['runnable'] = False
378
379        vm.shutdown()
380        self._machine_info[machine] = mi
381        return mi
382
383
384BINARY_INFO = {}
385
386
387def getBinaryInfo(args, binary):
388    if binary not in BINARY_INFO:
389        BINARY_INFO[binary] = QemuBinaryInfo(binary, args.devtype)
390    return BINARY_INFO[binary]
391
392
393def checkOneCase(args, testcase):
394    """Check one specific case
395
396    Returns a dictionary containing failure information on error,
397    or None on success
398    """
399    binary = testcase['binary']
400    accel = testcase['accel']
401    machine = testcase['machine']
402    device = testcase['device']
403
404    dbg("will test: %r", testcase)
405
406    args = ['-S', '-machine', '%s,accel=%s' % (machine, accel),
407            '-device', qemuOptsEscape(device)]
408    cmdline = ' '.join([binary] + args)
409    dbg("will launch QEMU: %s", cmdline)
410    vm = QEMUMachine(binary=binary, args=args)
411
412    exc_traceback = None
413    try:
414        vm.launch()
415    except KeyboardInterrupt:
416        raise
417    except:
418        exc_traceback = traceback.format_exc()
419        dbg("Exception while running test case")
420    finally:
421        vm.shutdown()
422        ec = vm.exitcode()
423        log = vm.get_log()
424
425    if exc_traceback is not None or ec != 0:
426        return {'exc_traceback':exc_traceback,
427                'exitcode':ec,
428                'log':log,
429                'testcase':testcase,
430                'cmdline':cmdline}
431
432
433def binariesToTest(args, testcase):
434    if args.qemu:
435        r = args.qemu
436    else:
437        r = glob.glob('./*-softmmu/qemu-system-*')
438    return r
439
440
441def accelsToTest(args, testcase):
442    if getBinaryInfo(args, testcase['binary']).kvm_available:
443        yield 'kvm'
444    yield 'tcg'
445
446
447def machinesToTest(args, testcase):
448    return getBinaryInfo(args, testcase['binary']).machines
449
450
451def devicesToTest(args, testcase):
452    return getBinaryInfo(args, testcase['binary']).user_devs
453
454
455TESTCASE_VARIABLES = [
456    ('binary', binariesToTest),
457    ('accel', accelsToTest),
458    ('machine', machinesToTest),
459    ('device', devicesToTest),
460]
461
462
463def genCases1(args, testcases, var, fn):
464    """Generate new testcases for one variable
465
466    If an existing item already has a variable set, don't
467    generate new items and just return it directly. This
468    allows the "-t" command-line option to be used to choose
469    a specific test case.
470    """
471    for testcase in testcases:
472        if var in testcase:
473            yield testcase.copy()
474        else:
475            for i in fn(args, testcase):
476                t = testcase.copy()
477                t[var] = i
478                yield t
479
480
481def genCases(args, testcase):
482    """Generate test cases for all variables
483    """
484    cases = [testcase.copy()]
485    for var, fn in TESTCASE_VARIABLES:
486        dbg("var: %r, fn: %r", var, fn)
487        cases = genCases1(args, cases, var, fn)
488    return cases
489
490
491def casesToTest(args, testcase):
492    cases = genCases(args, testcase)
493    if args.random:
494        cases = list(cases)
495        cases = random.sample(cases, min(args.random, len(cases)))
496    if args.debug:
497        cases = list(cases)
498        dbg("%d test cases to test", len(cases))
499    if args.shuffle:
500        cases = list(cases)
501        random.shuffle(cases)
502    return cases
503
504
505def logFailure(f, level):
506    t = f['testcase']
507    logger.log(level, "failed: %s", formatTestCase(t))
508    logger.log(level, "cmdline: %s", f['cmdline'])
509    for l in f['log'].strip().split('\n'):
510        logger.log(level, "log: %s", l)
511    logger.log(level, "exit code: %r", f['exitcode'])
512    if f['exc_traceback']:
513        logger.log(level, "exception:")
514        for l in f['exc_traceback'].split('\n'):
515            logger.log(level, "  %s", l.rstrip('\n'))
516
517
518def main():
519    parser = argparse.ArgumentParser(description="QEMU -device crash test")
520    parser.add_argument('-t', metavar='KEY=VALUE', nargs='*',
521                        help="Limit test cases to KEY=VALUE",
522                        action='append', dest='testcases', default=[])
523    parser.add_argument('-d', '--debug', action='store_true',
524                        help='debug output')
525    parser.add_argument('-v', '--verbose', action='store_true', default=True,
526                        help='verbose output')
527    parser.add_argument('-q', '--quiet', dest='verbose', action='store_false',
528                        help='non-verbose output')
529    parser.add_argument('-r', '--random', type=int, metavar='COUNT',
530                        help='run a random sample of COUNT test cases',
531                        default=0)
532    parser.add_argument('--shuffle', action='store_true',
533                        help='Run test cases in random order')
534    parser.add_argument('--dry-run', action='store_true',
535                        help="Don't run any tests, just generate list")
536    parser.add_argument('-D', '--devtype', metavar='TYPE',
537                        help="Test only device types that implement TYPE")
538    parser.add_argument('-Q', '--quick', action='store_true', default=True,
539                        help="Quick mode: skip test cases that are expected to fail")
540    parser.add_argument('-F', '--full', action='store_false', dest='quick',
541                        help="Full mode: test cases that are expected to fail")
542    parser.add_argument('--strict', action='store_true', dest='strict',
543                        help="Treat all warnings as fatal")
544    parser.add_argument('qemu', nargs='*', metavar='QEMU',
545                        help='QEMU binary to run')
546    args = parser.parse_args()
547
548    if args.debug:
549        lvl = logging.DEBUG
550    elif args.verbose:
551        lvl = logging.INFO
552    else:
553        lvl = logging.WARN
554    logging.basicConfig(stream=sys.stdout, level=lvl, format='%(levelname)s: %(message)s')
555
556    fatal_failures = []
557    wl_stats = {}
558    skipped = 0
559    total = 0
560
561    tc = {}
562    dbg("testcases: %r", args.testcases)
563    if args.testcases:
564        for t in chain(*args.testcases):
565            for kv in t.split():
566                k, v = kv.split('=', 1)
567                tc[k] = v
568
569    if len(binariesToTest(args, tc)) == 0:
570        print >>sys.stderr, "No QEMU binary found"
571        parser.print_usage(sys.stderr)
572        return 1
573
574    for t in casesToTest(args, tc):
575        logger.info("running test case: %s", formatTestCase(t))
576        total += 1
577
578        expected_match = findExpectedResult(t)
579        if (args.quick and
580                (expected_match or
581                 not getBinaryInfo(args, t['binary']).machineInfo(t['machine'])['runnable'])):
582            dbg("skipped: %s", formatTestCase(t))
583            skipped += 1
584            continue
585
586        if args.dry_run:
587            continue
588
589        try:
590            f = checkOneCase(args, t)
591        except KeyboardInterrupt:
592            break
593
594        if f:
595            i, wl = checkResultWhitelist(f)
596            dbg("testcase: %r, whitelist match: %r", t, wl)
597            wl_stats.setdefault(i, []).append(f)
598            level = wl.get('loglevel', logging.DEBUG)
599            logFailure(f, level)
600            if wl.get('fatal') or (args.strict and level >= logging.WARN):
601                fatal_failures.append(f)
602        else:
603            dbg("success: %s", formatTestCase(t))
604            if expected_match:
605                logger.warn("Didn't fail as expected: %s", formatTestCase(t))
606
607    logger.info("Total: %d test cases", total)
608    if skipped:
609        logger.info("Skipped %d test cases", skipped)
610
611    if args.debug:
612        stats = sorted([(len(wl_stats.get(i, [])), wl) for i, wl in enumerate(ERROR_WHITELIST)])
613        for count, wl in stats:
614            dbg("whitelist entry stats: %d: %r", count, wl)
615
616    if fatal_failures:
617        for f in fatal_failures:
618            t = f['testcase']
619            logger.error("Fatal failure: %s", formatTestCase(t))
620        logger.error("Fatal failures on some machine/device combinations")
621        return 1
622
623if __name__ == '__main__':
624    sys.exit(main())
625