iotests: Add virtio-scsi device helper
[qemu/ar7.git] / tests / qemu-iotests / iotests.py
blob84438e837cb347857dcb2c98afd1786969fe685b
1 from __future__ import print_function
2 # Common utilities and Python wrappers for qemu-iotests
4 # Copyright (C) 2012 IBM Corp.
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.
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.
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/>.
20 import errno
21 import os
22 import re
23 import subprocess
24 import string
25 import unittest
26 import sys
27 import struct
28 import json
29 import signal
30 import logging
31 import atexit
32 import io
33 from collections import OrderedDict
35 sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36 from qemu import qtest
39 # This will not work if arguments contain spaces but is necessary if we
40 # want to support the override options that ./check supports.
41 qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
42 if os.environ.get('QEMU_IMG_OPTIONS'):
43 qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
45 qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
46 if os.environ.get('QEMU_IO_OPTIONS'):
47 qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
49 qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
50 if os.environ.get('QEMU_NBD_OPTIONS'):
51 qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
53 qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
54 qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
56 imgfmt = os.environ.get('IMGFMT', 'raw')
57 imgproto = os.environ.get('IMGPROTO', 'file')
58 test_dir = os.environ.get('TEST_DIR')
59 output_dir = os.environ.get('OUTPUT_DIR', '.')
60 cachemode = os.environ.get('CACHEMODE')
61 qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
63 socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
65 luks_default_secret_object = 'secret,id=keysec0,data=' + \
66 os.environ.get('IMGKEYSECRET', '')
67 luks_default_key_secret_opt = 'key-secret=keysec0'
70 def qemu_img(*args):
71 '''Run qemu-img and return the exit code'''
72 devnull = open('/dev/null', 'r+')
73 exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
74 if exitcode < 0:
75 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
76 return exitcode
78 def ordered_qmp(qmsg, conv_keys=True):
79 # Dictionaries are not ordered prior to 3.6, therefore:
80 if isinstance(qmsg, list):
81 return [ordered_qmp(atom) for atom in qmsg]
82 if isinstance(qmsg, dict):
83 od = OrderedDict()
84 for k, v in sorted(qmsg.items()):
85 if conv_keys:
86 k = k.replace('_', '-')
87 od[k] = ordered_qmp(v, conv_keys=False)
88 return od
89 return qmsg
91 def qemu_img_create(*args):
92 args = list(args)
94 # default luks support
95 if '-f' in args and args[args.index('-f') + 1] == 'luks':
96 if '-o' in args:
97 i = args.index('-o')
98 if 'key-secret' not in args[i + 1]:
99 args[i + 1].append(luks_default_key_secret_opt)
100 args.insert(i + 2, '--object')
101 args.insert(i + 3, luks_default_secret_object)
102 else:
103 args = ['-o', luks_default_key_secret_opt,
104 '--object', luks_default_secret_object] + args
106 args.insert(0, 'create')
108 return qemu_img(*args)
110 def qemu_img_verbose(*args):
111 '''Run qemu-img without suppressing its output and return the exit code'''
112 exitcode = subprocess.call(qemu_img_args + list(args))
113 if exitcode < 0:
114 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
115 return exitcode
117 def qemu_img_pipe(*args):
118 '''Run qemu-img and return its output'''
119 subp = subprocess.Popen(qemu_img_args + list(args),
120 stdout=subprocess.PIPE,
121 stderr=subprocess.STDOUT,
122 universal_newlines=True)
123 exitcode = subp.wait()
124 if exitcode < 0:
125 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
126 return subp.communicate()[0]
128 def qemu_img_log(*args):
129 result = qemu_img_pipe(*args)
130 log(result, filters=[filter_testfiles])
131 return result
133 def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]):
134 args = [ 'info' ]
135 if imgopts:
136 args.append('--image-opts')
137 else:
138 args += [ '-f', imgfmt ]
139 args += extra_args
140 args.append(filename)
142 output = qemu_img_pipe(*args)
143 if not filter_path:
144 filter_path = filename
145 log(filter_img_info(output, filter_path))
147 def qemu_io(*args):
148 '''Run qemu-io and return the stdout data'''
149 args = qemu_io_args + list(args)
150 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
151 stderr=subprocess.STDOUT,
152 universal_newlines=True)
153 exitcode = subp.wait()
154 if exitcode < 0:
155 sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
156 return subp.communicate()[0]
158 def qemu_io_silent(*args):
159 '''Run qemu-io and return the exit code, suppressing stdout'''
160 args = qemu_io_args + list(args)
161 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
162 if exitcode < 0:
163 sys.stderr.write('qemu-io received signal %i: %s\n' %
164 (-exitcode, ' '.join(args)))
165 return exitcode
167 def get_virtio_scsi_device():
168 if qemu_default_machine == 's390-ccw-virtio':
169 return 'virtio-scsi-ccw'
170 return 'virtio-scsi-pci'
172 class QemuIoInteractive:
173 def __init__(self, *args):
174 self.args = qemu_io_args + list(args)
175 self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
176 stdout=subprocess.PIPE,
177 stderr=subprocess.STDOUT,
178 universal_newlines=True)
179 assert self._p.stdout.read(9) == 'qemu-io> '
181 def close(self):
182 self._p.communicate('q\n')
184 def _read_output(self):
185 pattern = 'qemu-io> '
186 n = len(pattern)
187 pos = 0
188 s = []
189 while pos != n:
190 c = self._p.stdout.read(1)
191 # check unexpected EOF
192 assert c != ''
193 s.append(c)
194 if c == pattern[pos]:
195 pos += 1
196 else:
197 pos = 0
199 return ''.join(s[:-n])
201 def cmd(self, cmd):
202 # quit command is in close(), '\n' is added automatically
203 assert '\n' not in cmd
204 cmd = cmd.strip()
205 assert cmd != 'q' and cmd != 'quit'
206 self._p.stdin.write(cmd + '\n')
207 self._p.stdin.flush()
208 return self._read_output()
211 def qemu_nbd(*args):
212 '''Run qemu-nbd in daemon mode and return the parent's exit code'''
213 return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
215 def qemu_nbd_early_pipe(*args):
216 '''Run qemu-nbd in daemon mode and return both the parent's exit code
217 and its output in case of an error'''
218 subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args),
219 stdout=subprocess.PIPE,
220 stderr=subprocess.STDOUT,
221 universal_newlines=True)
222 exitcode = subp.wait()
223 if exitcode < 0:
224 sys.stderr.write('qemu-nbd received signal %i: %s\n' %
225 (-exitcode,
226 ' '.join(qemu_nbd_args + ['--fork'] + list(args))))
227 if exitcode == 0:
228 return exitcode, ''
229 else:
230 return exitcode, subp.communicate()[0]
232 def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
233 '''Return True if two image files are identical'''
234 return qemu_img('compare', '-f', fmt1,
235 '-F', fmt2, img1, img2) == 0
237 def create_image(name, size):
238 '''Create a fully-allocated raw image with sector markers'''
239 file = open(name, 'wb')
240 i = 0
241 while i < size:
242 sector = struct.pack('>l504xl', i // 512, i // 512)
243 file.write(sector)
244 i = i + 512
245 file.close()
247 def image_size(img):
248 '''Return image's virtual size'''
249 r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
250 return json.loads(r)['virtual-size']
252 def is_str(val):
253 if sys.version_info.major >= 3:
254 return isinstance(val, str)
255 else:
256 return isinstance(val, str) or isinstance(val, unicode)
258 test_dir_re = re.compile(r"%s" % test_dir)
259 def filter_test_dir(msg):
260 return test_dir_re.sub("TEST_DIR", msg)
262 win32_re = re.compile(r"\r")
263 def filter_win32(msg):
264 return win32_re.sub("", msg)
266 qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
267 def filter_qemu_io(msg):
268 msg = filter_win32(msg)
269 return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
271 chown_re = re.compile(r"chown [0-9]+:[0-9]+")
272 def filter_chown(msg):
273 return chown_re.sub("chown UID:GID", msg)
275 def filter_qmp_event(event):
276 '''Filter a QMP event dict'''
277 event = dict(event)
278 if 'timestamp' in event:
279 event['timestamp']['seconds'] = 'SECS'
280 event['timestamp']['microseconds'] = 'USECS'
281 return event
283 def filter_qmp(qmsg, filter_fn):
284 '''Given a string filter, filter a QMP object's values.
285 filter_fn takes a (key, value) pair.'''
286 # Iterate through either lists or dicts;
287 if isinstance(qmsg, list):
288 items = enumerate(qmsg)
289 else:
290 items = qmsg.items()
292 for k, v in items:
293 if isinstance(v, list) or isinstance(v, dict):
294 qmsg[k] = filter_qmp(v, filter_fn)
295 else:
296 qmsg[k] = filter_fn(k, v)
297 return qmsg
299 def filter_testfiles(msg):
300 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
301 return msg.replace(prefix, 'TEST_DIR/PID-')
303 def filter_qmp_testfiles(qmsg):
304 def _filter(key, value):
305 if is_str(value):
306 return filter_testfiles(value)
307 return value
308 return filter_qmp(qmsg, _filter)
310 def filter_generated_node_ids(msg):
311 return re.sub("#block[0-9]+", "NODE_NAME", msg)
313 def filter_img_info(output, filename):
314 lines = []
315 for line in output.split('\n'):
316 if 'disk size' in line or 'actual-size' in line:
317 continue
318 line = line.replace(filename, 'TEST_IMG') \
319 .replace(imgfmt, 'IMGFMT')
320 line = re.sub('iters: [0-9]+', 'iters: XXX', line)
321 line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
322 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
323 lines.append(line)
324 return '\n'.join(lines)
326 def filter_imgfmt(msg):
327 return msg.replace(imgfmt, 'IMGFMT')
329 def filter_qmp_imgfmt(qmsg):
330 def _filter(key, value):
331 if is_str(value):
332 return filter_imgfmt(value)
333 return value
334 return filter_qmp(qmsg, _filter)
336 def log(msg, filters=[], indent=None):
337 '''Logs either a string message or a JSON serializable message (like QMP).
338 If indent is provided, JSON serializable messages are pretty-printed.'''
339 for flt in filters:
340 msg = flt(msg)
341 if isinstance(msg, dict) or isinstance(msg, list):
342 # Python < 3.4 needs to know not to add whitespace when pretty-printing:
343 separators = (', ', ': ') if indent is None else (',', ': ')
344 # Don't sort if it's already sorted
345 do_sort = not isinstance(msg, OrderedDict)
346 print(json.dumps(msg, sort_keys=do_sort,
347 indent=indent, separators=separators))
348 else:
349 print(msg)
351 class Timeout:
352 def __init__(self, seconds, errmsg = "Timeout"):
353 self.seconds = seconds
354 self.errmsg = errmsg
355 def __enter__(self):
356 signal.signal(signal.SIGALRM, self.timeout)
357 signal.setitimer(signal.ITIMER_REAL, self.seconds)
358 return self
359 def __exit__(self, type, value, traceback):
360 signal.setitimer(signal.ITIMER_REAL, 0)
361 return False
362 def timeout(self, signum, frame):
363 raise Exception(self.errmsg)
365 def file_pattern(name):
366 return "{0}-{1}".format(os.getpid(), name)
368 class FilePaths(object):
370 FilePaths is an auto-generated filename that cleans itself up.
372 Use this context manager to generate filenames and ensure that the file
373 gets deleted::
375 with FilePaths(['test.img']) as img_path:
376 qemu_img('create', img_path, '1G')
377 # migration_sock_path is automatically deleted
379 def __init__(self, names):
380 self.paths = []
381 for name in names:
382 self.paths.append(os.path.join(test_dir, file_pattern(name)))
384 def __enter__(self):
385 return self.paths
387 def __exit__(self, exc_type, exc_val, exc_tb):
388 try:
389 for path in self.paths:
390 os.remove(path)
391 except OSError:
392 pass
393 return False
395 class FilePath(FilePaths):
397 FilePath is a specialization of FilePaths that takes a single filename.
399 def __init__(self, name):
400 super(FilePath, self).__init__([name])
402 def __enter__(self):
403 return self.paths[0]
405 def file_path_remover():
406 for path in reversed(file_path_remover.paths):
407 try:
408 os.remove(path)
409 except OSError:
410 pass
413 def file_path(*names):
414 ''' Another way to get auto-generated filename that cleans itself up.
416 Use is as simple as:
418 img_a, img_b = file_path('a.img', 'b.img')
419 sock = file_path('socket')
422 if not hasattr(file_path_remover, 'paths'):
423 file_path_remover.paths = []
424 atexit.register(file_path_remover)
426 paths = []
427 for name in names:
428 filename = file_pattern(name)
429 path = os.path.join(test_dir, filename)
430 file_path_remover.paths.append(path)
431 paths.append(path)
433 return paths[0] if len(paths) == 1 else paths
435 def remote_filename(path):
436 if imgproto == 'file':
437 return path
438 elif imgproto == 'ssh':
439 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
440 else:
441 raise Exception("Protocol %s not supported" % (imgproto))
443 class VM(qtest.QEMUQtestMachine):
444 '''A QEMU VM'''
446 def __init__(self, path_suffix=''):
447 name = "qemu%s-%d" % (path_suffix, os.getpid())
448 super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
449 test_dir=test_dir,
450 socket_scm_helper=socket_scm_helper)
451 self._num_drives = 0
453 def add_object(self, opts):
454 self._args.append('-object')
455 self._args.append(opts)
456 return self
458 def add_device(self, opts):
459 self._args.append('-device')
460 self._args.append(opts)
461 return self
463 def add_drive_raw(self, opts):
464 self._args.append('-drive')
465 self._args.append(opts)
466 return self
468 def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
469 '''Add a virtio-blk drive to the VM'''
470 options = ['if=%s' % interface,
471 'id=drive%d' % self._num_drives]
473 if path is not None:
474 options.append('file=%s' % path)
475 options.append('format=%s' % format)
476 options.append('cache=%s' % cachemode)
478 if opts:
479 options.append(opts)
481 if format == 'luks' and 'key-secret' not in opts:
482 # default luks support
483 if luks_default_secret_object not in self._args:
484 self.add_object(luks_default_secret_object)
486 options.append(luks_default_key_secret_opt)
488 self._args.append('-drive')
489 self._args.append(','.join(options))
490 self._num_drives += 1
491 return self
493 def add_blockdev(self, opts):
494 self._args.append('-blockdev')
495 if isinstance(opts, str):
496 self._args.append(opts)
497 else:
498 self._args.append(','.join(opts))
499 return self
501 def add_incoming(self, addr):
502 self._args.append('-incoming')
503 self._args.append(addr)
504 return self
506 def pause_drive(self, drive, event=None):
507 '''Pause drive r/w operations'''
508 if not event:
509 self.pause_drive(drive, "read_aio")
510 self.pause_drive(drive, "write_aio")
511 return
512 self.qmp('human-monitor-command',
513 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
515 def resume_drive(self, drive):
516 self.qmp('human-monitor-command',
517 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
519 def hmp_qemu_io(self, drive, cmd):
520 '''Write to a given drive using an HMP command'''
521 return self.qmp('human-monitor-command',
522 command_line='qemu-io %s "%s"' % (drive, cmd))
524 def flatten_qmp_object(self, obj, output=None, basestr=''):
525 if output is None:
526 output = dict()
527 if isinstance(obj, list):
528 for i in range(len(obj)):
529 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
530 elif isinstance(obj, dict):
531 for key in obj:
532 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
533 else:
534 output[basestr[:-1]] = obj # Strip trailing '.'
535 return output
537 def qmp_to_opts(self, obj):
538 obj = self.flatten_qmp_object(obj)
539 output_list = list()
540 for key in obj:
541 output_list += [key + '=' + obj[key]]
542 return ','.join(output_list)
544 def get_qmp_events_filtered(self, wait=60.0):
545 result = []
546 for ev in self.get_qmp_events(wait=wait):
547 result.append(filter_qmp_event(ev))
548 return result
550 def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
551 full_cmd = OrderedDict((
552 ("execute", cmd),
553 ("arguments", ordered_qmp(kwargs))
555 log(full_cmd, filters, indent=indent)
556 result = self.qmp(cmd, **kwargs)
557 log(result, filters, indent=indent)
558 return result
560 # Returns None on success, and an error string on failure
561 def run_job(self, job, auto_finalize=True, auto_dismiss=False,
562 pre_finalize=None, cancel=False, use_log=True, wait=60.0):
564 run_job moves a job from creation through to dismissal.
566 :param job: String. ID of recently-launched job
567 :param auto_finalize: Bool. True if the job was launched with
568 auto_finalize. Defaults to True.
569 :param auto_dismiss: Bool. True if the job was launched with
570 auto_dismiss=True. Defaults to False.
571 :param pre_finalize: Callback. A callable that takes no arguments to be
572 invoked prior to issuing job-finalize, if any.
573 :param cancel: Bool. When true, cancels the job after the pre_finalize
574 callback.
575 :param use_log: Bool. When false, does not log QMP messages.
576 :param wait: Float. Timeout value specifying how long to wait for any
577 event, in seconds. Defaults to 60.0.
579 match_device = {'data': {'device': job}}
580 match_id = {'data': {'id': job}}
581 events = [
582 ('BLOCK_JOB_COMPLETED', match_device),
583 ('BLOCK_JOB_CANCELLED', match_device),
584 ('BLOCK_JOB_ERROR', match_device),
585 ('BLOCK_JOB_READY', match_device),
586 ('BLOCK_JOB_PENDING', match_id),
587 ('JOB_STATUS_CHANGE', match_id)
589 error = None
590 while True:
591 ev = filter_qmp_event(self.events_wait(events))
592 if ev['event'] != 'JOB_STATUS_CHANGE':
593 if use_log:
594 log(ev)
595 continue
596 status = ev['data']['status']
597 if status == 'aborting':
598 result = self.qmp('query-jobs')
599 for j in result['return']:
600 if j['id'] == job:
601 error = j['error']
602 if use_log:
603 log('Job failed: %s' % (j['error']))
604 elif status == 'pending' and not auto_finalize:
605 if pre_finalize:
606 pre_finalize()
607 if cancel and use_log:
608 self.qmp_log('job-cancel', id=job)
609 elif cancel:
610 self.qmp('job-cancel', id=job)
611 elif use_log:
612 self.qmp_log('job-finalize', id=job)
613 else:
614 self.qmp('job-finalize', id=job)
615 elif status == 'concluded' and not auto_dismiss:
616 if use_log:
617 self.qmp_log('job-dismiss', id=job)
618 else:
619 self.qmp('job-dismiss', id=job)
620 elif status == 'null':
621 return error
623 def enable_migration_events(self, name):
624 log('Enabling migration QMP events on %s...' % name)
625 log(self.qmp('migrate-set-capabilities', capabilities=[
627 'capability': 'events',
628 'state': True
632 def wait_migration(self):
633 while True:
634 event = self.event_wait('MIGRATION')
635 log(event, filters=[filter_qmp_event])
636 if event['data']['status'] == 'completed':
637 break
639 def node_info(self, node_name):
640 nodes = self.qmp('query-named-block-nodes')
641 for x in nodes['return']:
642 if x['node-name'] == node_name:
643 return x
644 return None
647 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
649 class QMPTestCase(unittest.TestCase):
650 '''Abstract base class for QMP test cases'''
652 def dictpath(self, d, path):
653 '''Traverse a path in a nested dict'''
654 for component in path.split('/'):
655 m = index_re.match(component)
656 if m:
657 component, idx = m.groups()
658 idx = int(idx)
660 if not isinstance(d, dict) or component not in d:
661 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
662 d = d[component]
664 if m:
665 if not isinstance(d, list):
666 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
667 try:
668 d = d[idx]
669 except IndexError:
670 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
671 return d
673 def assert_qmp_absent(self, d, path):
674 try:
675 result = self.dictpath(d, path)
676 except AssertionError:
677 return
678 self.fail('path "%s" has value "%s"' % (path, str(result)))
680 def assert_qmp(self, d, path, value):
681 '''Assert that the value for a specific path in a QMP dict
682 matches. When given a list of values, assert that any of
683 them matches.'''
685 result = self.dictpath(d, path)
687 # [] makes no sense as a list of valid values, so treat it as
688 # an actual single value.
689 if isinstance(value, list) and value != []:
690 for v in value:
691 if result == v:
692 return
693 self.fail('no match for "%s" in %s' % (str(result), str(value)))
694 else:
695 self.assertEqual(result, value,
696 'values not equal "%s" and "%s"'
697 % (str(result), str(value)))
699 def assert_no_active_block_jobs(self):
700 result = self.vm.qmp('query-block-jobs')
701 self.assert_qmp(result, 'return', [])
703 def assert_has_block_node(self, node_name=None, file_name=None):
704 """Issue a query-named-block-nodes and assert node_name and/or
705 file_name is present in the result"""
706 def check_equal_or_none(a, b):
707 return a == None or b == None or a == b
708 assert node_name or file_name
709 result = self.vm.qmp('query-named-block-nodes')
710 for x in result["return"]:
711 if check_equal_or_none(x.get("node-name"), node_name) and \
712 check_equal_or_none(x.get("file"), file_name):
713 return
714 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
715 (node_name, file_name, result))
717 def assert_json_filename_equal(self, json_filename, reference):
718 '''Asserts that the given filename is a json: filename and that its
719 content is equal to the given reference object'''
720 self.assertEqual(json_filename[:5], 'json:')
721 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
722 self.vm.flatten_qmp_object(reference))
724 def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
725 '''Cancel a block job and wait for it to finish, returning the event'''
726 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
727 self.assert_qmp(result, 'return', {})
729 if resume:
730 self.vm.resume_drive(drive)
732 cancelled = False
733 result = None
734 while not cancelled:
735 for event in self.vm.get_qmp_events(wait=wait):
736 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
737 event['event'] == 'BLOCK_JOB_CANCELLED':
738 self.assert_qmp(event, 'data/device', drive)
739 result = event
740 cancelled = True
741 elif event['event'] == 'JOB_STATUS_CHANGE':
742 self.assert_qmp(event, 'data/id', drive)
745 self.assert_no_active_block_jobs()
746 return result
748 def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
749 '''Wait for a block job to finish, returning the event'''
750 while True:
751 for event in self.vm.get_qmp_events(wait=wait):
752 if event['event'] == 'BLOCK_JOB_COMPLETED':
753 self.assert_qmp(event, 'data/device', drive)
754 self.assert_qmp_absent(event, 'data/error')
755 if check_offset:
756 self.assert_qmp(event, 'data/offset', event['data']['len'])
757 self.assert_no_active_block_jobs()
758 return event
759 elif event['event'] == 'JOB_STATUS_CHANGE':
760 self.assert_qmp(event, 'data/id', drive)
762 def wait_ready(self, drive='drive0'):
763 '''Wait until a block job BLOCK_JOB_READY event'''
764 f = {'data': {'type': 'mirror', 'device': drive } }
765 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
767 def wait_ready_and_cancel(self, drive='drive0'):
768 self.wait_ready(drive=drive)
769 event = self.cancel_and_wait(drive=drive)
770 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
771 self.assert_qmp(event, 'data/type', 'mirror')
772 self.assert_qmp(event, 'data/offset', event['data']['len'])
774 def complete_and_wait(self, drive='drive0', wait_ready=True):
775 '''Complete a block job and wait for it to finish'''
776 if wait_ready:
777 self.wait_ready(drive=drive)
779 result = self.vm.qmp('block-job-complete', device=drive)
780 self.assert_qmp(result, 'return', {})
782 event = self.wait_until_completed(drive=drive)
783 self.assert_qmp(event, 'data/type', 'mirror')
785 def pause_wait(self, job_id='job0'):
786 with Timeout(1, "Timeout waiting for job to pause"):
787 while True:
788 result = self.vm.qmp('query-block-jobs')
789 found = False
790 for job in result['return']:
791 if job['device'] == job_id:
792 found = True
793 if job['paused'] == True and job['busy'] == False:
794 return job
795 break
796 assert found
798 def pause_job(self, job_id='job0', wait=True):
799 result = self.vm.qmp('block-job-pause', device=job_id)
800 self.assert_qmp(result, 'return', {})
801 if wait:
802 return self.pause_wait(job_id)
803 return result
806 def notrun(reason):
807 '''Skip this test suite'''
808 # Each test in qemu-iotests has a number ("seq")
809 seq = os.path.basename(sys.argv[0])
811 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
812 print('%s not run: %s' % (seq, reason))
813 sys.exit(0)
815 def case_notrun(reason):
816 '''Skip this test case'''
817 # Each test in qemu-iotests has a number ("seq")
818 seq = os.path.basename(sys.argv[0])
820 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
821 ' [case not run] ' + reason + '\n')
823 def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
824 assert not (supported_fmts and unsupported_fmts)
826 if 'generic' in supported_fmts and \
827 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
828 # similar to
829 # _supported_fmt generic
830 # for bash tests
831 return
833 not_sup = supported_fmts and (imgfmt not in supported_fmts)
834 if not_sup or (imgfmt in unsupported_fmts):
835 notrun('not suitable for this image format: %s' % imgfmt)
837 def verify_protocol(supported=[], unsupported=[]):
838 assert not (supported and unsupported)
840 if 'generic' in supported:
841 return
843 not_sup = supported and (imgproto not in supported)
844 if not_sup or (imgproto in unsupported):
845 notrun('not suitable for this protocol: %s' % imgproto)
847 def verify_platform(supported_oses=['linux']):
848 if True not in [sys.platform.startswith(x) for x in supported_oses]:
849 notrun('not suitable for this OS: %s' % sys.platform)
851 def verify_cache_mode(supported_cache_modes=[]):
852 if supported_cache_modes and (cachemode not in supported_cache_modes):
853 notrun('not suitable for this cache mode: %s' % cachemode)
855 def supports_quorum():
856 return 'quorum' in qemu_img_pipe('--help')
858 def verify_quorum():
859 '''Skip test suite if quorum support is not available'''
860 if not supports_quorum():
861 notrun('quorum support missing')
863 def qemu_pipe(*args):
864 '''Run qemu with an option to print something and exit (e.g. a help option),
865 and return its output'''
866 args = [qemu_prog] + qemu_opts + list(args)
867 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
868 stderr=subprocess.STDOUT,
869 universal_newlines=True)
870 exitcode = subp.wait()
871 if exitcode < 0:
872 sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
873 ' '.join(args)))
874 return subp.communicate()[0]
876 def supported_formats(read_only=False):
877 '''Set 'read_only' to True to check ro-whitelist
878 Otherwise, rw-whitelist is checked'''
879 format_message = qemu_pipe("-drive", "format=help")
880 line = 1 if read_only else 0
881 return format_message.splitlines()[line].split(":")[1].split()
883 def skip_if_unsupported(required_formats=[], read_only=False):
884 '''Skip Test Decorator
885 Runs the test if all the required formats are whitelisted'''
886 def skip_test_decorator(func):
887 def func_wrapper(*args, **kwargs):
888 usf_list = list(set(required_formats) -
889 set(supported_formats(read_only)))
890 if usf_list:
891 case_notrun('{}: formats {} are not whitelisted'.format(
892 args[0], usf_list))
893 else:
894 return func(*args, **kwargs)
895 return func_wrapper
896 return skip_test_decorator
898 def execute_unittest(output, verbosity, debug):
899 runner = unittest.TextTestRunner(stream=output, descriptions=True,
900 verbosity=verbosity)
901 try:
902 # unittest.main() will use sys.exit(); so expect a SystemExit
903 # exception
904 unittest.main(testRunner=runner)
905 finally:
906 if not debug:
907 sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s',
908 r'Ran \1 tests', output.getvalue()))
910 def execute_test(test_function=None,
911 supported_fmts=[], supported_oses=['linux'],
912 supported_cache_modes=[], unsupported_fmts=[]):
913 """Run either unittest or script-style tests."""
915 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
916 # indicate that we're not being run via "check". There may be
917 # other things set up by "check" that individual test cases rely
918 # on.
919 if test_dir is None or qemu_default_machine is None:
920 sys.stderr.write('Please run this test via the "check" script\n')
921 sys.exit(os.EX_USAGE)
923 debug = '-d' in sys.argv
924 verbosity = 1
925 verify_image_format(supported_fmts, unsupported_fmts)
926 verify_platform(supported_oses)
927 verify_cache_mode(supported_cache_modes)
929 if debug:
930 output = sys.stdout
931 verbosity = 2
932 sys.argv.remove('-d')
933 else:
934 # We need to filter out the time taken from the output so that
935 # qemu-iotest can reliably diff the results against master output.
936 if sys.version_info.major >= 3:
937 output = io.StringIO()
938 else:
939 # io.StringIO is for unicode strings, which is not what
940 # 2.x's test runner emits.
941 output = io.BytesIO()
943 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
945 if not test_function:
946 execute_unittest(output, verbosity, debug)
947 else:
948 test_function()
950 def script_main(test_function, *args, **kwargs):
951 """Run script-style tests outside of the unittest framework"""
952 execute_test(test_function, *args, **kwargs)
954 def main(*args, **kwargs):
955 """Run tests using the unittest framework"""
956 execute_test(None, *args, **kwargs)