* Sonar qube 6 (#174)

* test class ModbusConn

* Sonar qube 3 (#178)

* add more unit tests

* GEN3: don't crash on overwritten msg in the receive buffer

* improve test coverage und reduce test delays

* reduce cognitive complexity
This commit is contained in:
Stefan Allius
2024-09-03 18:58:24 +02:00
committed by GitHub
parent 627ca97360
commit a9dc7e6847
9 changed files with 334 additions and 122 deletions

View File

@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased] ## [unreleased]
- GEN3: don't crash on overwritten msg in the receive buffer
- Reading the version string from the image updates it even if the image is re-pulled without re-deployment - Reading the version string from the image updates it even if the image is re-pulled without re-deployment
## [0.10.1] - 2024-08-10 ## [0.10.1] - 2024-08-10

View File

@@ -147,6 +147,7 @@ class AsyncStream():
logger.error( logger.error(
f"Exception for {self.addr}:\n" f"Exception for {self.addr}:\n"
f"{traceback.format_exc()}") f"{traceback.format_exc()}")
await asyncio.sleep(0) # be cooperative to other task
async def async_write(self, headline: str = 'Transmit to ') -> None: async def async_write(self, headline: str = 'Transmit to ') -> None:
"""Async write handler to transmit the send_buffer""" """Async write handler to transmit the send_buffer"""

View File

@@ -25,10 +25,10 @@ class ConnectionG3(AsyncStream, Talent):
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}') # logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
async def async_create_remote(self) -> None: async def async_create_remote(self) -> None:
pass # virtual interface pass # virtual interface # pragma: no cover
async def async_publ_mqtt(self) -> None: async def async_publ_mqtt(self) -> None:
pass # virtual interface pass # virtual interface # pragma: no cover
def healthy(self) -> bool: def healthy(self) -> bool:
logger.debug('ConnectionG3 healthy()') logger.debug('ConnectionG3 healthy()')

View File

@@ -294,6 +294,13 @@ class Talent(Message):
result = struct.unpack_from('!lB', buf, 0) result = struct.unpack_from('!lB', buf, 0)
msg_len = result[0] # len of complete message msg_len = result[0] # len of complete message
id_len = result[1] # len of variable id string id_len = result[1] # len of variable id string
if id_len > 17:
logger.warning(f'len of ID string must == 16 but is {id_len}')
self.inc_counter('Invalid_Msg_Format')
# erase broken recv buffer
self._recv_buffer = bytearray()
return
hdr_len = 5+id_len+2 hdr_len = 5+id_len+2

View File

@@ -31,10 +31,10 @@ class ConnectionG3P(AsyncStream, SolarmanV5):
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}') # logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
async def async_create_remote(self) -> None: async def async_create_remote(self) -> None:
pass # virtual interface pass # virtual interface # pragma: no cover
async def async_publ_mqtt(self) -> None: async def async_publ_mqtt(self) -> None:
pass # virtual interface pass # virtual interface # pragma: no cover
def healthy(self) -> bool: def healthy(self) -> bool:
logger.debug('ConnectionG3P healthy()') logger.debug('ConnectionG3P healthy()')

View File

