From a243fab9af2d879cc37653aa7af31d43ed9df684 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Sat, 25 Dec 2021 11:39:04 -0800 Subject: [PATCH] figured out how to unit test __reader() --- meshtastic/stream_interface.py | 24 +++++++------ meshtastic/tcp_interface.py | 27 ++++++++++----- meshtastic/tests/conftest.py | 1 + meshtastic/tests/test_stream_interface.py | 41 +++++++++++++++++++++-- 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index 3d4ee76..a1658f2 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -24,7 +24,6 @@ class StreamInterface(MeshInterface): """Constructor, opens a connection to self.stream Keyword Arguments: - devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None}) debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None}) @@ -33,15 +32,16 @@ class StreamInterface(MeshInterface): Exception: [description] """ - if not hasattr(self, 'stream'): + if not hasattr(self, 'stream') and not noProto: raise Exception( "StreamInterface is now abstract (to update existing code create SerialInterface instead)") self._rxBuf = bytes() # empty self._wantExit = False + self.stream = None + # FIXME, figure out why daemon=True causes reader thread to exit too early - self._rxThread = threading.Thread( - target=self.__reader, args=(), daemon=True) + self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True) MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto) @@ -93,7 +93,10 @@ class StreamInterface(MeshInterface): def _readBytes(self, length): """Read an array of bytes from our stream""" - return self.stream.read(length) + if self.stream: + return self.stream.read(length) + else: + return None def _sendToRadioImpl(self, toRadio): """Send a ToRadio protobuf to the device""" @@ -116,14 +119,15 @@ class StreamInterface(MeshInterface): def __reader(self): """The reader thread that reads bytes from our stream""" + logging.debug('in __reader()') empty = bytes() try: while not self._wantExit: - # logging.debug("reading character") + logging.debug("reading character") b = self._readBytes(1) - # logging.debug("In reader loop") - # logging.debug(f"read returned {b}") + logging.debug("In reader loop") + logging.debug(f"read returned {b}") if len(b) > 0: c = b[0] ptr = len(self._rxBuf) @@ -144,12 +148,12 @@ class StreamInterface(MeshInterface): if c != START2: self._rxBuf = empty # failed to find start2 elif ptr >= HEADER_LEN - 1: # we've at least got a header - # big endian length follos header + # big endian length follows header packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3] if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length if packetlen > MAX_TO_FROM_RADIO_SIZE: - self._rxBuf = empty # length ws out out bounds, restart + self._rxBuf = empty # length was out out bounds, restart if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN: try: diff --git a/meshtastic/tcp_interface.py b/meshtastic/tcp_interface.py index a892448..25e5ab8 100644 --- a/meshtastic/tcp_interface.py +++ b/meshtastic/tcp_interface.py @@ -17,18 +17,29 @@ class TCPInterface(StreamInterface): hostname {string} -- Hostname/IP address of the device to connect to """ - logging.debug(f"Connecting to {hostname}") - - server_address = (hostname, portNumber) - sock = socket.create_connection(server_address) - # Instead of wrapping as a stream, we use the native socket API # self.stream = sock.makefile('rw') self.stream = None - self.socket = sock - StreamInterface.__init__( - self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) + self.hostname = hostname + self.portNumber = portNumber + + if connectNow: + logging.debug(f"Connecting to {hostname}") + server_address = (hostname, portNumber) + sock = socket.create_connection(server_address) + self.socket = sock + else: + self.socket = None + + StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, + connectNow=connectNow) + + def myConnect(self): + """Connect to socket""" + server_address = (self.hostname, self.portNumber) + sock = socket.create_connection(server_address) + self.socket = sock def close(self): """Close a connection to the device""" diff --git a/meshtastic/tests/conftest.py b/meshtastic/tests/conftest.py index 12c566c..56df27c 100644 --- a/meshtastic/tests/conftest.py +++ b/meshtastic/tests/conftest.py @@ -8,6 +8,7 @@ import pytest from meshtastic.__main__ import Globals from ..mesh_interface import MeshInterface + @pytest.fixture def reset_globals(): """Fixture to reset globals.""" diff --git a/meshtastic/tests/test_stream_interface.py b/meshtastic/tests/test_stream_interface.py index a3e400c..57eb16f 100644 --- a/meshtastic/tests/test_stream_interface.py +++ b/meshtastic/tests/test_stream_interface.py @@ -1,6 +1,9 @@ """Meshtastic unit tests for stream_interface.py""" +import logging +import re +from unittest.mock import MagicMock import pytest from ..stream_interface import StreamInterface @@ -8,7 +11,41 @@ from ..stream_interface import StreamInterface @pytest.mark.unit def test_StreamInterface(): - """Test that we cannot instantiate a StreamInterface""" + """Test that we cannot instantiate a StreamInterface based on noProto""" with pytest.raises(Exception) as pytest_wrapped_e: - StreamInterface(noProto=True) + StreamInterface() assert pytest_wrapped_e.type == Exception + + +@pytest.mark.unit +def test_StreamInterface_with_noProto(caplog, reset_globals): + """Test that we can instantiate a StreamInterface based on nonProto + and we can read/write bytes from a mocked stream + """ + stream = MagicMock() + test_data = b'hello' + stream.read.return_value = test_data + with caplog.at_level(logging.DEBUG): + iface = StreamInterface(noProto=True) + iface.stream = stream + iface._writeBytes(test_data) + data = iface._readBytes(len(test_data)) + assert data == test_data + + +@pytest.mark.unit +def test_sendToRadioImpl(caplog, reset_globals): + """Test _sendToRadioImpl()""" + test_data = b'hello' + stream = MagicMock() + stream.read.return_value = test_data + toRadio = MagicMock() + toRadio.SerializeToString.return_value = test_data + with caplog.at_level(logging.DEBUG): + iface = StreamInterface(noProto=True, connectNow=False) + iface.stream = stream + iface.connect() + iface._sendToRadioImpl(toRadio) + assert re.search(r'Sending: ', caplog.text, re.MULTILINE) + assert re.search(r'reading character', caplog.text, re.MULTILINE) + assert re.search(r'In reader loop', caplog.text, re.MULTILINE)