]> git.proxmox.com Git - mirror_qemu.git/blame - tests/qemu-iotests/iotests.py
docs/devel/testing: add -gdb option to the debugging section of QEMU iotests
[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
b404b13b 19import atexit
55b11630 20import bz2
b404b13b
JS
21from collections import OrderedDict
22import faulthandler
b404b13b
JS
23import json
24import logging
f345cfd0
SH
25import os
26import re
55b11630 27import shutil
b404b13b
JS
28import signal
29import struct
f345cfd0 30import subprocess
ed338bb0 31import sys
b7719bca 32import time
7d814059 33from typing import (Any, Callable, Dict, Iterable,
f29f4c25 34 List, Optional, Sequence, TextIO, Tuple, Type, TypeVar)
b404b13b 35import unittest
f345cfd0 36
b7719bca
NS
37from contextlib import contextmanager
38
368e0620 39# pylint: disable=import-error, wrong-import-position
8f8fd9ed 40sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
beb6b57b 41from qemu.machine import qtest
2012453d 42from qemu.qmp import QMPMessage
02f3a911 43
52ea799e
JS
44# Use this logger for logging messages directly from the iotests module
45logger = logging.getLogger('qemu.iotests')
46logger.addHandler(logging.NullHandler())
47
48# Use this logger for messages that ought to be used for diff output.
49test_logger = logging.getLogger('qemu.iotests.diff_io')
50
51
aa1cbeb8
KW
52faulthandler.enable()
53
934659c4 54# This will not work if arguments contain spaces but is necessary if we
f345cfd0 55# want to support the override options that ./check supports.
934659c4
HR
56qemu_img_args = [os.environ.get('QEMU_IMG_PROG', 'qemu-img')]
57if os.environ.get('QEMU_IMG_OPTIONS'):
58 qemu_img_args += os.environ['QEMU_IMG_OPTIONS'].strip().split(' ')
59
60qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
61if os.environ.get('QEMU_IO_OPTIONS'):
62 qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
63
a4d925f8
AS
64qemu_io_args_no_fmt = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
65if os.environ.get('QEMU_IO_OPTIONS_NO_FMT'):
66 qemu_io_args_no_fmt += \
67 os.environ['QEMU_IO_OPTIONS_NO_FMT'].strip().split(' ')
68
81b6b2bc
KW
69qemu_nbd_prog = os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')
70qemu_nbd_args = [qemu_nbd_prog]
bec87774
HR
71if os.environ.get('QEMU_NBD_OPTIONS'):
72 qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
73
4c44b4a4 74qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
66613974 75qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
f345cfd0 76
cfb9b0b7
EGE
77gdb_qemu_env = os.environ.get('GDB_OPTIONS')
78qemu_gdb = []
79if gdb_qemu_env:
80 qemu_gdb = ['gdbserver'] + gdb_qemu_env.strip().split(' ')
81
f345cfd0
SH
82imgfmt = os.environ.get('IMGFMT', 'raw')
83imgproto = os.environ.get('IMGPROTO', 'file')
e8f8624d 84output_dir = os.environ.get('OUTPUT_DIR', '.')
3e0105e0
HR
85
86try:
87 test_dir = os.environ['TEST_DIR']
88 sock_dir = os.environ['SOCK_DIR']
89 cachemode = os.environ['CACHEMODE']
90 aiomode = os.environ['AIOMODE']
91 qemu_default_machine = os.environ['QEMU_DEFAULT_MACHINE']
92except KeyError:
93 # We are using these variables as proxies to indicate that we're
94 # not being run via "check". There may be other things set up by
95 # "check" that individual test cases rely on.
96 sys.stderr.write('Please run this test via the "check" script\n')
97 sys.exit(os.EX_USAGE)
f345cfd0 98
30b005d9
WX
99socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
100
85a353a0 101luks_default_secret_object = 'secret,id=keysec0,data=' + \
58ebcb65 102 os.environ.get('IMGKEYSECRET', '')
85a353a0
VSO
103luks_default_key_secret_opt = 'key-secret=keysec0'
104
55b11630
VSO
105sample_img_dir = os.environ['SAMPLE_IMG_DIR']
106
107
108def unarchive_sample_image(sample, fname):
109 sample_fname = os.path.join(sample_img_dir, sample + '.bz2')
110 with bz2.open(sample_fname) as f_in, open(fname, 'wb') as f_out:
111 shutil.copyfileobj(f_in, f_out)
112
85a353a0 113
91efbae9
KW
114def qemu_tool_pipe_and_status(tool: str, args: Sequence[str],
115 connect_stderr: bool = True) -> Tuple[str, int]:
d849acab 116 """
91efbae9 117 Run a tool and return both its output and its exit code
d849acab 118 """
91efbae9 119 stderr = subprocess.STDOUT if connect_stderr else None
ac4e14f5
EGE
120 with subprocess.Popen(args, stdout=subprocess.PIPE,
121 stderr=stderr, universal_newlines=True) as subp:
122 output = subp.communicate()[0]
123 if subp.returncode < 0:
124 cmd = ' '.join(args)
125 sys.stderr.write(f'{tool} received signal \
126 {-subp.returncode}: {cmd}\n')
127 return (output, subp.returncode)
d849acab 128
91efbae9
KW
129def qemu_img_pipe_and_status(*args: str) -> Tuple[str, int]:
130 """
131 Run qemu-img and return both its output and its exit code
132 """
133 full_args = qemu_img_args + list(args)
134 return qemu_tool_pipe_and_status('qemu-img', full_args)
135
d849acab
HR
136def qemu_img(*args: str) -> int:
137 '''Run qemu-img and return the exit code'''
138 return qemu_img_pipe_and_status(*args)[1]
f345cfd0 139
8a57a4be 140def ordered_qmp(qmsg, conv_keys=True):
039be85c
JS
141 # Dictionaries are not ordered prior to 3.6, therefore:
142 if isinstance(qmsg, list):
143 return [ordered_qmp(atom) for atom in qmsg]
144 if isinstance(qmsg, dict):
145 od = OrderedDict()
146 for k, v in sorted(qmsg.items()):
8a57a4be
HR
147 if conv_keys:
148 k = k.replace('_', '-')
149 od[k] = ordered_qmp(v, conv_keys=False)
039be85c
JS
150 return od
151 return qmsg
0706e87d 152
85a353a0
VSO
153def qemu_img_create(*args):
154 args = list(args)
155
156 # default luks support
157 if '-f' in args and args[args.index('-f') + 1] == 'luks':
158 if '-o' in args:
159 i = args.index('-o')
160 if 'key-secret' not in args[i + 1]:
161 args[i + 1].append(luks_default_key_secret_opt)
162 args.insert(i + 2, '--object')
163 args.insert(i + 3, luks_default_secret_object)
164 else:
165 args = ['-o', luks_default_key_secret_opt,
166 '--object', luks_default_secret_object] + args
167
168 args.insert(0, 'create')
169
170 return qemu_img(*args)
171
4b914b01
NS
172def qemu_img_measure(*args):
173 return json.loads(qemu_img_pipe("measure", "--output", "json", *args))
174
175def qemu_img_check(*args):
176 return json.loads(qemu_img_pipe("check", "--output", "json", *args))
177
d2ef210c 178def qemu_img_verbose(*args):
993d46ce 179 '''Run qemu-img without suppressing its output and return the exit code'''
2ef6093c
HR
180 exitcode = subprocess.call(qemu_img_args + list(args))
181 if exitcode < 0:
b031e9a5
JS
182 sys.stderr.write('qemu-img received signal %i: %s\n'
183 % (-exitcode, ' '.join(qemu_img_args + list(args))))
2ef6093c 184 return exitcode
d2ef210c 185
d849acab 186def qemu_img_pipe(*args: str) -> str:
3677e6f6 187 '''Run qemu-img and return its output'''
d849acab 188 return qemu_img_pipe_and_status(*args)[0]
3677e6f6 189
ac6fb43e
KW
190def qemu_img_log(*args):
191 result = qemu_img_pipe(*args)
192 log(result, filters=[filter_testfiles])
193 return result
194
4eabe051 195def img_info_log(filename, filter_path=None, imgopts=False, extra_args=()):
6a96d87c 196 args = ['info']
5ba141dc
KW
197 if imgopts:
198 args.append('--image-opts')
199 else:
6a96d87c 200 args += ['-f', imgfmt]
5ba141dc
KW
201 args += extra_args
202 args.append(filename)
203
204 output = qemu_img_pipe(*args)
6b605ade
KW
205 if not filter_path:
206 filter_path = filename
207 log(filter_img_info(output, filter_path))
208
f345cfd0
SH
209def qemu_io(*args):
210 '''Run qemu-io and return the stdout data'''
211 args = qemu_io_args + list(args)
f874e7fa 212 return qemu_tool_pipe_and_status('qemu-io', args)[0]
f345cfd0 213
a96f0350
KW
214def qemu_io_log(*args):
215 result = qemu_io(*args)
216 log(result, filters=[filter_testfiles, filter_qemu_io])
217 return result
218
745f2bf4
HR
219def qemu_io_silent(*args):
220 '''Run qemu-io and return the exit code, suppressing stdout'''
5b66a8fc
VSO
221 if '-f' in args or '--image-opts' in args:
222 default_args = qemu_io_args_no_fmt
223 else:
224 default_args = qemu_io_args
225
226 args = default_args + list(args)
745f2bf4
HR
227 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'))
228 if exitcode < 0:
229 sys.stderr.write('qemu-io received signal %i: %s\n' %
230 (-exitcode, ' '.join(args)))
231 return exitcode
232
23ee0ec2
VSO
233def qemu_io_silent_check(*args):
234 '''Run qemu-io and return the true if subprocess returned 0'''
235 args = qemu_io_args + list(args)
236 exitcode = subprocess.call(args, stdout=open('/dev/null', 'w'),
237 stderr=subprocess.STDOUT)
238 return exitcode == 0
239
9fa90eec
VSO
240class QemuIoInteractive:
241 def __init__(self, *args):
a1a7f56c 242 self.args = qemu_io_args_no_fmt + list(args)
ac4e14f5
EGE
243 # We need to keep the Popen objext around, and not
244 # close it immediately. Therefore, disable the pylint check:
245 # pylint: disable=consider-using-with
9fa90eec
VSO
246 self._p = subprocess.Popen(self.args, stdin=subprocess.PIPE,
247 stdout=subprocess.PIPE,
8eb5e674
HR
248 stderr=subprocess.STDOUT,
249 universal_newlines=True)
1f4b774a
VSO
250 out = self._p.stdout.read(9)
251 if out != 'qemu-io> ':
252 # Most probably qemu-io just failed to start.
253 # Let's collect the whole output and exit.
254 out += self._p.stdout.read()
255 self._p.wait(timeout=1)
256 raise ValueError(out)
9fa90eec
VSO
257
258 def close(self):
259 self._p.communicate('q\n')
260
261 def _read_output(self):
262 pattern = 'qemu-io> '
263 n = len(pattern)
264 pos = 0
265 s = []
266 while pos != n:
267 c = self._p.stdout.read(1)
268 # check unexpected EOF
269 assert c != ''
270 s.append(c)
271 if c == pattern[pos]:
272 pos += 1
273 else:
274 pos = 0
275
276 return ''.join(s[:-n])
277
278 def cmd(self, cmd):
279 # quit command is in close(), '\n' is added automatically
280 assert '\n' not in cmd
281 cmd = cmd.strip()
6a96d87c 282 assert cmd not in ('q', 'quit')
9fa90eec 283 self._p.stdin.write(cmd + '\n')
f544adf7 284 self._p.stdin.flush()
9fa90eec
VSO
285 return self._read_output()
286
287
bec87774
HR
288def qemu_nbd(*args):
289 '''Run qemu-nbd in daemon mode and return the parent's exit code'''
290 return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
291
91efbae9 292def qemu_nbd_early_pipe(*args: str) -> Tuple[int, str]:
e1e6eccd 293 '''Run qemu-nbd in daemon mode and return both the parent's exit code
6177b584 294 and its output in case of an error'''
91efbae9
KW
295 full_args = qemu_nbd_args + ['--fork'] + list(args)
296 output, returncode = qemu_tool_pipe_and_status('qemu-nbd', full_args,
297 connect_stderr=False)
298 return returncode, output if returncode else ''
e1e6eccd 299
81b6b2bc
KW
300def qemu_nbd_list_log(*args: str) -> str:
301 '''Run qemu-nbd to list remote exports'''
302 full_args = [qemu_nbd_prog, '-L'] + list(args)
303 output, _ = qemu_tool_pipe_and_status('qemu-nbd', full_args)
304 log(output, filters=[filter_testfiles, filter_nbd_exports])
305 return output
306
b7719bca 307@contextmanager
23ee0ec2 308def qemu_nbd_popen(*args):
b7719bca 309 '''Context manager running qemu-nbd within the context'''
3f7db418
VSO
310 pid_file = file_path("qemu_nbd_popen-nbd-pid-file")
311
312 assert not os.path.exists(pid_file)
b7719bca
NS
313
314 cmd = list(qemu_nbd_args)
315 cmd.extend(('--persistent', '--pid-file', pid_file))
316 cmd.extend(args)
317
318 log('Start NBD server')
ac4e14f5
EGE
319 with subprocess.Popen(cmd) as p:
320 try:
321 while not os.path.exists(pid_file):
322 if p.poll() is not None:
323 raise RuntimeError(
324 "qemu-nbd terminated with exit code {}: {}"
325 .format(p.returncode, ' '.join(cmd)))
326
327 time.sleep(0.01)
328 yield
329 finally:
330 if os.path.exists(pid_file):
331 os.remove(pid_file)
332 log('Kill NBD server')
333 p.kill()
334 p.wait()
23ee0ec2 335
e1b5c51f 336def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
3a3918c3 337 '''Return True if two image files are identical'''
e1b5c51f
PB
338 return qemu_img('compare', '-f', fmt1,
339 '-F', fmt2, img1, img2) == 0
3a3918c3 340
2499a096
SH
341def create_image(name, size):
342 '''Create a fully-allocated raw image with sector markers'''
ac4e14f5
EGE
343 with open(name, 'wb') as file:
344 i = 0
345 while i < size:
346 sector = struct.pack('>l504xl', i // 512, i // 512)
347 file.write(sector)
348 i = i + 512
2499a096 349
74f69050
FZ
350def image_size(img):
351 '''Return image's virtual size'''
352 r = qemu_img_pipe('info', '--output=json', '-f', imgfmt, img)
353 return json.loads(r)['virtual-size']
354
011a5761 355def is_str(val):
7e693a05 356 return isinstance(val, str)
011a5761 357
a2d1c8fd
DB
358test_dir_re = re.compile(r"%s" % test_dir)
359def filter_test_dir(msg):
360 return test_dir_re.sub("TEST_DIR", msg)
361
362win32_re = re.compile(r"\r")
363def filter_win32(msg):
364 return win32_re.sub("", msg)
365
b031e9a5
JS
366qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* "
367 r"\([0-9\/.inf]* [EPTGMKiBbytes]*\/sec "
368 r"and [0-9\/.inf]* ops\/sec\)")
a2d1c8fd
DB
369def filter_qemu_io(msg):
370 msg = filter_win32(msg)
b031e9a5
JS
371 return qemu_io_re.sub("X ops; XX:XX:XX.X "
372 "(XXX YYY/sec and XXX ops/sec)", msg)
a2d1c8fd
DB
373
374chown_re = re.compile(r"chown [0-9]+:[0-9]+")
375def filter_chown(msg):
376 return chown_re.sub("chown UID:GID", msg)
377
12314f2d
SH
378def filter_qmp_event(event):
379 '''Filter a QMP event dict'''
380 event = dict(event)
381 if 'timestamp' in event:
382 event['timestamp']['seconds'] = 'SECS'
383 event['timestamp']['microseconds'] = 'USECS'
384 return event
385
08fcd611
JS
386def filter_qmp(qmsg, filter_fn):
387 '''Given a string filter, filter a QMP object's values.
388 filter_fn takes a (key, value) pair.'''
389 # Iterate through either lists or dicts;
390 if isinstance(qmsg, list):
391 items = enumerate(qmsg)
392 else:
393 items = qmsg.items()
394
395 for k, v in items:
6a96d87c 396 if isinstance(v, (dict, list)):
08fcd611
JS
397 qmsg[k] = filter_qmp(v, filter_fn)
398 else:
399 qmsg[k] = filter_fn(k, v)
400 return qmsg
401
e234398a 402def filter_testfiles(msg):
df0e032b
VSO
403 pref1 = os.path.join(test_dir, "%s-" % (os.getpid()))
404 pref2 = os.path.join(sock_dir, "%s-" % (os.getpid()))
405 return msg.replace(pref1, 'TEST_DIR/PID-').replace(pref2, 'SOCK_DIR/PID-')
e234398a 406
08fcd611 407def filter_qmp_testfiles(qmsg):
6a96d87c 408 def _filter(_key, value):
56a6e5d0 409 if is_str(value):
08fcd611
JS
410 return filter_testfiles(value)
411 return value
412 return filter_qmp(qmsg, _filter)
413
2daba442
ML
414def filter_virtio_scsi(output: str) -> str:
415 return re.sub(r'(virtio-scsi)-(ccw|pci)', r'\1', output)
416
417def filter_qmp_virtio_scsi(qmsg):
418 def _filter(_key, value):
419 if is_str(value):
420 return filter_virtio_scsi(value)
421 return value
422 return filter_qmp(qmsg, _filter)
423
fa1151f8
JS
424def filter_generated_node_ids(msg):
425 return re.sub("#block[0-9]+", "NODE_NAME", msg)
426
6b605ade
KW
427def filter_img_info(output, filename):
428 lines = []
429 for line in output.split('\n'):
430 if 'disk size' in line or 'actual-size' in line:
431 continue
fd586ce8
KW
432 line = line.replace(filename, 'TEST_IMG')
433 line = filter_testfiles(line)
434 line = line.replace(imgfmt, 'IMGFMT')
6b605ade 435 line = re.sub('iters: [0-9]+', 'iters: XXX', line)
b031e9a5
JS
436 line = re.sub('uuid: [-a-f0-9]+',
437 'uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
438 line)
bab4feb2 439 line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
6b605ade
KW
440 lines.append(line)
441 return '\n'.join(lines)
442
f2ea0b20
HR
443def filter_imgfmt(msg):
444 return msg.replace(imgfmt, 'IMGFMT')
445
446def filter_qmp_imgfmt(qmsg):
6a96d87c 447 def _filter(_key, value):
f2ea0b20
HR
448 if is_str(value):
449 return filter_imgfmt(value)
450 return value
451 return filter_qmp(qmsg, _filter)
452
81b6b2bc
KW
453def filter_nbd_exports(output: str) -> str:
454 return re.sub(r'((min|opt|max) block): [0-9]+', r'\1: XXX', output)
455
1cd0dbfc
JS
456
457Msg = TypeVar('Msg', Dict[str, Any], List[Any], str)
458
459def log(msg: Msg,
460 filters: Iterable[Callable[[Msg], Msg]] = (),
461 indent: Optional[int] = None) -> None:
462 """
463 Logs either a string message or a JSON serializable message (like QMP).
464 If indent is provided, JSON serializable messages are pretty-printed.
465 """
a2d1c8fd
DB
466 for flt in filters:
467 msg = flt(msg)
6a96d87c 468 if isinstance(msg, (dict, list)):
0706e87d
JS
469 # Don't sort if it's already sorted
470 do_sort = not isinstance(msg, OrderedDict)
52ea799e 471 test_logger.info(json.dumps(msg, sort_keys=do_sort, indent=indent))
e21b5f34 472 else:
52ea799e 473 test_logger.info(msg)
a2d1c8fd 474
2c93c5cb 475class Timeout:
6a96d87c 476 def __init__(self, seconds, errmsg="Timeout"):
2c93c5cb
KW
477 self.seconds = seconds
478 self.errmsg = errmsg
479 def __enter__(self):
d3ec2022
EGE
480 if qemu_gdb:
481 return self
2c93c5cb
KW
482 signal.signal(signal.SIGALRM, self.timeout)
483 signal.setitimer(signal.ITIMER_REAL, self.seconds)
484 return self
6a96d87c 485 def __exit__(self, exc_type, value, traceback):
d3ec2022
EGE
486 if qemu_gdb:
487 return False
2c93c5cb
KW
488 signal.setitimer(signal.ITIMER_REAL, 0)
489 return False
490 def timeout(self, signum, frame):
491 raise Exception(self.errmsg)
492
de263986
JS
493def file_pattern(name):
494 return "{0}-{1}".format(os.getpid(), name)
f4844ac0 495
3192fad7 496class FilePath:
de263986 497 """
f765af87
NS
498 Context manager generating multiple file names. The generated files are
499 removed when exiting the context.
f4844ac0 500
f765af87
NS
501 Example usage:
502
3192fad7 503 with FilePath('a.img', 'b.img') as (img_a, img_b):
f765af87
NS
504 # Use img_a and img_b here...
505
506 # a.img and b.img are automatically removed here.
507
508 By default images are created in iotests.test_dir. To create sockets use
509 iotests.sock_dir:
510
3192fad7
NS
511 with FilePath('a.sock', base_dir=iotests.sock_dir) as sock:
512
513 For convenience, calling with one argument yields a single file instead of
514 a tuple with one item.
f4844ac0 515
de263986 516 """
a242b19e 517 def __init__(self, *names, base_dir=test_dir):
7cc002a0
NS
518 self.paths = [os.path.join(base_dir, file_pattern(name))
519 for name in names]
f4844ac0
SH
520
521 def __enter__(self):
3192fad7
NS
522 if len(self.paths) == 1:
523 return self.paths[0]
524 else:
525 return self.paths
f4844ac0
SH
526
527 def __exit__(self, exc_type, exc_val, exc_tb):
a7971702
NS
528 for path in self.paths:
529 try:
de263986 530 os.remove(path)
a7971702
NS
531 except OSError:
532 pass
f4844ac0
SH
533 return False
534
535
c5ff5a3c
HR
536def try_remove(img):
537 try:
538 os.remove(img)
539 except OSError:
540 pass
541
ef6e9228
VSO
542def file_path_remover():
543 for path in reversed(file_path_remover.paths):
c5ff5a3c 544 try_remove(path)
ef6e9228
VSO
545
546
93b78ea5 547def file_path(*names, base_dir=test_dir):
ef6e9228
VSO
548 ''' Another way to get auto-generated filename that cleans itself up.
549
550 Use is as simple as:
551
552 img_a, img_b = file_path('a.img', 'b.img')
553 sock = file_path('socket')
554 '''
555
556 if not hasattr(file_path_remover, 'paths'):
557 file_path_remover.paths = []
558 atexit.register(file_path_remover)
559
560 paths = []
561 for name in names:
de263986 562 filename = file_pattern(name)
93b78ea5 563 path = os.path.join(base_dir, filename)
ef6e9228
VSO
564 file_path_remover.paths.append(path)
565 paths.append(path)
566
567 return paths[0] if len(paths) == 1 else paths
568
5a259e86
KW
569def remote_filename(path):
570 if imgproto == 'file':
571 return path
572 elif imgproto == 'ssh':
b8c1f901 573 return "ssh://%s@127.0.0.1:22%s" % (os.environ.get('USER'), path)
5a259e86
KW
574 else:
575 raise Exception("Protocol %s not supported" % (imgproto))
ef6e9228 576
4c44b4a4 577class VM(qtest.QEMUQtestMachine):
f345cfd0
SH
578 '''A QEMU VM'''
579
5fcbdf50
HR
580 def __init__(self, path_suffix=''):
581 name = "qemu%s-%d" % (path_suffix, os.getpid())
d3ec2022 582 timer = 15.0 if not qemu_gdb else None
776b9974
EGE
583 super().__init__(qemu_prog, qemu_opts, wrapper=qemu_gdb,
584 name=name,
2ca6e26c 585 base_temp_dir=test_dir,
757ae0ec 586 socket_scm_helper=socket_scm_helper,
e2f948a8 587 sock_dir=sock_dir, qmp_timer=timer)
f345cfd0 588 self._num_drives = 0
30b005d9 589
ccc15f7d
SH
590 def add_object(self, opts):
591 self._args.append('-object')
592 self._args.append(opts)
593 return self
594
486b88bd
KW
595 def add_device(self, opts):
596 self._args.append('-device')
597 self._args.append(opts)
598 return self
599
78b666f4
FZ
600 def add_drive_raw(self, opts):
601 self._args.append('-drive')
602 self._args.append(opts)
603 return self
604
1d3d4b63 605 def add_drive(self, path, opts='', interface='virtio', img_format=imgfmt):
f345cfd0 606 '''Add a virtio-blk drive to the VM'''
8e492253 607 options = ['if=%s' % interface,
f345cfd0 608 'id=drive%d' % self._num_drives]
8e492253
HR
609
610 if path is not None:
611 options.append('file=%s' % path)
1d3d4b63 612 options.append('format=%s' % img_format)
fc17c259 613 options.append('cache=%s' % cachemode)
7156ca48 614 options.append('aio=%s' % aiomode)
8e492253 615
f345cfd0
SH
616 if opts:
617 options.append(opts)
618
1d3d4b63 619 if img_format == 'luks' and 'key-secret' not in opts:
85a353a0
VSO
620 # default luks support
621 if luks_default_secret_object not in self._args:
622 self.add_object(luks_default_secret_object)
623
624 options.append(luks_default_key_secret_opt)
625
f345cfd0
SH
626 self._args.append('-drive')
627 self._args.append(','.join(options))
628 self._num_drives += 1
629 return self
630
5694923a
HR
631 def add_blockdev(self, opts):
632 self._args.append('-blockdev')
633 if isinstance(opts, str):
634 self._args.append(opts)
635 else:
636 self._args.append(','.join(opts))
637 return self
638
12314f2d
SH
639 def add_incoming(self, addr):
640 self._args.append('-incoming')
641 self._args.append(addr)
642 return self
643
2012453d 644 def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage:
239bbcc0 645 cmd = 'human-monitor-command'
090744d5 646 kwargs: Dict[str, Any] = {'command-line': command_line}
239bbcc0
JS
647 if use_log:
648 return self.qmp_log(cmd, **kwargs)
649 else:
650 return self.qmp(cmd, **kwargs)
651
652 def pause_drive(self, drive: str, event: Optional[str] = None) -> None:
653 """Pause drive r/w operations"""
3cf53c77
FZ
654 if not event:
655 self.pause_drive(drive, "read_aio")
656 self.pause_drive(drive, "write_aio")
657 return
239bbcc0
JS
658 self.hmp(f'qemu-io {drive} "break {event} bp_{drive}"')
659
660 def resume_drive(self, drive: str) -> None:
661 """Resume drive r/w operations"""
662 self.hmp(f'qemu-io {drive} "remove_break bp_{drive}"')
663
664 def hmp_qemu_io(self, drive: str, cmd: str,
2012453d 665 use_log: bool = False) -> QMPMessage:
239bbcc0
JS
666 """Write to a given drive using an HMP command"""
667 return self.hmp(f'qemu-io {drive} "{cmd}"', use_log=use_log)
e3409362 668
62a94288
KW
669 def flatten_qmp_object(self, obj, output=None, basestr=''):
670 if output is None:
671 output = dict()
672 if isinstance(obj, list):
6a96d87c
JS
673 for i, item in enumerate(obj):
674 self.flatten_qmp_object(item, output, basestr + str(i) + '.')
62a94288
KW
675 elif isinstance(obj, dict):
676 for key in obj:
677 self.flatten_qmp_object(obj[key], output, basestr + key + '.')
678 else:
679 output[basestr[:-1]] = obj # Strip trailing '.'
680 return output
681
682 def qmp_to_opts(self, obj):
683 obj = self.flatten_qmp_object(obj)
684 output_list = list()
685 for key in obj:
686 output_list += [key + '=' + obj[key]]
687 return ','.join(output_list)
688
8b6f5f8b 689 def get_qmp_events_filtered(self, wait=60.0):
5ad1dbf7
KW
690 result = []
691 for ev in self.get_qmp_events(wait=wait):
692 result.append(filter_qmp_event(ev))
693 return result
62a94288 694
4eabe051 695 def qmp_log(self, cmd, filters=(), indent=None, **kwargs):
0706e87d
JS
696 full_cmd = OrderedDict((
697 ("execute", cmd),
039be85c 698 ("arguments", ordered_qmp(kwargs))
0706e87d 699 ))
55cd64ea 700 log(full_cmd, filters, indent=indent)
e234398a 701 result = self.qmp(cmd, **kwargs)
55cd64ea 702 log(result, filters, indent=indent)
e234398a
KW
703 return result
704
6a4e88e1 705 # Returns None on success, and an error string on failure
ac6fb43e 706 def run_job(self, job, auto_finalize=True, auto_dismiss=False,
52ea799e 707 pre_finalize=None, cancel=False, wait=60.0):
d443b74b
JS
708 """
709 run_job moves a job from creation through to dismissal.
710
711 :param job: String. ID of recently-launched job
712 :param auto_finalize: Bool. True if the job was launched with
713 auto_finalize. Defaults to True.
714 :param auto_dismiss: Bool. True if the job was launched with
715 auto_dismiss=True. Defaults to False.
716 :param pre_finalize: Callback. A callable that takes no arguments to be
717 invoked prior to issuing job-finalize, if any.
718 :param cancel: Bool. When true, cancels the job after the pre_finalize
719 callback.
d443b74b
JS
720 :param wait: Float. Timeout value specifying how long to wait for any
721 event, in seconds. Defaults to 60.0.
722 """
d6a79af0
JS
723 match_device = {'data': {'device': job}}
724 match_id = {'data': {'id': job}}
725 events = [
726 ('BLOCK_JOB_COMPLETED', match_device),
727 ('BLOCK_JOB_CANCELLED', match_device),
728 ('BLOCK_JOB_ERROR', match_device),
729 ('BLOCK_JOB_READY', match_device),
730 ('BLOCK_JOB_PENDING', match_id),
731 ('JOB_STATUS_CHANGE', match_id)
732 ]
6a4e88e1 733 error = None
fc47d851 734 while True:
55824e09 735 ev = filter_qmp_event(self.events_wait(events, timeout=wait))
d6a79af0 736 if ev['event'] != 'JOB_STATUS_CHANGE':
52ea799e 737 log(ev)
d6a79af0
JS
738 continue
739 status = ev['data']['status']
740 if status == 'aborting':
741 result = self.qmp('query-jobs')
742 for j in result['return']:
743 if j['id'] == job:
744 error = j['error']
52ea799e 745 log('Job failed: %s' % (j['error']))
4688c4e3 746 elif status == 'ready':
52ea799e 747 self.qmp_log('job-complete', id=job)
d6a79af0
JS
748 elif status == 'pending' and not auto_finalize:
749 if pre_finalize:
750 pre_finalize()
52ea799e 751 if cancel:
d443b74b 752 self.qmp_log('job-cancel', id=job)
15427f63 753 else:
52ea799e 754 self.qmp_log('job-finalize', id=job)
d6a79af0 755 elif status == 'concluded' and not auto_dismiss:
52ea799e 756 self.qmp_log('job-dismiss', id=job)
d6a79af0
JS
757 elif status == 'null':
758 return error
fc47d851 759
e9dbd1ca
KW
760 # Returns None on success, and an error string on failure
761 def blockdev_create(self, options, job_id='job0', filters=None):
762 if filters is None:
763 filters = [filter_qmp_testfiles]
764 result = self.qmp_log('blockdev-create', filters=filters,
765 job_id=job_id, options=options)
766
767 if 'return' in result:
768 assert result['return'] == {}
769 job_result = self.run_job(job_id)
770 else:
771 job_result = result['error']
772
773 log("")
774 return job_result
775
980448f1
KW
776 def enable_migration_events(self, name):
777 log('Enabling migration QMP events on %s...' % name)
778 log(self.qmp('migrate-set-capabilities', capabilities=[
779 {
780 'capability': 'events',
781 'state': True
782 }
783 ]))
784
4bf63c80 785 def wait_migration(self, expect_runstate: Optional[str]) -> bool:
980448f1
KW
786 while True:
787 event = self.event_wait('MIGRATION')
503c2b31
KW
788 # We use the default timeout, and with a timeout, event_wait()
789 # never returns None
790 assert event
791
980448f1 792 log(event, filters=[filter_qmp_event])
4bf63c80 793 if event['data']['status'] in ('completed', 'failed'):
980448f1 794 break
4bf63c80
HR
795
796 if event['data']['status'] == 'completed':
797 # The event may occur in finish-migrate, so wait for the expected
798 # post-migration runstate
799 runstate = None
800 while runstate != expect_runstate:
801 runstate = self.qmp('query-status')['return']['status']
802 return True
803 else:
804 return False
980448f1 805
ef7afd63
HR
806 def node_info(self, node_name):
807 nodes = self.qmp('query-named-block-nodes')
808 for x in nodes['return']:
809 if x['node-name'] == node_name:
810 return x
811 return None
812
5c4343b8
VSO
813 def query_bitmaps(self):
814 res = self.qmp("query-named-block-nodes")
815 return {device['node-name']: device['dirty-bitmaps']
816 for device in res['return'] if 'dirty-bitmaps' in device}
817
818 def get_bitmap(self, node_name, bitmap_name, recording=None, bitmaps=None):
819 """
820 get a specific bitmap from the object returned by query_bitmaps.
821 :param recording: If specified, filter results by the specified value.
822 :param bitmaps: If specified, use it instead of call query_bitmaps()
823 """
824 if bitmaps is None:
825 bitmaps = self.query_bitmaps()
826
827 for bitmap in bitmaps[node_name]:
828 if bitmap.get('name', '') == bitmap_name:
6a96d87c 829 if recording is None or bitmap.get('recording') == recording:
5c4343b8
VSO
830 return bitmap
831 return None
832
833 def check_bitmap_status(self, node_name, bitmap_name, fields):
834 ret = self.get_bitmap(node_name, bitmap_name)
835
836 return fields.items() <= ret.items()
837
6a3d0f1e
HR
838 def assert_block_path(self, root, path, expected_node, graph=None):
839 """
840 Check whether the node under the given path in the block graph
841 is @expected_node.
842
843 @root is the node name of the node where the @path is rooted.
844
845 @path is a string that consists of child names separated by
846 slashes. It must begin with a slash.
847
848 Examples for @root + @path:
849 - root="qcow2-node", path="/backing/file"
850 - root="quorum-node", path="/children.2/file"
851
852 Hypothetically, @path could be empty, in which case it would
853 point to @root. However, in practice this case is not useful
854 and hence not allowed.
855
856 @expected_node may be None. (All elements of the path but the
857 leaf must still exist.)
858
859 @graph may be None or the result of an x-debug-query-block-graph
860 call that has already been performed.
861 """
862 if graph is None:
863 graph = self.qmp('x-debug-query-block-graph')['return']
864
865 iter_path = iter(path.split('/'))
866
867 # Must start with a /
868 assert next(iter_path) == ''
869
870 node = next((node for node in graph['nodes'] if node['name'] == root),
871 None)
872
873 # An empty @path is not allowed, so the root node must be present
874 assert node is not None, 'Root node %s not found' % root
875
876 for child_name in iter_path:
877 assert node is not None, 'Cannot follow path %s%s' % (root, path)
878
879 try:
6a96d87c
JS
880 node_id = next(edge['child'] for edge in graph['edges']
881 if (edge['parent'] == node['id'] and
882 edge['name'] == child_name))
883
884 node = next(node for node in graph['nodes']
885 if node['id'] == node_id)
6a3d0f1e 886
6a3d0f1e
HR
887 except StopIteration:
888 node = None
889
890 if node is None:
891 assert expected_node is None, \
892 'No node found under %s (but expected %s)' % \
893 (path, expected_node)
894 else:
895 assert node['name'] == expected_node, \
896 'Found node %s under %s (but expected %s)' % \
897 (node['name'], path, expected_node)
7898f74e 898
f345cfd0
SH
899index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
900
901class QMPTestCase(unittest.TestCase):
902 '''Abstract base class for QMP test cases'''
903
6a96d87c
JS
904 def __init__(self, *args, **kwargs):
905 super().__init__(*args, **kwargs)
906 # Many users of this class set a VM property we rely on heavily
907 # in the methods below.
908 self.vm = None
909
f345cfd0
SH
910 def dictpath(self, d, path):
911 '''Traverse a path in a nested dict'''
912 for component in path.split('/'):
913 m = index_re.match(component)
914 if m:
915 component, idx = m.groups()
916 idx = int(idx)
917
918 if not isinstance(d, dict) or component not in d:
b031e9a5 919 self.fail(f'failed path traversal for "{path}" in "{d}"')
f345cfd0
SH
920 d = d[component]
921
922 if m:
923 if not isinstance(d, list):
b031e9a5
JS
924 self.fail(f'path component "{component}" in "{path}" '
925 f'is not a list in "{d}"')
f345cfd0
SH
926 try:
927 d = d[idx]
928 except IndexError:
b031e9a5
JS
929 self.fail(f'invalid index "{idx}" in path "{path}" '
930 f'in "{d}"')
f345cfd0
SH
931 return d
932
90f0b711
PB
933 def assert_qmp_absent(self, d, path):
934 try:
935 result = self.dictpath(d, path)
936 except AssertionError:
937 return
938 self.fail('path "%s" has value "%s"' % (path, str(result)))
939
f345cfd0 940 def assert_qmp(self, d, path, value):
a93a42bd
HR
941 '''Assert that the value for a specific path in a QMP dict
942 matches. When given a list of values, assert that any of
943 them matches.'''
944
f345cfd0 945 result = self.dictpath(d, path)
a93a42bd
HR
946
947 # [] makes no sense as a list of valid values, so treat it as
948 # an actual single value.
949 if isinstance(value, list) and value != []:
950 for v in value:
951 if result == v:
952 return
953 self.fail('no match for "%s" in %s' % (str(result), str(value)))
954 else:
955 self.assertEqual(result, value,
dbf231d7 956 '"%s" is "%s", expected "%s"'
6a96d87c 957 % (path, str(result), str(value)))
f345cfd0 958
ecc1c88e
SH
959 def assert_no_active_block_jobs(self):
960 result = self.vm.qmp('query-block-jobs')
961 self.assert_qmp(result, 'return', [])
962
e71fc0ba
FZ
963 def assert_has_block_node(self, node_name=None, file_name=None):
964 """Issue a query-named-block-nodes and assert node_name and/or
965 file_name is present in the result"""
966 def check_equal_or_none(a, b):
6a96d87c 967 return a is None or b is None or a == b
e71fc0ba
FZ
968 assert node_name or file_name
969 result = self.vm.qmp('query-named-block-nodes')
970 for x in result["return"]:
971 if check_equal_or_none(x.get("node-name"), node_name) and \
972 check_equal_or_none(x.get("file"), file_name):
973 return
6a96d87c
JS
974 self.fail("Cannot find %s %s in result:\n%s" %
975 (node_name, file_name, result))
e71fc0ba 976
e07375f5
HR
977 def assert_json_filename_equal(self, json_filename, reference):
978 '''Asserts that the given filename is a json: filename and that its
979 content is equal to the given reference object'''
980 self.assertEqual(json_filename[:5], 'json:')
b031e9a5
JS
981 self.assertEqual(
982 self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
983 self.vm.flatten_qmp_object(reference)
984 )
e07375f5 985
b031e9a5
JS
986 def cancel_and_wait(self, drive='drive0', force=False,
987 resume=False, wait=60.0):
2575fe16
SH
988 '''Cancel a block job and wait for it to finish, returning the event'''
989 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
990 self.assert_qmp(result, 'return', {})
991
3cf53c77
FZ
992 if resume:
993 self.vm.resume_drive(drive)
994
2575fe16
SH
995 cancelled = False
996 result = None
997 while not cancelled:
8b6f5f8b 998 for event in self.vm.get_qmp_events(wait=wait):
2575fe16
SH
999 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
1000 event['event'] == 'BLOCK_JOB_CANCELLED':
1001 self.assert_qmp(event, 'data/device', drive)
1002 result = event
1003 cancelled = True
1dac83f1
KW
1004 elif event['event'] == 'JOB_STATUS_CHANGE':
1005 self.assert_qmp(event, 'data/id', drive)
1006
2575fe16
SH
1007
1008 self.assert_no_active_block_jobs()
1009 return result
1010
b031e9a5
JS
1011 def wait_until_completed(self, drive='drive0', check_offset=True,
1012 wait=60.0, error=None):
0dbe8a1b 1013 '''Wait for a block job to finish, returning the event'''
c3988519 1014 while True:
8b6f5f8b 1015 for event in self.vm.get_qmp_events(wait=wait):
0dbe8a1b
SH
1016 if event['event'] == 'BLOCK_JOB_COMPLETED':
1017 self.assert_qmp(event, 'data/device', drive)
216656f5
HR
1018 if error is None:
1019 self.assert_qmp_absent(event, 'data/error')
1020 if check_offset:
1021 self.assert_qmp(event, 'data/offset',
1022 event['data']['len'])
1023 else:
1024 self.assert_qmp(event, 'data/error', error)
c3988519
PX
1025 self.assert_no_active_block_jobs()
1026 return event
6a96d87c 1027 if event['event'] == 'JOB_STATUS_CHANGE':
1dac83f1 1028 self.assert_qmp(event, 'data/id', drive)
0dbe8a1b 1029
866323f3 1030 def wait_ready(self, drive='drive0'):
6a96d87c 1031 """Wait until a BLOCK_JOB_READY event, and return the event."""
c682bf18
HR
1032 return self.vm.events_wait([
1033 ('BLOCK_JOB_READY',
1034 {'data': {'type': 'mirror', 'device': drive}}),
1035 ('BLOCK_JOB_READY',
1036 {'data': {'type': 'commit', 'device': drive}})
1037 ])
866323f3
FZ
1038
1039 def wait_ready_and_cancel(self, drive='drive0'):
1040 self.wait_ready(drive=drive)
1041 event = self.cancel_and_wait(drive=drive)
fa1cfb40 1042 self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
866323f3
FZ
1043 self.assert_qmp(event, 'data/type', 'mirror')
1044 self.assert_qmp(event, 'data/offset', event['data']['len'])
1045
216656f5
HR
1046 def complete_and_wait(self, drive='drive0', wait_ready=True,
1047 completion_error=None):
866323f3
FZ
1048 '''Complete a block job and wait for it to finish'''
1049 if wait_ready:
1050 self.wait_ready(drive=drive)
1051
1052 result = self.vm.qmp('block-job-complete', device=drive)
1053 self.assert_qmp(result, 'return', {})
1054
216656f5 1055 event = self.wait_until_completed(drive=drive, error=completion_error)
c682bf18 1056 self.assertTrue(event['data']['type'] in ['mirror', 'commit'])
866323f3 1057
f03d9d24 1058 def pause_wait(self, job_id='job0'):
e1df89bb 1059 with Timeout(3, "Timeout waiting for job to pause"):
2c93c5cb
KW
1060 while True:
1061 result = self.vm.qmp('query-block-jobs')
c1bac161 1062 found = False
2c93c5cb 1063 for job in result['return']:
c1bac161
VSO
1064 if job['device'] == job_id:
1065 found = True
6a96d87c 1066 if job['paused'] and not job['busy']:
c1bac161
VSO
1067 return job
1068 break
1069 assert found
2c93c5cb 1070
f03d9d24
JS
1071 def pause_job(self, job_id='job0', wait=True):
1072 result = self.vm.qmp('block-job-pause', device=job_id)
1073 self.assert_qmp(result, 'return', {})
1074 if wait:
1075 return self.pause_wait(job_id)
1076 return result
1077
6be01225
HR
1078 def case_skip(self, reason):
1079 '''Skip this test case'''
1080 case_notrun(reason)
1081 self.skipTest(reason)
1082
2c93c5cb 1083
f345cfd0
SH
1084def notrun(reason):
1085 '''Skip this test suite'''
1086 # Each test in qemu-iotests has a number ("seq")
1087 seq = os.path.basename(sys.argv[0])
1088
ce090f65 1089 open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
52ea799e 1090 logger.warning("%s not run: %s", seq, reason)
f345cfd0
SH
1091 sys.exit(0)
1092
57ed557f 1093def case_notrun(reason):
6be01225
HR
1094 '''Mark this test case as not having been run (without actually
1095 skipping it, that is left to the caller). See
1096 QMPTestCase.case_skip() for a variant that actually skips the
1097 current test case.'''
1098
57ed557f
AS
1099 # Each test in qemu-iotests has a number ("seq")
1100 seq = os.path.basename(sys.argv[0])
1101
1102 open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
1103 ' [case not run] ' + reason + '\n')
1104
59c29869
JS
1105def _verify_image_format(supported_fmts: Sequence[str] = (),
1106 unsupported_fmts: Sequence[str] = ()) -> None:
f48351d2
VSO
1107 if 'generic' in supported_fmts and \
1108 os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
1109 # similar to
1110 # _supported_fmt generic
1111 # for bash tests
f7125522 1112 supported_fmts = ()
f48351d2
VSO
1113
1114 not_sup = supported_fmts and (imgfmt not in supported_fmts)
1115 if not_sup or (imgfmt in unsupported_fmts):
3f5c4076 1116 notrun('not suitable for this image format: %s' % imgfmt)
f345cfd0 1117
d2a839ed
HR
1118 if imgfmt == 'luks':
1119 verify_working_luks()
1120
59c29869
JS
1121def _verify_protocol(supported: Sequence[str] = (),
1122 unsupported: Sequence[str] = ()) -> None:
5a259e86
KW
1123 assert not (supported and unsupported)
1124
1125 if 'generic' in supported:
1126 return
1127
1128 not_sup = supported and (imgproto not in supported)
1129 if not_sup or (imgproto in unsupported):
1130 notrun('not suitable for this protocol: %s' % imgproto)
1131
59c29869
JS
1132def _verify_platform(supported: Sequence[str] = (),
1133 unsupported: Sequence[str] = ()) -> None:
7d814059
JS
1134 if any((sys.platform.startswith(x) for x in unsupported)):
1135 notrun('not suitable for this OS: %s' % sys.platform)
72b29030 1136
7d814059 1137 if supported:
72b29030
JS
1138 if not any((sys.platform.startswith(x) for x in supported)):
1139 notrun('not suitable for this OS: %s' % sys.platform)
bc521696 1140
59c29869 1141def _verify_cache_mode(supported_cache_modes: Sequence[str] = ()) -> None:
ac8bd439
VSO
1142 if supported_cache_modes and (cachemode not in supported_cache_modes):
1143 notrun('not suitable for this cache mode: %s' % cachemode)
1144
cd8f5b75 1145def _verify_aio_mode(supported_aio_modes: Sequence[str] = ()) -> None:
7156ca48
AM
1146 if supported_aio_modes and (aiomode not in supported_aio_modes):
1147 notrun('not suitable for this aio mode: %s' % aiomode)
1148
18654716
VSO
1149def _verify_formats(required_formats: Sequence[str] = ()) -> None:
1150 usf_list = list(set(required_formats) - set(supported_formats()))
1151 if usf_list:
1152 notrun(f'formats {usf_list} are not whitelisted')
1153
f203080b
VSO
1154
1155def _verify_virtio_blk() -> None:
1156 out = qemu_pipe('-M', 'none', '-device', 'help')
1157 if 'virtio-blk' not in out:
1158 notrun('Missing virtio-blk in QEMU binary')
1159
359a8562
LV
1160def _verify_virtio_scsi_pci_or_ccw() -> None:
1161 out = qemu_pipe('-M', 'none', '-device', 'help')
1162 if 'virtio-scsi-pci' not in out and 'virtio-scsi-ccw' not in out:
1163 notrun('Missing virtio-scsi-pci or virtio-scsi-ccw in QEMU binary')
1164
f203080b 1165
b0f90495
AG
1166def supports_quorum():
1167 return 'quorum' in qemu_img_pipe('--help')
1168
3f647b51
SS
1169def verify_quorum():
1170 '''Skip test suite if quorum support is not available'''
b0f90495 1171 if not supports_quorum():
3f647b51
SS
1172 notrun('quorum support missing')
1173
6649f4bd
HR
1174def has_working_luks() -> Tuple[bool, str]:
1175 """
1176 Check whether our LUKS driver can actually create images
1177 (this extends to LUKS encryption for qcow2).
1178
1179 If not, return the reason why.
1180 """
1181
1182 img_file = f'{test_dir}/luks-test.luks'
1183 (output, status) = \
1184 qemu_img_pipe_and_status('create', '-f', 'luks',
1185 '--object', luks_default_secret_object,
1186 '-o', luks_default_key_secret_opt,
1187 '-o', 'iter-time=10',
1188 img_file, '1G')
1189 try:
1190 os.remove(img_file)
1191 except OSError:
1192 pass
1193
1194 if status != 0:
1195 reason = output
1196 for line in output.splitlines():
1197 if img_file + ':' in line:
1198 reason = line.split(img_file + ':', 1)[1].strip()
1199 break
1200
1201 return (False, reason)
1202 else:
1203 return (True, '')
1204
1205def verify_working_luks():
1206 """
1207 Skip test suite if LUKS does not work
1208 """
1209 (working, reason) = has_working_luks()
1210 if not working:
1211 notrun(reason)
1212
91efbae9 1213def qemu_pipe(*args: str) -> str:
b031e9a5
JS
1214 """
1215 Run qemu with an option to print something and exit (e.g. a help option).
1216
1217 :return: QEMU's stdout output.
1218 """
91efbae9
KW
1219 full_args = [qemu_prog] + qemu_opts + list(args)
1220 output, _ = qemu_tool_pipe_and_status('qemu', full_args)
49438972 1221 return output
57ed557f
AS
1222
1223def supported_formats(read_only=False):
1224 '''Set 'read_only' to True to check ro-whitelist
1225 Otherwise, rw-whitelist is checked'''
767de537
HR
1226
1227 if not hasattr(supported_formats, "formats"):
1228 supported_formats.formats = {}
1229
1230 if read_only not in supported_formats.formats:
1231 format_message = qemu_pipe("-drive", "format=help")
1232 line = 1 if read_only else 0
1233 supported_formats.formats[read_only] = \
1234 format_message.splitlines()[line].split(":")[1].split()
1235
1236 return supported_formats.formats[read_only]
57ed557f 1237
4eabe051 1238def skip_if_unsupported(required_formats=(), read_only=False):
57ed557f
AS
1239 '''Skip Test Decorator
1240 Runs the test if all the required formats are whitelisted'''
1241 def skip_test_decorator(func):
cd8f5b75
KW
1242 def func_wrapper(test_case: QMPTestCase, *args: List[Any],
1243 **kwargs: Dict[str, Any]) -> None:
7448be83
HR
1244 if callable(required_formats):
1245 fmts = required_formats(test_case)
1246 else:
1247 fmts = required_formats
1248
1249 usf_list = list(set(fmts) - set(supported_formats(read_only)))
57ed557f 1250 if usf_list:
b031e9a5
JS
1251 msg = f'{test_case}: formats {usf_list} are not whitelisted'
1252 test_case.case_skip(msg)
57ed557f 1253 else:
cd8f5b75 1254 func(test_case, *args, **kwargs)
57ed557f
AS
1255 return func_wrapper
1256 return skip_test_decorator
1257
ff3caf5a
HR
1258def skip_for_formats(formats: Sequence[str] = ()) \
1259 -> Callable[[Callable[[QMPTestCase, List[Any], Dict[str, Any]], None]],
1260 Callable[[QMPTestCase, List[Any], Dict[str, Any]], None]]:
1261 '''Skip Test Decorator
1262 Skips the test for the given formats'''
1263 def skip_test_decorator(func):
1264 def func_wrapper(test_case: QMPTestCase, *args: List[Any],
1265 **kwargs: Dict[str, Any]) -> None:
1266 if imgfmt in formats:
1267 msg = f'{test_case}: Skipped for format {imgfmt}'
1268 test_case.case_skip(msg)
1269 else:
1270 func(test_case, *args, **kwargs)
1271 return func_wrapper
1272 return skip_test_decorator
1273
d926f4dd
KW
1274def skip_if_user_is_root(func):
1275 '''Skip Test Decorator
1276 Runs the test only without root permissions'''
1277 def func_wrapper(*args, **kwargs):
1278 if os.getuid() == 0:
1279 case_notrun('{}: cannot be run as root'.format(args[0]))
6a96d87c 1280 return None
d926f4dd
KW
1281 else:
1282 return func(*args, **kwargs)
1283 return func_wrapper
1284
f29f4c25
PB
1285# We need to filter out the time taken from the output so that
1286# qemu-iotest can reliably diff the results against master output,
1287# and hide skipped tests from the reference output.
1288
1289class ReproducibleTestResult(unittest.TextTestResult):
1290 def addSkip(self, test, reason):
1291 # Same as TextTestResult, but print dot instead of "s"
1292 unittest.TestResult.addSkip(self, test, reason)
1293 if self.showAll:
1294 self.stream.writeln("skipped {0!r}".format(reason))
1295 elif self.dots:
1296 self.stream.write(".")
1297 self.stream.flush()
1298
1299class ReproducibleStreamWrapper:
1300 def __init__(self, stream: TextIO):
1301 self.stream = stream
1302
1303 def __getattr__(self, attr):
1304 if attr in ('stream', '__getstate__'):
1305 raise AttributeError(attr)
1306 return getattr(self.stream, attr)
1307
1308 def write(self, arg=None):
1309 arg = re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', arg)
1310 arg = re.sub(r' \(skipped=\d+\)', r'', arg)
1311 self.stream.write(arg)
1312
1313class ReproducibleTestRunner(unittest.TextTestRunner):
1314 def __init__(self, stream: Optional[TextIO] = None,
1315 resultclass: Type[unittest.TestResult] = ReproducibleTestResult,
1316 **kwargs: Any) -> None:
1317 rstream = ReproducibleStreamWrapper(stream or sys.stdout)
1318 super().__init__(stream=rstream, # type: ignore
1319 descriptions=True,
1320 resultclass=resultclass,
1321 **kwargs)
1322
00dbc85e 1323def execute_unittest(argv: List[str], debug: bool = False) -> None:
7d814059
JS
1324 """Executes unittests within the calling module."""
1325
00dbc85e
PB
1326 # Some tests have warnings, especially ResourceWarnings for unclosed
1327 # files and sockets. Ignore them for now to ensure reproducibility of
1328 # the test output.
1329 unittest.main(argv=argv,
1330 testRunner=ReproducibleTestRunner,
1331 verbosity=2 if debug else 1,
1332 warnings=None if sys.warnoptions else 'ignore')
c6a92369 1333
7d814059
JS
1334def execute_setup_common(supported_fmts: Sequence[str] = (),
1335 supported_platforms: Sequence[str] = (),
1336 supported_cache_modes: Sequence[str] = (),
1337 supported_aio_modes: Sequence[str] = (),
1338 unsupported_fmts: Sequence[str] = (),
1339 supported_protocols: Sequence[str] = (),
18654716
VSO
1340 unsupported_protocols: Sequence[str] = (),
1341 required_fmts: Sequence[str] = ()) -> bool:
7d814059
JS
1342 """
1343 Perform necessary setup for either script-style or unittest-style tests.
1344
1345 :return: Bool; Whether or not debug mode has been requested via the CLI.
1346 """
1347 # Note: Python 3.6 and pylint do not like 'Collection' so use 'Sequence'.
c0088d79 1348
44a46a9c
JS
1349 debug = '-d' in sys.argv
1350 if debug:
1351 sys.argv.remove('-d')
1352 logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
1353
59c29869
JS
1354 _verify_image_format(supported_fmts, unsupported_fmts)
1355 _verify_protocol(supported_protocols, unsupported_protocols)
1356 _verify_platform(supported=supported_platforms)
1357 _verify_cache_mode(supported_cache_modes)
1358 _verify_aio_mode(supported_aio_modes)
18654716 1359 _verify_formats(required_fmts)
f203080b 1360 _verify_virtio_blk()
c6a92369 1361
7d814059
JS
1362 return debug
1363
1364def execute_test(*args, test_function=None, **kwargs):
1365 """Run either unittest or script-style tests."""
1366
1367 debug = execute_setup_common(*args, **kwargs)
456a2d5a 1368 if not test_function:
00dbc85e 1369 execute_unittest(sys.argv, debug)
456a2d5a
JS
1370 else:
1371 test_function()
1372
52ea799e
JS
1373def activate_logging():
1374 """Activate iotests.log() output to stdout for script-style tests."""
1375 handler = logging.StreamHandler(stream=sys.stdout)
1376 formatter = logging.Formatter('%(message)s')
1377 handler.setFormatter(formatter)
1378 test_logger.addHandler(handler)
1379 test_logger.setLevel(logging.INFO)
1380 test_logger.propagate = False
1381
7d814059
JS
1382# This is called from script-style iotests without a single point of entry
1383def script_initialize(*args, **kwargs):
1384 """Initialize script-style tests without running any tests."""
52ea799e 1385 activate_logging()
7d814059
JS
1386 execute_setup_common(*args, **kwargs)
1387
1388# This is called from script-style iotests with a single point of entry
456a2d5a
JS
1389def script_main(test_function, *args, **kwargs):
1390 """Run script-style tests outside of the unittest framework"""
52ea799e 1391 activate_logging()
7d814059 1392 execute_test(*args, test_function=test_function, **kwargs)
f345cfd0 1393
7d814059 1394# This is called from unittest style iotests
456a2d5a
JS
1395def main(*args, **kwargs):
1396 """Run tests using the unittest framework"""
7d814059 1397 execute_test(*args, **kwargs)