@@ -427,100 +427,120 @@ class Infos:
return None return None
elif singleton: elif singleton:
return None return None
prfx = ha_prfx + node_id
# check if we have details for home assistant # check if we have details for home assistant
if 'ha' in row: if 'ha' in row:
ha = row['ha'] return self.__ha_conf(row, key, ha_prfx, node_id, snr, sug_area)
if 'comp' in ha:
component = ha['comp']
else:
component = 'sensor'
attr = {}
if 'name' in ha:
attr['name'] = ha['name']
else:
attr['name'] = row['name'][-1]
attr['stat_t'] = prfx + row['name'][0]
attr['dev_cla'] = ha['dev_cla']
attr['stat_cla'] = ha['stat_cla']
attr['uniq_id'] = ha['id']+snr
if 'val_tpl' in ha:
attr['val_tpl'] = ha['val_tpl']
elif 'fmt' in ha:
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for {key} do"
" not have a 'val_tpl' nor a 'fmt' value")
# add unit_of_meas only, if status_class isn't none. If
# status_cla is None we want a number format and not line
# graph in home assistant. A unit will change the number
# format to a line graph
if 'unit' in row and attr['stat_cla'] is not None:
attr['unit_of_meas'] = row['unit'] # 'unit_of_meas'
if 'icon' in ha:
attr['ic'] = ha['icon'] # icon for the entity
if 'nat_prc' in ha: # pragma: no cover
attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats
if 'ent_cat' in ha:
attr['ent_cat'] = ha['ent_cat'] # diagnostic, config
# enabled_by_default is deactivated, since it avoid the via
# setup of the devices. It seems, that there is a bug in home
# assistant. tested with 'Home Assistant 2023.10.4'
# if 'en' in ha: # enabled_by_default
# attr['en'] = ha['en']
if 'dev' in ha:
device = self.info_devs[ha['dev']]
if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501
return None
dev = {}
# the same name for 'name' and 'suggested area', so we get
# dedicated devices in home assistant with short value
# name and headline
if (sug_area == '' or
('singleton' in device and device['singleton'])):
dev['name'] = device['name']
dev['sa'] = device['name']
else:
dev['name'] = device['name']+' - '+sug_area
dev['sa'] = device['name']+' - '+sug_area
if 'via' in device: # add the link to the parent device
via = device['via']
if via in self.info_devs:
via_dev = self.info_devs[via]
if 'singleton' in via_dev and via_dev['singleton']:
dev['via_device'] = via
else:
dev['via_device'] = f"{via}_{snr}"
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for "
f"{key} has an invalid via value: "
f"{via}")
for key in ('mdl', 'mf', 'sw', 'hw'): # add optional
# values fpr 'modell', 'manufacturer', 'sw version' and
# 'hw version'
if key in device:
data = self.dev_value(device[key])
if data is not None:
dev[key] = data
if 'singleton' in device and device['singleton']:
dev['ids'] = [f"{ha['dev']}"]
else:
dev['ids'] = [f"{ha['dev']}_{snr}"]
attr['dev'] = dev
origin = {}
origin['name'] = self.app_name
origin['sw'] = self.version
attr['o'] = origin
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for {key} "
"missing 'dev' value for ha register")
return json.dumps(attr), component, node_id, attr['uniq_id']
return None return None
def __ha_conf(self, row, key, ha_prfx, node_id, snr,
sug_area: str) -> tuple[str, str, str, str] | None:
ha = row['ha']
if 'comp' in ha:
component = ha['comp']
else:
component = 'sensor'
attr = self.__build_attr(row, key, ha_prfx, node_id, snr)
if 'dev' in ha:
device = self.info_devs[ha['dev']]
if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501
return None
attr['dev'] = self.__build_dev(device, key, ha, snr,
sug_area)
attr['o'] = self.__build_origin()
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for {key} "
"missing 'dev' value for ha register")
return json.dumps(attr), component, node_id, attr['uniq_id']
def __build_attr(self, row, key, ha_prfx, node_id, snr):
attr = {}
ha = row['ha']
if 'name' in ha:
attr['name'] = ha['name']
else:
attr['name'] = row['name'][-1]
prfx = ha_prfx + node_id
attr['stat_t'] = prfx + row['name'][0]
attr['dev_cla'] = ha['dev_cla']
attr['stat_cla'] = ha['stat_cla']
attr['uniq_id'] = ha['id']+snr
if 'val_tpl' in ha:
attr['val_tpl'] = ha['val_tpl']
elif 'fmt' in ha:
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for {key} do"
" not have a 'val_tpl' nor a 'fmt' value")
# add unit_of_meas only, if status_class isn't none. If
# status_cla is None we want a number format and not line
# graph in home assistant. A unit will change the number
# format to a line graph
if 'unit' in row and attr['stat_cla'] is not None:
attr['unit_of_meas'] = row['unit'] # 'unit_of_meas'
if 'icon' in ha:
attr['ic'] = ha['icon'] # icon for the entity
if 'nat_prc' in ha: # pragma: no cover
attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats
if 'ent_cat' in ha:
attr['ent_cat'] = ha['ent_cat'] # diagnostic, config
# enabled_by_default is deactivated, since it avoid the via
# setup of the devices. It seems, that there is a bug in home
# assistant. tested with 'Home Assistant 2023.10.4'
# if 'en' in ha: # enabled_by_default
# attr['en'] = ha['en']
return attr
def __build_dev(self, device, key, ha, snr, sug_area):
dev = {}
singleton = 'singleton' in device and device['singleton']
# the same name for 'name' and 'suggested area', so we get
# dedicated devices in home assistant with short value
# name and headline
if (sug_area == '' or singleton):
dev['name'] = device['name']
dev['sa'] = device['name']
else:
dev['name'] = device['name']+' - '+sug_area
dev['sa'] = device['name']+' - '+sug_area
self.__add_via_dev(dev, device, key, snr)
for key in ('mdl', 'mf', 'sw', 'hw'): # add optional
# values fpr 'modell', 'manufacturer', 'sw version' and
# 'hw version'
if key in device:
data = self.dev_value(device[key])
if data is not None:
dev[key] = data
if singleton:
dev['ids'] = [f"{ha['dev']}"]
else:
dev['ids'] = [f"{ha['dev']}_{snr}"]
return dev
def __add_via_dev(self, dev, device, key, snr):
if 'via' in device: # add the link to the parent device
via = device['via']
if via in self.info_devs:
via_dev = self.info_devs[via]
if 'singleton' in via_dev and via_dev['singleton']:
dev['via_device'] = via
else:
dev['via_device'] = f"{via}_{snr}"
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for "
f"{key} has an invalid via value: "
f"{via}")
def __build_origin(self):
origin = {}
origin['name'] = self.app_name
origin['sw'] = self.version
return origin
def ha_remove(self, key, node_id, snr) -> tuple[str, str, str, str] | None: def ha_remove(self, key, node_id, snr) -> tuple[str, str, str, str] | None:
'''Method to build json unregister struct for home-assistant '''Method to build json unregister struct for home-assistant
to remove topics per auto configuration. Only for inverer topics. to remove topics per auto configuration. Only for inverer topics.

