Skip to content

Commit 08c1fd5

Browse files
JSydllBastian-Krause
authored andcommitted
labgrid/qemudriver: tie QEMU and QMP monitor start/stop to on_activate()/on_deactivate()
Until now the QEMU process and QMP monitor start was tied to the on()/off() methods. This feels unnatural, preventing the user from interacting with the QEMU process via monitor commands before the emulation starts and meant starting a new process on each power cycle. Rework the driver to start QEMU and the QMP monitor in on_activate(), allowing interaction via monitor_command after activation. The on() and off() methods interact only via QMP now. All methods relying on a started QEMU and QMP monitor instance are decorated with @Driver.check_active now. The atexit handling is no longer required since the target's atexit handler already calls the driver's on_deactivate(). Signed-off-by: Joschka Seydell <joschka@seydell.org> Signed-off-by: Bastian Krause <bst@pengutronix.de>
1 parent 0559386 commit 08c1fd5

File tree

2 files changed

+52
-41
lines changed

2 files changed

+52
-41
lines changed

labgrid/driver/qemudriver.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""The QEMUDriver implements a driver to use a QEMU target"""
2-
import atexit
32
import select
43
import shlex
54
import shutil
@@ -104,17 +103,6 @@ def __attrs_post_init__(self):
104103
self._socket = None
105104
self._clientsocket = None
106105
self._forwarded_ports = {}
107-
atexit.register(self._atexit)
108-
109-
def _atexit(self):
110-
if not self._child:
111-
return
112-
self._child.terminate()
113-
try:
114-
self._child.communicate(timeout=1)
115-
except subprocess.TimeoutExpired:
116-
self._child.kill()
117-
self._child.communicate(timeout=1)
118106

119107
def get_qemu_version(self, qemu_bin):
120108
p = subprocess.run([qemu_bin, "-version"], stdout=subprocess.PIPE, encoding="utf-8")
@@ -247,25 +235,8 @@ def on_activate(self):
247235
self._cmd.append("-serial")
248236
self._cmd.append("chardev:serialsocket")
249237

250-
def on_deactivate(self):
251-
if self.status:
252-
self.off()
253-
if self._clientsocket:
254-
self._clientsocket.close()
255-
self._clientsocket = None
256-
self._socket.close()
257-
self._socket = None
258-
shutil.rmtree(self._tempdir)
259-
260-
@step()
261-
def on(self):
262-
"""Start the QEMU subprocess, accept the unix socket connection and
263-
afterwards start the emulator using a QMP Command"""
264-
if self.status:
265-
return
266238
self.logger.debug("Starting with: %s", self._cmd)
267-
self._child = subprocess.Popen(
268-
self._cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
239+
self._child = subprocess.Popen(self._cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
269240

270241
# prepare for timeout handing
271242
self._clientsocket, address = self._socket.accept()
@@ -282,37 +253,64 @@ def on(self):
282253
) from exc
283254
raise
284255

285-
self.status = 1
256+
def on_deactivate(self):
257+
if self._child and self._child.poll() is None:
258+
self.monitor_command("quit")
259+
try:
260+
self._child.communicate(timeout=1)
261+
except subprocess.TimeoutExpired:
262+
self._child.kill()
263+
self._child.communicate(timeout=1)
264+
265+
self._child = None
266+
self.qmp = None
267+
268+
if self._clientsocket:
269+
self._clientsocket.close()
270+
self._clientsocket = None
271+
272+
if self._socket:
273+
self._socket.close()
274+
self._socket = None
275+
if self._tempdir:
276+
shutil.rmtree(self._tempdir)
277+
self._tempdir = None
278+
279+
@Driver.check_active
280+
@step()
281+
def on(self):
282+
"""Starts the emulation using a QMP command"""
283+
if self.status:
284+
return
286285

287286
# Restore port forwards
288287
for v in self._forwarded_ports.values():
289288
self._add_port_forward(*v)
290289

291290
self.monitor_command("cont")
291+
self.status = 1
292292

293+
@Driver.check_active
293294
@step()
294295
def off(self):
295-
"""Stop the emulator using a monitor command and await the exitcode"""
296+
"""Stops and resets the emulation using monitor commands"""
296297
if not self.status:
297298
return
298-
self.monitor_command('quit')
299-
if self._child.wait() != 0:
300-
self._child.communicate()
301-
raise IOError
302-
self._child = None
299+
self.monitor_command('stop')
300+
self.monitor_command('system_reset')
303301
self.status = 0
304302

303+
@Driver.check_active
304+
@step()
305305
def cycle(self):
306306
"""Cycle the emulator by restarting it"""
307307
self.off()
308308
self.on()
309309

310+
@Driver.check_active
310311
@step(result=True, args=['command', 'arguments'])
311312
def monitor_command(self, command, arguments={}):
312313
"""Execute a monitor_command via the QMP"""
313-
if not self.status:
314-
raise ExecutionError(
315-
"Can't use monitor command on non-running target")
316314
return self.qmp.execute(command, arguments)
317315

318316
def _add_port_forward(self, proto, local_address, local_port, remote_address, remote_port):
@@ -321,10 +319,12 @@ def _add_port_forward(self, proto, local_address, local_port, remote_address, re
321319
{"command-line": f"hostfwd_add {proto}:{local_address}:{local_port}-{remote_address}:{remote_port}"},
322320
)
323321

322+
@Driver.check_active
324323
def add_port_forward(self, proto, local_address, local_port, remote_address, remote_port):
325324
self._add_port_forward(proto, local_address, local_port, remote_address, remote_port)
326325
self._forwarded_ports[(proto, local_address, local_port)] = (proto, local_address, local_port, remote_address, remote_port)
327326

327+
@Driver.check_active
328328
def remove_port_forward(self, proto, local_address, local_port):
329329
del self._forwarded_ports[(proto, local_address, local_port)]
330330
self.monitor_command(

tests/test_qemudriver.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,22 @@ def qemu_mock(mocker):
6464
version_mock.return_value.returncode = 0
6565
version_mock.return_value.stdout = "QEMU emulator version 4.2.1"
6666

67+
@pytest.fixture
68+
def qemu_qmp_mock(mocker):
69+
monitor_mock = mocker.patch('labgrid.driver.qemudriver.QMPMonitor')
70+
monitor_mock.return_value.execute.return_value = {'return': {}}
71+
return monitor_mock
72+
6773
def test_qemu_instance(qemu_driver):
6874
assert (isinstance(qemu_driver, QEMUDriver))
6975

70-
def test_qemu_activate_deactivate(qemu_target, qemu_driver):
76+
def test_qemu_activate_deactivate(qemu_target, qemu_driver, qemu_qmp_mock):
7177
qemu_target.activate(qemu_driver)
78+
79+
qemu_driver.monitor_command("info")
80+
qemu_qmp_mock.assert_called_once()
81+
qemu_qmp_mock.return_value.execute.assert_called_with("info", {})
82+
7283
qemu_target.deactivate(qemu_driver)
7384

7485
def test_qemu_on_off(qemu_target, qemu_driver):

0 commit comments

Comments
 (0)