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