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