From f3139a8aa0fd51c8282d60b3ba132aa439a3643c Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 12 Jan 2022 15:50:16 -0800 Subject: [PATCH 1/3] add more unit tests --- meshtastic/__init__.py | 33 ++++++++++++------- meshtastic/tests/test_init.py | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 meshtastic/tests/test_init.py diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index 98bc9dc..19f669e 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -132,6 +132,7 @@ def _onTextReceive(iface, asDict): # # Usually btw this problem is caused by apps sending binary data but setting the payload type to # text. + logging.debug(f'in _onTextReceive() asDict:{asDict}') try: asBytes = asDict["decoded"]["payload"] asDict["decoded"]["text"] = asBytes.decode("utf-8") @@ -142,22 +143,30 @@ def _onTextReceive(iface, asDict): def _onPositionReceive(iface, asDict): """Special auto parsing for received messages""" - p = asDict["decoded"]["position"] - iface._fixupPosition(p) - # update node DB as needed - iface._getOrCreateByNum(asDict["from"])["position"] = p + logging.debug(f'in _onPositionReceive() asDict:{asDict}') + if 'decoded' in asDict: + if 'position' in asDict['decoded'] and 'from' in asDict: + p = asDict["decoded"]["position"] + logging.debug(f'p:{p}') + p = iface._fixupPosition(p) + logging.debug(f'after fixup p:{p}') + # update node DB as needed + iface._getOrCreateByNum(asDict["from"])["position"] = p def _onNodeInfoReceive(iface, asDict): """Special auto parsing for received messages""" - p = asDict["decoded"]["user"] - # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg - # update node DB as needed - n = iface._getOrCreateByNum(asDict["from"]) - n["user"] = p - # We now have a node ID, make sure it is uptodate in that table - iface.nodes[p["id"]] = n - _receiveInfoUpdate(iface, asDict) + logging.debug(f'in _onNodeInfoReceive() asDict:{asDict}') + if 'decoded' in asDict: + if 'user' in asDict['decoded'] and 'from' in asDict: + p = asDict["decoded"]["user"] + # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg + # update node DB as needed + n = iface._getOrCreateByNum(asDict["from"]) + n["user"] = p + # We now have a node ID, make sure it is uptodate in that table + iface.nodes[p["id"]] = n + _receiveInfoUpdate(iface, asDict) def _receiveInfoUpdate(iface, asDict): diff --git a/meshtastic/tests/test_init.py b/meshtastic/tests/test_init.py new file mode 100644 index 0000000..75c9609 --- /dev/null +++ b/meshtastic/tests/test_init.py @@ -0,0 +1,61 @@ +"""Meshtastic unit tests for __init__.py""" + +import re +import logging + +from unittest.mock import MagicMock +import pytest + +from meshtastic.__init__ import _onTextReceive, _onPositionReceive, _onNodeInfoReceive +from ..serial_interface import SerialInterface +from ..globals import Globals + + +@pytest.mark.unit +def test_init_onTextReceive_with_exception(caplog): + """Test _onTextReceive""" + args = MagicMock() + Globals.getInstance().set_args(args) + iface = MagicMock(autospec=SerialInterface) + packet = {} + with caplog.at_level(logging.DEBUG): + _onTextReceive(iface, packet) + assert re.search(r'in _onTextReceive', caplog.text, re.MULTILINE) + assert re.search(r'Malformatted', caplog.text, re.MULTILINE) + + +@pytest.mark.unit +def test_init_onPositionReceive(caplog): + """Test _onPositionReceive""" + args = MagicMock() + Globals.getInstance().set_args(args) + iface = MagicMock(autospec=SerialInterface) + packet = { + 'from': 'foo', + 'decoded': { + 'position': {} + } + } + with caplog.at_level(logging.DEBUG): + _onPositionReceive(iface, packet) + assert re.search(r'in _onPositionReceive', caplog.text, re.MULTILINE) + + +@pytest.mark.unit +def test_init_onNodeInfoReceive(caplog, iface_with_nodes): + """Test _onNodeInfoReceive""" + args = MagicMock() + Globals.getInstance().set_args(args) + iface = iface_with_nodes + iface.myInfo.my_node_num = 2475227164 + packet = { + 'from': 'foo', + 'decoded': { + 'user': { + 'id': 'bar', + }, + } + } + with caplog.at_level(logging.DEBUG): + _onNodeInfoReceive(iface, packet) + assert re.search(r'in _onNodeInfoReceive', caplog.text, re.MULTILINE) From 48265e73b1e3ce33208627053ca6201c4dae6d56 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 12 Jan 2022 15:54:07 -0800 Subject: [PATCH 2/3] no need to import pygatt here --- meshtastic/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index 19f669e..9bd4a72 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -82,9 +82,6 @@ from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2, environmental_measurement_pb2, remote_hardware_pb2, channel_pb2, radioconfig_pb2, util) -if platform.system() == 'Linux': - # pylint: disable=E0401 - import pygatt # Note: To follow PEP224, comments should be after the module variable. From ad8f2222db8980b01fef267f537f06c436946c9c Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 12 Jan 2022 16:50:29 -0800 Subject: [PATCH 3/3] cover a few more lines --- meshtastic/stream_interface.py | 6 ++---- meshtastic/tcp_interface.py | 8 +++++++- meshtastic/tests/test_tcp_interface.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index 0db74e3..335ef5a 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -62,8 +62,7 @@ class StreamInterface(MeshInterface): # because we want to ensure it is looking for START1) p = bytearray([START2] * 32) self._writeBytes(p) - if not self.noProto: - time.sleep(0.1) # wait 100ms to give device time to start running + time.sleep(0.1) # wait 100ms to give device time to start running self._rxThread.start() @@ -90,8 +89,7 @@ class StreamInterface(MeshInterface): self.stream.write(b) self.stream.flush() # we sleep here to give the TBeam a chance to work - if not self.noProto: - time.sleep(0.1) + time.sleep(0.1) def _readBytes(self, length): """Read an array of bytes from our stream""" diff --git a/meshtastic/tcp_interface.py b/meshtastic/tcp_interface.py index 37e34bb..eb26287 100644 --- a/meshtastic/tcp_interface.py +++ b/meshtastic/tcp_interface.py @@ -33,6 +33,12 @@ class TCPInterface(StreamInterface): StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) + def _socket_shutdown(self): + """Shutdown the socket. + Note: Broke out this line so the exception could be unit tested. + """ + self.socket.shutdown(socket.SHUT_RDWR) + def myConnect(self): """Connect to socket""" server_address = (self.hostname, self.portNumber) @@ -48,7 +54,7 @@ class TCPInterface(StreamInterface): self._wantExit = True if not self.socket is None: try: - self.socket.shutdown(socket.SHUT_RDWR) + self._socket_shutdown() except: pass # Ignore errors in shutdown, because we might have a race with the server self.socket.close() diff --git a/meshtastic/tests/test_tcp_interface.py b/meshtastic/tests/test_tcp_interface.py index 574dfd1..6d49359 100644 --- a/meshtastic/tests/test_tcp_interface.py +++ b/meshtastic/tests/test_tcp_interface.py @@ -27,6 +27,23 @@ def test_TCPInterface(capsys): iface.close() +@pytest.mark.unit +def test_TCPInterface_exception(): + """Test that we can instantiate a TCPInterface""" + + def throw_an_exception(): + raise ValueError("Fake exception.") + + with patch('meshtastic.tcp_interface.TCPInterface._socket_shutdown') as mock_shutdown: + mock_shutdown.side_effect = throw_an_exception + with patch('socket.socket') as mock_socket: + iface = TCPInterface(hostname='localhost', noProto=True) + iface.myConnect() + iface.close() + assert mock_socket.called + assert mock_shutdown.called + + @pytest.mark.unit def test_TCPInterface_without_connecting(): """Test that we can instantiate a TCPInterface with connectNow as false"""