xref: /openbmc/qemu/tests/qemu-iotests/iotests.py (revision ae3c12a0)
1from __future__ import print_function
2# Common utilities and Python wrappers for qemu-iotests
3#
4# Copyright (C) 2012 IBM Corp.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19
20import errno
21import os
22import re
23import subprocess
24import string
25import unittest
26import sys
27import struct
28import json
29import signal
30import logging
31import atexit
32import io
33from collections import OrderedDict
34
35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36from qemu import qtest
37
38
39# This will not work if arguments contain spaces but is necessary if we
40# want to support the override options that ./check supports.
41qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
42if os.environ.get('QEMU_IMG_OPTIONS'):
43    qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
44
45qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
46if os.environ.get('QEMU_IO_OPTIONS'):
47    qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
48
49qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
50if os.environ.get('QEMU_NBD_OPTIONS'):
51    qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
52
53qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
54qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
55
56imgfmt = os.environ.get('IMGFMT', 'raw')
57imgproto = os.environ.get('IMGPROTO', 'file')
58test_dir = os.environ.get('TEST_DIR')
59output_dir = os.environ.get('OUTPUT_DIR', '.')
60cachemode = os.environ.get('CACHEMODE')
61qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
62
63socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
64debug = False
65
66luks_default_secret_object = 'secret,id=keysec0,data=' + \
67                             os.environ.get('IMGKEYSECRET', '')
68luks_default_key_secret_opt = 'key-secret=keysec0'
69
70
71def qemu_img(*args):
72    '''Run qemu-img and return the exit code'''
73    devnull = open('/dev/null', 'r+')
74    exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
75    if exitcode < 0:
76        sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
77    return exitcode
78
79def ordered_qmp(qmsg, conv_keys=True):
80    # Dictionaries are not ordered prior to 3.6, therefore:
81    if isinstance(qmsg, list):
82        return [ordered_qmp(atom) for atom in qmsg]
83    if isinstance(qmsg, dict):
84        od = OrderedDict()
85        for k, v in sorted(qmsg.items()):
86            if conv_keys:
87                k = k.replace('_', '-')
88            od[k] = ordered_qmp(v, conv_keys=False)
89        return od
90    return qmsg
91
92def qemu_img_create(*args):
93    args = list(args)
94
95    # default luks support
96    if '-f' in args and args[args.index('-f') + 1] == 'luks':
97        if '-o' in args:
98            i = args.index('-o')
99            if 'key-secret' not in args[i + 1]:
100                args[i + 1].append(luks_default_key_secret_opt)
101                args.insert(i + 2, '--object')
102                args.insert(i + 3, luks_default_secret_object)
103        else:
104            args = ['-o', luks_default_key_secret_opt,
105                    '--object', luks_default_secret_object] + args
106
107    args.insert(0, 'create')
108
109    return qemu_img(*args)
110
111def qemu_img_verbose(*args):
112    '''Run qemu-img without suppressing its output and return the exit code'''
113    exitcode = subprocess.call(qemu_img_args + list(args))
114    if exitcode < 0:
115        sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
116    return exitcode
117
118def qemu_img_pipe(*args):
119    '''Run qemu-img and return its output'''
120    subp = subprocess.Popen(qemu_img_args + list(args),
121                            stdout=subprocess.PIPE,
122                            stderr=subprocess.STDOUT,
123                            universal_newlines=True)
124    exitcode = subp.wait()
125    if exitcode < 0:
126        sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
127    return subp.communicate()[0]
128
129def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]):
130    args = [ 'info' ]
131    if imgopts:
132        args.append('--image-opts')
133    else:
134        args += [ '-f', imgfmt ]
135    args += extra_args
136    args.append(filename)
137
138    output = qemu_img_pipe(*args)
139    if not filter_path:
140        filter_path = filename
141    log(filter_img_info(output, filter_path))
142
143def qemu_io(*args):
144    '''Run qemu-io and return the stdout data'''
145    args = qemu_io_args + list(args)
146    subp = subprocess.Popen(args, stdout=subprocess.PIPE,
147                            stderr=subprocess.STDOUT,
148                            universal_newlines=True)
149    exitcode = subp.wait()
150    if exitcode < 0:
151        sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
152    return subp.communicate()[0]
153
154def qemu_io_silent(*args):
155    '''Run qemu-io and return the exit code, suppressing stdout'''
156    args = qemu_io_args + list(args)
157    exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
158    if exitcode < 0:
159        sys.stderr.write('qemu-io received signal %i: %s\n' %
160                         (-exitcode, ' '.join(args)))
161    return exitcode
162
163
164class QemuIoInteractive:
165    def __init__(self, *args):
166        self.args = qemu_io_args + list(args)
167        self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
168                                   stdout=subprocess.PIPE,
169                                   stderr=subprocess.STDOUT,
170                                   universal_newlines=True)
171        assert self._p.stdout.read(9) == 'qemu-io> '
172
173    def close(self):
174        self._p.communicate('q\n')
175
176    def _read_output(self):
177        pattern = 'qemu-io> '
178        n = len(pattern)
179        pos = 0
180        s = []
181        while pos != n:
182            c = self._p.stdout.read(1)
183            # check unexpected EOF
184            assert c != ''
185            s.append(c)
186            if c == pattern[pos]:
187                pos += 1
188            else:
189                pos = 0
190
191        return ''.join(s[:-n])
192
193    def cmd(self, cmd):
194        # quit command is in close(), '\n' is added automatically
195        assert '\n' not in cmd
196        cmd = cmd.strip()
197        assert cmd != 'q' and cmd != 'quit'
198        self._p.stdin.write(cmd + '\n')
199        self._p.stdin.flush()
200        return self._read_output()
201
202
203def qemu_nbd(*args):
204    '''Run qemu-nbd in daemon mode and return the parent's exit code'''
205    return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
206
207def qemu_nbd_pipe(*args):
208    '''Run qemu-nbd in daemon mode and return both the parent's exit code
209       and its output'''
210    subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args),
211                            stdout=subprocess.PIPE,
212                            stderr=subprocess.STDOUT,
213                            universal_newlines=True)
214    exitcode = subp.wait()
215    if exitcode < 0:
216        sys.stderr.write('qemu-nbd received signal %i: %s\n' %
217                         (-exitcode,
218                          ' '.join(qemu_nbd_args + ['--fork'] + list(args))))
219    return exitcode, subp.communicate()[0]
220
221def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
222    '''Return True if two image files are identical'''
223    return qemu_img('compare', '-f', fmt1,
224                    '-F', fmt2, img1, img2) == 0
225
226def create_image(name, size):
227    '''Create a fully-allocated raw image with sector markers'''
228    file = open(name, 'wb')
229    i = 0
230    while i < size:
231        sector = struct.pack('>l504xl', i // 512, i // 512)
232        file.write(sector)
233        i = i + 512
234    file.close()
235
236def image_size(img):
237    '''Return image's virtual size'''
238    r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
239    return json.loads(r)['virtual-size']
240
241def is_str(val):
242    if sys.version_info.major >= 3:
243        return isinstance(val, str)
244    else:
245        return isinstance(val, str) or isinstance(val, unicode)
246
247test_dir_re = re.compile(r"%s" % test_dir)
248def filter_test_dir(msg):
249    return test_dir_re.sub("TEST_DIR", msg)
250
251win32_re = re.compile(r"\r")
252def filter_win32(msg):
253    return win32_re.sub("", msg)
254
255qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
256def filter_qemu_io(msg):
257    msg = filter_win32(msg)
258    return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
259
260chown_re = re.compile(r"chown [0-9]+:[0-9]+")
261def filter_chown(msg):
262    return chown_re.sub("chown UID:GID", msg)
263
264def filter_qmp_event(event):
265    '''Filter a QMP event dict'''
266    event = dict(event)
267    if 'timestamp' in event:
268        event['timestamp']['seconds'] = 'SECS'
269        event['timestamp']['microseconds'] = 'USECS'
270    return event
271
272def filter_qmp(qmsg, filter_fn):
273    '''Given a string filter, filter a QMP object's values.
274    filter_fn takes a (key, value) pair.'''
275    # Iterate through either lists or dicts;
276    if isinstance(qmsg, list):
277        items = enumerate(qmsg)
278    else:
279        items = qmsg.items()
280
281    for k, v in items:
282        if isinstance(v, list) or isinstance(v, dict):
283            qmsg[k] = filter_qmp(v, filter_fn)
284        else:
285            qmsg[k] = filter_fn(k, v)
286    return qmsg
287
288def filter_testfiles(msg):
289    prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
290    return msg.replace(prefix, 'TEST_DIR/PID-')
291
292def filter_qmp_testfiles(qmsg):
293    def _filter(key, value):
294        if is_str(value):
295            return filter_testfiles(value)
296        return value
297    return filter_qmp(qmsg, _filter)
298
299def filter_generated_node_ids(msg):
300    return re.sub("#block[0-9]+", "NODE_NAME", msg)
301
302def filter_img_info(output, filename):
303    lines = []
304    for line in output.split('\n'):
305        if 'disk size' in line or 'actual-size' in line:
306            continue
307        line = line.replace(filename, 'TEST_IMG') \
308                   .replace(imgfmt, 'IMGFMT')
309        line = re.sub('iters: [0-9]+', 'iters: XXX', line)
310        line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
311        line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
312        lines.append(line)
313    return '\n'.join(lines)
314
315def filter_imgfmt(msg):
316    return msg.replace(imgfmt, 'IMGFMT')
317
318def filter_qmp_imgfmt(qmsg):
319    def _filter(key, value):
320        if is_str(value):
321            return filter_imgfmt(value)
322        return value
323    return filter_qmp(qmsg, _filter)
324
325def log(msg, filters=[], indent=None):
326    '''Logs either a string message or a JSON serializable message (like QMP).
327    If indent is provided, JSON serializable messages are pretty-printed.'''
328    for flt in filters:
329        msg = flt(msg)
330    if isinstance(msg, dict) or isinstance(msg, list):
331        # Python < 3.4 needs to know not to add whitespace when pretty-printing:
332        separators = (', ', ': ') if indent is None else (',', ': ')
333        # Don't sort if it's already sorted
334        do_sort = not isinstance(msg, OrderedDict)
335        print(json.dumps(msg, sort_keys=do_sort,
336                         indent=indent, separators=separators))
337    else:
338        print(msg)
339
340class Timeout:
341    def __init__(self, seconds, errmsg = "Timeout"):
342        self.seconds = seconds
343        self.errmsg = errmsg
344    def __enter__(self):
345        signal.signal(signal.SIGALRM, self.timeout)
346        signal.setitimer(signal.ITIMER_REAL, self.seconds)
347        return self
348    def __exit__(self, type, value, traceback):
349        signal.setitimer(signal.ITIMER_REAL, 0)
350        return False
351    def timeout(self, signum, frame):
352        raise Exception(self.errmsg)
353
354
355class FilePath(object):
356    '''An auto-generated filename that cleans itself up.
357
358    Use this context manager to generate filenames and ensure that the file
359    gets deleted::
360
361        with TestFilePath('test.img') as img_path:
362            qemu_img('create', img_path, '1G')
363        # migration_sock_path is automatically deleted
364    '''
365    def __init__(self, name):
366        filename = '{0}-{1}'.format(os.getpid(), name)
367        self.path = os.path.join(test_dir, filename)
368
369    def __enter__(self):
370        return self.path
371
372    def __exit__(self, exc_type, exc_val, exc_tb):
373        try:
374            os.remove(self.path)
375        except OSError:
376            pass
377        return False
378
379
380def file_path_remover():
381    for path in reversed(file_path_remover.paths):
382        try:
383            os.remove(path)
384        except OSError:
385            pass
386
387
388def file_path(*names):
389    ''' Another way to get auto-generated filename that cleans itself up.
390
391    Use is as simple as:
392
393    img_a, img_b = file_path('a.img', 'b.img')
394    sock = file_path('socket')
395    '''
396
397    if not hasattr(file_path_remover, 'paths'):
398        file_path_remover.paths = []
399        atexit.register(file_path_remover)
400
401    paths = []
402    for name in names:
403        filename = '{0}-{1}'.format(os.getpid(), name)
404        path = os.path.join(test_dir, filename)
405        file_path_remover.paths.append(path)
406        paths.append(path)
407
408    return paths[0] if len(paths) == 1 else paths
409
410def remote_filename(path):
411    if imgproto == 'file':
412        return path
413    elif imgproto == 'ssh':
414        return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
415    else:
416        raise Exception("Protocol %s not supported" % (imgproto))
417
418class VM(qtest.QEMUQtestMachine):
419    '''A QEMU VM'''
420
421    def __init__(self, path_suffix=''):
422        name = "qemu%s-%d" % (path_suffix, os.getpid())
423        super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
424                                 test_dir=test_dir,
425                                 socket_scm_helper=socket_scm_helper)
426        self._num_drives = 0
427
428    def add_object(self, opts):
429        self._args.append('-object')
430        self._args.append(opts)
431        return self
432
433    def add_device(self, opts):
434        self._args.append('-device')
435        self._args.append(opts)
436        return self
437
438    def add_drive_raw(self, opts):
439        self._args.append('-drive')
440        self._args.append(opts)
441        return self
442
443    def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
444        '''Add a virtio-blk drive to the VM'''
445        options = ['if=%s' % interface,
446                   'id=drive%d' % self._num_drives]
447
448        if path is not None:
449            options.append('file=%s' % path)
450            options.append('format=%s' % format)
451            options.append('cache=%s' % cachemode)
452
453        if opts:
454            options.append(opts)
455
456        if format == 'luks' and 'key-secret' not in opts:
457            # default luks support
458            if luks_default_secret_object not in self._args:
459                self.add_object(luks_default_secret_object)
460
461            options.append(luks_default_key_secret_opt)
462
463        self._args.append('-drive')
464        self._args.append(','.join(options))
465        self._num_drives += 1
466        return self
467
468    def add_blockdev(self, opts):
469        self._args.append('-blockdev')
470        if isinstance(opts, str):
471            self._args.append(opts)
472        else:
473            self._args.append(','.join(opts))
474        return self
475
476    def add_incoming(self, addr):
477        self._args.append('-incoming')
478        self._args.append(addr)
479        return self
480
481    def pause_drive(self, drive, event=None):
482        '''Pause drive r/w operations'''
483        if not event:
484            self.pause_drive(drive, "read_aio")
485            self.pause_drive(drive, "write_aio")
486            return
487        self.qmp('human-monitor-command',
488                    command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
489
490    def resume_drive(self, drive):
491        self.qmp('human-monitor-command',
492                    command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
493
494    def hmp_qemu_io(self, drive, cmd):
495        '''Write to a given drive using an HMP command'''
496        return self.qmp('human-monitor-command',
497                        command_line='qemu-io %s "%s"' % (drive, cmd))
498
499    def flatten_qmp_object(self, obj, output=None, basestr=''):
500        if output is None:
501            output = dict()
502        if isinstance(obj, list):
503            for i in range(len(obj)):
504                self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
505        elif isinstance(obj, dict):
506            for key in obj:
507                self.flatten_qmp_object(obj[key], output, basestr + key + '.')
508        else:
509            output[basestr[:-1]] = obj # Strip trailing '.'
510        return output
511
512    def qmp_to_opts(self, obj):
513        obj = self.flatten_qmp_object(obj)
514        output_list = list()
515        for key in obj:
516            output_list += [key + '=' + obj[key]]
517        return ','.join(output_list)
518
519    def get_qmp_events_filtered(self, wait=True):
520        result = []
521        for ev in self.get_qmp_events(wait=wait):
522            result.append(filter_qmp_event(ev))
523        return result
524
525    def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
526        full_cmd = OrderedDict((
527            ("execute", cmd),
528            ("arguments", ordered_qmp(kwargs))
529        ))
530        log(full_cmd, filters, indent=indent)
531        result = self.qmp(cmd, **kwargs)
532        log(result, filters, indent=indent)
533        return result
534
535    # Returns None on success, and an error string on failure
536    def run_job(self, job, auto_finalize=True, auto_dismiss=False):
537        error = None
538        while True:
539            for ev in self.get_qmp_events_filtered(wait=True):
540                if ev['event'] == 'JOB_STATUS_CHANGE':
541                    status = ev['data']['status']
542                    if status == 'aborting':
543                        result = self.qmp('query-jobs')
544                        for j in result['return']:
545                            if j['id'] == job:
546                                error = j['error']
547                                log('Job failed: %s' % (j['error']))
548                    elif status == 'pending' and not auto_finalize:
549                        self.qmp_log('job-finalize', id=job)
550                    elif status == 'concluded' and not auto_dismiss:
551                        self.qmp_log('job-dismiss', id=job)
552                    elif status == 'null':
553                        return error
554                else:
555                    log(ev)
556
557    def node_info(self, node_name):
558        nodes = self.qmp('query-named-block-nodes')
559        for x in nodes['return']:
560            if x['node-name'] == node_name:
561                return x
562        return None
563
564
565index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
566
567class QMPTestCase(unittest.TestCase):
568    '''Abstract base class for QMP test cases'''
569
570    def dictpath(self, d, path):
571        '''Traverse a path in a nested dict'''
572        for component in path.split('/'):
573            m = index_re.match(component)
574            if m:
575                component, idx = m.groups()
576                idx = int(idx)
577
578            if not isinstance(d, dict) or component not in d:
579                self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
580            d = d[component]
581
582            if m:
583                if not isinstance(d, list):
584                    self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
585                try:
586                    d = d[idx]
587                except IndexError:
588                    self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
589        return d
590
591    def assert_qmp_absent(self, d, path):
592        try:
593            result = self.dictpath(d, path)
594        except AssertionError:
595            return
596        self.fail('path "%s" has value "%s"' % (path, str(result)))
597
598    def assert_qmp(self, d, path, value):
599        '''Assert that the value for a specific path in a QMP dict
600           matches.  When given a list of values, assert that any of
601           them matches.'''
602
603        result = self.dictpath(d, path)
604
605        # [] makes no sense as a list of valid values, so treat it as
606        # an actual single value.
607        if isinstance(value, list) and value != []:
608            for v in value:
609                if result == v:
610                    return
611            self.fail('no match for "%s" in %s' % (str(result), str(value)))
612        else:
613            self.assertEqual(result, value,
614                             'values not equal "%s" and "%s"'
615                                 % (str(result), str(value)))
616
617    def assert_no_active_block_jobs(self):
618        result = self.vm.qmp('query-block-jobs')
619        self.assert_qmp(result, 'return', [])
620
621    def assert_has_block_node(self, node_name=None, file_name=None):
622        """Issue a query-named-block-nodes and assert node_name and/or
623        file_name is present in the result"""
624        def check_equal_or_none(a, b):
625            return a == None or b == None or a == b
626        assert node_name or file_name
627        result = self.vm.qmp('query-named-block-nodes')
628        for x in result["return"]:
629            if check_equal_or_none(x.get("node-name"), node_name) and \
630                    check_equal_or_none(x.get("file"), file_name):
631                return
632        self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
633                (node_name, file_name, result))
634
635    def assert_json_filename_equal(self, json_filename, reference):
636        '''Asserts that the given filename is a json: filename and that its
637           content is equal to the given reference object'''
638        self.assertEqual(json_filename[:5], 'json:')
639        self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
640                         self.vm.flatten_qmp_object(reference))
641
642    def cancel_and_wait(self, drive='drive0', force=False, resume=False):
643        '''Cancel a block job and wait for it to finish, returning the event'''
644        result = self.vm.qmp('block-job-cancel', device=drive, force=force)
645        self.assert_qmp(result, 'return', {})
646
647        if resume:
648            self.vm.resume_drive(drive)
649
650        cancelled = False
651        result = None
652        while not cancelled:
653            for event in self.vm.get_qmp_events(wait=True):
654                if event['event'] == 'BLOCK_JOB_COMPLETED' or \
655                   event['event'] == 'BLOCK_JOB_CANCELLED':
656                    self.assert_qmp(event, 'data/device', drive)
657                    result = event
658                    cancelled = True
659                elif event['event'] == 'JOB_STATUS_CHANGE':
660                    self.assert_qmp(event, 'data/id', drive)
661
662
663        self.assert_no_active_block_jobs()
664        return result
665
666    def wait_until_completed(self, drive='drive0', check_offset=True):
667        '''Wait for a block job to finish, returning the event'''
668        while True:
669            for event in self.vm.get_qmp_events(wait=True):
670                if event['event'] == 'BLOCK_JOB_COMPLETED':
671                    self.assert_qmp(event, 'data/device', drive)
672                    self.assert_qmp_absent(event, 'data/error')
673                    if check_offset:
674                        self.assert_qmp(event, 'data/offset', event['data']['len'])
675                    self.assert_no_active_block_jobs()
676                    return event
677                elif event['event'] == 'JOB_STATUS_CHANGE':
678                    self.assert_qmp(event, 'data/id', drive)
679
680    def wait_ready(self, drive='drive0'):
681        '''Wait until a block job BLOCK_JOB_READY event'''
682        f = {'data': {'type': 'mirror', 'device': drive } }
683        event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
684
685    def wait_ready_and_cancel(self, drive='drive0'):
686        self.wait_ready(drive=drive)
687        event = self.cancel_and_wait(drive=drive)
688        self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
689        self.assert_qmp(event, 'data/type', 'mirror')
690        self.assert_qmp(event, 'data/offset', event['data']['len'])
691
692    def complete_and_wait(self, drive='drive0', wait_ready=True):
693        '''Complete a block job and wait for it to finish'''
694        if wait_ready:
695            self.wait_ready(drive=drive)
696
697        result = self.vm.qmp('block-job-complete', device=drive)
698        self.assert_qmp(result, 'return', {})
699
700        event = self.wait_until_completed(drive=drive)
701        self.assert_qmp(event, 'data/type', 'mirror')
702
703    def pause_wait(self, job_id='job0'):
704        with Timeout(1, "Timeout waiting for job to pause"):
705            while True:
706                result = self.vm.qmp('query-block-jobs')
707                found = False
708                for job in result['return']:
709                    if job['device'] == job_id:
710                        found = True
711                        if job['paused'] == True and job['busy'] == False:
712                            return job
713                        break
714                assert found
715
716    def pause_job(self, job_id='job0', wait=True):
717        result = self.vm.qmp('block-job-pause', device=job_id)
718        self.assert_qmp(result, 'return', {})
719        if wait:
720            return self.pause_wait(job_id)
721        return result
722
723
724def notrun(reason):
725    '''Skip this test suite'''
726    # Each test in qemu-iotests has a number ("seq")
727    seq = os.path.basename(sys.argv[0])
728
729    open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
730    print('%s not run: %s' % (seq, reason))
731    sys.exit(0)
732
733def case_notrun(reason):
734    '''Skip this test case'''
735    # Each test in qemu-iotests has a number ("seq")
736    seq = os.path.basename(sys.argv[0])
737
738    open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
739        '    [case not run] ' + reason + '\n')
740
741def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
742    assert not (supported_fmts and unsupported_fmts)
743
744    if 'generic' in supported_fmts and \
745            os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
746        # similar to
747        #   _supported_fmt generic
748        # for bash tests
749        return
750
751    not_sup = supported_fmts and (imgfmt not in supported_fmts)
752    if not_sup or (imgfmt in unsupported_fmts):
753        notrun('not suitable for this image format: %s' % imgfmt)
754
755def verify_protocol(supported=[], unsupported=[]):
756    assert not (supported and unsupported)
757
758    if 'generic' in supported:
759        return
760
761    not_sup = supported and (imgproto not in supported)
762    if not_sup or (imgproto in unsupported):
763        notrun('not suitable for this protocol: %s' % imgproto)
764
765def verify_platform(supported_oses=['linux']):
766    if True not in [sys.platform.startswith(x) for x in supported_oses]:
767        notrun('not suitable for this OS: %s' % sys.platform)
768
769def verify_cache_mode(supported_cache_modes=[]):
770    if supported_cache_modes and (cachemode not in supported_cache_modes):
771        notrun('not suitable for this cache mode: %s' % cachemode)
772
773def supports_quorum():
774    return 'quorum' in qemu_img_pipe('--help')
775
776def verify_quorum():
777    '''Skip test suite if quorum support is not available'''
778    if not supports_quorum():
779        notrun('quorum support missing')
780
781def qemu_pipe(*args):
782    '''Run qemu with an option to print something and exit (e.g. a help option),
783    and return its output'''
784    args = [qemu_prog] + qemu_opts + list(args)
785    subp = subprocess.Popen(args, stdout=subprocess.PIPE,
786                            stderr=subprocess.STDOUT,
787                            universal_newlines=True)
788    exitcode = subp.wait()
789    if exitcode < 0:
790        sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
791                         ' '.join(args)))
792    return subp.communicate()[0]
793
794def supported_formats(read_only=False):
795    '''Set 'read_only' to True to check ro-whitelist
796       Otherwise, rw-whitelist is checked'''
797    format_message = qemu_pipe("-drive", "format=help")
798    line = 1 if read_only else 0
799    return format_message.splitlines()[line].split(":")[1].split()
800
801def skip_if_unsupported(required_formats=[], read_only=False):
802    '''Skip Test Decorator
803       Runs the test if all the required formats are whitelisted'''
804    def skip_test_decorator(func):
805        def func_wrapper(*args, **kwargs):
806            usf_list = list(set(required_formats) -
807                            set(supported_formats(read_only)))
808            if usf_list:
809                case_notrun('{}: formats {} are not whitelisted'.format(
810                    args[0], usf_list))
811            else:
812                return func(*args, **kwargs)
813        return func_wrapper
814    return skip_test_decorator
815
816def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
817         unsupported_fmts=[]):
818    '''Run tests'''
819
820    global debug
821
822    # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
823    # indicate that we're not being run via "check". There may be
824    # other things set up by "check" that individual test cases rely
825    # on.
826    if test_dir is None or qemu_default_machine is None:
827        sys.stderr.write('Please run this test via the "check" script\n')
828        sys.exit(os.EX_USAGE)
829
830    debug = '-d' in sys.argv
831    verbosity = 1
832    verify_image_format(supported_fmts, unsupported_fmts)
833    verify_platform(supported_oses)
834    verify_cache_mode(supported_cache_modes)
835
836    if debug:
837        output = sys.stdout
838        verbosity = 2
839        sys.argv.remove('-d')
840    else:
841        # We need to filter out the time taken from the output so that
842        # qemu-iotest can reliably diff the results against master output.
843        if sys.version_info.major >= 3:
844            output = io.StringIO()
845        else:
846            # io.StringIO is for unicode strings, which is not what
847            # 2.x's test runner emits.
848            output = io.BytesIO()
849
850    logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
851
852    class MyTestRunner(unittest.TextTestRunner):
853        def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
854            unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
855
856    # unittest.main() will use sys.exit() so expect a SystemExit exception
857    try:
858        unittest.main(testRunner=MyTestRunner)
859    finally:
860        if not debug:
861            sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))
862