141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
"""Actual HIL constructors must not pulse CH340 reset on POSIX serial open."""
|
|
|
|
import contextlib
|
|
import importlib.util
|
|
import io
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
from types import ModuleType, SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
BASE = Path(__file__).resolve().parents[1] / "tools/hil"
|
|
sys.path.insert(0, str(BASE))
|
|
import ethernet_kiss_probe as probe
|
|
import ethernet_tap_relay as tap
|
|
|
|
|
|
class FakeSerial:
|
|
def __init__(self, **kwargs):
|
|
self.dsrdtr = False
|
|
self._dtr = True
|
|
self._rts = True
|
|
self.driver_rts = True
|
|
self.driver_dtr = True
|
|
self.is_open = False
|
|
self.resets = 0
|
|
self.writes = []
|
|
self.trace = []
|
|
|
|
@property
|
|
def dtr(self):
|
|
return self._dtr
|
|
|
|
@dtr.setter
|
|
def dtr(self, value):
|
|
self._dtr = value
|
|
if self.is_open:
|
|
self._apply_dtr(value)
|
|
|
|
@property
|
|
def rts(self):
|
|
return self._rts
|
|
|
|
@rts.setter
|
|
def rts(self, value):
|
|
self._rts = value
|
|
if self.is_open:
|
|
self.driver_rts = value
|
|
self.trace.append(("rts", value))
|
|
|
|
def _apply_dtr(self, value):
|
|
if not value and self.driver_rts:
|
|
self.resets += 1
|
|
self.driver_dtr = value
|
|
self.trace.append(("dtr", value))
|
|
|
|
def open(self):
|
|
self.is_open = True
|
|
# POSIX pySerial's real open order: DTR before RTS unless deferred.
|
|
if not self.dsrdtr:
|
|
self._apply_dtr(self._dtr)
|
|
self.rts = self._rts
|
|
|
|
def close(self):
|
|
self.is_open = False
|
|
|
|
def write(self, data):
|
|
self.writes.append(data)
|
|
return len(data)
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
def fileno(self):
|
|
return 77
|
|
|
|
|
|
def serial_modules(port):
|
|
serial = ModuleType("serial")
|
|
serial.Serial = Mock(return_value=port)
|
|
tools = ModuleType("serial.tools")
|
|
list_ports = ModuleType("serial.tools.list_ports")
|
|
info = SimpleNamespace(device="/dev/ttyUSB-bench", serial_number="bench-only",
|
|
vid=0x1A86, pid=0x7523)
|
|
list_ports.comports = Mock(return_value=[info])
|
|
tools.list_ports = list_ports
|
|
serial.tools = tools
|
|
return {"serial": serial, "serial.tools": tools, "serial.tools.list_ports": list_ports}
|
|
|
|
|
|
@unittest.skipUnless(os.name == "posix", "POSIX CH340 pin-order behavior")
|
|
class EthernetHilSerialTests(unittest.TestCase):
|
|
def assert_safe(self, port):
|
|
self.assertEqual(port.resets, 0)
|
|
self.assertEqual(port.trace[0], ("rts", False))
|
|
self.assertFalse(port.dsrdtr)
|
|
self.assertFalse(port.driver_rts)
|
|
self.assertFalse(port.driver_dtr)
|
|
|
|
def test_reset_simulator_detects_original_plain_open_bug(self):
|
|
port = FakeSerial()
|
|
port.dtr = port.rts = False
|
|
port.open()
|
|
self.assertEqual(port.resets, 1)
|
|
|
|
def test_raw_kiss_probe_uses_shared_safe_opener(self):
|
|
port = FakeSerial()
|
|
with patch.dict(sys.modules, serial_modules(port)), patch.object(probe.threading, "Thread"):
|
|
endpoint = probe.KissEndpoint("serial:///dev/ttyUSB-bench")
|
|
endpoint.close()
|
|
self.assert_safe(port)
|
|
|
|
def test_tap_serial_transport_uses_shared_safe_opener(self):
|
|
port = FakeSerial()
|
|
with patch.dict(sys.modules, serial_modules(port)):
|
|
endpoint = tap.KissStream("serial:///dev/ttyUSB-bench", 115200)
|
|
endpoint.close()
|
|
self.assert_safe(port)
|
|
|
|
def test_companion_cli_uses_shared_safe_opener(self):
|
|
port = FakeSerial()
|
|
spec = importlib.util.spec_from_file_location("ethernet_bench_serial_under_test", BASE / "ethernet_bench_serial.py")
|
|
module = importlib.util.module_from_spec(spec)
|
|
with patch.dict(sys.modules, serial_modules(port)):
|
|
spec.loader.exec_module(module)
|
|
with patch.object(sys, "argv", ["ethernet_bench_serial.py", "--port", "/dev/ttyUSB-bench",
|
|
"--command", "get ethernet"]), \
|
|
patch.object(module, "read_quiet", return_value="OK"), \
|
|
patch.object(module, "read_command_reply", return_value="get ethernet\r\n -> on\r\n> "), \
|
|
contextlib.redirect_stdout(io.StringIO()):
|
|
module.main()
|
|
self.assert_safe(port)
|
|
self.assertIn(b"get ethernet\n", port.writes)
|
|
self.assertFalse(port.is_open)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|