# test_with_pytest.py import pytest import asyncio from aiomqtt import MqttCodeError from mock import patch from enum import Enum from singleton import Singleton from cnf.config import Config from infos import Infos from mqtt import Mqtt from inverter_base import InverterBase from messages import Message, State from proxy import Proxy from modbus_tcp import ModbusConn, ModbusTcp pytest_plugins = ('pytest_asyncio',) # initialize the proxy statistics Infos.static_init() @pytest.fixture(scope="module", autouse=True) def module_init(): Singleton._instances.clear() yield @pytest.fixture(scope="module") def test_port(): return 1883 @pytest.fixture(scope="module") def test_hostname(): # if getenv("GITHUB_ACTIONS") == "true": # return 'mqtt' # else: return 'test.mosquitto.org' @pytest.fixture def config_conn(test_hostname, test_port): Config.act_config = { 'mqtt':{ 'host': test_hostname, 'port': test_port, 'user': '', 'passwd': '' }, 'ha':{ 'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'test_1', 'proxy_unique_id': '' }, 'solarman':{ 'host': 'access1.solarmanpv.com', 'port': 10000 }, 'inverters':{ 'allow_all': True, "R170000000000001":{ 'node_id': 'inv_1' }, "Y170000000000001":{ 'node_id': 'inv_2', 'monitor_sn': 2000000000, 'modbus_polling': True, 'suggested_area': "", 'sensor_list': 0x2b0, 'client_mode':{ 'host': '192.168.0.1', 'port': 8899, 'forward': True } } } } class FakeReader(): RD_TEST_0_BYTES = 1 RD_TEST_TIMEOUT = 2 RD_TEST_13_BYTES = 3 RD_TEST_SW_EXCEPT = 4 RD_TEST_OS_ERROR = 5 def __init__(self): self.on_recv = asyncio.Event() self.test = self.RD_TEST_0_BYTES async def read(self, max_len: int): print(f'fakeReader test: {self.test}') await self.on_recv.wait() if self.test == self.RD_TEST_0_BYTES: return b'' elif self.test == self.RD_TEST_13_BYTES: print('fakeReader return 13 bytes') self.test = self.RD_TEST_0_BYTES return b'test-data-req' elif self.test == self.RD_TEST_TIMEOUT: raise TimeoutError elif self.test == self.RD_TEST_SW_EXCEPT: self.test = self.RD_TEST_0_BYTES self.unknown_var += 1 elif self.test == self.RD_TEST_OS_ERROR: self.test = self.RD_TEST_0_BYTES raise ConnectionRefusedError def feed_eof(self): return class FakeWriter(): def __init__(self, conn='remote.intern'): self.conn = conn self.closing = False def write(self, buf: bytes): return async def drain(self): await asyncio.sleep(0) def get_extra_info(self, sel: str): if sel == 'peername': return self.conn elif sel == 'sockname': return 'sock:1234' assert False def is_closing(self): return self.closing def close(self): self.closing = True async def wait_closed(self): await asyncio.sleep(0) @pytest.fixture def patch_open(): async def new_conn(conn): await asyncio.sleep(0) return FakeReader(), FakeWriter(conn) def new_open(host: str, port: int): return new_conn(f'{host}:{port}') with patch.object(asyncio, 'open_connection', new_open) as conn: yield conn @pytest.fixture def patch_open_timeout(): def new_open(host: str, port: int): raise TimeoutError with patch.object(asyncio, 'open_connection', new_open) as conn: yield conn @pytest.fixture def patch_open_value_error(): def new_open(host: str, port: int): raise ValueError with patch.object(asyncio, 'open_connection', new_open) as conn: yield conn @pytest.fixture def patch_open_conn_abort(): def new_open(host: str, port: int): raise ConnectionAbortedError with patch.object(asyncio, 'open_connection', new_open) as conn: yield conn @pytest.fixture def patch_no_mqtt(): with patch.object(Mqtt, 'publish') as conn: yield conn @pytest.fixture def patch_mqtt_err(): def new_publish(self, key, data): raise MqttCodeError(None) with patch.object(Mqtt, 'publish', new_publish) as conn: yield conn @pytest.fixture def patch_mqtt_except(): def new_publish(self, key, data): raise ValueError("Test") with patch.object(Mqtt, 'publish', new_publish) as conn: yield conn @pytest.mark.asyncio async def test_modbus_conn(patch_open): _ = patch_open assert Infos.stat['proxy']['Inverter_Cnt'] == 0 async with ModbusConn('test.local', 1234) as inverter: stream = inverter.local.stream assert stream.node_id == 'G3P' assert stream.addr == ('test.local:1234') assert type(stream.ifc._reader) is FakeReader assert type(stream.ifc._writer) is FakeWriter assert Infos.stat['proxy']['Inverter_Cnt'] == 1 del inverter for _ in InverterBase: assert False assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_modbus_no_cnf(): assert Infos.stat['proxy']['Inverter_Cnt'] == 0 loop = asyncio.get_event_loop() ModbusTcp(loop) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_modbus_timeout(config_conn, patch_open_timeout): _ = config_conn _ = patch_open_timeout assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 loop = asyncio.get_event_loop() ModbusTcp(loop) await asyncio.sleep(0.01) for m in Message: if (m.node_id == 'inv_2'): assert False await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_modbus_value_err(config_conn, patch_open_value_error): _ = config_conn _ = patch_open_value_error assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 loop = asyncio.get_event_loop() ModbusTcp(loop) await asyncio.sleep(0.01) for m in Message: if (m.node_id == 'inv_2'): assert False await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_modbus_conn_abort(config_conn, patch_open_conn_abort): _ = config_conn _ = patch_open_conn_abort assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 loop = asyncio.get_event_loop() ModbusTcp(loop) await asyncio.sleep(0.01) for m in Message: if (m.node_id == 'inv_2'): assert False await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open): _ = config_conn _ = patch_open _ = patch_no_mqtt assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 ModbusTcp(asyncio.get_event_loop()) await asyncio.sleep(0.01) test = 0 for m in Message: if (m.node_id == 'inv_2'): test += 1 assert Infos.stat['proxy']['Inverter_Cnt'] == 1 m.shutdown_started = True m.ifc._reader.on_recv.set() del m assert 1 == test await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open): _ = config_conn _ = patch_open _ = patch_no_mqtt assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 ModbusTcp(asyncio.get_event_loop(), tim_restart= 0) await asyncio.sleep(0.01) test = 0 for m in Message: if (m.node_id == 'inv_2'): assert Infos.stat['proxy']['Inverter_Cnt'] == 1 test += 1 if test == 1: m.shutdown_started = False m.ifc._reader.on_recv.set() await asyncio.sleep(0.1) assert m.state == State.closed await asyncio.sleep(0.1) else: m.shutdown_started = True m.ifc._reader.on_recv.set() del m assert 2 == test await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_mqtt_err(config_conn, patch_mqtt_err, patch_open): _ = config_conn _ = patch_open _ = patch_mqtt_err assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 ModbusTcp(asyncio.get_event_loop(), tim_restart= 0) await asyncio.sleep(0.01) test = 0 for m in Message: if (m.node_id == 'inv_2'): assert Infos.stat['proxy']['Inverter_Cnt'] == 1 test += 1 if test == 1: m.shutdown_started = False m.ifc._reader.on_recv.set() await asyncio.sleep(0.1) assert m.state == State.closed await asyncio.sleep(0.1) await asyncio.sleep(0.1) else: m.shutdown_started = True m.ifc._reader.on_recv.set() del m await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @pytest.mark.asyncio async def test_mqtt_except(config_conn, patch_mqtt_except, patch_open): _ = config_conn _ = patch_open _ = patch_mqtt_except assert asyncio.get_running_loop() Proxy.class_init() assert Infos.stat['proxy']['Inverter_Cnt'] == 0 ModbusTcp(asyncio.get_event_loop(), tim_restart= 0) await asyncio.sleep(0.01) test = 0 for m in Message: if (m.node_id == 'inv_2'): assert Infos.stat['proxy']['Inverter_Cnt'] == 1 test += 1 if test == 1: m.shutdown_started = False m.ifc._reader.on_recv.set() await asyncio.sleep(0.1) assert m.state == State.closed await asyncio.sleep(0.1) else: m.shutdown_started = True m.ifc._reader.on_recv.set() del m await asyncio.sleep(0.01) assert Infos.stat['proxy']['Inverter_Cnt'] == 0