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