]> Git Repo - qemu.git/blob - tests/qemu-iotests/iotests.py
Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' 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 debug = False
65
66 luks_default_secret_object = 'secret,id=keysec0,data=' + \
67                              os.environ.get('IMGKEYSECRET', '')
68 luks_default_key_secret_opt = 'key-secret=keysec0'
69
70
71 def qemu_img(*args):
72     '''Run qemu-img and return the exit code'''
73     devnull = open('/dev/null', 'r+')
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
78
79 def ordered_qmp(qmsg, conv_keys=True):
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()):
86             if conv_keys:
87                 k = k.replace('_', '-')
88             od[k] = ordered_qmp(v, conv_keys=False)
89         return od
90     return qmsg
91
92 def 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
111 def qemu_img_verbose(*args):
112     '''Run qemu-img without suppressing its output and return the exit code'''
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
117
118 def qemu_img_pipe(*args):
119     '''Run qemu-img and return its output'''
120     subp = subprocess.Popen(qemu_img_args + list(args),
121                             stdout=subprocess.PIPE,
122                             stderr=subprocess.STDOUT,
123                             universal_newlines=True)
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]
128
129 def qemu_img_log(*args):
130     result = qemu_img_pipe(*args)
131     log(result, filters=[filter_testfiles])
132     return result
133
134 def 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)
144     if not filter_path:
145         filter_path = filename
146     log(filter_img_info(output, filter_path))
147
148 def qemu_io(*args):
149     '''Run qemu-io and return the stdout data'''
150     args = qemu_io_args + list(args)
151     subp = subprocess.Popen(args, stdout=subprocess.PIPE,
152                             stderr=subprocess.STDOUT,
153                             universal_newlines=True)
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]
158
159 def 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
168
169 class 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,
174                                    stderr=subprocess.STDOUT,
175                                    universal_newlines=True)
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')
204         self._p.stdin.flush()
205         return self._read_output()
206
207
208 def 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
212 def qemu_nbd_early_pipe(*args):
213     '''Run qemu-nbd in daemon mode and return both the parent's exit code
214        and its output in case of an error'''
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))))
224     if exitcode == 0:
225         return exitcode, ''
226     else:
227         return exitcode, subp.communicate()[0]
228
229 def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
230     '''Return True if two image files are identical'''
231     return qemu_img('compare', '-f', fmt1,
232                     '-F', fmt2, img1, img2) == 0
233
234 def create_image(name, size):
235     '''Create a fully-allocated raw image with sector markers'''
236     file = open(name, 'wb')
237     i = 0
238     while i < size:
239         sector = struct.pack('>l504xl', i // 512, i // 512)
240         file.write(sector)
241         i = i + 512
242     file.close()
243
244 def 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
249 def 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
255 test_dir_re = re.compile(r"%s" % test_dir)
256 def filter_test_dir(msg):
257     return test_dir_re.sub("TEST_DIR", msg)
258
259 win32_re = re.compile(r"\r")
260 def filter_win32(msg):
261     return win32_re.sub("", msg)
262
263 qemu_io_re = re.compile(r"[0-9]* ops; [0-9\/:. sec]* \([0-9\/.inf]* [EPTGMKiBbytes]*\/sec and [0-9\/.inf]* ops\/sec\)")
264 def 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
268 chown_re = re.compile(r"chown [0-9]+:[0-9]+")
269 def filter_chown(msg):
270     return chown_re.sub("chown UID:GID", msg)
271
272 def 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
280 def 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
296 def filter_testfiles(msg):
297     prefix = os.path.join(test_dir, "%s-" % (os.getpid()))
298     return msg.replace(prefix, 'TEST_DIR/PID-')
299
300 def filter_qmp_testfiles(qmsg):
301     def _filter(key, value):
302         if is_str(value):
303             return filter_testfiles(value)
304         return value
305     return filter_qmp(qmsg, _filter)
306
307 def filter_generated_node_ids(msg):
308     return re.sub("#block[0-9]+", "NODE_NAME", msg)
309
310 def 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)
319         line = re.sub('cid: [0-9]+', 'cid: XXXXXXXXXX', line)
320         lines.append(line)
321     return '\n'.join(lines)
322
323 def filter_imgfmt(msg):
324     return msg.replace(imgfmt, 'IMGFMT')
325
326 def 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
333 def 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.'''
336     for flt in filters:
337         msg = flt(msg)
338     if isinstance(msg, dict) or isinstance(msg, list):
339         # Python < 3.4 needs to know not to add whitespace when pretty-printing:
340         separators = (', ', ': ') if indent is None else (',', ': ')
341         # Don't sort if it's already sorted
342         do_sort = not isinstance(msg, OrderedDict)
343         print(json.dumps(msg, sort_keys=do_sort,
344                          indent=indent, separators=separators))
345     else:
346         print(msg)
347
348 class 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
362
363 class 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
388 def 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
396 def 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
418 def remote_filename(path):
419     if imgproto == 'file':
420         return path
421     elif imgproto == 'ssh':
422         return "ssh://%[email protected]:22%s" % (os.environ.get('USER'), path)
423     else:
424         raise Exception("Protocol %s not supported" % (imgproto))
425
426 class VM(qtest.QEMUQtestMachine):
427     '''A QEMU VM'''
428
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,
433                                  socket_scm_helper=socket_scm_helper)
434         self._num_drives = 0
435
436     def add_object(self, opts):
437         self._args.append('-object')
438         self._args.append(opts)
439         return self
440
441     def add_device(self, opts):
442         self._args.append('-device')
443         self._args.append(opts)
444         return self
445
446     def add_drive_raw(self, opts):
447         self._args.append('-drive')
448         self._args.append(opts)
449         return self
450
451     def add_drive(self, path, opts='', interface='virtio', format=imgfmt):
452         '''Add a virtio-blk drive to the VM'''
453         options = ['if=%s' % interface,
454                    'id=drive%d' % self._num_drives]
455
456         if path is not None:
457             options.append('file=%s' % path)
458             options.append('format=%s' % format)
459             options.append('cache=%s' % cachemode)
460
461         if opts:
462             options.append(opts)
463
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
471         self._args.append('-drive')
472         self._args.append(','.join(options))
473         self._num_drives += 1
474         return self
475
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
484     def add_incoming(self, addr):
485         self._args.append('-incoming')
486         self._args.append(addr)
487         return self
488
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
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
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
527     def get_qmp_events_filtered(self, wait=60.0):
528         result = []
529         for ev in self.get_qmp_events(wait=wait):
530             result.append(filter_qmp_event(ev))
531         return result
532
533     def qmp_log(self, cmd, filters=[], indent=None, **kwargs):
534         full_cmd = OrderedDict((
535             ("execute", cmd),
536             ("arguments", ordered_qmp(kwargs))
537         ))
538         log(full_cmd, filters, indent=indent)
539         result = self.qmp(cmd, **kwargs)
540         log(result, filters, indent=indent)
541         return result
542
543     # Returns None on success, and an error string on failure
544     def run_job(self, job, auto_finalize=True, auto_dismiss=False,
545                 pre_finalize=None, use_log=True, wait=60.0):
546         match_device = {'data': {'device': job}}
547         match_id = {'data': {'id': job}}
548         events = [
549             ('BLOCK_JOB_COMPLETED', match_device),
550             ('BLOCK_JOB_CANCELLED', match_device),
551             ('BLOCK_JOB_ERROR', match_device),
552             ('BLOCK_JOB_READY', match_device),
553             ('BLOCK_JOB_PENDING', match_id),
554             ('JOB_STATUS_CHANGE', match_id)
555         ]
556         error = None
557         while True:
558             ev = filter_qmp_event(self.events_wait(events))
559             if ev['event'] != 'JOB_STATUS_CHANGE':
560                 if use_log:
561                     log(ev)
562                 continue
563             status = ev['data']['status']
564             if status == 'aborting':
565                 result = self.qmp('query-jobs')
566                 for j in result['return']:
567                     if j['id'] == job:
568                         error = j['error']
569                         if use_log:
570                             log('Job failed: %s' % (j['error']))
571             elif status == 'pending' and not auto_finalize:
572                 if pre_finalize:
573                     pre_finalize()
574                 if use_log:
575                     self.qmp_log('job-finalize', id=job)
576                 else:
577                     self.qmp('job-finalize', id=job)
578             elif status == 'concluded' and not auto_dismiss:
579                 if use_log:
580                     self.qmp_log('job-dismiss', id=job)
581                 else:
582                     self.qmp('job-dismiss', id=job)
583             elif status == 'null':
584                 return error
585
586     def enable_migration_events(self, name):
587         log('Enabling migration QMP events on %s...' % name)
588         log(self.qmp('migrate-set-capabilities', capabilities=[
589             {
590                 'capability': 'events',
591                 'state': True
592             }
593         ]))
594
595     def wait_migration(self):
596         while True:
597             event = self.event_wait('MIGRATION')
598             log(event, filters=[filter_qmp_event])
599             if event['data']['status'] == 'completed':
600                 break
601
602     def node_info(self, node_name):
603         nodes = self.qmp('query-named-block-nodes')
604         for x in nodes['return']:
605             if x['node-name'] == node_name:
606                 return x
607         return None
608
609
610 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
611
612 class QMPTestCase(unittest.TestCase):
613     '''Abstract base class for QMP test cases'''
614
615     def dictpath(self, d, path):
616         '''Traverse a path in a nested dict'''
617         for component in path.split('/'):
618             m = index_re.match(component)
619             if m:
620                 component, idx = m.groups()
621                 idx = int(idx)
622
623             if not isinstance(d, dict) or component not in d:
624                 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
625             d = d[component]
626
627             if m:
628                 if not isinstance(d, list):
629                     self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
630                 try:
631                     d = d[idx]
632                 except IndexError:
633                     self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
634         return d
635
636     def assert_qmp_absent(self, d, path):
637         try:
638             result = self.dictpath(d, path)
639         except AssertionError:
640             return
641         self.fail('path "%s" has value "%s"' % (path, str(result)))
642
643     def assert_qmp(self, d, path, value):
644         '''Assert that the value for a specific path in a QMP dict
645            matches.  When given a list of values, assert that any of
646            them matches.'''
647
648         result = self.dictpath(d, path)
649
650         # [] makes no sense as a list of valid values, so treat it as
651         # an actual single value.
652         if isinstance(value, list) and value != []:
653             for v in value:
654                 if result == v:
655                     return
656             self.fail('no match for "%s" in %s' % (str(result), str(value)))
657         else:
658             self.assertEqual(result, value,
659                              'values not equal "%s" and "%s"'
660                                  % (str(result), str(value)))
661
662     def assert_no_active_block_jobs(self):
663         result = self.vm.qmp('query-block-jobs')
664         self.assert_qmp(result, 'return', [])
665
666     def assert_has_block_node(self, node_name=None, file_name=None):
667         """Issue a query-named-block-nodes and assert node_name and/or
668         file_name is present in the result"""
669         def check_equal_or_none(a, b):
670             return a == None or b == None or a == b
671         assert node_name or file_name
672         result = self.vm.qmp('query-named-block-nodes')
673         for x in result["return"]:
674             if check_equal_or_none(x.get("node-name"), node_name) and \
675                     check_equal_or_none(x.get("file"), file_name):
676                 return
677         self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
678                 (node_name, file_name, result))
679
680     def assert_json_filename_equal(self, json_filename, reference):
681         '''Asserts that the given filename is a json: filename and that its
682            content is equal to the given reference object'''
683         self.assertEqual(json_filename[:5], 'json:')
684         self.assertEqual(self.vm.flatten_qmp_object(json.loads(json_filename[5:])),
685                          self.vm.flatten_qmp_object(reference))
686
687     def cancel_and_wait(self, drive='drive0', force=False, resume=False, wait=60.0):
688         '''Cancel a block job and wait for it to finish, returning the event'''
689         result = self.vm.qmp('block-job-cancel', device=drive, force=force)
690         self.assert_qmp(result, 'return', {})
691
692         if resume:
693             self.vm.resume_drive(drive)
694
695         cancelled = False
696         result = None
697         while not cancelled:
698             for event in self.vm.get_qmp_events(wait=wait):
699                 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
700                    event['event'] == 'BLOCK_JOB_CANCELLED':
701                     self.assert_qmp(event, 'data/device', drive)
702                     result = event
703                     cancelled = True
704                 elif event['event'] == 'JOB_STATUS_CHANGE':
705                     self.assert_qmp(event, 'data/id', drive)
706
707
708         self.assert_no_active_block_jobs()
709         return result
710
711     def wait_until_completed(self, drive='drive0', check_offset=True, wait=60.0):
712         '''Wait for a block job to finish, returning the event'''
713         while True:
714             for event in self.vm.get_qmp_events(wait=wait):
715                 if event['event'] == 'BLOCK_JOB_COMPLETED':
716                     self.assert_qmp(event, 'data/device', drive)
717                     self.assert_qmp_absent(event, 'data/error')
718                     if check_offset:
719                         self.assert_qmp(event, 'data/offset', event['data']['len'])
720                     self.assert_no_active_block_jobs()
721                     return event
722                 elif event['event'] == 'JOB_STATUS_CHANGE':
723                     self.assert_qmp(event, 'data/id', drive)
724
725     def wait_ready(self, drive='drive0'):
726         '''Wait until a block job BLOCK_JOB_READY event'''
727         f = {'data': {'type': 'mirror', 'device': drive } }
728         event = self.vm.event_wait(name='BLOCK_JOB_READY', match=f)
729
730     def wait_ready_and_cancel(self, drive='drive0'):
731         self.wait_ready(drive=drive)
732         event = self.cancel_and_wait(drive=drive)
733         self.assertEqual(event['event'], 'BLOCK_JOB_COMPLETED')
734         self.assert_qmp(event, 'data/type', 'mirror')
735         self.assert_qmp(event, 'data/offset', event['data']['len'])
736
737     def complete_and_wait(self, drive='drive0', wait_ready=True):
738         '''Complete a block job and wait for it to finish'''
739         if wait_ready:
740             self.wait_ready(drive=drive)
741
742         result = self.vm.qmp('block-job-complete', device=drive)
743         self.assert_qmp(result, 'return', {})
744
745         event = self.wait_until_completed(drive=drive)
746         self.assert_qmp(event, 'data/type', 'mirror')
747
748     def pause_wait(self, job_id='job0'):
749         with Timeout(1, "Timeout waiting for job to pause"):
750             while True:
751                 result = self.vm.qmp('query-block-jobs')
752                 found = False
753                 for job in result['return']:
754                     if job['device'] == job_id:
755                         found = True
756                         if job['paused'] == True and job['busy'] == False:
757                             return job
758                         break
759                 assert found
760
761     def pause_job(self, job_id='job0', wait=True):
762         result = self.vm.qmp('block-job-pause', device=job_id)
763         self.assert_qmp(result, 'return', {})
764         if wait:
765             return self.pause_wait(job_id)
766         return result
767
768
769 def notrun(reason):
770     '''Skip this test suite'''
771     # Each test in qemu-iotests has a number ("seq")
772     seq = os.path.basename(sys.argv[0])
773
774     open('%s/%s.notrun' % (output_dir, seq), 'w').write(reason + '\n')
775     print('%s not run: %s' % (seq, reason))
776     sys.exit(0)
777
778 def case_notrun(reason):
779     '''Skip this test case'''
780     # Each test in qemu-iotests has a number ("seq")
781     seq = os.path.basename(sys.argv[0])
782
783     open('%s/%s.casenotrun' % (output_dir, seq), 'a').write(
784         '    [case not run] ' + reason + '\n')
785
786 def verify_image_format(supported_fmts=[], unsupported_fmts=[]):
787     assert not (supported_fmts and unsupported_fmts)
788
789     if 'generic' in supported_fmts and \
790             os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
791         # similar to
792         #   _supported_fmt generic
793         # for bash tests
794         return
795
796     not_sup = supported_fmts and (imgfmt not in supported_fmts)
797     if not_sup or (imgfmt in unsupported_fmts):
798         notrun('not suitable for this image format: %s' % imgfmt)
799
800 def verify_protocol(supported=[], unsupported=[]):
801     assert not (supported and unsupported)
802
803     if 'generic' in supported:
804         return
805
806     not_sup = supported and (imgproto not in supported)
807     if not_sup or (imgproto in unsupported):
808         notrun('not suitable for this protocol: %s' % imgproto)
809
810 def verify_platform(supported_oses=['linux']):
811     if True not in [sys.platform.startswith(x) for x in supported_oses]:
812         notrun('not suitable for this OS: %s' % sys.platform)
813
814 def verify_cache_mode(supported_cache_modes=[]):
815     if supported_cache_modes and (cachemode not in supported_cache_modes):
816         notrun('not suitable for this cache mode: %s' % cachemode)
817
818 def supports_quorum():
819     return 'quorum' in qemu_img_pipe('--help')
820
821 def verify_quorum():
822     '''Skip test suite if quorum support is not available'''
823     if not supports_quorum():
824         notrun('quorum support missing')
825
826 def qemu_pipe(*args):
827     '''Run qemu with an option to print something and exit (e.g. a help option),
828     and return its output'''
829     args = [qemu_prog] + qemu_opts + list(args)
830     subp = subprocess.Popen(args, stdout=subprocess.PIPE,
831                             stderr=subprocess.STDOUT,
832                             universal_newlines=True)
833     exitcode = subp.wait()
834     if exitcode < 0:
835         sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode,
836                          ' '.join(args)))
837     return subp.communicate()[0]
838
839 def supported_formats(read_only=False):
840     '''Set 'read_only' to True to check ro-whitelist
841        Otherwise, rw-whitelist is checked'''
842     format_message = qemu_pipe("-drive", "format=help")
843     line = 1 if read_only else 0
844     return format_message.splitlines()[line].split(":")[1].split()
845
846 def skip_if_unsupported(required_formats=[], read_only=False):
847     '''Skip Test Decorator
848        Runs the test if all the required formats are whitelisted'''
849     def skip_test_decorator(func):
850         def func_wrapper(*args, **kwargs):
851             usf_list = list(set(required_formats) -
852                             set(supported_formats(read_only)))
853             if usf_list:
854                 case_notrun('{}: formats {} are not whitelisted'.format(
855                     args[0], usf_list))
856             else:
857                 return func(*args, **kwargs)
858         return func_wrapper
859     return skip_test_decorator
860
861 def main(supported_fmts=[], supported_oses=['linux'], supported_cache_modes=[],
862          unsupported_fmts=[]):
863     '''Run tests'''
864
865     global debug
866
867     # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to
868     # indicate that we're not being run via "check". There may be
869     # other things set up by "check" that individual test cases rely
870     # on.
871     if test_dir is None or qemu_default_machine is None:
872         sys.stderr.write('Please run this test via the "check" script\n')
873         sys.exit(os.EX_USAGE)
874
875     debug = '-d' in sys.argv
876     verbosity = 1
877     verify_image_format(supported_fmts, unsupported_fmts)
878     verify_platform(supported_oses)
879     verify_cache_mode(supported_cache_modes)
880
881     if debug:
882         output = sys.stdout
883         verbosity = 2
884         sys.argv.remove('-d')
885     else:
886         # We need to filter out the time taken from the output so that
887         # qemu-iotest can reliably diff the results against master output.
888         if sys.version_info.major >= 3:
889             output = io.StringIO()
890         else:
891             # io.StringIO is for unicode strings, which is not what
892             # 2.x's test runner emits.
893             output = io.BytesIO()
894
895     logging.basicConfig(level=(logging.DEBUG if debug else logging.WARN))
896
897     class MyTestRunner(unittest.TextTestRunner):
898         def __init__(self, stream=output, descriptions=True, verbosity=verbosity):
899             unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
900
901     # unittest.main() will use sys.exit() so expect a SystemExit exception
902     try:
903         unittest.main(testRunner=MyTestRunner)
904     finally:
905         if not debug:
906             sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))
This page took 0.077789 seconds and 4 git commands to generate.