View File

@@ -4,6 +4,7 @@ import asyncio
from mock import patch from mock import patch
from enum import Enum from enum import Enum
from enum import Enum
from app.src.singleton import Singleton from app.src.singleton import Singleton
from app.src.config import Config from app.src.config import Config
from app.src.infos import Infos from app.src.infos import Infos
@@ -11,6 +12,10 @@ from app.src.mqtt import Mqtt
from app.src.messages import Message, State from app.src.messages import Message, State
from app.src.inverter import Inverter from app.src.inverter import Inverter
from app.src.modbus_tcp import ModbusConn, ModbusTcp from app.src.modbus_tcp import ModbusConn, ModbusTcp
from app.src.mqtt import Mqtt
from app.src.messages import Message, State
from app.src.inverter import Inverter
from app.src.modbus_tcp import ModbusConn, ModbusTcp
pytest_plugins = ('pytest_asyncio',) pytest_plugins = ('pytest_asyncio',)
@@ -77,6 +82,7 @@ class TestType(Enum):
test = TestType.RD_TEST_0_BYTES test = TestType.RD_TEST_0_BYTES
class FakeReader(): class FakeReader():
def __init__(self): def __init__(self):
self.on_recv = asyncio.Event() self.on_recv = asyncio.Event()
@@ -110,7 +116,7 @@ class FakeWriter():
@pytest.fixture @pytest.fixture
def patch_open(): def patch_open():
async def new_conn(conn): async def new_conn(conn):
await asyncio.sleep(0.01) await asyncio.sleep(0)
return FakeReader(), FakeWriter() return FakeReader(), FakeWriter()
def new_open(host: str, port: int): def new_open(host: str, port: int):
@@ -127,6 +133,11 @@ def patch_no_mqtt():
with patch.object(Mqtt, 'publish') as conn: with patch.object(Mqtt, 'publish') as conn:
yield conn yield conn
@pytest.fixture
def patch_no_mqtt():
with patch.object(Mqtt, 'publish') as conn:
yield conn
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_modbus_conn(patch_open): async def test_modbus_conn(patch_open):
@@ -161,12 +172,12 @@ async def test_modbus_cnf1(config_conn, patch_open):
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
ModbusTcp(loop) ModbusTcp(loop)
await asyncio.sleep(0.1) await asyncio.sleep(0.01)
for m in Message: for m in Message:
if (m.node_id == 'inv_2'): if (m.node_id == 'inv_2'):
assert False assert False
await asyncio.sleep(0.1) await asyncio.sleep(0.01)
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -181,7 +192,7 @@ async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open):
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
ModbusTcp(asyncio.get_event_loop()) ModbusTcp(asyncio.get_event_loop())
await asyncio.sleep(0.1) await asyncio.sleep(0.01)
test = 0 test = 0
for m in Message: for m in Message:
if (m.node_id == 'inv_2'): if (m.node_id == 'inv_2'):
@@ -192,14 +203,13 @@ async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open):
del m del m
assert 1 == test assert 1 == test
await asyncio.sleep(0.1) await asyncio.sleep(0.01)
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
# check that the connection is released # check that the connection is released
for m in Message: for m in Message:
if (m.node_id == 'inv_2'): if (m.node_id == 'inv_2'):
assert False assert False
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open): async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open):
_ = config_conn _ = config_conn
@@ -211,32 +221,24 @@ async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open):
test = TestType.RD_TEST_0_BYTES test = TestType.RD_TEST_0_BYTES
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0.1) ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)
await asyncio.sleep(0.1) await asyncio.sleep(0.01)
test = 0 test = 0
for m in Message: for m in Message:
if (m.node_id == 'inv_2'): if (m.node_id == 'inv_2'):
assert Infos.stat['proxy']['Inverter_Cnt'] == 1 assert Infos.stat['proxy']['Inverter_Cnt'] == 1
m.shutdown_started = False
m.reader.on_recv.set()
test += 1 test += 1
await asyncio.sleep(0.1) if test == 1:
assert m.state == State.closed m.shutdown_started = False
m.reader.on_recv.set()
assert 1 == test await asyncio.sleep(0.1)
await asyncio.sleep(0.1) assert m.state == State.closed
assert Infos.stat['proxy']['Inverter_Cnt'] == 1 await asyncio.sleep(0.1)
# check that the connection is released else:
for m in Message: m.shutdown_started = True
if (m.node_id == 'inv_2'): m.reader.on_recv.set()
test += 1 del m
m.shutdown_started = True
m.reader.on_recv.set()
del m
assert 3 == test assert 2 == test
await asyncio.sleep(0.1) await asyncio.sleep(0.01)
assert Infos.stat['proxy']['Inverter_Cnt'] == 0 assert Infos.stat['proxy']['Inverter_Cnt'] == 0
for m in Message:
if (m.node_id == 'inv_2'):
assert False

