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