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