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