]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
iotests: Filter second BLOCK_JOB_ERROR from 229
[mirror_qemu.git] / tests / qemu-iotests / iotests.py
CommitLineData
f03868bd 1from __future__ import print_function
f345cfd0
SH
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
c1c71e49 20import errno
f345cfd0
SH
21import os
22import re
23import subprocess
4f450568 24import string
f345cfd0 25import unittest
ed338bb0 26import sys
2499a096 27import struct
74f69050 28import json
2c93c5cb 29import signal
43851b5b 30import logging
ef6e9228 31import atexit
2d894bee 32import io
0706e87d 33from collections import OrderedDict
f345cfd0 34
02f3a911
VSO
35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
36import qtest
37
f345cfd0 38
934659c4 39# This will not work if arguments contain spaces but is necessary if we
f345cfd0 40# want to support the override options that ./check supports.
934659c4
HR
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
bec87774
HR
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
4c44b4a4 53qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
66613974 54qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
f345cfd0
SH
55
56imgfmt = os.environ.get('IMGFMT', 'raw')
57imgproto = os.environ.get('IMGPROTO', 'file')
5a8fabf3 58test_dir = os.environ.get('TEST_DIR')
e8f8624d 59output_dir = os.environ.get('OUTPUT_DIR', '.')
58cc2ae1 60cachemode = os.environ.get('CACHEMODE')
e166b414 61qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
f345cfd0 62
30b005d9 63socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
c0088d79 64debug = False
30b005d9 65
85a353a0 66luks_default_secret_object = 'secret,id=keysec0,data=' + \
58ebcb65 67 os.environ.get('IMGKEYSECRET', '')
85a353a0
VSO
68luks_default_key_secret_opt = 'key-secret=keysec0'
69
70
f345cfd0
SH
71def qemu_img(*args):
72 '''Run qemu-img and return the exit code'''
73 devnull = open('/dev/null', 'r+')
2ef6093c
HR
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
f345cfd0 78
0706e87d
JS
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
85a353a0
VSO
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
d2ef210c 108def qemu_img_verbose(*args):
993d46ce 109 '''Run qemu-img without suppressing its output and return the exit code'''
2ef6093c
HR
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
d2ef210c 114
3677e6f6
HR
115def qemu_img_pipe(*args):
116 '''Run qemu-img and return its output'''
491e5e85
DB
117 subp = subprocess.Popen(qemu_img_args + list(args),
118 stdout=subprocess.PIPE,
8eb5e674
HR
119 stderr=subprocess.STDOUT,
120 universal_newlines=True)
2ef6093c
HR
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]
3677e6f6 125
5ba141dc
KW
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)
6b605ade
KW
136 if not filter_path:
137 filter_path = filename
138 log(filter_img_info(output, filter_path))
139
f345cfd0
SH
140def qemu_io(*args):
141 '''Run qemu-io and return the stdout data'''
142 args = qemu_io_args + list(args)
491e5e85 143 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
8eb5e674
HR
144 stderr=subprocess.STDOUT,
145 universal_newlines=True)
2ef6093c
HR
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]
f345cfd0 150
745f2bf4
HR
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
9fa90eec
VSO
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,
8eb5e674
HR
166 stderr=subprocess.STDOUT,
167 universal_newlines=True)
9fa90eec
VSO
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')
f544adf7 196 self._p.stdin.flush()
9fa90eec
VSO
197 return self._read_output()
198
199
bec87774
HR
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
e1e6eccd
HR
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
e1b5c51f 218def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 219 '''Return True if two image files are identical'''
e1b5c51f
PB
220 return qemu_img('compare', '-f', fmt1,
221 '-F', fmt2, img1, img2) == 0
3a3918c3 222
2499a096
SH
223def create_image(name, size):
224 '''Create a fully-allocated raw image with sector markers'''
8eb5e674 225 file = open(name, 'wb')
2499a096
SH
226 i = 0
227 while i < size:
9a3a9a63 228 sector = struct.pack('>l504xl', i // 512, i // 512)
2499a096
SH
229 file.write(sector)
230 i = i + 512
231 file.close()
232
74f69050
FZ
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
a2d1c8fd
DB
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
12314f2d
SH
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
08fcd611
JS
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
e234398a
KW
279def filter_testfiles(msg):
280 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
281 return msg.replace(prefix, 'TEST_DIR/PID-')
282
08fcd611
JS
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
fa1151f8
JS
290def filter_generated_node_ids(msg):
291 return re.sub("#block[0-9]+", "NODE_NAME", msg)
292
6b605ade
KW
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)
bab4feb2 302 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
6b605ade
KW
303 lines.append(line)
304 return '\n'.join(lines)
305
55cd64ea
JS
306def log(msg, filters=[], indent=None):
307 '''Logs either a string message or a JSON serializable message (like QMP).
308 If indent is provided, JSON serializable messages are pretty-printed.'''
a2d1c8fd
DB
309 for flt in filters:
310 msg = flt(msg)
0706e87d 311 if isinstance(msg, dict) or isinstance(msg, list):
55cd64ea
JS
312 # Python < 3.4 needs to know not to add whitespace when pretty-printing:
313 separators = (', ', ': ') if indent is None else (',', ': ')
0706e87d
JS
314 # Don't sort if it's already sorted
315 do_sort = not isinstance(msg, OrderedDict)
55cd64ea
JS
316 print(json.dumps(msg, sort_keys=do_sort,
317 indent=indent, separators=separators))
e21b5f34
HR
318 else:
319 print(msg)
a2d1c8fd 320
2c93c5cb
KW
321class Timeout:
322 def __init__(self, seconds, errmsg = "Timeout"):
323 self.seconds = seconds
324 self.errmsg = errmsg
325 def __enter__(self):
326 signal.signal(signal.SIGALRM, self.timeout)
327 signal.setitimer(signal.ITIMER_REAL, self.seconds)
328 return self
329 def __exit__(self, type, value, traceback):
330 signal.setitimer(signal.ITIMER_REAL, 0)
331 return False
332 def timeout(self, signum, frame):
333 raise Exception(self.errmsg)
334
f4844ac0
SH
335
336class FilePath(object):
337 '''An auto-generated filename that cleans itself up.
338
339 Use this context manager to generate filenames and ensure that the file
340 gets deleted::
341
342 with TestFilePath('test.img') as img_path:
343 qemu_img('create', img_path, '1G')
344 # migration_sock_path is automatically deleted
345 '''
346 def __init__(self, name):
347 filename = '{0}-{1}'.format(os.getpid(), name)
348 self.path = os.path.join(test_dir, filename)
349
350 def __enter__(self):
351 return self.path
352
353 def __exit__(self, exc_type, exc_val, exc_tb):
354 try:
355 os.remove(self.path)
356 except OSError:
357 pass
358 return False
359
360
ef6e9228
VSO
361def file_path_remover():
362 for path in reversed(file_path_remover.paths):
363 try:
364 os.remove(path)
365 except OSError:
366 pass
367
368
369def file_path(*names):
370 ''' Another way to get auto-generated filename that cleans itself up.
371
372 Use is as simple as:
373
374 img_a, img_b = file_path('a.img', 'b.img')
375 sock = file_path('socket')
376 '''
377
378 if not hasattr(file_path_remover, 'paths'):
379 file_path_remover.paths = []
380 atexit.register(file_path_remover)
381
382 paths = []
383 for name in names:
384 filename = '{0}-{1}'.format(os.getpid(), name)
385 path = os.path.join(test_dir, filename)
386 file_path_remover.paths.append(path)
387 paths.append(path)
388
389 return paths[0] if len(paths) == 1 else paths
390
5a259e86
KW
391def remote_filename(path):
392 if imgproto == 'file':
393 return path
394 elif imgproto == 'ssh':
395 return "ssh://127.0.0.1%s" % (path)
396 else:
397 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 398
4c44b4a4 399class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
400 '''A QEMU VM'''
401
5fcbdf50
HR
402 def __init__(self, path_suffix=''):
403 name = "qemu%s-%d" % (path_suffix, os.getpid())
404 super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
405 test_dir=test_dir,
4c44b4a4 406 socket_scm_helper=socket_scm_helper)
f345cfd0 407 self._num_drives = 0
30b005d9 408
ccc15f7d
SH
409 def add_object(self, opts):
410 self._args.append('-object')
411 self._args.append(opts)
412 return self
413
486b88bd
KW
414 def add_device(self, opts):
415 self._args.append('-device')
416 self._args.append(opts)
417 return self
418
78b666f4
FZ
419 def add_drive_raw(self, opts):
420 self._args.append('-drive')
421 self._args.append(opts)
422 return self
423
e1b5c51f 424 def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
f345cfd0 425 '''Add a virtio-blk drive to the VM'''
8e492253 426 options = ['if=%s' % interface,
f345cfd0 427 'id=drive%d' % self._num_drives]
8e492253
HR
428
429 if path is not None:
430 options.append('file=%s' % path)
e1b5c51f 431 options.append('format=%s' % format)
fc17c259 432 options.append('cache=%s' % cachemode)
8e492253 433
f345cfd0
SH
434 if opts:
435 options.append(opts)
436
85a353a0
VSO
437 if format == 'luks' and 'key-secret' not in opts:
438 # default luks support
439 if luks_default_secret_object not in self._args:
440 self.add_object(luks_default_secret_object)
441
442 options.append(luks_default_key_secret_opt)
443
f345cfd0
SH
444 self._args.append('-drive')
445 self._args.append(','.join(options))
446 self._num_drives += 1
447 return self
448
5694923a
HR
449 def add_blockdev(self, opts):
450 self._args.append('-blockdev')
451 if isinstance(opts, str):
452 self._args.append(opts)
453 else:
454 self._args.append(','.join(opts))
455 return self
456
12314f2d
SH
457 def add_incoming(self, addr):
458 self._args.append('-incoming')
459 self._args.append(addr)
460 return self
461
3cf53c77
FZ
462 def pause_drive(self, drive, event=None):
463 '''Pause drive r/w operations'''
464 if not event:
465 self.pause_drive(drive, "read_aio")
466 self.pause_drive(drive, "write_aio")
467 return
468 self.qmp('human-monitor-command',
469 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
470
471 def resume_drive(self, drive):
472 self.qmp('human-monitor-command',
473 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
474
e3409362
IM
475 def hmp_qemu_io(self, drive, cmd):
476 '''Write to a given drive using an HMP command'''
477 return self.qmp('human-monitor-command',
478 command_line='qemu-io %s "%s"' % (drive, cmd))
479
62a94288
KW
480 def flatten_qmp_object(self, obj, output=None, basestr=''):
481 if output is None:
482 output = dict()
483 if isinstance(obj, list):
484 for i in range(len(obj)):
485 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
486 elif isinstance(obj, dict):
487 for key in obj:
488 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
489 else:
490 output[basestr[:-1]] = obj # Strip trailing '.'
491 return output
492
493 def qmp_to_opts(self, obj):
494 obj = self.flatten_qmp_object(obj)
495 output_list = list()
496 for key in obj:
497 output_list += [key + '=' + obj[key]]
498 return ','.join(output_list)
499
5ad1dbf7
KW
500 def get_qmp_events_filtered(self, wait=True):
501 result = []
502 for ev in self.get_qmp_events(wait=wait):
503 result.append(filter_qmp_event(ev))
504 return result
62a94288 505
55cd64ea 506 def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
0706e87d
JS
507 full_cmd = OrderedDict((
508 ("execute", cmd),
509 ("arguments", ordered_kwargs(kwargs))
510 ))
55cd64ea 511 log(full_cmd, filters, indent=indent)
e234398a 512 result = self.qmp(cmd, **kwargs)
55cd64ea 513 log(result, filters, indent=indent)
e234398a
KW
514 return result
515
fc47d851
KW
516 def run_job(self, job, auto_finalize=True, auto_dismiss=False):
517 while True:
518 for ev in self.get_qmp_events_filtered(wait=True):
519 if ev['event'] == 'JOB_STATUS_CHANGE':
520 status = ev['data']['status']
521 if status == 'aborting':
522 result = self.qmp('query-jobs')
523 for j in result['return']:
524 if j['id'] == job:
525 log('Job failed: %s' % (j['error']))
526 elif status == 'pending' and not auto_finalize:
527 self.qmp_log('job-finalize', id=job)
528 elif status == 'concluded' and not auto_dismiss:
529 self.qmp_log('job-dismiss', id=job)
530 elif status == 'null':
531 return
532 else:
533 iotests.log(ev)
534
7898f74e 535
f345cfd0
SH
536index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
537
538class QMPTestCase(unittest.TestCase):
539 '''Abstract base class for QMP test cases'''
540
541 def dictpath(self, d, path):
542 '''Traverse a path in a nested dict'''
543 for component in path.split('/'):
544 m = index_re.match(component)
545 if m:
546 component, idx = m.groups()
547 idx = int(idx)
548
549 if not isinstance(d, dict) or component not in d:
550 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
551 d = d[component]
552
553 if m:
554 if not isinstance(d, list):
555 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
556 try:
557 d = d[idx]
558 except IndexError:
559 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
560 return d
561
90f0b711
PB
562 def assert_qmp_absent(self, d, path):
563 try:
564 result = self.dictpath(d, path)
565 except AssertionError:
566 return
567 self.fail('path "%s" has value "%s"' % (path, str(result)))
568
f345cfd0
SH
569 def assert_qmp(self, d, path, value):
570 '''Assert that the value for a specific path in a QMP dict matches'''
571 result = self.dictpath(d, path)
572 self.assertEqual(result, value, 'values not equal "%s" and "%s"' % (str(result), str(value)))
573
ecc1c88e
SH
574 def assert_no_active_block_jobs(self):
575 result = self.vm.qmp('query-block-jobs')
576 self.assert_qmp(result, 'return', [])
577
e71fc0ba
FZ
578 def assert_has_block_node(self, node_name=None, file_name=None):
579 """Issue a query-named-block-nodes and assert node_name and/or
580 file_name is present in the result"""
581 def check_equal_or_none(a, b):
582 return a == None or b == None or a == b
583 assert node_name or file_name
584 result = self.vm.qmp('query-named-block-nodes')
585 for x in result["return"]:
586 if check_equal_or_none(x.get("node-name"), node_name) and \
587 check_equal_or_none(x.get("file"), file_name):
588 return
589 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
590 (node_name, file_name, result))
591
e07375f5
HR
592 def assert_json_filename_equal(self, json_filename, reference):
593 '''Asserts that the given filename is a json: filename and that its
594 content is equal to the given reference object'''
595 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
596 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
597 self.vm.flatten_qmp_object(reference))
e07375f5 598
3cf53c77 599 def cancel_and_wait(self, drive='drive0', force=False, resume=False):
2575fe16
SH
600 '''Cancel a block job and wait for it to finish, returning the event'''
601 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
602 self.assert_qmp(result, 'return', {})
603
3cf53c77
FZ
604 if resume:
605 self.vm.resume_drive(drive)
606
2575fe16
SH
607 cancelled = False
608 result = None
609 while not cancelled:
610 for event in self.vm.get_qmp_events(wait=True):
611 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
612 event['event'] == 'BLOCK_JOB_CANCELLED':
613 self.assert_qmp(event, 'data/device', drive)
614 result = event
615 cancelled = True
1dac83f1
KW
616 elif event['event'] == 'JOB_STATUS_CHANGE':
617 self.assert_qmp(event, 'data/id', drive)
618
2575fe16
SH
619
620 self.assert_no_active_block_jobs()
621 return result
622
9974ad40 623 def wait_until_completed(self, drive='drive0', check_offset=True):
0dbe8a1b 624 '''Wait for a block job to finish, returning the event'''
c3988519 625 while True:
0dbe8a1b
SH
626 for event in self.vm.get_qmp_events(wait=True):
627 if event['event'] == 'BLOCK_JOB_COMPLETED':
628 self.assert_qmp(event, 'data/device', drive)
629 self.assert_qmp_absent(event, 'data/error')
9974ad40 630 if check_offset:
1d3ba15a 631 self.assert_qmp(event, 'data/offset', event['data']['len'])
c3988519
PX
632 self.assert_no_active_block_jobs()
633 return event
1dac83f1
KW
634 elif event['event'] == 'JOB_STATUS_CHANGE':
635 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 636
866323f3
FZ
637 def wait_ready(self, drive='drive0'):
638 '''Wait until a block job BLOCK_JOB_READY event'''
d7b25297
FZ
639 f = {'data': {'type': 'mirror', 'device': drive } }
640 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
641
642 def wait_ready_and_cancel(self, drive='drive0'):
643 self.wait_ready(drive=drive)
644 event = self.cancel_and_wait(drive=drive)
fa1cfb40 645 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
646 self.assert_qmp(event, 'data/type', 'mirror')
647 self.assert_qmp(event, 'data/offset', event['data']['len'])
648
649 def complete_and_wait(self, drive='drive0', wait_ready=True):
650 '''Complete a block job and wait for it to finish'''
651 if wait_ready:
652 self.wait_ready(drive=drive)
653
654 result = self.vm.qmp('block-job-complete', device=drive)
655 self.assert_qmp(result, 'return', {})
656
657 event = self.wait_until_completed(drive=drive)
658 self.assert_qmp(event, 'data/type', 'mirror')
659
f03d9d24 660 def pause_wait(self, job_id='job0'):
2c93c5cb
KW
661 with Timeout(1, "Timeout waiting for job to pause"):
662 while True:
663 result = self.vm.qmp('query-block-jobs')
c1bac161 664 found = False
2c93c5cb 665 for job in result['return']:
c1bac161
VSO
666 if job['device'] == job_id:
667 found = True
668 if job['paused'] == True and job['busy'] == False:
669 return job
670 break
671 assert found
2c93c5cb 672
f03d9d24
JS
673 def pause_job(self, job_id='job0', wait=True):
674 result = self.vm.qmp('block-job-pause', device=job_id)
675 self.assert_qmp(result, 'return', {})
676 if wait:
677 return self.pause_wait(job_id)
678 return result
679
2c93c5cb 680
f345cfd0
SH
681def notrun(reason):
682 '''Skip this test suite'''
683 # Each test in qemu-iotests has a number ("seq")
684 seq = os.path.basename(sys.argv[0])
685
e8f8624d 686 open('%s/%s.notrun' % (output_dir, seq), 'wb').write(reason + '\n')
f03868bd 687 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
688 sys.exit(0)
689
3f5c4076 690def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
f48351d2
VSO
691 assert not (supported_fmts and unsupported_fmts)
692
693 if 'generic' in supported_fmts and \
694 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
695 # similar to
696 # _supported_fmt generic
697 # for bash tests
698 return
699
700 not_sup = supported_fmts and (imgfmt not in supported_fmts)
701 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 702 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 703
5a259e86
KW
704def verify_protocol(supported=[], unsupported=[]):
705 assert not (supported and unsupported)
706
707 if 'generic' in supported:
708 return
709
710 not_sup = supported and (imgproto not in supported)
711 if not_sup or (imgproto in unsupported):
712 notrun('not suitable for this protocol: %s' % imgproto)
713
c6a92369 714def verify_platform(supported_oses=['linux']):
79e7a019 715 if True not in [sys.platform.startswith(x) for x in supported_oses]:
bc521696
FZ
716 notrun('not suitable for this OS: %s' % sys.platform)
717
ac8bd439
VSO
718def verify_cache_mode(supported_cache_modes=[]):
719 if supported_cache_modes and (cachemode not in supported_cache_modes):
720 notrun('not suitable for this cache mode: %s' % cachemode)
721
b0f90495
AG
722def supports_quorum():
723 return 'quorum' in qemu_img_pipe('--help')
724
3f647b51
SS
725def verify_quorum():
726 '''Skip test suite if quorum support is not available'''
b0f90495 727 if not supports_quorum():
3f647b51
SS
728 notrun('quorum support missing')
729
febc8c86
VSO
730def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
731 unsupported_fmts=[]):
c6a92369
DB
732 '''Run tests'''
733
c0088d79
KW
734 global debug
735
5a8fabf3
SS
736 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
737 # indicate that we're not being run via "check". There may be
738 # other things set up by "check" that individual test cases rely
739 # on.
740 if test_dir is None or qemu_default_machine is None:
741 sys.stderr.write('Please run this test via the "check" script\n')
742 sys.exit(os.EX_USAGE)
743
c6a92369
DB
744 debug = '-d' in sys.argv
745 verbosity = 1
febc8c86 746 verify_image_format(supported_fmts, unsupported_fmts)
c6a92369 747 verify_platform(supported_oses)
ac8bd439 748 verify_cache_mode(supported_cache_modes)
c6a92369 749
aa4f592a
FZ
750 if debug:
751 output = sys.stdout
752 verbosity = 2
753 sys.argv.remove('-d')
754 else:
2d894bee
HR
755 # We need to filter out the time taken from the output so that
756 # qemu-iotest can reliably diff the results against master output.
757 if sys.version_info.major >= 3:
758 output = io.StringIO()
759 else:
760 # io.StringIO is for unicode strings, which is not what
761 # 2.x's test runner emits.
762 output = io.BytesIO()
f345cfd0 763
43851b5b
EH
764 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
765
f345cfd0 766 class MyTestRunner(unittest.TextTestRunner):
aa4f592a 767 def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
f345cfd0
SH
768 unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
769
770 # unittest.main() will use sys.exit() so expect a SystemExit exception
771 try:
772 unittest.main(testRunner=MyTestRunner)
773 finally:
aa4f592a
FZ
774 if not debug:
775 sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))