]> git.proxmox.com Git - qemu.git/blob - tests/qemu-iotests/iotests.py
Merge remote-tracking branch 'bonzini/scsi-next' into staging
[qemu.git] / tests / qemu-iotests / iotests.py
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
19 import os
20 import re
21 import subprocess
22 import string
23 import unittest
24 import sys; sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'scripts', 'qmp'))
25 import qmp
26 import struct
27
28 __all__ = ['imgfmt', 'imgproto', 'test_dir' 'qemu_img', 'qemu_io',
29 'VM', 'QMPTestCase', 'notrun', 'main']
30
31 # This will not work if arguments or path contain spaces but is necessary if we
32 # want to support the override options that ./check supports.
33 qemu_img_args = os.environ.get('QEMU_IMG', 'qemu-img').strip().split(' ')
34 qemu_io_args = os.environ.get('QEMU_IO', 'qemu-io').strip().split(' ')
35 qemu_args = os.environ.get('QEMU', 'qemu').strip().split(' ')
36
37 imgfmt = os.environ.get('IMGFMT', 'raw')
38 imgproto = os.environ.get('IMGPROTO', 'file')
39 test_dir = os.environ.get('TEST_DIR', '/var/tmp')
40
41 socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
42
43 def qemu_img(*args):
44 '''Run qemu-img and return the exit code'''
45 devnull = open('/dev/null', 'r+')
46 return subprocess.call(qemu_img_args + list(args), stdin=devnull, stdout=devnull)
47
48 def qemu_img_verbose(*args):
49 '''Run qemu-img without suppressing its output and return the exit code'''
50 return subprocess.call(qemu_img_args + list(args))
51
52 def qemu_io(*args):
53 '''Run qemu-io and return the stdout data'''
54 args = qemu_io_args + list(args)
55 return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]
56
57 def compare_images(img1, img2):
58 '''Return True if two image files are identical'''
59 return qemu_img('compare', '-f', imgfmt,
60 '-F', imgfmt, img1, img2) == 0
61
62 def create_image(name, size):
63 '''Create a fully-allocated raw image with sector markers'''
64 file = open(name, 'w')
65 i = 0
66 while i < size:
67 sector = struct.pack('>l504xl', i / 512, i / 512)
68 file.write(sector)
69 i = i + 512
70 file.close()
71
72 class VM(object):
73 '''A QEMU VM'''
74
75 def __init__(self):
76 self._monitor_path = os.path.join(test_dir, 'qemu-mon.%d' % os.getpid())
77 self._qemu_log_path = os.path.join(test_dir, 'qemu-log.%d' % os.getpid())
78 self._args = qemu_args + ['-chardev',
79 'socket,id=mon,path=' + self._monitor_path,
80 '-mon', 'chardev=mon,mode=control',
81 '-qtest', 'stdio', '-machine', 'accel=qtest',
82 '-display', 'none', '-vga', 'none']
83 self._num_drives = 0
84
85 # This can be used to add an unused monitor instance.
86 def add_monitor_telnet(self, ip, port):
87 args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
88 self._args.append('-monitor')
89 self._args.append(args)
90
91 def add_drive(self, path, opts=''):
92 '''Add a virtio-blk drive to the VM'''
93 options = ['if=virtio',
94 'format=%s' % imgfmt,
95 'cache=none',
96 'file=%s' % path,
97 'id=drive%d' % self._num_drives]
98 if opts:
99 options.append(opts)
100
101 self._args.append('-drive')
102 self._args.append(','.join(options))
103 self._num_drives += 1
104 return self
105
106 def hmp_qemu_io(self, drive, cmd):
107 '''Write to a given drive using an HMP command'''
108 return self.qmp('human-monitor-command',
109 command_line='qemu-io %s "%s"' % (drive, cmd))
110
111 def add_fd(self, fd, fdset, opaque, opts=''):
112 '''Pass a file descriptor to the VM'''
113 options = ['fd=%d' % fd,
114 'set=%d' % fdset,
115 'opaque=%s' % opaque]
116 if opts:
117 options.append(opts)
118
119 self._args.append('-add-fd')
120 self._args.append(','.join(options))
121 return self
122
123 def send_fd_scm(self, fd_file_path):
124 # In iotest.py, the qmp should always use unix socket.
125 assert self._qmp.is_scm_available()
126 bin = socket_scm_helper
127 if os.path.exists(bin) == False:
128 print "Scm help program does not present, path '%s'." % bin
129 return -1
130 fd_param = ["%s" % bin,
131 "%d" % self._qmp.get_sock_fd(),
132 "%s" % fd_file_path]
133 devnull = open('/dev/null', 'rb')
134 p = subprocess.Popen(fd_param, stdin=devnull, stdout=sys.stdout,
135 stderr=sys.stderr)
136 return p.wait()
137
138 def launch(self):
139 '''Launch the VM and establish a QMP connection'''
140 devnull = open('/dev/null', 'rb')
141 qemulog = open(self._qemu_log_path, 'wb')
142 try:
143 self._qmp = qmp.QEMUMonitorProtocol(self._monitor_path, server=True)
144 self._popen = subprocess.Popen(self._args, stdin=devnull, stdout=qemulog,
145 stderr=subprocess.STDOUT)
146 self._qmp.accept()
147 except:
148 os.remove(self._monitor_path)
149 raise
150
151 def shutdown(self):
152 '''Terminate the VM and clean up'''
153 if not self._popen is None:
154 self._qmp.cmd('quit')
155 self._popen.wait()
156 os.remove(self._monitor_path)
157 os.remove(self._qemu_log_path)
158 self._popen = None
159
160 underscore_to_dash = string.maketrans('_', '-')
161 def qmp(self, cmd, **args):
162 '''Invoke a QMP command and return the result dict'''
163 qmp_args = dict()
164 for k in args.keys():
165 qmp_args[k.translate(self.underscore_to_dash)] = args[k]
166
167 return self._qmp.cmd(cmd, args=qmp_args)
168
169 def get_qmp_event(self, wait=False):
170 '''Poll for one queued QMP events and return it'''
171 return self._qmp.pull_event(wait=wait)
172
173 def get_qmp_events(self, wait=False):
174 '''Poll for queued QMP events and return a list of dicts'''
175 events = self._qmp.get_events(wait=wait)
176 self._qmp.clear_events()
177 return events
178
179 index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
180
181 class QMPTestCase(unittest.TestCase):
182 '''Abstract base class for QMP test cases'''
183
184 def dictpath(self, d, path):
185 '''Traverse a path in a nested dict'''
186 for component in path.split('/'):
187 m = index_re.match(component)
188 if m:
189 component, idx = m.groups()
190 idx = int(idx)
191
192 if not isinstance(d, dict) or component not in d:
193 self.fail('failed path traversal for "%s" in "%s"' % (path, str(d)))
194 d = d[component]
195
196 if m:
197 if not isinstance(d, list):
198 self.fail('path component "%s" in "%s" is not a list in "%s"' % (component, path, str(d)))
199 try:
200 d = d[idx]
201 except IndexError:
202 self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
203 return d
204
205 def assert_qmp_absent(self, d, path):
206 try:
207 result = self.dictpath(d, path)
208 except AssertionError:
209 return
210 self.fail('path "%s" has value "%s"' % (path, str(result)))
211
212 def assert_qmp(self, d, path, value):
213 '''Assert that the value for a specific path in a QMP dict matches'''
214 result = self.dictpath(d, path)
215 self.assertEqual(result, value, 'values not equal "%s" and "%s"' % (str(result), str(value)))
216
217 def assert_no_active_block_jobs(self):
218 result = self.vm.qmp('query-block-jobs')
219 self.assert_qmp(result, 'return', [])
220
221 def cancel_and_wait(self, drive='drive0', force=False):
222 '''Cancel a block job and wait for it to finish, returning the event'''
223 result = self.vm.qmp('block-job-cancel', device=drive, force=force)
224 self.assert_qmp(result, 'return', {})
225
226 cancelled = False
227 result = None
228 while not cancelled:
229 for event in self.vm.get_qmp_events(wait=True):
230 if event['event'] == 'BLOCK_JOB_COMPLETED' or \
231 event['event'] == 'BLOCK_JOB_CANCELLED':
232 self.assert_qmp(event, 'data/device', drive)
233 result = event
234 cancelled = True
235
236 self.assert_no_active_block_jobs()
237 return result
238
239 def wait_until_completed(self, drive='drive0'):
240 '''Wait for a block job to finish, returning the event'''
241 completed = False
242 while not completed:
243 for event in self.vm.get_qmp_events(wait=True):
244 if event['event'] == 'BLOCK_JOB_COMPLETED':
245 self.assert_qmp(event, 'data/device', drive)
246 self.assert_qmp_absent(event, 'data/error')
247 self.assert_qmp(event, 'data/offset', self.image_len)
248 self.assert_qmp(event, 'data/len', self.image_len)
249 completed = True
250
251 self.assert_no_active_block_jobs()
252 return event
253
254 def notrun(reason):
255 '''Skip this test suite'''
256 # Each test in qemu-iotests has a number ("seq")
257 seq = os.path.basename(sys.argv[0])
258
259 open('%s.notrun' % seq, 'wb').write(reason + '\n')
260 print '%s not run: %s' % (seq, reason)
261 sys.exit(0)
262
263 def main(supported_fmts=[]):
264 '''Run tests'''
265
266 if supported_fmts and (imgfmt not in supported_fmts):
267 notrun('not suitable for this image format: %s' % imgfmt)
268
269 # We need to filter out the time taken from the output so that qemu-iotest
270 # can reliably diff the results against master output.
271 import StringIO
272 output = StringIO.StringIO()
273
274 class MyTestRunner(unittest.TextTestRunner):
275 def __init__(self, stream=output, descriptions=True, verbosity=1):
276 unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
277
278 # unittest.main() will use sys.exit() so expect a SystemExit exception
279 try:
280 unittest.main(testRunner=MyTestRunner)
281 finally:
282 sys.stderr.write(re.sub(r'Ran (\d+) tests? in [\d.]+s', r'Ran \1 tests', output.getvalue()))