View File

@@ -97,7 +97,7 @@ async def test_mqtt_no_config(config_no_conn):
try: try:
m = Mqtt(cb) m = Mqtt(cb)
assert m.task assert m.task
await asyncio.sleep(1) await asyncio.sleep(0)
assert not on_connect.is_set() assert not on_connect.is_set()
try: try:
await m.publish('homeassistant/status', 'online') await m.publish('homeassistant/status', 'online')

View File

@@ -526,6 +526,166 @@ def broken_recv_buf(): # There are two message in the buffer, but the second has
msg += b'\x08\x00\x00\x00\x00\x31' msg += b'\x08\x00\x00\x00\x00\x31'
return msg return msg
@pytest.fixture
def multiple_recv_buf(): # There are three message in the buffer, but the second has overwritten the first partly
msg = b'\x00\x00\x05\x02\x10R170000000000001\x91\x04\x01\x90\x00\x01\x10R170000000000001'
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x61\x08'
msg += b'\x00\x00\x00\xa3\x00\x00\x00\x64\x53\x00\x01'
msg += b'\x00\x00\x00\xc8\x53\x00\x00\x00\x00\x01\x2c\x53\x00\x02\x00\x00' # | ....S.....,S....
msg += b'\x01\x90\x49\x00\x00\x00\x00\x00\x00\x01\x91\x53\x00\x00\x00\x00' # | ..I........S....
msg += b'\x13\x10\x52\x31\x37\x45\x37\x33\x30\x37\x30\x32\x31\x44\x30\x30' # | ..R17E7307021D00
msg += b'\x36\x41\x91\x22\x00\x00\x03\xbf\x10\x52\x31\x37\x45\x37\x33\x30' # | 6A.".....R17E730
msg += b'\x37\x30\x32\x31\x44\x30\x30\x36\x41\x91\x71\x0e\x10\x00\x00\x10' # | 7021D006A.q.....
msg += b'\x52\x31\x37\x45\x37\x33\x30\x37\x30\x32\x31\x44\x30\x30\x36\x41' # | R17E7307021D006A
msg += b'\x01\x00\x00\x01\x91\xa3\xfe\xaf\x98\x00\x00\x00\x35\x00\x09\x2b' # | ............5..+
msg += b'\xa8\x54\x10\x52\x53\x57\x5f\x34\x30\x30\x5f\x56\x31\x2e\x30\x30' # | .T.RSW_400_V1.00
msg += b'\x2e\x31\x37\x00\x09\x27\xc0\x54\x06\x52\x61\x79\x6d\x6f\x6e\x00' # | .17..'.T.Raymon.
msg += b'\x09\x2f\x90\x54\x0b\x52\x53\x57\x2d\x31\x2d\x31\x30\x30\x30\x31' # | ./.T.RSW-1-10001
msg += b'\x00\x09\x5a\x88\x54\x0f\x74\x2e\x72\x61\x79\x6d\x6f\x6e\x69\x6f' # | ..Z.T.t.raymonio
msg += b'\x74\x2e\x63\x6f\x6d\x00\x09\x5a\xec\x54\x1c\x6c\x6f\x67\x67\x65' # | t.com..Z.T.logge
msg += b'\x72\x2e\x74\x61\x6c\x65\x6e\x74\x2d\x6d\x6f\x6e\x69\x74\x6f\x72' # | r.talent-monitor
msg += b'\x69\x6e\x67\x2e\x63\x6f\x6d\x00\x0d\x2f\x00\x54\x10\xff\xff\xff' # | ing.com../.T....
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x32' # | ...............2
msg += b'\xe8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .T..............
msg += b'\xff\xff\xff\x00\x0d\x36\xd0\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....6.T........
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x3a\xb8\x54\x10\xff' # | ...........:.T..
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
msg += b'\x0d\x3e\xa0\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .>.T............
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x42\x88\x54\x10\xff\xff\xff\xff\xff' # | .......B.T......
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x46\x70\x54' # | .............FpT
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
msg += b'\xff\x00\x0d\x4a\x58\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...JXT..........
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x4e\x40\x54\x10\xff\xff\xff' # | .........N@T....
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x52' # | ...............R
msg += b'\x28\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | (T..............
msg += b'\xff\xff\xff\x00\x0d\x56\x10\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....V.T........
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x59\xf8\x54\x10\xff' # | ...........Y.T..
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
msg += b'\x0d\x5d\xe0\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .].T............
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x61\xc8\x54\x10\xff\xff\xff\xff\xff' # | .......a.T......
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x65\xb0\x54' # | .............e.T
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
msg += b'\xff\x00\x0d\x69\x98\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...i.T..........
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x6d\x80\x54\x10\xff\xff\xff' # | .........m.T....
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x71' # | ...............q
msg += b'\x68\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | hT..............
msg += b'\xff\xff\xff\x00\x0d\x75\x50\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....uPT........
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x79\x38\x54\x10\xff' # | ...........y8T..
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
msg += b'\x0d\x7d\x20\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .} T............
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x81\x08\x54\x10\xff\xff\xff\xff\xff' # | .........T......
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x84\xf0\x54' # | ...............T
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
msg += b'\xff\x00\x0d\x88\xd8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .....T..........
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x8c\xc0\x54\x10\xff\xff\xff' # | ...........T....
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x90' # | ................
msg += b'\xa8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .T..............
msg += b'\xff\xff\xff\x00\x0d\x94\x90\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .......T........
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x98\x78\x54\x10\xff' # | ............xT..
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
msg += b'\x0d\x00\x20\x49\x00\x00\x00\x01\x00\x0c\x35\x00\x49\x00\x00\x00' # | .. I......5.I...
msg += b'\x28\x00\x0c\x96\xa8\x49\x00\x00\x01\x69\x00\x0c\x7f\x38\x49\x00' # | (....I...i...8I.
msg += b'\x00\x00\x01\x00\x0c\xfc\x38\x49\x00\x00\x00\x01\x00\x0c\xf8\x50' # | ......8I.......P
msg += b'\x49\x00\x00\x01\x2c\x00\x0c\x63\xe0\x49\x00\x00\x00\x00\x00\x0c' # | I...,..c.I......
msg += b'\x67\xc8\x49\x00\x00\x00\x00\x00\x0c\x50\x58\x49\x00\x00\x00\x01' # | g.I......PXI....
msg += b'\x00\x09\x5e\x70\x49\x00\x00\x13\x8d\x00\x09\x5e\xd4\x49\x00\x00' # | ..^pI......^.I..
msg += b'\x13\x8d\x00\x09\x5b\x50\x49\x00\x00\x00\x02\x00\x0d\x04\x08\x49' # | ....[PI........I
msg += b'\x00\x00\x00\x00\x00\x07\xa1\x84\x49\x00\x00\x00\x01\x00\x0c\x50' # | ........I......P
msg += b'\x59\x49\x00\x00\x00\x3e\x00\x0d\x1f\x60\x49\x00\x00\x00\x00\x00' # | YI...>...`I.....
msg += b'\x0d\x23\x48\x49\xff\xff\xff\xff\x00\x0d\x27\x30\x49\xff\xff\xff' # | .#HI......'0I...
msg += b'\xff\x00\x0d\x2b\x18\x4c\x00\x00\x00\x00\xff\xff\xff\xff\x00\x0c' # | ...+.L..........
msg += b'\xa2\x60\x49\x00\x00\x00\x00\x00\x00\x05\x02\x10\x52\x31\x37\x45' # | .`I.........R17E
msg += b'\x37\x33\x30\x37\x30\x32\x31\x44\x30\x30\x36\x41\x91\x04\x01\x90' # | 7307021D006A....
msg += b'\x00\x01\x10\x54\x31\x37\x45\x37\x33\x30\x37\x30\x32\x31\x44\x30' # | ...T17E7307021D0
msg += b'\x30\x36\x41\x01\x00\x00\x01\x91\xa3\xfe\xb3\x80\x00\x00\x00\xa3' # | 06A.............
msg += b'\x00\x00\x00\x64\x53\x00\x01\x00\x00\x00\xc8\x53\x00\x00\x00\x00' # | ...dS......S....
msg += b'\x01\x2c\x53\x00\x02\x00\x00\x01\x90\x49\x00\x00\x00\x00\x00\x00' # | .,S......I......
msg += b'\x01\x91\x53\x00\x00\x00\x00\x01\x92\x53\x00\x00\x00\x00\x01\x93' # | ..S......S......
msg += b'\x53\x00\x00\x00\x00\x01\x94\x53\x00\x00\x00\x00\x01\x95\x53\x00' # | S......S......S.
msg += b'\x00\x00\x00\x01\x96\x53\x00\x00\x00\x00\x01\x97\x53\x00\x00\x00' # | .....S......S...
msg += b'\x00\x01\x98\x53\x00\x00\x00\x00\x01\x99\x53\x00\x00\x00\x00\x01' # | ...S......S.....
msg += b'\x9a\x53\x00\x00\x00\x00\x01\x9b\x53\x00\x00\x00\x00\x01\x9c\x53' # | .S......S......S
msg += b'\x00\x00\x00\x00\x01\x9d\x53\x00\x00\x00\x00\x01\x9e\x53\x00\x00' # | ......S......S..
msg += b'\x00\x00\x01\x9f\x53\x00\x00\x00\x00\x01\xa0\x53\x00\x00\x00\x00' # | ....S......S....
msg += b'\x01\xf4\x49\x00\x00\x00\x00\x00\x00\x01\xf5\x53\x00\x00\x00\x00' # | ..I........S....
msg += b'\x01\xf6\x53\x00\x00\x00\x00\x01\xf7\x53\x00\x00\x00\x00\x01\xf8' # | ..S......S......
msg += b'\x53\x00\x00\x00\x00\x01\xf9\x53\x00\x00\x00\x00\x01\xfa\x53\x00' # | S......S......S.
msg += b'\x00\x00\x00\x01\xfb\x53\x00\x00\x00\x00\x01\xfc\x53\x00\x00\x00' # | .....S......S...
msg += b'\x00\x01\xfd\x53\x00\x00\x00\x00\x01\xfe\x53\x00\x00\x00\x00\x01' # | ...S......S.....
msg += b'\xff\x53\x00\x00\x00\x00\x02\x00\x53\x00\x00\x00\x00\x02\x01\x53' # | .S......S......S
msg += b'\x00\x00\x00\x00\x02\x02\x53\x00\x00\x00\x00\x02\x03\x53\x00\x00' # | ......S......S..
msg += b'\x00\x00\x02\x04\x53\x00\x00\x00\x00\x02\x58\x49\x00\x00\x00\x00' # | ....S.....XI....
msg += b'\x00\x00\x02\x59\x53\x00\x00\x00\x00\x02\x5a\x53\x00\x00\x00\x00' # | ...YS.....ZS....
msg += b'\x02\x5b\x53\x00\x00\x00\x00\x02\x5c\x53\x00\x00\x00\x00\x02\x5d' # | .[S.....\S.....]
msg += b'\x53\x00\x00\x00\x00\x02\x5e\x53\x00\x00\x00\x00\x02\x5f\x53\x00' # | S.....^S....._S.
msg += b'\x00\x00\x00\x02\x60\x53\x00\x00\x00\x00\x02\x61\x53\x00\x00\x00' # | ....`S.....aS...
msg += b'\x00\x02\x62\x53\x00\x00\x00\x00\x02\x63\x53\x00\x00\x00\x00\x02' # | ..bS.....cS.....
msg += b'\x64\x53\x00\x00\x00\x00\x02\x65\x53\x00\x00\x00\x00\x02\x66\x53' # | dS.....eS.....fS
msg += b'\x00\x00\x00\x00\x02\x67\x53\x00\x00\x00\x00\x02\x68\x53\x00\x00' # | .....gS.....hS..
msg += b'\x00\x00\x02\xbc\x49\x00\x00\x00\x00\x00\x00\x02\xbd\x53\x00\x00' # | ....I........S..
msg += b'\x00\x00\x02\xbe\x53\x00\x00\x00\x00\x02\xbf\x53\x00\x00\x00\x00' # | ....S......S....
msg += b'\x02\xc0\x53\x00\x00\x00\x00\x02\xc1\x53\x00\x00\x00\x00\x02\xc2' # | ..S......S......
msg += b'\x53\x00\x00\x00\x00\x02\xc3\x53\x00\x00\x00\x00\x02\xc4\x53\x00' # | S......S......S.
msg += b'\x00\x00\x00\x02\xc5\x53\x00\x00\x00\x00\x02\xc6\x53\x00\x00\x00' # | .....S......S...
msg += b'\x00\x02\xc7\x53\x00\x00\x00\x00\x02\xc8\x53\x00\x00\x00\x00\x02' # | ...S......S.....
msg += b'\xc9\x53\x00\x00\x00\x00\x02\xca\x53\x00\x00\x00\x00\x02\xcb\x53' # | .S......S......S
msg += b'\x00\x00\x00\x00\x02\xcc\x53\x00\x00\x00\x00\x03\x20\x53\x00\x00' # | ......S..... S..
msg += b'\x00\x00\x03\x84\x53\x51\x09\x00\x00\x03\xe8\x46\x43\x65\x99\x9a' # | ....SQ.....FCe..
msg += b'\x00\x00\x04\x4c\x46\x3e\xd7\x0a\x3d\x00\x00\x04\xb0\x46\x42\x48' # | ...LF>..=....FBH
msg += b'\x28\xf6\x00\x00\x05\x14\x53\x00\x1f\x00\x00\x05\x78\x53\x00\x00' # | (.....S.....xS..
msg += b'\x00\x00\x05\xdc\x53\x02\x58\x00\x00\x06\x40\x46\x42\xc1\x33\x33' # | ....S.X...@FB.33
msg += b'\x00\x00\x06\xa4\x46\x3f\x33\x33\x33\x00\x00\x07\x08\x46\x00\x00' # | ....F?333....F..
msg += b'\x00\x00\x00\x00\x07\x6c\x46\x00\x00\x00\x00\x00\x00\x07\xd0\x46' # | .....lF........F
msg += b'\x42\x05\x99\x9a\x00\x00\x08\x34\x46\x40\x41\xeb\x85\x00\x00\x08' # | B......4F@A.....
msg += b'\x98\x46\x42\xcb\x66\x66\x00\x00\x08\xfc\x46\x00\x00\x00\x00\x00' # | .FB.ff....F.....
msg += b'\x00\x09\x60\x46\x00\x00\x00\x00\x00\x00\x09\xc4\x46\x00\x00\x00' # | ..`F........F...
msg += b'\x00\x00\x00\x0a\x28\x46\x00\x00\x00\x00\x00\x00\x0a\x8c\x46\x00' # | ....(F........F.
msg += b'\x00\x00\x00\x00\x00\x0a\xf0\x46\x00\x00\x00\x00\x00\x00\x0b\x54' # | .......F.......T
msg += b'\x46\x3f\x19\x99\x9a\x00\x00\x0b\xb8\x46\x43\xf3\x95\xc3\x00\x00' # | F?.......FC.....
msg += b'\x0c\x1c\x46\x00\x00\x00\x00\x00\x00\x0c\x80\x46\x43\x04\x4a\x3d' # | ..F........FC.J=
msg += b'\x00\x00\x0c\xe4\x46\x3f\x23\xd7\x0a\x00\x00\x0d\x48\x46\x43\xbf' # | ....F?#.....HFC.
msg += b'\x9e\xb8\x00\x00\x0d\xac\x46\x00\x00\x00\x00\x00\x00\x0e\x10\x46' # | ......F........F
msg += b'\x00\x00\x00\x00\x00\x00\x0e\x74\x46\x00\x00\x00\x00\x00\x00\x0e' # | .......tF.......
msg += b'\xd8\x46\x00\x00\x00\x00\x00\x00\x0f\x3c\x53\x00\x00\x00\x00\x0f' # | .F.......<S.....
msg += b'\xa0\x53\x00\x00\x00\x00\x10\x04\x53\x55\xaa\x00\x00\x10\x68\x53' # | .S......SU....hS
msg += b'\x00\x00\x00\x00\x10\xcc\x53\x00\x00\x00\x00\x11\x30\x53\x00\x00' # | ......S.....0S..
msg += b'\x00\x00\x11\x94\x53\x00\x00\x00\x00\x11\xf8\x53\xff\xff\x00\x00' # | ....S......S....
msg += b'\x12\x5c\x53\x03\x20\x00\x00\x12\xc0\x53\x00\x02\x00\x00\x13\x24' # | .\S. ....S.....$
msg += b'\x53\x04\x00\x00\x00\x13\x88\x53\x04\x00\x00\x00\x13\xec\x53\x04' # | S......S......S.
msg += b'\x00\x00\x00\x14\x50\x53\x04\x00\x00\x00\x14\xb4\x53\x00\x01\x00' # | ....PS......S...
msg += b'\x00\x15\x18\x53\x08\x1e\x00\x00\x15\x7c\x53\x00\x00\x00\x00\x27' # | ...S.....|S....'
msg += b'\x10\x53\x00\x02\x00\x00\x27\x74\x53\x00\x3c\x00\x00\x27\xd8\x53' # | .S....'tS.<..'.S
msg += b'\x00\x68\x00\x00\x28\x3c\x53\x05\x00\x00\x00\x28\xa0\x46\x43\x79' # | .h..(<S....(.FCy
msg += b'\x00\x00\x00\x00\x29\x04\x46\x43\x48\x00\x00\x00\x00\x29\x68\x46' # | ....).FCH....)hF
msg += b'\x42\x48\x33\x33\x00\x00\x29\xcc\x46\x42\x3e\x3d\x71\x00\x00\x2a' # | BH33..).FB>=q..*
msg += b'\x30\x53\x00\x01\x00\x00\x2a\x94\x46\x43\x37\x00\x00\x00\x00\x2a' # | 0S....*.FC7....*
msg += b'\xf8\x46\x42\xce\x00\x00\x00\x00\x2b\x5c\x53\x00\x96\x00\x00\x2b' # | .FB.....+\S....+
msg += b'\xc0\x53\x00\x10\x00\x00\x2c\x24\x46\x43\x90\x00\x00\x00\x00\x2c' # | .S....,$FC.....,
msg += b'\x88\x46\x43\x95\x00\x00\x00\x00\x2c\xec\x53\x00\x06\x00\x00\x2d' # | .FC.....,.S....-
msg += b'\x50\x53\x00\x06\x00\x00\x2d\xb4\x46\x43\x7d\x00\x00\x00\x00\x2e' # | PS....-.FC}.....
msg += b'\x18\x46\x42\x3d\xeb\x85\x00\x00\x2e\x7c\x46\x42\x3d\xeb\x85\x00' # | .FB=.....|FB=...
msg += b'\x00\x2e\xe0\x53\x00\x03\x00\x00\x2f\x44\x53\x00\x03\x00\x00\x2f' # | ...S..../DS..../
msg += b'\xa8\x46\x42\x4d\xeb\x85\x00\x00\x30\x0c\x46\x42\x4d\xeb\x85\x00' # | .FBM....0.FBM...
msg += b'\x00\x30\x70\x53\x00\x03\x00\x00\x30\xd4\x53\x00\x03\x00\x00\x31' # | .0pS....0.S....1
msg += b'\x38\x46\x42\x08\x00\x00\x00\x00\x31\x9c\x53\x00\x05\x00\x00\x32' # | 8FB.....1.S....2
msg += b'\x00\x53\x01\x61\x00\x00\x32\x64\x53\x00\x01\x00\x00\x32\xc8\x53' # | .S.a..2dS....2.S
msg += b'\x13\x9c\x00\x00\x33\x2c\x53\x0f\xa0\x00\x00\x33\x90\x53\x00\x4f' # | ....3,S....3.S.O
msg += b'\x00\x00\x33\xf4\x53\x00\x66\x00\x00\x34\x58\x53\x03\xe8\x00\x00' # | ..3.S.f..4XS....
msg += b'\x34\xbc\x53\x04\x00\x00\x00\x35\x20\x53\x09\xc4\x00\x00\x35\x84' # | 4.S....5 S....5.
msg += b'\x53\x07\xc6\x00\x00\x35\xe8\x53\x13\x8c\x00\x00\x36\x4c\x53\x12' # | S....5.S....6LS.
msg += b'\x94\x00\x01\x38\x80\x53\x00\x02\x00\x01\x38\x81\x53\x00\x01\x00' # | ...8.S....8.S...
msg += b'\x01\x38\x82\x53\x00\x01\x00\x01\x38\x83\x53\x00\x00\x00\x00\x00' # | .8.S....8.S.....
msg += b'\x8b\x10\x52\x31\x37\x45\x37\x33\x30\x37\x30\x32\x31\x44\x30\x30' # | ..R17E7307021D00
msg += b'\x36\x41\x91\x04\x01\x90\x00\x01\x10\x54\x31\x37\x45\x37\x33\x30' # | 6A.......T17E730
msg += b'\x37\x30\x32\x31\x44\x30\x30\x36\x41\x01\x00\x00\x01\x91\xa3\xfe' # | 7021D006A.......
msg += b'\xb3\x80\x00\x00\x00\x06\x00\x00\x00\x0a\x54\x08\x4d\x69\x63\x72' # | ..........T.Micr
msg += b'\x6f\x69\x6e\x76\x00\x00\x00\x14\x54\x04\x54\x53\x55\x4e\x00\x00' # | oinv....T.TSUN..
msg += b'\x00\x1e\x54\x07\x56\x35\x2e\x31\x2e\x30\x39\x00\x00\x00\x28\x54' # | ..T.V5.1.09...(T
msg += b'\x10\x54\x31\x37\x45\x37\x33\x30\x37\x30\x32\x31\x44\x30\x30\x36' # | .T17E7307021D006
msg += b'\x41\x00\x00\x00\x32\x54\x0a\x54\x53\x4f\x4c\x2d\x4d\x53\x36\x30' # | A...2T.TSOL-MS60
msg += b'\x30\x00\x00\x00\x3c\x54\x05\x41\x2c\x42\x2c\x43' # | 0...<T.A,B,C'
return msg
def test_read_message(msg_contact_info): def test_read_message(msg_contact_info):
m = MemoryStream(msg_contact_info, (0,)) m = MemoryStream(msg_contact_info, (0,))
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
@@ -1617,3 +1777,24 @@ def test_broken_recv_buf(config_tsun_allow_all, broken_recv_buf):
assert m.db.stat['proxy']['Invalid_Data_Type'] == 1 assert m.db.stat['proxy']['Invalid_Data_Type'] == 1
m.close() m.close()
def test_multiiple_recv_buf(config_tsun_allow_all, multiple_recv_buf):
_ = config_tsun_allow_all
m = MemoryStream(multiple_recv_buf, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.db.stat['proxy']['Invalid_Msg_Format'] = 0
m.db.stat['proxy']['Invalid_Data_Type'] = 0
m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.msg_count == 1
assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001'
assert m.msg_recvd[0]['ctrl']==145
assert m.msg_recvd[0]['msg_id']==4
assert m.msg_recvd[0]['header_len']==23
assert m.msg_recvd[0]['data_len']==1263
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
assert m.db.stat['proxy']['Invalid_Data_Type'] == 1
m.close()