]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
iotests: Flush in iotests.py's QemuIoInteractive
[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
f345cfd0 32
02f3a911
VSO
33sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
34import qtest
35
f345cfd0 36
934659c4 37# This will not work if arguments contain spaces but is necessary if we
f345cfd0 38# want to support the override options that ./check supports.
934659c4
HR
39qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
40if os.environ.get('QEMU_IMG_OPTIONS'):
41 qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
42
43qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
44if os.environ.get('QEMU_IO_OPTIONS'):
45 qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
46
bec87774
HR
47qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
48if os.environ.get('QEMU_NBD_OPTIONS'):
49 qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
50
4c44b4a4 51qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
66613974 52qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
f345cfd0
SH
53
54imgfmt = os.environ.get('IMGFMT', 'raw')
55imgproto = os.environ.get('IMGPROTO', 'file')
5a8fabf3 56test_dir = os.environ.get('TEST_DIR')
e8f8624d 57output_dir = os.environ.get('OUTPUT_DIR', '.')
58cc2ae1 58cachemode = os.environ.get('CACHEMODE')
e166b414 59qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE')
f345cfd0 60
30b005d9 61socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
c0088d79 62debug = False
30b005d9 63
85a353a0
VSO
64luks_default_secret_object = 'secret,id=keysec0,data=' + \
65 os.environ['IMGKEYSECRET']
66luks_default_key_secret_opt = 'key-secret=keysec0'
67
68
f345cfd0
SH
69def qemu_img(*args):
70 '''Run qemu-img and return the exit code'''
71 devnull = open('/dev/null', 'r+')
2ef6093c
HR
72 exitcode = subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
73 if exitcode < 0:
74 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
75 return exitcode
f345cfd0 76
85a353a0
VSO
77def qemu_img_create(*args):
78 args = list(args)
79
80 # default luks support
81 if '-f' in args and args[args.index('-f') + 1] == 'luks':
82 if '-o' in args:
83 i = args.index('-o')
84 if 'key-secret' not in args[i + 1]:
85 args[i + 1].append(luks_default_key_secret_opt)
86 args.insert(i + 2, '--object')
87 args.insert(i + 3, luks_default_secret_object)
88 else:
89 args = ['-o', luks_default_key_secret_opt,
90 '--object', luks_default_secret_object] + args
91
92 args.insert(0, 'create')
93
94 return qemu_img(*args)
95
d2ef210c 96def qemu_img_verbose(*args):
993d46ce 97 '''Run qemu-img without suppressing its output and return the exit code'''
2ef6093c
HR
98 exitcode = subprocess.call(qemu_img_args + list(args))
99 if exitcode < 0:
100 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
101 return exitcode
d2ef210c 102
3677e6f6
HR
103def qemu_img_pipe(*args):
104 '''Run qemu-img and return its output'''
491e5e85
DB
105 subp = subprocess.Popen(qemu_img_args + list(args),
106 stdout=subprocess.PIPE,
107 stderr=subprocess.STDOUT)
2ef6093c
HR
108 exitcode = subp.wait()
109 if exitcode < 0:
110 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
111 return subp.communicate()[0]
3677e6f6 112
5ba141dc
KW
113def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]):
114 args = [ 'info' ]
115 if imgopts:
116 args.append('--image-opts')
117 else:
118 args += [ '-f', imgfmt ]
119 args += extra_args
120 args.append(filename)
121
122 output = qemu_img_pipe(*args)
6b605ade
KW
123 if not filter_path:
124 filter_path = filename
125 log(filter_img_info(output, filter_path))
126
f345cfd0
SH
127def qemu_io(*args):
128 '''Run qemu-io and return the stdout data'''
129 args = qemu_io_args + list(args)
491e5e85
DB
130 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
131 stderr=subprocess.STDOUT)
2ef6093c
HR
132 exitcode = subp.wait()
133 if exitcode < 0:
134 sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
135 return subp.communicate()[0]
f345cfd0 136
745f2bf4
HR
137def qemu_io_silent(*args):
138 '''Run qemu-io and return the exit code, suppressing stdout'''
139 args = qemu_io_args + list(args)
140 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
141 if exitcode < 0:
142 sys.stderr.write('qemu-io received signal %i: %s\n' %
143 (-exitcode, ' '.join(args)))
144 return exitcode
145
9fa90eec
VSO
146
147class QemuIoInteractive:
148 def __init__(self, *args):
149 self.args = qemu_io_args + list(args)
150 self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
151 stdout=subprocess.PIPE,
152 stderr=subprocess.STDOUT)
153 assert self._p.stdout.read(9) == 'qemu-io> '
154
155 def close(self):
156 self._p.communicate('q\n')
157
158 def _read_output(self):
159 pattern = 'qemu-io> '
160 n = len(pattern)
161 pos = 0
162 s = []
163 while pos != n:
164 c = self._p.stdout.read(1)
165 # check unexpected EOF
166 assert c != ''
167 s.append(c)
168 if c == pattern[pos]:
169 pos += 1
170 else:
171 pos = 0
172
173 return ''.join(s[:-n])
174
175 def cmd(self, cmd):
176 # quit command is in close(), '\n' is added automatically
177 assert '\n' not in cmd
178 cmd = cmd.strip()
179 assert cmd != 'q' and cmd != 'quit'
180 self._p.stdin.write(cmd + '\n')
f544adf7 181 self._p.stdin.flush()
9fa90eec
VSO
182 return self._read_output()
183
184
bec87774
HR
185def qemu_nbd(*args):
186 '''Run qemu-nbd in daemon mode and return the parent's exit code'''
187 return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
188
e1b5c51f 189def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 190 '''Return True if two image files are identical'''
e1b5c51f
PB
191 return qemu_img('compare', '-f', fmt1,
192 '-F', fmt2, img1, img2) == 0
3a3918c3 193
2499a096
SH
194def create_image(name, size):
195 '''Create a fully-allocated raw image with sector markers'''
196 file = open(name, 'w')
197 i = 0
198 while i < size:
199 sector = struct.pack('>l504xl', i / 512, i / 512)
200 file.write(sector)
201 i = i + 512
202 file.close()
203
74f69050
FZ
204def image_size(img):
205 '''Return image's virtual size'''
206 r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
207 return json.loads(r)['virtual-size']
208
a2d1c8fd
DB
209test_dir_re = re.compile(r"%s" % test_dir)
210def filter_test_dir(msg):
211 return test_dir_re.sub("TEST_DIR", msg)
212
213win32_re = re.compile(r"\r")
214def filter_win32(msg):
215 return win32_re.sub("", msg)
216
217qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
218def filter_qemu_io(msg):
219 msg = filter_win32(msg)
220 return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
221
222chown_re = re.compile(r"chown [0-9]+:[0-9]+")
223def filter_chown(msg):
224 return chown_re.sub("chown UID:GID", msg)
225
12314f2d
SH
226def filter_qmp_event(event):
227 '''Filter a QMP event dict'''
228 event = dict(event)
229 if 'timestamp' in event:
230 event['timestamp']['seconds'] = 'SECS'
231 event['timestamp']['microseconds'] = 'USECS'
232 return event
233
e234398a
KW
234def filter_testfiles(msg):
235 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
236 return msg.replace(prefix, 'TEST_DIR/PID-')
237
6b605ade
KW
238def filter_img_info(output, filename):
239 lines = []
240 for line in output.split('\n'):
241 if 'disk size' in line or 'actual-size' in line:
242 continue
243 line = line.replace(filename, 'TEST_IMG') \
244 .replace(imgfmt, 'IMGFMT')
245 line = re.sub('iters: [0-9]+', 'iters: XXX', line)
246 line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
247 lines.append(line)
248 return '\n'.join(lines)
249
a2d1c8fd
DB
250def log(msg, filters=[]):
251 for flt in filters:
252 msg = flt(msg)
f03868bd 253 print(msg)
a2d1c8fd 254
2c93c5cb
KW
255class Timeout:
256 def __init__(self, seconds, errmsg = "Timeout"):
257 self.seconds = seconds
258 self.errmsg = errmsg
259 def __enter__(self):
260 signal.signal(signal.SIGALRM, self.timeout)
261 signal.setitimer(signal.ITIMER_REAL, self.seconds)
262 return self
263 def __exit__(self, type, value, traceback):
264 signal.setitimer(signal.ITIMER_REAL, 0)
265 return False
266 def timeout(self, signum, frame):
267 raise Exception(self.errmsg)
268
f4844ac0
SH
269
270class FilePath(object):
271 '''An auto-generated filename that cleans itself up.
272
273 Use this context manager to generate filenames and ensure that the file
274 gets deleted::
275
276 with TestFilePath('test.img') as img_path:
277 qemu_img('create', img_path, '1G')
278 # migration_sock_path is automatically deleted
279 '''
280 def __init__(self, name):
281 filename = '{0}-{1}'.format(os.getpid(), name)
282 self.path = os.path.join(test_dir, filename)
283
284 def __enter__(self):
285 return self.path
286
287 def __exit__(self, exc_type, exc_val, exc_tb):
288 try:
289 os.remove(self.path)
290 except OSError:
291 pass
292 return False
293
294
ef6e9228
VSO
295def file_path_remover():
296 for path in reversed(file_path_remover.paths):
297 try:
298 os.remove(path)
299 except OSError:
300 pass
301
302
303def file_path(*names):
304 ''' Another way to get auto-generated filename that cleans itself up.
305
306 Use is as simple as:
307
308 img_a, img_b = file_path('a.img', 'b.img')
309 sock = file_path('socket')
310 '''
311
312 if not hasattr(file_path_remover, 'paths'):
313 file_path_remover.paths = []
314 atexit.register(file_path_remover)
315
316 paths = []
317 for name in names:
318 filename = '{0}-{1}'.format(os.getpid(), name)
319 path = os.path.join(test_dir, filename)
320 file_path_remover.paths.append(path)
321 paths.append(path)
322
323 return paths[0] if len(paths) == 1 else paths
324
5a259e86
KW
325def remote_filename(path):
326 if imgproto == 'file':
327 return path
328 elif imgproto == 'ssh':
329 return "ssh://127.0.0.1%s" % (path)
330 else:
331 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 332
4c44b4a4 333class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
334 '''A QEMU VM'''
335
5fcbdf50
HR
336 def __init__(self, path_suffix=''):
337 name = "qemu%s-%d" % (path_suffix, os.getpid())
338 super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
339 test_dir=test_dir,
4c44b4a4 340 socket_scm_helper=socket_scm_helper)
f345cfd0 341 self._num_drives = 0
30b005d9 342
ccc15f7d
SH
343 def add_object(self, opts):
344 self._args.append('-object')
345 self._args.append(opts)
346 return self
347
486b88bd
KW
348 def add_device(self, opts):
349 self._args.append('-device')
350 self._args.append(opts)
351 return self
352
78b666f4
FZ
353 def add_drive_raw(self, opts):
354 self._args.append('-drive')
355 self._args.append(opts)
356 return self
357
e1b5c51f 358 def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
f345cfd0 359 '''Add a virtio-blk drive to the VM'''
8e492253 360 options = ['if=%s' % interface,
f345cfd0 361 'id=drive%d' % self._num_drives]
8e492253
HR
362
363 if path is not None:
364 options.append('file=%s' % path)
e1b5c51f 365 options.append('format=%s' % format)
fc17c259 366 options.append('cache=%s' % cachemode)
8e492253 367
f345cfd0
SH
368 if opts:
369 options.append(opts)
370
85a353a0
VSO
371 if format == 'luks' and 'key-secret' not in opts:
372 # default luks support
373 if luks_default_secret_object not in self._args:
374 self.add_object(luks_default_secret_object)
375
376 options.append(luks_default_key_secret_opt)
377
f345cfd0
SH
378 self._args.append('-drive')
379 self._args.append(','.join(options))
380 self._num_drives += 1
381 return self
382
5694923a
HR
383 def add_blockdev(self, opts):
384 self._args.append('-blockdev')
385 if isinstance(opts, str):
386 self._args.append(opts)
387 else:
388 self._args.append(','.join(opts))
389 return self
390
12314f2d
SH
391 def add_incoming(self, addr):
392 self._args.append('-incoming')
393 self._args.append(addr)
394 return self
395
3cf53c77
FZ
396 def pause_drive(self, drive, event=None):
397 '''Pause drive r/w operations'''
398 if not event:
399 self.pause_drive(drive, "read_aio")
400 self.pause_drive(drive, "write_aio")
401 return
402 self.qmp('human-monitor-command',
403 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
404
405 def resume_drive(self, drive):
406 self.qmp('human-monitor-command',
407 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
408
e3409362
IM
409 def hmp_qemu_io(self, drive, cmd):
410 '''Write to a given drive using an HMP command'''
411 return self.qmp('human-monitor-command',
412 command_line='qemu-io %s "%s"' % (drive, cmd))
413
62a94288
KW
414 def flatten_qmp_object(self, obj, output=None, basestr=''):
415 if output is None:
416 output = dict()
417 if isinstance(obj, list):
418 for i in range(len(obj)):
419 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
420 elif isinstance(obj, dict):
421 for key in obj:
422 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
423 else:
424 output[basestr[:-1]] = obj # Strip trailing '.'
425 return output
426
427 def qmp_to_opts(self, obj):
428 obj = self.flatten_qmp_object(obj)
429 output_list = list()
430 for key in obj:
431 output_list += [key + '=' + obj[key]]
432 return ','.join(output_list)
433
5ad1dbf7
KW
434 def get_qmp_events_filtered(self, wait=True):
435 result = []
436 for ev in self.get_qmp_events(wait=wait):
437 result.append(filter_qmp_event(ev))
438 return result
62a94288 439
e234398a
KW
440 def qmp_log(self, cmd, filters=[filter_testfiles], **kwargs):
441 logmsg = "{'execute': '%s', 'arguments': %s}" % (cmd, kwargs)
442 log(logmsg, filters)
443 result = self.qmp(cmd, **kwargs)
444 log(str(result), filters)
445 return result
446
fc47d851
KW
447 def run_job(self, job, auto_finalize=True, auto_dismiss=False):
448 while True:
449 for ev in self.get_qmp_events_filtered(wait=True):
450 if ev['event'] == 'JOB_STATUS_CHANGE':
451 status = ev['data']['status']
452 if status == 'aborting':
453 result = self.qmp('query-jobs')
454 for j in result['return']:
455 if j['id'] == job:
456 log('Job failed: %s' % (j['error']))
457 elif status == 'pending' and not auto_finalize:
458 self.qmp_log('job-finalize', id=job)
459 elif status == 'concluded' and not auto_dismiss:
460 self.qmp_log('job-dismiss', id=job)
461 elif status == 'null':
462 return
463 else:
464 iotests.log(ev)
465
7898f74e 466
f345cfd0
SH
467index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
468
469class QMPTestCase(unittest.TestCase):
470 '''Abstract base class for QMP test cases'''
471
472 def dictpath(self, d, path):
473 '''Traverse a path in a nested dict'''
474 for component in path.split('/'):
475 m = index_re.match(component)
476 if m:
477 component, idx = m.groups()
478 idx = int(idx)
479
480 if not isinstance(d, dict) or component not in d:
481 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
482 d = d[component]
483
484 if m:
485 if not isinstance(d, list):
486 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
487 try:
488 d = d[idx]
489 except IndexError:
490 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
491 return d
492
90f0b711
PB
493 def assert_qmp_absent(self, d, path):
494 try:
495 result = self.dictpath(d, path)
496 except AssertionError:
497 return
498 self.fail('path "%s" has value "%s"' % (path, str(result)))
499
f345cfd0
SH
500 def assert_qmp(self, d, path, value):
501 '''Assert that the value for a specific path in a QMP dict matches'''
502 result = self.dictpath(d, path)
503 self.assertEqual(result, value, 'values not equal "%s" and "%s"' % (str(result), str(value)))
504
ecc1c88e
SH
505 def assert_no_active_block_jobs(self):
506 result = self.vm.qmp('query-block-jobs')
507 self.assert_qmp(result, 'return', [])
508
e71fc0ba
FZ
509 def assert_has_block_node(self, node_name=None, file_name=None):
510 """Issue a query-named-block-nodes and assert node_name and/or
511 file_name is present in the result"""
512 def check_equal_or_none(a, b):
513 return a == None or b == None or a == b
514 assert node_name or file_name
515 result = self.vm.qmp('query-named-block-nodes')
516 for x in result["return"]:
517 if check_equal_or_none(x.get("node-name"), node_name) and \
518 check_equal_or_none(x.get("file"), file_name):
519 return
520 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
521 (node_name, file_name, result))
522
e07375f5
HR
523 def assert_json_filename_equal(self, json_filename, reference):
524 '''Asserts that the given filename is a json: filename and that its
525 content is equal to the given reference object'''
526 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
527 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
528 self.vm.flatten_qmp_object(reference))
e07375f5 529
3cf53c77 530 def cancel_and_wait(self, drive='drive0', force=False, resume=False):
2575fe16
SH
531 '''Cancel a block job and wait for it to finish, returning the event'''
532 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
533 self.assert_qmp(result, 'return', {})
534
3cf53c77
FZ
535 if resume:
536 self.vm.resume_drive(drive)
537
2575fe16
SH
538 cancelled = False
539 result = None
540 while not cancelled:
541 for event in self.vm.get_qmp_events(wait=True):
542 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
543 event['event'] == 'BLOCK_JOB_CANCELLED':
544 self.assert_qmp(event, 'data/device', drive)
545 result = event
546 cancelled = True
1dac83f1
KW
547 elif event['event'] == 'JOB_STATUS_CHANGE':
548 self.assert_qmp(event, 'data/id', drive)
549
2575fe16
SH
550
551 self.assert_no_active_block_jobs()
552 return result
553
9974ad40 554 def wait_until_completed(self, drive='drive0', check_offset=True):
0dbe8a1b 555 '''Wait for a block job to finish, returning the event'''
c3988519 556 while True:
0dbe8a1b
SH
557 for event in self.vm.get_qmp_events(wait=True):
558 if event['event'] == 'BLOCK_JOB_COMPLETED':
559 self.assert_qmp(event, 'data/device', drive)
560 self.assert_qmp_absent(event, 'data/error')
9974ad40 561 if check_offset:
1d3ba15a 562 self.assert_qmp(event, 'data/offset', event['data']['len'])
c3988519
PX
563 self.assert_no_active_block_jobs()
564 return event
1dac83f1
KW
565 elif event['event'] == 'JOB_STATUS_CHANGE':
566 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 567
866323f3
FZ
568 def wait_ready(self, drive='drive0'):
569 '''Wait until a block job BLOCK_JOB_READY event'''
d7b25297
FZ
570 f = {'data': {'type': 'mirror', 'device': drive } }
571 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
572
573 def wait_ready_and_cancel(self, drive='drive0'):
574 self.wait_ready(drive=drive)
575 event = self.cancel_and_wait(drive=drive)
576 self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED')
577 self.assert_qmp(event, 'data/type', 'mirror')
578 self.assert_qmp(event, 'data/offset', event['data']['len'])
579
580 def complete_and_wait(self, drive='drive0', wait_ready=True):
581 '''Complete a block job and wait for it to finish'''
582 if wait_ready:
583 self.wait_ready(drive=drive)
584
585 result = self.vm.qmp('block-job-complete', device=drive)
586 self.assert_qmp(result, 'return', {})
587
588 event = self.wait_until_completed(drive=drive)
589 self.assert_qmp(event, 'data/type', 'mirror')
590
f03d9d24 591 def pause_wait(self, job_id='job0'):
2c93c5cb
KW
592 with Timeout(1, "Timeout waiting for job to pause"):
593 while True:
594 result = self.vm.qmp('query-block-jobs')
c1bac161 595 found = False
2c93c5cb 596 for job in result['return']:
c1bac161
VSO
597 if job['device'] == job_id:
598 found = True
599 if job['paused'] == True and job['busy'] == False:
600 return job
601 break
602 assert found
2c93c5cb 603
f03d9d24
JS
604 def pause_job(self, job_id='job0', wait=True):
605 result = self.vm.qmp('block-job-pause', device=job_id)
606 self.assert_qmp(result, 'return', {})
607 if wait:
608 return self.pause_wait(job_id)
609 return result
610
2c93c5cb 611
f345cfd0
SH
612def notrun(reason):
613 '''Skip this test suite'''
614 # Each test in qemu-iotests has a number ("seq")
615 seq = os.path.basename(sys.argv[0])
616
e8f8624d 617 open('%s/%s.notrun' % (output_dir, seq), 'wb').write(reason + '\n')
f03868bd 618 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
619 sys.exit(0)
620
3f5c4076 621def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
f48351d2
VSO
622 assert not (supported_fmts and unsupported_fmts)
623
624 if 'generic' in supported_fmts and \
625 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
626 # similar to
627 # _supported_fmt generic
628 # for bash tests
629 return
630
631 not_sup = supported_fmts and (imgfmt not in supported_fmts)
632 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 633 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 634
5a259e86
KW
635def verify_protocol(supported=[], unsupported=[]):
636 assert not (supported and unsupported)
637
638 if 'generic' in supported:
639 return
640
641 not_sup = supported and (imgproto not in supported)
642 if not_sup or (imgproto in unsupported):
643 notrun('not suitable for this protocol: %s' % imgproto)
644
c6a92369 645def verify_platform(supported_oses=['linux']):
79e7a019 646 if True not in [sys.platform.startswith(x) for x in supported_oses]:
bc521696
FZ
647 notrun('not suitable for this OS: %s' % sys.platform)
648
ac8bd439
VSO
649def verify_cache_mode(supported_cache_modes=[]):
650 if supported_cache_modes and (cachemode not in supported_cache_modes):
651 notrun('not suitable for this cache mode: %s' % cachemode)
652
b0f90495
AG
653def supports_quorum():
654 return 'quorum' in qemu_img_pipe('--help')
655
3f647b51
SS
656def verify_quorum():
657 '''Skip test suite if quorum support is not available'''
b0f90495 658 if not supports_quorum():
3f647b51
SS
659 notrun('quorum support missing')
660
febc8c86
VSO
661def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
662 unsupported_fmts=[]):
c6a92369
DB
663 '''Run tests'''
664
c0088d79
KW
665 global debug
666
5a8fabf3
SS
667 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
668 # indicate that we're not being run via "check". There may be
669 # other things set up by "check" that individual test cases rely
670 # on.
671 if test_dir is None or qemu_default_machine is None:
672 sys.stderr.write('Please run this test via the "check" script\n')
673 sys.exit(os.EX_USAGE)
674
c6a92369
DB
675 debug = '-d' in sys.argv
676 verbosity = 1
febc8c86 677 verify_image_format(supported_fmts, unsupported_fmts)
c6a92369 678 verify_platform(supported_oses)
ac8bd439 679 verify_cache_mode(supported_cache_modes)
c6a92369 680
f345cfd0
SH
681 # We need to filter out the time taken from the output so that qemu-iotest
682 # can reliably diff the results against master output.
683 import StringIO
aa4f592a
FZ
684 if debug:
685 output = sys.stdout
686 verbosity = 2
687 sys.argv.remove('-d')
688 else:
689 output = StringIO.StringIO()
f345cfd0 690
43851b5b
EH
691 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
692
f345cfd0 693 class MyTestRunner(unittest.TextTestRunner):
aa4f592a 694 def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
f345cfd0
SH
695 unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
696
697 # unittest.main() will use sys.exit() so expect a SystemExit exception
698 try:
699 unittest.main(testRunner=MyTestRunner)
700 finally:
aa4f592a
FZ
701 if not debug:
702 sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))