A filter is added to allow callers to request very specific events to be pulled from the event queue, while leaving undesired events still in the stream.
This allows to poll for completion data for multiple asynchronous events in any arbitrary order. A new timeout context is added to the qmp pull_event method's wait parameter to allow tests to fail if they do not complete within some expected period of time. Signed-off-by: John Snow <js...@redhat.com> --- scripts/qmp/qmp.py | 6 +++++- tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/scripts/qmp/qmp.py b/scripts/qmp/qmp.py index 20b6ec7..7f9ac1b 100644 --- a/scripts/qmp/qmp.py +++ b/scripts/qmp/qmp.py @@ -140,7 +140,8 @@ class QEMUMonitorProtocol: """ Get and delete the first available QMP event. - @param wait: block until an event is available (bool) + @param wait: block until an event is available. If a float is provided, + use this as a timeout value. (bool, float) """ self.__sock.setblocking(0) try: @@ -151,7 +152,10 @@ class QEMUMonitorProtocol: pass self.__sock.setblocking(1) if not self.__events and wait: + if isinstance(wait, float): + self.__sock.settimeout(wait) self.__json_read(only_event=True) + self.__sock.settimeout(None) event = self.__events[0] del self.__events[0] return event diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 1402854..e93e623 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -78,6 +78,23 @@ def create_image(name, size): i = i + 512 file.close() +# Test if 'match' is a recursive subset of 'event' +def event_match(event, match=None): + if match is None: + return True + + for key in match: + if key in event: + if isinstance(event[key], dict): + if not event_match(event[key], match[key]): + return False + elif event[key] != match[key]: + return False + else: + return False + + return True + class VM(object): '''A QEMU VM''' @@ -92,6 +109,7 @@ class VM(object): '-machine', 'accel=qtest', '-display', 'none', '-vga', 'none'] self._num_drives = 0 + self._events = [] # This can be used to add an unused monitor instance. def add_monitor_telnet(self, ip, port): @@ -202,14 +220,34 @@ class VM(object): def get_qmp_event(self, wait=False): '''Poll for one queued QMP events and return it''' + if len(self._events) > 0: + return self._events.pop(0) return self._qmp.pull_event(wait=wait) def get_qmp_events(self, wait=False): '''Poll for queued QMP events and return a list of dicts''' events = self._qmp.get_events(wait=wait) + events.extend(self._events) + del self._events[:] self._qmp.clear_events() return events + def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0, match=None): + # Search cached events + for event in self._events: + if (event['event'] == name) and event_match(event, match): + self._events.remove(event) + return event + + # Poll for new events + while True: + event = self._qmp.pull_event(wait=timeout) + if (event['event'] == name) and event_match(event, match): + return event + self._events.append(event) + + return None + index_re = re.compile(r'([^\[]+)\[([^\]]+)\]') class QMPTestCase(unittest.TestCase): -- 2.1.0