diff options
Diffstat (limited to 'pw_emu/py/pw_emu')
-rw-r--r-- | pw_emu/py/pw_emu/__main__.py | 76 | ||||
-rw-r--r-- | pw_emu/py/pw_emu/core.py | 133 | ||||
-rw-r--r-- | pw_emu/py/pw_emu/frontend.py | 69 | ||||
-rw-r--r-- | pw_emu/py/pw_emu/qemu.py | 28 | ||||
-rw-r--r-- | pw_emu/py/pw_emu/renode.py | 5 |
5 files changed, 173 insertions, 138 deletions
diff --git a/pw_emu/py/pw_emu/__main__.py b/pw_emu/py/pw_emu/__main__.py index 253edd931..cba0ac322 100644 --- a/pw_emu/py/pw_emu/__main__.py +++ b/pw_emu/py/pw_emu/__main__.py @@ -33,21 +33,21 @@ _TERM_CMD = ['python', '-m', 'serial', '--raw'] def _cmd_gdb_cmds(emu, args: argparse.Namespace) -> None: - """Run gdb commands in batch mode.""" + """Run ``gdb`` commands in batch mode.""" emu.run_gdb_cmds(args.gdb_cmd, executable=args.executable, pause=args.pause) def _cmd_load(emu: Emulator, args: argparse.Namespace) -> None: - """Load an executable image via gdb start executing it if pause is - not set""" + """Load an executable image via ``gdb`` and start executing it if + ``--pause`` is not set""" args.gdb_cmd = ['load'] _cmd_gdb_cmds(emu, args) def _cmd_start(emu: Emulator, args: argparse.Namespace) -> None: - """Launch the emulator and start executing, unless pause is set.""" + """Launch the emulator and start executing, unless ``--pause`` is set.""" if args.runner: emu.set_emu(args.runner) @@ -103,7 +103,7 @@ def _get_miniterm(emu: Emulator, chan: str) -> Miniterm: def _cmd_run(emu: Emulator, args: argparse.Namespace) -> None: """Start the emulator and connect the terminal to a channel. Stop - the emulator when exiting the terminal""" + the emulator when exiting the terminal.""" emu.start( target=args.target, @@ -137,7 +137,7 @@ def _cmd_run(emu: Emulator, args: argparse.Namespace) -> None: def _cmd_restart(emu: Emulator, args: argparse.Namespace) -> None: - """Restart the emulator and start executing, unless pause is set.""" + """Restart the emulator and start executing, unless ``--pause`` is set.""" if emu.running(): emu.stop() @@ -145,7 +145,7 @@ def _cmd_restart(emu: Emulator, args: argparse.Namespace) -> None: def _cmd_stop(emu: Emulator, _args: argparse.Namespace) -> None: - """Stop the emulator""" + """Stop the emulator.""" emu.stop() @@ -157,7 +157,7 @@ def _cmd_reset(emu: Emulator, _args: argparse.Namespace) -> None: def _cmd_gdb(emu: Emulator, args: argparse.Namespace) -> None: - """Start a gdb interactive session""" + """Start a ``gdb`` interactive session.""" executable = args.executable if args.executable else "" @@ -223,7 +223,10 @@ def get_parser() -> argparse.ArgumentParser: parser.add_argument( '-i', '--instance', - help='instance to use (default: %(default)s)', + help=( + 'Run multiple instances simultaneously by assigning each instance ' + 'an ID (default: ``%(default)s``)' + ), type=str, metavar='STRING', default='default', @@ -231,14 +234,17 @@ def get_parser() -> argparse.ArgumentParser: parser.add_argument( '-C', '--working-dir', - help='path to working directory (default: %(default)s)', + help=( + 'Absolute path to the working directory ' + '(default: ``%(default)s``)' + ), type=Path, default=os.getenv('PW_EMU_WDIR'), ) parser.add_argument( '-c', '--config', - help='path config file (default: %(default)s)', + help='Absolute path to config file (default: ``%(default)s``)', type=str, default=None, ) @@ -264,37 +270,37 @@ def get_parser() -> argparse.ArgumentParser: '--file', '-f', metavar='FILE', - help='file to load before starting', + help='File to load before starting', ) subparser.add_argument( '--runner', '-r', - help='emulator to use, automatically detected if not set', + help='The emulator to use (automatically detected if not set)', choices=[None, 'qemu', 'renode'], default=None, ) subparser.add_argument( '--args', '-a', - help='options to pass to the emulator', + help='Options to pass to the emulator', ) subparser.add_argument( '--pause', '-p', action='store_true', - help='pause the emulator after starting it', + help='Pause the emulator after starting it', ) subparser.add_argument( '--debug', '-d', action='store_true', - help='start the emulator in debug mode', + help='Start the emulator in debug mode', ) subparser.add_argument( '--foreground', '-F', action='store_true', - help='start the emulator in foreground mode', + help='Start the emulator in foreground mode', ) run = add_cmd('run', _cmd_run) @@ -305,17 +311,17 @@ def get_parser() -> argparse.ArgumentParser: run.add_argument( 'file', metavar='FILE', - help='file to load before starting', + help='File to load before starting', ) run.add_argument( '--args', '-a', - help='options to pass to the emulator', + help='Options to pass to the emulator', ) run.add_argument( '--channel', '-n', - help='channel to connect the terminal to', + help='Channel to connect the terminal to', ) stop = add_cmd('stop', _cmd_stop) @@ -324,19 +330,19 @@ def get_parser() -> argparse.ArgumentParser: load.add_argument( 'executable', metavar='FILE', - help='file to load via gdb', + help='File to load via ``gdb``', ) load.add_argument( '--pause', '-p', - help='pause the emulator after loading the file', + help='Pause the emulator after loading the file', action='store_true', ) load.add_argument( '--offset', '-o', metavar='ADDRESS', - help='address to load the file at', + help='Address to load the file at', ) reset = add_cmd('reset', _cmd_reset) @@ -346,62 +352,62 @@ def get_parser() -> argparse.ArgumentParser: '--executable', '-e', metavar='FILE', - help='file to use for the debugging session', + help='File to use for the debugging session', ) prop_ls = add_cmd('prop-ls', _cmd_prop_ls) prop_ls.add_argument( 'path', - help='path of the emulator object', + help='Absolute path to the emulator object', ) prop_get = add_cmd('prop-get', _cmd_prop_get) prop_get.add_argument( 'path', - help='path of the emulator object', + help='Absolute path to the emulator object', ) prop_get.add_argument( 'property', - help='name of the object property', + help='Name of the object property', ) prop_set = add_cmd('prop-set', _cmd_prop_set) prop_set.add_argument( 'path', - help='path of the emulator object', + help='Absolute path to the emulator object', ) prop_set.add_argument( 'property', - help='name of the object property', + help='Name of the object property', ) prop_set.add_argument( 'value', - help='value to set for the object property', + help='Value to set for the object property', ) gdb_cmds = add_cmd('gdb-cmds', _cmd_gdb_cmds) gdb_cmds.add_argument( '--pause', '-p', - help='do not resume execution after running the commands', + help='Do not resume execution after running the commands', action='store_true', ) gdb_cmds.add_argument( '--executable', '-e', metavar='FILE', - help='executable to use while running the gdb commands', + help='Executable to use while running ``gdb`` commands', ) gdb_cmds.add_argument( 'gdb_cmd', nargs='+', - help='gdb command to execute', + help='``gdb`` command to execute', ) term = add_cmd('term', _cmd_term) term.add_argument( 'channel', - help='channel name', + help='Channel name', ) resume = add_cmd('resume', _cmd_resume) @@ -435,7 +441,7 @@ def main() -> int: ) try: - emu = Emulator(args.working_dir, args.config) + emu = Emulator(Path(args.working_dir), args.config) args.func(emu, args) except Error as err: print(err) diff --git a/pw_emu/py/pw_emu/core.py b/pw_emu/py/pw_emu/core.py index 1e5c2ea9f..536fcedee 100644 --- a/pw_emu/py/pw_emu/core.py +++ b/pw_emu/py/pw_emu/core.py @@ -40,7 +40,7 @@ _LAUNCHER_LOG = logging.getLogger('pw_qemu.core.launcher') def _stop_process(pid: int) -> None: - """Gracefully stop a running process.""" + """Gracefully stops a running process.""" try: proc = psutil.Process(pid) @@ -132,7 +132,7 @@ class InvalidChannelType(Error): class WrongEmulator(Error): - """Exception raised if an different backend is running.""" + """Exception raised if a different backend is running.""" def __init__(self, exp: str, found: str) -> None: super().__init__(f'wrong emulator: expected `{exp}, found {found}`') @@ -143,6 +143,8 @@ class RunError(Error): def __init__(self, proc: str, msg: str) -> None: super().__init__(f'error running `{proc}`: {msg}') + self.proc = proc + self.msg = msg class InvalidPropertyPath(Error): @@ -160,7 +162,7 @@ class InvalidProperty(Error): class HandlesError(Error): - """Exception raised while trying to load emulator handles.""" + """Exception raised if the load of an emulator handle fails.""" def __init__(self, msg: str) -> None: super().__init__(f'error loading handles: {msg}') @@ -244,33 +246,33 @@ class Handles: self.procs: Dict[str, Handles.Proc] = {} def add_channel_tcp(self, name: str, host: str, port: int) -> None: - """Add a TCP channel.""" + """Adds a TCP channel.""" self.channels[name] = self.TcpChannel(host, port) def add_channel_pty(self, name: str, path: str) -> None: - """Add a pty channel.""" + """Adds a pty channel.""" self.channels[name] = self.PtyChannel(path) def add_proc(self, name: str, pid: int) -> None: - """Add a pid.""" + """Adds a process ID.""" self.procs[name] = self.Proc(pid) def set_target(self, target: str) -> None: - """Set the target.""" + """Sets the target.""" self.target = target def set_gdb_cmd(self, cmd: List[str]) -> None: - """Set the gdb command.""" + """Sets the ``gdb`` command.""" self.gdb_cmd = cmd.copy() def _stop_processes(handles: Handles, wdir: Path) -> None: - """Stop all processes for a (partially) running emulator instance. + """Stops all processes for a (partially) running emulator instance. Remove pid files as well. """ @@ -291,16 +293,16 @@ class Config: target: Optional[str] = None, emu: Optional[str] = None, ) -> None: - """Load the emulator configuration. + """Loads the emulator configuration. If no configuration file path is given, the root project configuration is used. - This method set ups the generic configuration (e.g. gdb). + This method set ups the generic configuration (e.g. ``gdb``). - It loads emulator target files and gathers them under the 'targets' key - for each emulator backend. The 'targets' settings in the configuration - file takes precedence over the loaded target files. + It loads emulator target files and gathers them under the ``targets`` + key for each emulator backend. The ``targets`` settings in the + configuration file takes precedence over the loaded target files. """ try: @@ -336,7 +338,8 @@ class Config: def set_target(self, target: str) -> None: """Sets the current target. - The current target is used by the get_target method. + The current target is used by the + :py:meth:`pw_emu.core.Config.get_target` method. """ @@ -383,19 +386,21 @@ class Config: optional: bool = True, entry_type: Optional[Type] = None, ) -> Any: - """Get a config entry. + """Gets a config entry. - keys is a list of string that identifies the config entry, e.g. - ['targets', 'test-target'] is going to look in the config dicionary for - ['targets']['test-target']. + ``keys`` identifies the config entry, e.g. + ``['targets', 'test-target']`` looks in the config dictionary for + ``['targets']['test-target']``. - If the option is not found and optional is True it returns None if - entry_type is none or a new (empty) object of type entry_type. + If the option is not found and optional is ``True`` it returns ``None`` + if ``entry_type`` is ``None`` or a new (empty) object of type + ``entry_type``. - If the option is not found an optional is False it raises ConfigError. + If the option is not found and ``optional`` is ``False`` it raises + ``ConfigError``. - If entry_type is not None it will check the option to be of - that type. If it is not it will raise ConfigError. + If ``entry_type`` is not ``None`` it checks the option to be of + that type. If it is not it will raise ``ConfigError``. """ @@ -435,7 +440,7 @@ class Config: optional: bool = True, entry_type: Optional[Type] = None, ) -> Any: - """Get a config option starting at ['targets'][target].""" + """Gets a config option starting at ``['targets'][target]``.""" if not self._target: raise Error('target not set') @@ -447,7 +452,7 @@ class Config: optional: bool = True, entry_type: Optional[Type] = None, ) -> Any: - """Get a config option starting at [emu].""" + """Gets a config option starting at ``[emu]``.""" if not self._emu: raise Error('emu not set') @@ -459,7 +464,7 @@ class Config: optional: bool = True, entry_type: Optional[Type] = None, ) -> Any: - """Get a config option starting at ['targets'][target][emu].""" + """Gets a config option starting at ``['targets'][target][emu]``.""" if not self._emu or not self._target: raise Error('emu or target not set') @@ -469,7 +474,7 @@ class Config: class Connector(ABC): - """Interface between a running emulator and the user visible APIs.""" + """Interface between a running emulator and the user-visible APIs.""" def __init__(self, wdir: Path) -> None: self._wdir = wdir @@ -479,7 +484,7 @@ class Connector(ABC): @staticmethod def get(wdir: Path) -> Any: - """Return a connector instace for a given emulator type.""" + """Returns a connector instance for a given emulator type.""" handles = Handles.load(wdir) config = Config(handles.config) emu = handles.emu @@ -496,7 +501,7 @@ class Connector(ABC): return self._handles.emu def get_gdb_cmd(self) -> List[str]: - """Returns the configured gdb command.""" + """Returns the configured ``gdb`` command.""" return self._handles.gdb_cmd def get_config_path(self) -> Path: @@ -519,8 +524,8 @@ class Connector(ABC): raise InvalidChannelName(name, self._target, channels) def get_channel_path(self, name: str) -> str: - """Returns the channel path. Raises InvalidChannelType if this - is not a pty channel. + """Returns the channel path. Raises ``InvalidChannelType`` if this + is not a ``pty`` channel. """ @@ -532,8 +537,8 @@ class Connector(ABC): raise InvalidChannelName(name, self._target, self._channels.keys()) def get_channel_addr(self, name: str) -> tuple: - """Returns a pair of (host, port) for the channel. Raises - InvalidChannelType if this is not a tcp channel. + """Returns a pair of ``(host, port)`` for the channel. Raises + ``InvalidChannelType`` if this is not a ``tcp`` channel. """ @@ -549,11 +554,11 @@ class Connector(ABC): name: str, timeout: Optional[float] = None, ) -> io.RawIOBase: - """Returns a file object for a given host exposed device. + """Returns a file object for a given host-exposed device. - If timeout is None than reads and writes are blocking. If - timeout is zero the stream is operating in non-blocking - mode. Otherwise read and write will timeout after the given + If ``timeout`` is ``None`` then reads and writes are blocking. If + ``timeout`` is ``0`` the stream is operating in non-blocking + mode. Otherwise reads and writes will timeout after the given value. """ @@ -579,8 +584,14 @@ class Connector(ABC): def get_channels(self) -> List[str]: return self._handles.channels.keys() + def get_logs(self) -> str: + """Returns the emulator logs.""" + + log_path = self._wdir / f'{self._handles.emu}.log' + return log_path.read_text() + def stop(self) -> None: - """Stop the emulator.""" + """Stops the emulator.""" _stop_processes(self._handles, self._wdir) @@ -596,7 +607,7 @@ class Connector(ABC): return False def running(self) -> bool: - """Check if the main emulator process is already running.""" + """Checks if the main emulator process is already running.""" try: return psutil.pid_exists(self._handles.procs[self._handles.emu].pid) @@ -605,11 +616,11 @@ class Connector(ABC): @abstractmethod def reset(self) -> None: - """Perform a software reset.""" + """Performs a software reset.""" @abstractmethod def cont(self) -> None: - """Resume the emulator's execution.""" + """Resumes the emulator's execution.""" @abstractmethod def list_properties(self, path: str) -> List[Any]: @@ -632,7 +643,7 @@ class Launcher(ABC): emu: str, config_path: Optional[Path] = None, ) -> None: - """Initializes a Launcher instance.""" + """Initializes a ``Launcher`` instance.""" self._wdir: Optional[Path] = None """Working directory""" @@ -673,7 +684,7 @@ class Launcher(ABC): debug: bool = False, args: Optional[str] = None, ) -> List[str]: - """Pre start work, returns command to start the emulator. + """Pre-start work, returns command to start the emulator. The target and emulator configuration can be accessed through :py:attr:`pw_emu.core.Launcher._config` with @@ -685,9 +696,9 @@ class Launcher(ABC): @abstractmethod def _post_start(self) -> None: - """Post start work, finalize emulator handles. + """Post-start work, finalize emulator handles. - Perform any post start emulator initialization and finalize the emulator + Perform any post-start emulator initialization and finalize the emulator handles information. Typically an internal monitor channel is used to inquire information @@ -700,7 +711,7 @@ class Launcher(ABC): @abstractmethod def _get_connector(self, wdir: Path) -> Connector: - """Get a connector for this emulator type.""" + """Gets a connector for this emulator type.""" def _path(self, name: Union[Path, str]) -> Path: """Returns the full path for a given emulator file.""" @@ -895,20 +906,20 @@ class Launcher(ABC): foreground: bool = False, args: Optional[str] = None, ) -> Connector: - """Start the emulator for the given target. + """Starts the emulator for the given target. - If file is set that the emulator will load the file before starting. + If ``file`` is set the emulator loads that file before starting. - If pause is True the emulator is paused. + If ``pause`` is ``True`` the emulator gets paused. - If debug is True the emulator is run in foreground with debug output - enabled. This is useful for seeing errors, traces, etc. + If ``debug`` is ``True`` the emulator runs in the foreground with + debug output enabled. This is useful for seeing errors, traces, etc. - If foreground is True the emulator is run in foreground otherwise it is - started in daemon mode. This is useful when there is another process - controlling the emulator's life cycle (e.g. cuttlefish) + If ``foreground`` is ``True`` the emulator is run in the foreground + otherwise it is started in daemon mode. This is useful when there is + another process controlling the emulator's life cycle, e.g. cuttlefish. - args are passed directly to the emulator + ``args`` are passed directly to the emulator. """ @@ -946,7 +957,15 @@ class Launcher(ABC): self._stop_procs() raise err - self._post_start() + try: + self._post_start() + except RunError as err: + self._handles.save(wdir) + connector = self._get_connector(self._wdir) + if not connector.running(): + msg = err.msg + '; dumping logs:\n' + connector.get_logs() + raise RunError(err.proc, msg) + raise err self._handles.save(wdir) if proc: diff --git a/pw_emu/py/pw_emu/frontend.py b/pw_emu/py/pw_emu/frontend.py index 8ecf7d3fe..e54355d90 100644 --- a/pw_emu/py/pw_emu/frontend.py +++ b/pw_emu/py/pw_emu/frontend.py @@ -74,22 +74,22 @@ class Emulator: foreground: bool = False, args: Optional[str] = None, ) -> None: - """Start the emulator for the given target. + """Starts the emulator for the given ``target``. - If file is set the emulator will load the file before starting. + If ``file`` is set the emulator loads the file before starting. - If pause is True the emulator is paused until the debugger is + If ``pause`` is ``True`` the emulator pauses until the debugger is connected. - If debug is True the emulator is run in foreground with debug + If ``debug`` is ``True`` the emulator runs in the foreground with debug output enabled. This is useful for seeing errors, traces, etc. - If foreground is True the emulator is run in foreground otherwise - it is started in daemon mode. This is useful when there is - another process controlling the emulator's life cycle - (e.g. cuttlefish) + If ``foreground`` is ``True`` the emulator runs in the foreground, + otherwise it starts in daemon mode. Foreground mode is useful when + there is another process controlling the emulator's life cycle, + e.g. cuttlefish. - args are passed directly to the emulator + ``args`` are passed directly to the emulator. """ if self._connector: @@ -115,7 +115,7 @@ class Emulator: return self._connector def running(self) -> bool: - """Check if the main emulator process is already running.""" + """Checks if the main emulator process is already running.""" try: return self._c().running() @@ -133,7 +133,7 @@ class Emulator: return self._c().stop() def get_gdb_remote(self) -> str: - """Return a string that can be passed to the target remote gdb + """Returns a string that can be passed to the target remote ``gdb`` command. """ @@ -150,7 +150,7 @@ class Emulator: raise InvalidChannelType(chan_type) def get_gdb_cmd(self) -> List[str]: - """Returns the gdb command for current target.""" + """Returns the ``gdb`` command for current target.""" return self._c().get_gdb_cmd() def run_gdb_cmds( @@ -159,14 +159,12 @@ class Emulator: executable: Optional[str] = None, pause: bool = False, ) -> subprocess.CompletedProcess: - """Connect to the target and run the given commands silently + """Connects to the target and runs the given commands silently in batch mode. - The executable is optional but it may be required by some gdb - commands. + ``executable`` is optional but may be required by some ``gdb`` commands. - If pause is set do not continue execution after running the - given commands. + If ``pause`` is set, execution stops after running the given commands. """ @@ -188,18 +186,18 @@ class Emulator: return subprocess.run(cmd, capture_output=True) def reset(self) -> None: - """Perform a software reset.""" + """Performs a software reset.""" self._c().reset() def list_properties(self, path: str) -> List[Dict]: """Returns the property list for an emulator object. - The object is identified by a full path. The path is target - specific and the format of the path is backend specific. + The object is identified by a full path. The path is + target-specific and the format of the path is backend-specific. - qemu path example: /machine/unattached/device[10] + QEMU path example: ``/machine/unattached/device[10]`` - renode path example: sysbus.uart + renode path example: ``sysbus.uart`` """ return self._c().list_properties(path) @@ -217,23 +215,23 @@ class Emulator: def get_channel_type(self, name: str) -> str: """Returns the channel type - Currently `pty` or `tcp` are the only supported types. + Currently ``pty`` and ``tcp`` are the only supported types. """ return self._c().get_channel_type(name) def get_channel_path(self, name: str) -> str: - """Returns the channel path. Raises InvalidChannelType if this - is not a pty channel. + """Returns the channel path. Raises ``InvalidChannelType`` if this + is not a ``pty`` channel. """ return self._c().get_channel_path(name) def get_channel_addr(self, name: str) -> tuple: - """Returns a pair of (host, port) for the channel. Raises - InvalidChannelType if this is not a tcp channel. + """Returns a pair of ``(host, port)`` for the channel. Raises + ``InvalidChannelType`` if this is not a TCP channel. """ @@ -244,10 +242,10 @@ class Emulator: name: str, timeout: Optional[float] = None, ) -> io.RawIOBase: - """Returns a file object for a given host exposed device. + """Returns a file object for a given host-exposed device. - If timeout is None than reads and writes are blocking. If - timeout is zero the stream is operating in non-blocking + If ``timeout`` is ``None`` than reads and writes are blocking. If + ``timeout`` is ``0`` the stream is operating in non-blocking mode. Otherwise read and write will timeout after the given value. @@ -261,12 +259,12 @@ class Emulator: return self._c().get_channels() def set_emu(self, emu: str) -> None: - """Set the emulator type for this instance.""" + """Sets the emulator type for this instance.""" self._launcher = Launcher.get(emu, self._config_path) def cont(self) -> None: - """Resume the emulator's execution.""" + """Resumes the emulator's execution.""" self._c().cont() @@ -276,11 +274,10 @@ class TemporaryEmulator(Emulator): Manages emulator instances that run in temporary working directories. The emulator instance is stopped and the working - directory is cleared when the with block completes. + directory is cleared when the ``with`` block completes. - It also supports interoperability with the pw emu cli, i.e. - starting the emulator with the CLI and controlling / interacting - with it from the API. + It also supports interoperability with the ``pw_emu`` cli, e.g. starting the + emulator with the CLI and then controlling it from the Python API. Usage example: diff --git a/pw_emu/py/pw_emu/qemu.py b/pw_emu/py/pw_emu/qemu.py index 50112cda3..5d6ece5d6 100644 --- a/pw_emu/py/pw_emu/qemu.py +++ b/pw_emu/py/pw_emu/qemu.py @@ -31,6 +31,7 @@ from pw_emu.core import ( Launcher, Error, InvalidChannelType, + RunError, WrongEmulator, ) @@ -50,12 +51,17 @@ class QmpClient: def __init__(self, stream: io.RawIOBase): self._stream = stream - json.loads(self._stream.readline()) - cmd = json.dumps({'execute': 'qmp_capabilities'}) - self._stream.write(cmd.encode('utf-8')) - resp = json.loads(self._stream.readline().decode('ascii')) - if not 'return' in resp: - raise QmpError(f'qmp init failed: {resp.get("error")}') + # Perform the QMP "capabilities negotiation" handshake. + # https://wiki.qemu.org/Documentation/QMP#Capabilities_Negotiation + # + # When the QMP connection is established, QEMU first sends a greeting + # message with its version and capabilities. Then the client sends + # 'qmp_capabilities' to exit capabilities negotiation mode. The result + # is an empty 'return'. + # + # self.request() will consume both the initial greeting and the + # subsequent 'return' response. + self.request('qmp_capabilities') def request(self, cmd: str, args: Optional[Dict[str, Any]] = None) -> Any: """Issue a command using the qmp interface. @@ -285,9 +291,15 @@ class QemuLauncher(Launcher): def _post_start(self) -> None: assert self._qmp_init_sock is not None - conn, _ = self._qmp_init_sock.accept() + try: + conn, _ = self._qmp_init_sock.accept() + except (KeyboardInterrupt, socket.timeout): + raise RunError('qemu', 'qmp connection failed') self._qmp_init_sock.close() - qmp = QmpClient(conn.makefile('rwb', buffering=0)) + try: + qmp = QmpClient(conn.makefile('rwb', buffering=0)) + except json.decoder.JSONDecodeError: + raise RunError('qemu', 'qmp handshake failed') conn.close() resp = qmp.request('query-chardev') diff --git a/pw_emu/py/pw_emu/renode.py b/pw_emu/py/pw_emu/renode.py index 1e3ccb548..75113246b 100644 --- a/pw_emu/py/pw_emu/renode.py +++ b/pw_emu/py/pw_emu/renode.py @@ -22,10 +22,11 @@ from typing import Optional, List, Any from pw_emu.core import ( Connector, + Error, Handles, InvalidChannelType, Launcher, - Error, + RunError, WrongEmulator, ) @@ -155,7 +156,7 @@ class RenodeLauncher(Launcher): if not connected: msg = 'failed to connect to robot channel' msg += f'({robot.host}:{robot.port}): {err}' - raise RenodeRobotError(msg) + raise RunError('renode', msg) sock.close() |