Files
MeshCore/test/test_ethernet_hil_serial_open.py
T

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()