]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
iotests: remove default filters from qmp_log
[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
e1b5c51f 204def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 205 '''Return True if two image files are identical'''
e1b5c51f
PB
206 return qemu_img('compare', '-f', fmt1,
207 '-F', fmt2, img1, img2) == 0
3a3918c3 208
2499a096
SH
209def create_image(name, size):
210 '''Create a fully-allocated raw image with sector markers'''
8eb5e674 211 file = open(name, 'wb')
2499a096
SH
212 i = 0
213 while i < size:
9a3a9a63 214 sector = struct.pack('>l504xl', i // 512, i // 512)
2499a096
SH
215 file.write(sector)
216 i = i + 512
217 file.close()
218
74f69050
FZ
219def image_size(img):
220 '''Return image's virtual size'''
221 r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
222 return json.loads(r)['virtual-size']
223
a2d1c8fd
DB
224test_dir_re = re.compile(r"%s" % test_dir)
225def filter_test_dir(msg):
226 return test_dir_re.sub("TEST_DIR", msg)
227
228win32_re = re.compile(r"\r")
229def filter_win32(msg):
230 return win32_re.sub("", msg)
231
232qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
233def filter_qemu_io(msg):
234 msg = filter_win32(msg)
235 return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
236
237chown_re = re.compile(r"chown [0-9]+:[0-9]+")
238def filter_chown(msg):
239 return chown_re.sub("chown UID:GID", msg)
240
12314f2d
SH
241def filter_qmp_event(event):
242 '''Filter a QMP event dict'''
243 event = dict(event)
244 if 'timestamp' in event:
245 event['timestamp']['seconds'] = 'SECS'
246 event['timestamp']['microseconds'] = 'USECS'
247 return event
248
e234398a
KW
249def filter_testfiles(msg):
250 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
251 return msg.replace(prefix, 'TEST_DIR/PID-')
252
fa1151f8
JS
253def filter_generated_node_ids(msg):
254 return re.sub("#block[0-9]+", "NODE_NAME", msg)
255
6b605ade
KW
256def filter_img_info(output, filename):
257 lines = []
258 for line in output.split('\n'):
259 if 'disk size' in line or 'actual-size' in line:
260 continue
261 line = line.replace(filename, 'TEST_IMG') \
262 .replace(imgfmt, 'IMGFMT')
263 line = re.sub('iters: [0-9]+', 'iters: XXX', line)
264 line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
265 lines.append(line)
266 return '\n'.join(lines)
267
a2d1c8fd
DB
268def log(msg, filters=[]):
269 for flt in filters:
270 msg = flt(msg)
0706e87d
JS
271 if isinstance(msg, dict) or isinstance(msg, list):
272 # Don't sort if it's already sorted
273 do_sort = not isinstance(msg, OrderedDict)
274 print(json.dumps(msg, sort_keys=do_sort))
e21b5f34
HR
275 else:
276 print(msg)
a2d1c8fd 277
2c93c5cb
KW
278class Timeout:
279 def __init__(self, seconds, errmsg = "Timeout"):
280 self.seconds = seconds
281 self.errmsg = errmsg
282 def __enter__(self):
283 signal.signal(signal.SIGALRM, self.timeout)
284 signal.setitimer(signal.ITIMER_REAL, self.seconds)
285 return self
286 def __exit__(self, type, value, traceback):
287 signal.setitimer(signal.ITIMER_REAL, 0)
288 return False
289 def timeout(self, signum, frame):
290 raise Exception(self.errmsg)
291
f4844ac0
SH
292
293class FilePath(object):
294 '''An auto-generated filename that cleans itself up.
295
296 Use this context manager to generate filenames and ensure that the file
297 gets deleted::
298
299 with TestFilePath('test.img') as img_path:
300 qemu_img('create', img_path, '1G')
301 # migration_sock_path is automatically deleted
302 '''
303 def __init__(self, name):
304 filename = '{0}-{1}'.format(os.getpid(), name)
305 self.path = os.path.join(test_dir, filename)
306
307 def __enter__(self):
308 return self.path
309
310 def __exit__(self, exc_type, exc_val, exc_tb):
311 try:
312 os.remove(self.path)
313 except OSError:
314 pass
315 return False
316
317
ef6e9228
VSO
318def file_path_remover():
319 for path in reversed(file_path_remover.paths):
320 try:
321 os.remove(path)
322 except OSError:
323 pass
324
325
326def file_path(*names):
327 ''' Another way to get auto-generated filename that cleans itself up.
328
329 Use is as simple as:
330
331 img_a, img_b = file_path('a.img', 'b.img')
332 sock = file_path('socket')
333 '''
334
335 if not hasattr(file_path_remover, 'paths'):
336 file_path_remover.paths = []
337 atexit.register(file_path_remover)
338
339 paths = []
340 for name in names:
341 filename = '{0}-{1}'.format(os.getpid(), name)
342 path = os.path.join(test_dir, filename)
343 file_path_remover.paths.append(path)
344 paths.append(path)
345
346 return paths[0] if len(paths) == 1 else paths
347
5a259e86
KW
348def remote_filename(path):
349 if imgproto == 'file':
350 return path
351 elif imgproto == 'ssh':
352 return "ssh://127.0.0.1%s" % (path)
353 else:
354 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 355
4c44b4a4 356class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
357 '''A QEMU VM'''
358
5fcbdf50
HR
359 def __init__(self, path_suffix=''):
360 name = "qemu%s-%d" % (path_suffix, os.getpid())
361 super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
362 test_dir=test_dir,
4c44b4a4 363 socket_scm_helper=socket_scm_helper)
f345cfd0 364 self._num_drives = 0
30b005d9 365
ccc15f7d
SH
366 def add_object(self, opts):
367 self._args.append('-object')
368 self._args.append(opts)
369 return self
370
486b88bd
KW
371 def add_device(self, opts):
372 self._args.append('-device')
373 self._args.append(opts)
374 return self
375
78b666f4
FZ
376 def add_drive_raw(self, opts):
377 self._args.append('-drive')
378 self._args.append(opts)
379 return self
380
e1b5c51f 381 def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
f345cfd0 382 '''Add a virtio-blk drive to the VM'''
8e492253 383 options = ['if=%s' % interface,
f345cfd0 384 'id=drive%d' % self._num_drives]
8e492253
HR
385
386 if path is not None:
387 options.append('file=%s' % path)
e1b5c51f 388 options.append('format=%s' % format)
fc17c259 389 options.append('cache=%s' % cachemode)
8e492253 390
f345cfd0
SH
391 if opts:
392 options.append(opts)
393
85a353a0
VSO
394 if format == 'luks' and 'key-secret' not in opts:
395 # default luks support
396 if luks_default_secret_object not in self._args:
397 self.add_object(luks_default_secret_object)
398
399 options.append(luks_default_key_secret_opt)
400
f345cfd0
SH
401 self._args.append('-drive')
402 self._args.append(','.join(options))
403 self._num_drives += 1
404 return self
405
5694923a
HR
406 def add_blockdev(self, opts):
407 self._args.append('-blockdev')
408 if isinstance(opts, str):
409 self._args.append(opts)
410 else:
411 self._args.append(','.join(opts))
412 return self
413
12314f2d
SH
414 def add_incoming(self, addr):
415 self._args.append('-incoming')
416 self._args.append(addr)
417 return self
418
3cf53c77
FZ
419 def pause_drive(self, drive, event=None):
420 '''Pause drive r/w operations'''
421 if not event:
422 self.pause_drive(drive, "read_aio")
423 self.pause_drive(drive, "write_aio")
424 return
425 self.qmp('human-monitor-command',
426 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
427
428 def resume_drive(self, drive):
429 self.qmp('human-monitor-command',
430 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
431
e3409362
IM
432 def hmp_qemu_io(self, drive, cmd):
433 '''Write to a given drive using an HMP command'''
434 return self.qmp('human-monitor-command',
435 command_line='qemu-io %s "%s"' % (drive, cmd))
436
62a94288
KW
437 def flatten_qmp_object(self, obj, output=None, basestr=''):
438 if output is None:
439 output = dict()
440 if isinstance(obj, list):
441 for i in range(len(obj)):
442 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
443 elif isinstance(obj, dict):
444 for key in obj:
445 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
446 else:
447 output[basestr[:-1]] = obj # Strip trailing '.'
448 return output
449
450 def qmp_to_opts(self, obj):
451 obj = self.flatten_qmp_object(obj)
452 output_list = list()
453 for key in obj:
454 output_list += [key + '=' + obj[key]]
455 return ','.join(output_list)
456
5ad1dbf7
KW
457 def get_qmp_events_filtered(self, wait=True):
458 result = []
459 for ev in self.get_qmp_events(wait=wait):
460 result.append(filter_qmp_event(ev))
461 return result
62a94288 462
f8ca8609 463 def qmp_log(self, cmd, filters=[], **kwargs):
0706e87d
JS
464 full_cmd = OrderedDict((
465 ("execute", cmd),
466 ("arguments", ordered_kwargs(kwargs))
467 ))
468 logmsg = json.dumps(full_cmd)
e234398a
KW
469 log(logmsg, filters)
470 result = self.qmp(cmd, **kwargs)
e21b5f34 471 log(json.dumps(result, sort_keys=True), filters)
e234398a
KW
472 return result
473
fc47d851
KW
474 def run_job(self, job, auto_finalize=True, auto_dismiss=False):
475 while True:
476 for ev in self.get_qmp_events_filtered(wait=True):
477 if ev['event'] == 'JOB_STATUS_CHANGE':
478 status = ev['data']['status']
479 if status == 'aborting':
480 result = self.qmp('query-jobs')
481 for j in result['return']:
482 if j['id'] == job:
483 log('Job failed: %s' % (j['error']))
484 elif status == 'pending' and not auto_finalize:
485 self.qmp_log('job-finalize', id=job)
486 elif status == 'concluded' and not auto_dismiss:
487 self.qmp_log('job-dismiss', id=job)
488 elif status == 'null':
489 return
490 else:
491 iotests.log(ev)
492
7898f74e 493
f345cfd0
SH
494index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
495
496class QMPTestCase(unittest.TestCase):
497 '''Abstract base class for QMP test cases'''
498
499 def dictpath(self, d, path):
500 '''Traverse a path in a nested dict'''
501 for component in path.split('/'):
502 m = index_re.match(component)
503 if m:
504 component, idx = m.groups()
505 idx = int(idx)
506
507 if not isinstance(d, dict) or component not in d:
508 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
509 d = d[component]
510
511 if m:
512 if not isinstance(d, list):
513 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
514 try:
515 d = d[idx]
516 except IndexError:
517 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
518 return d
519
90f0b711
PB
520 def assert_qmp_absent(self, d, path):
521 try:
522 result = self.dictpath(d, path)
523 except AssertionError:
524 return
525 self.fail('path "%s" has value "%s"' % (path, str(result)))
526
f345cfd0
SH
527 def assert_qmp(self, d, path, value):
528 '''Assert that the value for a specific path in a QMP dict matches'''
529 result = self.dictpath(d, path)
530 self.assertEqual(result, value, 'values not equal "%s" and "%s"' % (str(result), str(value)))
531
ecc1c88e
SH
532 def assert_no_active_block_jobs(self):
533 result = self.vm.qmp('query-block-jobs')
534 self.assert_qmp(result, 'return', [])
535
e71fc0ba
FZ
536 def assert_has_block_node(self, node_name=None, file_name=None):
537 """Issue a query-named-block-nodes and assert node_name and/or
538 file_name is present in the result"""
539 def check_equal_or_none(a, b):
540 return a == None or b == None or a == b
541 assert node_name or file_name
542 result = self.vm.qmp('query-named-block-nodes')
543 for x in result["return"]:
544 if check_equal_or_none(x.get("node-name"), node_name) and \
545 check_equal_or_none(x.get("file"), file_name):
546 return
547 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
548 (node_name, file_name, result))
549
e07375f5
HR
550 def assert_json_filename_equal(self, json_filename, reference):
551 '''Asserts that the given filename is a json: filename and that its
552 content is equal to the given reference object'''
553 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
554 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
555 self.vm.flatten_qmp_object(reference))
e07375f5 556
3cf53c77 557 def cancel_and_wait(self, drive='drive0', force=False, resume=False):
2575fe16
SH
558 '''Cancel a block job and wait for it to finish, returning the event'''
559 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
560 self.assert_qmp(result, 'return', {})
561
3cf53c77
FZ
562 if resume:
563 self.vm.resume_drive(drive)
564
2575fe16
SH
565 cancelled = False
566 result = None
567 while not cancelled:
568 for event in self.vm.get_qmp_events(wait=True):
569 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
570 event['event'] == 'BLOCK_JOB_CANCELLED':
571 self.assert_qmp(event, 'data/device', drive)
572 result = event
573 cancelled = True
1dac83f1
KW
574 elif event['event'] == 'JOB_STATUS_CHANGE':
575 self.assert_qmp(event, 'data/id', drive)
576
2575fe16
SH
577
578 self.assert_no_active_block_jobs()
579 return result
580
9974ad40 581 def wait_until_completed(self, drive='drive0', check_offset=True):
0dbe8a1b 582 '''Wait for a block job to finish, returning the event'''
c3988519 583 while True:
0dbe8a1b
SH
584 for event in self.vm.get_qmp_events(wait=True):
585 if event['event'] == 'BLOCK_JOB_COMPLETED':
586 self.assert_qmp(event, 'data/device', drive)
587 self.assert_qmp_absent(event, 'data/error')
9974ad40 588 if check_offset:
1d3ba15a 589 self.assert_qmp(event, 'data/offset', event['data']['len'])
c3988519
PX
590 self.assert_no_active_block_jobs()
591 return event
1dac83f1
KW
592 elif event['event'] == 'JOB_STATUS_CHANGE':
593 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 594
866323f3
FZ
595 def wait_ready(self, drive='drive0'):
596 '''Wait until a block job BLOCK_JOB_READY event'''
d7b25297
FZ
597 f = {'data': {'type': 'mirror', 'device': drive } }
598 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
599
600 def wait_ready_and_cancel(self, drive='drive0'):
601 self.wait_ready(drive=drive)
602 event = self.cancel_and_wait(drive=drive)
fa1cfb40 603 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
604 self.assert_qmp(event, 'data/type', 'mirror')
605 self.assert_qmp(event, 'data/offset', event['data']['len'])
606
607 def complete_and_wait(self, drive='drive0', wait_ready=True):
608 '''Complete a block job and wait for it to finish'''
609 if wait_ready:
610 self.wait_ready(drive=drive)
611
612 result = self.vm.qmp('block-job-complete', device=drive)
613 self.assert_qmp(result, 'return', {})
614
615 event = self.wait_until_completed(drive=drive)
616 self.assert_qmp(event, 'data/type', 'mirror')
617
f03d9d24 618 def pause_wait(self, job_id='job0'):
2c93c5cb
KW
619 with Timeout(1, "Timeout waiting for job to pause"):
620 while True:
621 result = self.vm.qmp('query-block-jobs')
c1bac161 622 found = False
2c93c5cb 623 for job in result['return']:
c1bac161
VSO
624 if job['device'] == job_id:
625 found = True
626 if job['paused'] == True and job['busy'] == False:
627 return job
628 break
629 assert found
2c93c5cb 630
f03d9d24
JS
631 def pause_job(self, job_id='job0', wait=True):
632 result = self.vm.qmp('block-job-pause', device=job_id)
633 self.assert_qmp(result, 'return', {})
634 if wait:
635 return self.pause_wait(job_id)
636 return result
637
2c93c5cb 638
f345cfd0
SH
639def notrun(reason):
640 '''Skip this test suite'''
641 # Each test in qemu-iotests has a number ("seq")
642 seq = os.path.basename(sys.argv[0])
643
e8f8624d 644 open('%s/%s.notrun' % (output_dir, seq), 'wb').write(reason + '\n')
f03868bd 645 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
646 sys.exit(0)
647
3f5c4076 648def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
f48351d2
VSO
649 assert not (supported_fmts and unsupported_fmts)
650
651 if 'generic' in supported_fmts and \
652 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
653 # similar to
654 # _supported_fmt generic
655 # for bash tests
656 return
657
658 not_sup = supported_fmts and (imgfmt not in supported_fmts)
659 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 660 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 661
5a259e86
KW
662def verify_protocol(supported=[], unsupported=[]):
663 assert not (supported and unsupported)
664
665 if 'generic' in supported:
666 return
667
668 not_sup = supported and (imgproto not in supported)
669 if not_sup or (imgproto in unsupported):
670 notrun('not suitable for this protocol: %s' % imgproto)
671
c6a92369 672def verify_platform(supported_oses=['linux']):
79e7a019 673 if True not in [sys.platform.startswith(x) for x in supported_oses]:
bc521696
FZ
674 notrun('not suitable for this OS: %s' % sys.platform)
675
ac8bd439
VSO
676def verify_cache_mode(supported_cache_modes=[]):
677 if supported_cache_modes and (cachemode not in supported_cache_modes):
678 notrun('not suitable for this cache mode: %s' % cachemode)
679
b0f90495
AG
680def supports_quorum():
681 return 'quorum' in qemu_img_pipe('--help')
682
3f647b51
SS
683def verify_quorum():
684 '''Skip test suite if quorum support is not available'''
b0f90495 685 if not supports_quorum():
3f647b51
SS
686 notrun('quorum support missing')
687
febc8c86
VSO
688def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
689 unsupported_fmts=[]):
c6a92369
DB
690 '''Run tests'''
691
c0088d79
KW
692 global debug
693
5a8fabf3
SS
694 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
695 # indicate that we're not being run via "check". There may be
696 # other things set up by "check" that individual test cases rely
697 # on.
698 if test_dir is None or qemu_default_machine is None:
699 sys.stderr.write('Please run this test via the "check" script\n')
700 sys.exit(os.EX_USAGE)
701
c6a92369
DB
702 debug = '-d' in sys.argv
703 verbosity = 1
febc8c86 704 verify_image_format(supported_fmts, unsupported_fmts)
c6a92369 705 verify_platform(supported_oses)
ac8bd439 706 verify_cache_mode(supported_cache_modes)
c6a92369 707
aa4f592a
FZ
708 if debug:
709 output = sys.stdout
710 verbosity = 2
711 sys.argv.remove('-d')
712 else:
2d894bee
HR
713 # We need to filter out the time taken from the output so that
714 # qemu-iotest can reliably diff the results against master output.
715 if sys.version_info.major >= 3:
716 output = io.StringIO()
717 else:
718 # io.StringIO is for unicode strings, which is not what
719 # 2.x's test runner emits.
720 output = io.BytesIO()
f345cfd0 721
43851b5b
EH
722 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
723
f345cfd0 724 class MyTestRunner(unittest.TextTestRunner):
aa4f592a 725 def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
f345cfd0
SH
726 unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
727
728 # unittest.main() will use sys.exit() so expect a SystemExit exception
729 try:
730 unittest.main(testRunner=MyTestRunner)
731 finally:
aa4f592a
FZ
732 if not debug:
733 sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))