]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
blockdev-backup: don't check aio_context too early
[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
8f8fd9ed
CR
35sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
36from qemu import qtest
02f3a911 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
8a57a4be 79def ordered_qmp(qmsg, conv_keys=True):
039be85c
JS
80 # Dictionaries are not ordered prior to 3.6, therefore:
81 if isinstance(qmsg, list):
82 return [ordered_qmp(atom) for atom in qmsg]
83 if isinstance(qmsg, dict):
84 od = OrderedDict()
85 for k, v in sorted(qmsg.items()):
8a57a4be
HR
86 if conv_keys:
87 k = k.replace('_', '-')
88 od[k] = ordered_qmp(v, conv_keys=False)
039be85c
JS
89 return od
90 return qmsg
0706e87d 91
85a353a0
VSO
92def qemu_img_create(*args):
93 args = list(args)
94
95 # default luks support
96 if '-f' in args and args[args.index('-f') + 1] == 'luks':
97 if '-o' in args:
98 i = args.index('-o')
99 if 'key-secret' not in args[i + 1]:
100 args[i + 1].append(luks_default_key_secret_opt)
101 args.insert(i + 2, '--object')
102 args.insert(i + 3, luks_default_secret_object)
103 else:
104 args = ['-o', luks_default_key_secret_opt,
105 '--object', luks_default_secret_object] + args
106
107 args.insert(0, 'create')
108
109 return qemu_img(*args)
110
d2ef210c 111def qemu_img_verbose(*args):
993d46ce 112 '''Run qemu-img without suppressing its output and return the exit code'''
2ef6093c
HR
113 exitcode = subprocess.call(qemu_img_args + list(args))
114 if exitcode < 0:
115 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
116 return exitcode
d2ef210c 117
3677e6f6
HR
118def qemu_img_pipe(*args):
119 '''Run qemu-img and return its output'''
491e5e85
DB
120 subp = subprocess.Popen(qemu_img_args + list(args),
121 stdout=subprocess.PIPE,
8eb5e674
HR
122 stderr=subprocess.STDOUT,
123 universal_newlines=True)
2ef6093c
HR
124 exitcode = subp.wait()
125 if exitcode < 0:
126 sys.stderr.write('qemu-img received signal %i: %s\n' % (-exitcode, ' '.join(qemu_img_args + list(args))))
127 return subp.communicate()[0]
3677e6f6 128
ac6fb43e
KW
129def qemu_img_log(*args):
130 result = qemu_img_pipe(*args)
131 log(result, filters=[filter_testfiles])
132 return result
133
5ba141dc
KW
134def img_info_log(filename, filter_path=None, imgopts=False, extra_args=[]):
135 args = [ 'info' ]
136 if imgopts:
137 args.append('--image-opts')
138 else:
139 args += [ '-f', imgfmt ]
140 args += extra_args
141 args.append(filename)
142
143 output = qemu_img_pipe(*args)
6b605ade
KW
144 if not filter_path:
145 filter_path = filename
146 log(filter_img_info(output, filter_path))
147
f345cfd0
SH
148def qemu_io(*args):
149 '''Run qemu-io and return the stdout data'''
150 args = qemu_io_args + list(args)
491e5e85 151 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
8eb5e674
HR
152 stderr=subprocess.STDOUT,
153 universal_newlines=True)
2ef6093c
HR
154 exitcode = subp.wait()
155 if exitcode < 0:
156 sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
157 return subp.communicate()[0]
f345cfd0 158
745f2bf4
HR
159def qemu_io_silent(*args):
160 '''Run qemu-io and return the exit code, suppressing stdout'''
161 args = qemu_io_args + list(args)
162 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
163 if exitcode < 0:
164 sys.stderr.write('qemu-io received signal %i: %s\n' %
165 (-exitcode, ' '.join(args)))
166 return exitcode
167
9fa90eec
VSO
168
169class QemuIoInteractive:
170 def __init__(self, *args):
171 self.args = qemu_io_args + list(args)
172 self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
173 stdout=subprocess.PIPE,
8eb5e674
HR
174 stderr=subprocess.STDOUT,
175 universal_newlines=True)
9fa90eec
VSO
176 assert self._p.stdout.read(9) == 'qemu-io> '
177
178 def close(self):
179 self._p.communicate('q\n')
180
181 def _read_output(self):
182 pattern = 'qemu-io> '
183 n = len(pattern)
184 pos = 0
185 s = []
186 while pos != n:
187 c = self._p.stdout.read(1)
188 # check unexpected EOF
189 assert c != ''
190 s.append(c)
191 if c == pattern[pos]:
192 pos += 1
193 else:
194 pos = 0
195
196 return ''.join(s[:-n])
197
198 def cmd(self, cmd):
199 # quit command is in close(), '\n' is added automatically
200 assert '\n' not in cmd
201 cmd = cmd.strip()
202 assert cmd != 'q' and cmd != 'quit'
203 self._p.stdin.write(cmd + '\n')
f544adf7 204 self._p.stdin.flush()
9fa90eec
VSO
205 return self._read_output()
206
207
bec87774
HR
208def qemu_nbd(*args):
209 '''Run qemu-nbd in daemon mode and return the parent's exit code'''
210 return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
211
6177b584 212def qemu_nbd_early_pipe(*args):
e1e6eccd 213 '''Run qemu-nbd in daemon mode and return both the parent's exit code
6177b584 214 and its output in case of an error'''
e1e6eccd
HR
215 subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args),
216 stdout=subprocess.PIPE,
217 stderr=subprocess.STDOUT,
218 universal_newlines=True)
219 exitcode = subp.wait()
220 if exitcode < 0:
221 sys.stderr.write('qemu-nbd received signal %i: %s\n' %
222 (-exitcode,
223 ' '.join(qemu_nbd_args + ['--fork'] + list(args))))
6177b584
HR
224 if exitcode == 0:
225 return exitcode, ''
226 else:
227 return exitcode, subp.communicate()[0]
e1e6eccd 228
e1b5c51f 229def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 230 '''Return True if two image files are identical'''
e1b5c51f
PB
231 return qemu_img('compare', '-f', fmt1,
232 '-F', fmt2, img1, img2) == 0
3a3918c3 233
2499a096
SH
234def create_image(name, size):
235 '''Create a fully-allocated raw image with sector markers'''
8eb5e674 236 file = open(name, 'wb')
2499a096
SH
237 i = 0
238 while i < size:
9a3a9a63 239 sector = struct.pack('>l504xl', i // 512, i // 512)
2499a096
SH
240 file.write(sector)
241 i = i + 512
242 file.close()
243
74f69050
FZ
244def image_size(img):
245 '''Return image's virtual size'''
246 r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
247 return json.loads(r)['virtual-size']
248
011a5761
HR
249def is_str(val):
250 if sys.version_info.major >= 3:
251 return isinstance(val, str)
252 else:
253 return isinstance(val, str) or isinstance(val, unicode)
254
a2d1c8fd
DB
255test_dir_re = re.compile(r"%s" % test_dir)
256def filter_test_dir(msg):
257 return test_dir_re.sub("TEST_DIR", msg)
258
259win32_re = re.compile(r"\r")
260def filter_win32(msg):
261 return win32_re.sub("", msg)
262
263qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
264def filter_qemu_io(msg):
265 msg = filter_win32(msg)
266 return qemu_io_re.sub("X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)", msg)
267
268chown_re = re.compile(r"chown [0-9]+:[0-9]+")
269def filter_chown(msg):
270 return chown_re.sub("chown UID:GID", msg)
271
12314f2d
SH
272def filter_qmp_event(event):
273 '''Filter a QMP event dict'''
274 event = dict(event)
275 if 'timestamp' in event:
276 event['timestamp']['seconds'] = 'SECS'
277 event['timestamp']['microseconds'] = 'USECS'
278 return event
279
08fcd611
JS
280def filter_qmp(qmsg, filter_fn):
281 '''Given a string filter, filter a QMP object's values.
282 filter_fn takes a (key, value) pair.'''
283 # Iterate through either lists or dicts;
284 if isinstance(qmsg, list):
285 items = enumerate(qmsg)
286 else:
287 items = qmsg.items()
288
289 for k, v in items:
290 if isinstance(v, list) or isinstance(v, dict):
291 qmsg[k] = filter_qmp(v, filter_fn)
292 else:
293 qmsg[k] = filter_fn(k, v)
294 return qmsg
295
e234398a
KW
296def filter_testfiles(msg):
297 prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
298 return msg.replace(prefix, 'TEST_DIR/PID-')
299
08fcd611
JS
300def filter_qmp_testfiles(qmsg):
301 def _filter(key, value):
56a6e5d0 302 if is_str(value):
08fcd611
JS
303 return filter_testfiles(value)
304 return value
305 return filter_qmp(qmsg, _filter)
306
fa1151f8
JS
307def filter_generated_node_ids(msg):
308 return re.sub("#block[0-9]+", "NODE_NAME", msg)
309
6b605ade
KW
310def filter_img_info(output, filename):
311 lines = []
312 for line in output.split('\n'):
313 if 'disk size' in line or 'actual-size' in line:
314 continue
315 line = line.replace(filename, 'TEST_IMG') \
316 .replace(imgfmt, 'IMGFMT')
317 line = re.sub('iters: [0-9]+', 'iters: XXX', line)
318 line = re.sub('uuid: [-a-f0-9]+', 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', line)
bab4feb2 319 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
6b605ade
KW
320 lines.append(line)
321 return '\n'.join(lines)
322
f2ea0b20
HR
323def filter_imgfmt(msg):
324 return msg.replace(imgfmt, 'IMGFMT')
325
326def filter_qmp_imgfmt(qmsg):
327 def _filter(key, value):
328 if is_str(value):
329 return filter_imgfmt(value)
330 return value
331 return filter_qmp(qmsg, _filter)
332
55cd64ea
JS
333def log(msg, filters=[], indent=None):
334 '''Logs either a string message or a JSON serializable message (like QMP).
335 If indent is provided, JSON serializable messages are pretty-printed.'''
a2d1c8fd
DB
336 for flt in filters:
337 msg = flt(msg)
0706e87d 338 if isinstance(msg, dict) or isinstance(msg, list):
55cd64ea
JS
339 # Python < 3.4 needs to know not to add whitespace when pretty-printing:
340 separators = (', ', ': ') if indent is None else (',', ': ')
0706e87d
JS
341 # Don't sort if it's already sorted
342 do_sort = not isinstance(msg, OrderedDict)
55cd64ea
JS
343 print(json.dumps(msg, sort_keys=do_sort,
344 indent=indent, separators=separators))
e21b5f34
HR
345 else:
346 print(msg)
a2d1c8fd 347
2c93c5cb
KW
348class Timeout:
349 def __init__(self, seconds, errmsg = "Timeout"):
350 self.seconds = seconds
351 self.errmsg = errmsg
352 def __enter__(self):
353 signal.signal(signal.SIGALRM, self.timeout)
354 signal.setitimer(signal.ITIMER_REAL, self.seconds)
355 return self
356 def __exit__(self, type, value, traceback):
357 signal.setitimer(signal.ITIMER_REAL, 0)
358 return False
359 def timeout(self, signum, frame):
360 raise Exception(self.errmsg)
361
f4844ac0
SH
362
363class FilePath(object):
364 '''An auto-generated filename that cleans itself up.
365
366 Use this context manager to generate filenames and ensure that the file
367 gets deleted::
368
369 with TestFilePath('test.img') as img_path:
370 qemu_img('create', img_path, '1G')
371 # migration_sock_path is automatically deleted
372 '''
373 def __init__(self, name):
374 filename = '{0}-{1}'.format(os.getpid(), name)
375 self.path = os.path.join(test_dir, filename)
376
377 def __enter__(self):
378 return self.path
379
380 def __exit__(self, exc_type, exc_val, exc_tb):
381 try:
382 os.remove(self.path)
383 except OSError:
384 pass
385 return False
386
387
ef6e9228
VSO
388def file_path_remover():
389 for path in reversed(file_path_remover.paths):
390 try:
391 os.remove(path)
392 except OSError:
393 pass
394
395
396def file_path(*names):
397 ''' Another way to get auto-generated filename that cleans itself up.
398
399 Use is as simple as:
400
401 img_a, img_b = file_path('a.img', 'b.img')
402 sock = file_path('socket')
403 '''
404
405 if not hasattr(file_path_remover, 'paths'):
406 file_path_remover.paths = []
407 atexit.register(file_path_remover)
408
409 paths = []
410 for name in names:
411 filename = '{0}-{1}'.format(os.getpid(), name)
412 path = os.path.join(test_dir, filename)
413 file_path_remover.paths.append(path)
414 paths.append(path)
415
416 return paths[0] if len(paths) == 1 else paths
417
5a259e86
KW
418def remote_filename(path):
419 if imgproto == 'file':
420 return path
421 elif imgproto == 'ssh':
b8c1f901 422 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
5a259e86
KW
423 else:
424 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 425
4c44b4a4 426class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
427 '''A QEMU VM'''
428
5fcbdf50
HR
429 def __init__(self, path_suffix=''):
430 name = "qemu%s-%d" % (path_suffix, os.getpid())
431 super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
432 test_dir=test_dir,
4c44b4a4 433 socket_scm_helper=socket_scm_helper)
f345cfd0 434 self._num_drives = 0
30b005d9 435
ccc15f7d
SH
436 def add_object(self, opts):
437 self._args.append('-object')
438 self._args.append(opts)
439 return self
440
486b88bd
KW
441 def add_device(self, opts):
442 self._args.append('-device')
443 self._args.append(opts)
444 return self
445
78b666f4
FZ
446 def add_drive_raw(self, opts):
447 self._args.append('-drive')
448 self._args.append(opts)
449 return self
450
e1b5c51f 451 def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
f345cfd0 452 '''Add a virtio-blk drive to the VM'''
8e492253 453 options = ['if=%s' % interface,
f345cfd0 454 'id=drive%d' % self._num_drives]
8e492253
HR
455
456 if path is not None:
457 options.append('file=%s' % path)
e1b5c51f 458 options.append('format=%s' % format)
fc17c259 459 options.append('cache=%s' % cachemode)
8e492253 460
f345cfd0
SH
461 if opts:
462 options.append(opts)
463
85a353a0
VSO
464 if format == 'luks' and 'key-secret' not in opts:
465 # default luks support
466 if luks_default_secret_object not in self._args:
467 self.add_object(luks_default_secret_object)
468
469 options.append(luks_default_key_secret_opt)
470
f345cfd0
SH
471 self._args.append('-drive')
472 self._args.append(','.join(options))
473 self._num_drives += 1
474 return self
475
5694923a
HR
476 def add_blockdev(self, opts):
477 self._args.append('-blockdev')
478 if isinstance(opts, str):
479 self._args.append(opts)
480 else:
481 self._args.append(','.join(opts))
482 return self
483
12314f2d
SH
484 def add_incoming(self, addr):
485 self._args.append('-incoming')
486 self._args.append(addr)
487 return self
488
3cf53c77
FZ
489 def pause_drive(self, drive, event=None):
490 '''Pause drive r/w operations'''
491 if not event:
492 self.pause_drive(drive, "read_aio")
493 self.pause_drive(drive, "write_aio")
494 return
495 self.qmp('human-monitor-command',
496 command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
497
498 def resume_drive(self, drive):
499 self.qmp('human-monitor-command',
500 command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
501
e3409362
IM
502 def hmp_qemu_io(self, drive, cmd):
503 '''Write to a given drive using an HMP command'''
504 return self.qmp('human-monitor-command',
505 command_line='qemu-io %s "%s"' % (drive, cmd))
506
62a94288
KW
507 def flatten_qmp_object(self, obj, output=None, basestr=''):
508 if output is None:
509 output = dict()
510 if isinstance(obj, list):
511 for i in range(len(obj)):
512 self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
513 elif isinstance(obj, dict):
514 for key in obj:
515 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
516 else:
517 output[basestr[:-1]] = obj # Strip trailing '.'
518 return output
519
520 def qmp_to_opts(self, obj):
521 obj = self.flatten_qmp_object(obj)
522 output_list = list()
523 for key in obj:
524 output_list += [key + '=' + obj[key]]
525 return ','.join(output_list)
526
5ad1dbf7
KW
527 def get_qmp_events_filtered(self, wait=True):
528 result = []
529 for ev in self.get_qmp_events(wait=wait):
530 result.append(filter_qmp_event(ev))
531 return result
62a94288 532
55cd64ea 533 def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
0706e87d
JS
534 full_cmd = OrderedDict((
535 ("execute", cmd),
039be85c 536 ("arguments", ordered_qmp(kwargs))
0706e87d 537 ))
55cd64ea 538 log(full_cmd, filters, indent=indent)
e234398a 539 result = self.qmp(cmd, **kwargs)
55cd64ea 540 log(result, filters, indent=indent)
e234398a
KW
541 return result
542
6a4e88e1 543 # Returns None on success, and an error string on failure
ac6fb43e
KW
544 def run_job(self, job, auto_finalize=True, auto_dismiss=False,
545 pre_finalize=None):
6a4e88e1 546 error = None
fc47d851
KW
547 while True:
548 for ev in self.get_qmp_events_filtered(wait=True):
549 if ev['event'] == 'JOB_STATUS_CHANGE':
550 status = ev['data']['status']
551 if status == 'aborting':
552 result = self.qmp('query-jobs')
553 for j in result['return']:
554 if j['id'] == job:
6a4e88e1 555 error = j['error']
fc47d851
KW
556 log('Job failed: %s' % (j['error']))
557 elif status == 'pending' and not auto_finalize:
ac6fb43e
KW
558 if pre_finalize:
559 pre_finalize()
fc47d851
KW
560 self.qmp_log('job-finalize', id=job)
561 elif status == 'concluded' and not auto_dismiss:
562 self.qmp_log('job-dismiss', id=job)
563 elif status == 'null':
6a4e88e1 564 return error
fc47d851 565 else:
86a4f599 566 log(ev)
fc47d851 567
ef7afd63
HR
568 def node_info(self, node_name):
569 nodes = self.qmp('query-named-block-nodes')
570 for x in nodes['return']:
571 if x['node-name'] == node_name:
572 return x
573 return None
574
7898f74e 575
f345cfd0
SH
576index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
577
578class QMPTestCase(unittest.TestCase):
579 '''Abstract base class for QMP test cases'''
580
581 def dictpath(self, d, path):
582 '''Traverse a path in a nested dict'''
583 for component in path.split('/'):
584 m = index_re.match(component)
585 if m:
586 component, idx = m.groups()
587 idx = int(idx)
588
589 if not isinstance(d, dict) or component not in d:
590 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
591 d = d[component]
592
593 if m:
594 if not isinstance(d, list):
595 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
596 try:
597 d = d[idx]
598 except IndexError:
599 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
600 return d
601
90f0b711
PB
602 def assert_qmp_absent(self, d, path):
603 try:
604 result = self.dictpath(d, path)
605 except AssertionError:
606 return
607 self.fail('path "%s" has value "%s"' % (path, str(result)))
608
f345cfd0 609 def assert_qmp(self, d, path, value):
a93a42bd
HR
610 '''Assert that the value for a specific path in a QMP dict
611 matches. When given a list of values, assert that any of
612 them matches.'''
613
f345cfd0 614 result = self.dictpath(d, path)
a93a42bd
HR
615
616 # [] makes no sense as a list of valid values, so treat it as
617 # an actual single value.
618 if isinstance(value, list) and value != []:
619 for v in value:
620 if result == v:
621 return
622 self.fail('no match for "%s" in %s' % (str(result), str(value)))
623 else:
624 self.assertEqual(result, value,
625 'values not equal "%s" and "%s"'
626 % (str(result), str(value)))
f345cfd0 627
ecc1c88e
SH
628 def assert_no_active_block_jobs(self):
629 result = self.vm.qmp('query-block-jobs')
630 self.assert_qmp(result, 'return', [])
631
e71fc0ba
FZ
632 def assert_has_block_node(self, node_name=None, file_name=None):
633 """Issue a query-named-block-nodes and assert node_name and/or
634 file_name is present in the result"""
635 def check_equal_or_none(a, b):
636 return a == None or b == None or a == b
637 assert node_name or file_name
638 result = self.vm.qmp('query-named-block-nodes')
639 for x in result["return"]:
640 if check_equal_or_none(x.get("node-name"), node_name) and \
641 check_equal_or_none(x.get("file"), file_name):
642 return
643 self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
644 (node_name, file_name, result))
645
e07375f5
HR
646 def assert_json_filename_equal(self, json_filename, reference):
647 '''Asserts that the given filename is a json: filename and that its
648 content is equal to the given reference object'''
649 self.assertEqual(json_filename[:5], 'json:')
62a94288
KW
650 self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
651 self.vm.flatten_qmp_object(reference))
e07375f5 652
3cf53c77 653 def cancel_and_wait(self, drive='drive0', force=False, resume=False):
2575fe16
SH
654 '''Cancel a block job and wait for it to finish, returning the event'''
655 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
656 self.assert_qmp(result, 'return', {})
657
3cf53c77
FZ
658 if resume:
659 self.vm.resume_drive(drive)
660
2575fe16
SH
661 cancelled = False
662 result = None
663 while not cancelled:
664 for event in self.vm.get_qmp_events(wait=True):
665 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
666 event['event'] == 'BLOCK_JOB_CANCELLED':
667 self.assert_qmp(event, 'data/device', drive)
668 result = event
669 cancelled = True
1dac83f1
KW
670 elif event['event'] == 'JOB_STATUS_CHANGE':
671 self.assert_qmp(event, 'data/id', drive)
672
2575fe16
SH
673
674 self.assert_no_active_block_jobs()
675 return result
676
9974ad40 677 def wait_until_completed(self, drive='drive0', check_offset=True):
0dbe8a1b 678 '''Wait for a block job to finish, returning the event'''
c3988519 679 while True:
0dbe8a1b
SH
680 for event in self.vm.get_qmp_events(wait=True):
681 if event['event'] == 'BLOCK_JOB_COMPLETED':
682 self.assert_qmp(event, 'data/device', drive)
683 self.assert_qmp_absent(event, 'data/error')
9974ad40 684 if check_offset:
1d3ba15a 685 self.assert_qmp(event, 'data/offset', event['data']['len'])
c3988519
PX
686 self.assert_no_active_block_jobs()
687 return event
1dac83f1
KW
688 elif event['event'] == 'JOB_STATUS_CHANGE':
689 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 690
866323f3
FZ
691 def wait_ready(self, drive='drive0'):
692 '''Wait until a block job BLOCK_JOB_READY event'''
d7b25297
FZ
693 f = {'data': {'type': 'mirror', 'device': drive } }
694 event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
866323f3
FZ
695
696 def wait_ready_and_cancel(self, drive='drive0'):
697 self.wait_ready(drive=drive)
698 event = self.cancel_and_wait(drive=drive)
fa1cfb40 699 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
700 self.assert_qmp(event, 'data/type', 'mirror')
701 self.assert_qmp(event, 'data/offset', event['data']['len'])
702
703 def complete_and_wait(self, drive='drive0', wait_ready=True):
704 '''Complete a block job and wait for it to finish'''
705 if wait_ready:
706 self.wait_ready(drive=drive)
707
708 result = self.vm.qmp('block-job-complete', device=drive)
709 self.assert_qmp(result, 'return', {})
710
711 event = self.wait_until_completed(drive=drive)
712 self.assert_qmp(event, 'data/type', 'mirror')
713
f03d9d24 714 def pause_wait(self, job_id='job0'):
2c93c5cb
KW
715 with Timeout(1, "Timeout waiting for job to pause"):
716 while True:
717 result = self.vm.qmp('query-block-jobs')
c1bac161 718 found = False
2c93c5cb 719 for job in result['return']:
c1bac161
VSO
720 if job['device'] == job_id:
721 found = True
722 if job['paused'] == True and job['busy'] == False:
723 return job
724 break
725 assert found
2c93c5cb 726
f03d9d24
JS
727 def pause_job(self, job_id='job0', wait=True):
728 result = self.vm.qmp('block-job-pause', device=job_id)
729 self.assert_qmp(result, 'return', {})
730 if wait:
731 return self.pause_wait(job_id)
732 return result
733
2c93c5cb 734
f345cfd0
SH
735def notrun(reason):
736 '''Skip this test suite'''
737 # Each test in qemu-iotests has a number ("seq")
738 seq = os.path.basename(sys.argv[0])
739
ce090f65 740 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
f03868bd 741 print('%s not run: %s' % (seq, reason))
f345cfd0
SH
742 sys.exit(0)
743
57ed557f
AS
744def case_notrun(reason):
745 '''Skip this test case'''
746 # Each test in qemu-iotests has a number ("seq")
747 seq = os.path.basename(sys.argv[0])
748
749 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
750 ' [case not run] ' + reason + '\n')
751
3f5c4076 752def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
f48351d2
VSO
753 assert not (supported_fmts and unsupported_fmts)
754
755 if 'generic' in supported_fmts and \
756 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
757 # similar to
758 # _supported_fmt generic
759 # for bash tests
760 return
761
762 not_sup = supported_fmts and (imgfmt not in supported_fmts)
763 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 764 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 765
5a259e86
KW
766def verify_protocol(supported=[], unsupported=[]):
767 assert not (supported and unsupported)
768
769 if 'generic' in supported:
770 return
771
772 not_sup = supported and (imgproto not in supported)
773 if not_sup or (imgproto in unsupported):
774 notrun('not suitable for this protocol: %s' % imgproto)
775
c6a92369 776def verify_platform(supported_oses=['linux']):
79e7a019 777 if True not in [sys.platform.startswith(x) for x in supported_oses]:
bc521696
FZ
778 notrun('not suitable for this OS: %s' % sys.platform)
779
ac8bd439
VSO
780def verify_cache_mode(supported_cache_modes=[]):
781 if supported_cache_modes and (cachemode not in supported_cache_modes):
782 notrun('not suitable for this cache mode: %s' % cachemode)
783
b0f90495
AG
784def supports_quorum():
785 return 'quorum' in qemu_img_pipe('--help')
786
3f647b51
SS
787def verify_quorum():
788 '''Skip test suite if quorum support is not available'''
b0f90495 789 if not supports_quorum():
3f647b51
SS
790 notrun('quorum support missing')
791
57ed557f
AS
792def qemu_pipe(*args):
793 '''Run qemu with an option to print something and exit (e.g. a help option),
794 and return its output'''
795 args = [qemu_prog] + qemu_opts + list(args)
796 subp = subprocess.Popen(args, stdout=subprocess.PIPE,
797 stderr=subprocess.STDOUT,
798 universal_newlines=True)
799 exitcode = subp.wait()
800 if exitcode < 0:
801 sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
802 ' '.join(args)))
803 return subp.communicate()[0]
804
805def supported_formats(read_only=False):
806 '''Set 'read_only' to True to check ro-whitelist
807 Otherwise, rw-whitelist is checked'''
808 format_message = qemu_pipe("-drive", "format=help")
809 line = 1 if read_only else 0
810 return format_message.splitlines()[line].split(":")[1].split()
811
812def skip_if_unsupported(required_formats=[], read_only=False):
813 '''Skip Test Decorator
814 Runs the test if all the required formats are whitelisted'''
815 def skip_test_decorator(func):
816 def func_wrapper(*args, **kwargs):
817 usf_list = list(set(required_formats) -
818 set(supported_formats(read_only)))
819 if usf_list:
820 case_notrun('{}: formats {} are not whitelisted'.format(
821 args[0], usf_list))
822 else:
823 return func(*args, **kwargs)
824 return func_wrapper
825 return skip_test_decorator
826
febc8c86
VSO
827def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
828 unsupported_fmts=[]):
c6a92369
DB
829 '''Run tests'''
830
c0088d79
KW
831 global debug
832
5a8fabf3
SS
833 # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
834 # indicate that we're not being run via "check". There may be
835 # other things set up by "check" that individual test cases rely
836 # on.
837 if test_dir is None or qemu_default_machine is None:
838 sys.stderr.write('Please run this test via the "check" script\n')
839 sys.exit(os.EX_USAGE)
840
c6a92369
DB
841 debug = '-d' in sys.argv
842 verbosity = 1
febc8c86 843 verify_image_format(supported_fmts, unsupported_fmts)
c6a92369 844 verify_platform(supported_oses)
ac8bd439 845 verify_cache_mode(supported_cache_modes)
c6a92369 846
aa4f592a
FZ
847 if debug:
848 output = sys.stdout
849 verbosity = 2
850 sys.argv.remove('-d')
851 else:
2d894bee
HR
852 # We need to filter out the time taken from the output so that
853 # qemu-iotest can reliably diff the results against master output.
854 if sys.version_info.major >= 3:
855 output = io.StringIO()
856 else:
857 # io.StringIO is for unicode strings, which is not what
858 # 2.x's test runner emits.
859 output = io.BytesIO()
f345cfd0 860
43851b5b
EH
861 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
862
f345cfd0 863 class MyTestRunner(unittest.TextTestRunner):
aa4f592a 864 def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
f345cfd0
SH
865 unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
866
867 # unittest.main() will use sys.exit() so expect a SystemExit exception
868 try:
869 unittest.main(testRunner=MyTestRunner)
870 finally:
aa4f592a
FZ
871 if not debug:
872 sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))