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