You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add Renode to CI workflows — install steps added for Linux (.deb) and macOS (brew)
Convert ADR to JEP — replaced with docs/internal/jeps/JEP-0010-renode-integration.md
[LOW] flash() deferred loading undocumented — docstring now explains store-for-later vs hot-load behavior
MEDIUM — Still open
close() leaks SocketStream (driver.py, @raballew) close() sets self._monitor = None without closing the underlying TCP socket. off() properly calls disconnect(), but close() is synchronous and drops the reference. Restructure so shared cleanup lives in a single helper, or do a best-effort sync close of the socket.
Private attribute traversal across sibling drivers (driver.py:96, @raballew) RenodeFlasher.flash() navigates self.parent.children["power"]._process and ._monitor directly. Same pattern in Renode.monitor_cmd(). Fix: add a public method to RenodePower (e.g., send_monitor_command()) that encapsulates monitor interaction.
Shared mutable parent state for firmware loading (driver.py:89, @raballew) RenodeFlasher writes self.parent._firmware_path / _load_command, RenodePower.on() reads them. Implicit contract with no interface enforcement. Fix: make firmware loading explicit — have RenodePower.on() accept firmware parameters, or coordinate through a method on Renode.
Test gaps — error/cleanup paths (driver_test.py, @raballew)
Missing tests for:
on() fails mid-startup → verify process cleanup (the except Exception: await self.off(); raise path)
connect() with persistent OSError → verify TimeoutError from fail_after
_read_until_prompt with empty receive() → verify ConnectionError
Idempotency tests lack assertions (driver_test.py, @raballew) test_power_on_idempotent and test_power_off_idempotent have zero assertions — they only verify "doesn't crash." Patch Popen/RenodeMonitor and assert they were NOT called.
LOW — Still open
Dead hasattr guard (driver.py:91, @raballew) hasattr(self.parent.children["power"], "_process") is always True (dataclass field with default=None). Replace with power._process is not None and power._monitor is not None.
off() inconsistent state on terminate() failure (driver.py:189, @raballew)
If terminate() raises (e.g., ProcessLookupError on already-exited process), self._process = None is never reached. Wrap in try/finally.
TOCTOU race in _find_free_port() (driver.py:47, @raballew)
The ephemeral port is discovered by binding and immediately releasing a socket. A local process could race to bind the same port before Renode does. The QEMU driver avoids this with Unix domain sockets. Consider switching if Renode supports it.
on() method too long (driver.py:167, @raballew)
~53 lines handling multiple responsibilities. Extract monitor setup into a private method like _configure_simulation().
Decorative separator comments in tests (driver_test.py:28, @raballew)
Six # 5.1 RenodeMonitor unit tests style separators reference internal plan task numbers and restate what the class name already says. Remove them.
Minor test coverage gaps (driver_test.py, @raballew)
Missing tests for: (1) _find_renode() when binary not on PATH; (2) RenodePower.read()NotImplementedError; (3) monitor_cmd() success path with allow_raw_monitor=True; (4) close() timeout-kill branch where wait() raises TimeoutExpired.
Summary
Follow-up items from the review of PR #533 (Add Renode emulator driver) to be addressed in a separate PR.
Already addressed in PR #533
The following items from @raballew and @mangelajo were resolved in the PR itself:
load_command— validated against_ALLOWED_LOAD_COMMANDSallowlistmonitor_cmdpassthrough — gated behindallow_raw_monitorconfig flag (defaults tofalse)connect()execute()has no timeout — wrapped infail_after(timeout)with 30s defaultexecute()newline injection — rejects\n/\rwithValueErrorpython/pyproject.tomljumpstarter-all— added to dependenciesflash()defaults toLoadELFfor all files — ELF magic byte detection added (_detect_load_command())_is_promptfalse positives — constrained to_expected_promptssetexecute()newline injection — rejects newline characters.deb) and macOS (brew)docs/internal/jeps/JEP-0010-renode-integration.mdflash()deferred loading undocumented — docstring now explains store-for-later vs hot-load behaviorMEDIUM — Still open
close()leaks SocketStream (driver.py, @raballew)close()setsself._monitor = Nonewithout closing the underlying TCP socket.off()properly callsdisconnect(), butclose()is synchronous and drops the reference. Restructure so shared cleanup lives in a single helper, or do a best-effort sync close of the socket.Private attribute traversal across sibling drivers (
driver.py:96, @raballew)RenodeFlasher.flash()navigatesself.parent.children["power"]._processand._monitordirectly. Same pattern inRenode.monitor_cmd().Fix: add a public method to
RenodePower(e.g.,send_monitor_command()) that encapsulates monitor interaction.Shared mutable parent state for firmware loading (
driver.py:89, @raballew)RenodeFlasherwritesself.parent._firmware_path/_load_command,RenodePower.on()reads them. Implicit contract with no interface enforcement.Fix: make firmware loading explicit — have
RenodePower.on()accept firmware parameters, or coordinate through a method onRenode.Test gaps — error/cleanup paths (
driver_test.py, @raballew)Missing tests for:
on()fails mid-startup → verify process cleanup (theexcept Exception: await self.off(); raisepath)connect()with persistentOSError→ verifyTimeoutErrorfromfail_after_read_until_promptwith emptyreceive()→ verifyConnectionErrorIdempotency tests lack assertions (
driver_test.py, @raballew)test_power_on_idempotentandtest_power_off_idempotenthave zero assertions — they only verify "doesn't crash." PatchPopen/RenodeMonitorand assert they were NOT called.LOW — Still open
Dead
hasattrguard (driver.py:91, @raballew)hasattr(self.parent.children["power"], "_process")is alwaysTrue(dataclass field withdefault=None). Replace withpower._process is not None and power._monitor is not None.off()inconsistent state onterminate()failure (driver.py:189, @raballew)If
terminate()raises (e.g.,ProcessLookupErroron already-exited process),self._process = Noneis never reached. Wrap intry/finally.TOCTOU race in
_find_free_port()(driver.py:47, @raballew)The ephemeral port is discovered by binding and immediately releasing a socket. A local process could race to bind the same port before Renode does. The QEMU driver avoids this with Unix domain sockets. Consider switching if Renode supports it.
on()method too long (driver.py:167, @raballew)~53 lines handling multiple responsibilities. Extract monitor setup into a private method like
_configure_simulation().Decorative separator comments in tests (
driver_test.py:28, @raballew)Six
# 5.1 RenodeMonitor unit testsstyle separators reference internal plan task numbers and restate what the class name already says. Remove them.Minor test coverage gaps (
driver_test.py, @raballew)Missing tests for: (1)
_find_renode()when binary not on PATH; (2)RenodePower.read()NotImplementedError; (3)monitor_cmd()success path withallow_raw_monitor=True; (4)close()timeout-kill branch wherewait()raisesTimeoutExpired.Source PR: #533
Reviewers: @raballew @mangelajo