SonarCloud setup (#168)

* Code Cleanup (#158)

* print coverage report

* create sonar-project property file

* install all py dependencies in one step

* code cleanup

* reduce cognitive complexity

* do not build on *.yml changes

* optimise versionstring handling (#159)

- Reading the version string from the image updates
  it even if the image is re-pulled without re-deployment

* fix linter warning

* exclude *.pyi filese

* ignore some rules for tests

* cleanup (#160)

* Sonar qube 3 (#163)

fix SonarQube warnings in modbus.py

* Sonar qube 3 (#164)


* fix SonarQube warnings

* Sonar qube 3 (#165)

* cleanup

* Add support for TSUN Titan inverter
Fixes #161


* fix SonarQube warnings

* fix error

* rename field "config"

* SonarQube reads flake8 output

* don't stop on flake8 errors

* flake8 scan only app/src for SonarQube

* update flake8 run

* ignore flake8 C901

* cleanup

* fix linter warnings

* ignore changed *.yml files

* read sensor list solarman data packets

* catch 'No route to' error and log only in debug mode

* fix unit tests

* add sensor_list configuration

* adapt unit tests

* fix SonarQube warnings

* Sonar qube 3 (#166)

* add unittests for mqtt.py

* add mock

* move test requirements into a file

* fix unit tests

* fix formating

* initial version

* fix SonarQube warning
This commit is contained in:
Stefan Allius
2024-08-23 21:24:01 +02:00
committed by GitHub
parent db1169f61f
commit 58b42f7d7c
27 changed files with 899 additions and 500 deletions

View File

@@ -1,5 +1,6 @@
# test_with_pytest.py
import pytest, logging, asyncio
from math import isclose
from app.src.gen3.talent import Talent, Control
from app.src.config import Config
from app.src.infos import Infos, Register
@@ -27,7 +28,7 @@ class MemoryStream(Talent):
super().__init__(server_side)
if server_side:
self.mb.timeout = 0.4 # overwrite for faster testing
self.mb_start_timeout = 0.5
self.mb_first_timeout = 0.5
self.mb_timeout = 0.5
self.writer = Writer()
self.__msg = msg
@@ -97,12 +98,12 @@ class MemoryStream(Talent):
@pytest.fixture
def msg_contact_info(): # Contact Info message
Config.config = {'tsun':{'enabled': True}}
Config.act_config = {'tsun':{'enabled': True}}
return b'\x00\x00\x00\x2c\x10R170000000000001\x91\x00\x08solarhub\x0fsolarhub\x40123456'
@pytest.fixture
def msg_contact_info_long_id(): # Contact Info message with longer ID
Config.config = {'tsun':{'enabled': True}}
Config.act_config = {'tsun':{'enabled': True}}
return b'\x00\x00\x00\x2d\x11R1700000000000011\x91\x00\x08solarhub\x0fsolarhub\x40123456'
@pytest.fixture
@@ -352,19 +353,19 @@ def msg_unknown(): # Get Time Request message
@pytest.fixture
def config_tsun_allow_all():
Config.config = {'tsun':{'enabled': True}, 'inverters':{'allow_all':True}}
Config.act_config = {'tsun':{'enabled': True}, 'inverters':{'allow_all':True}}
@pytest.fixture
def config_no_tsun_inv1():
Config.config = {'tsun':{'enabled': False},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof'}}}
Config.act_config = {'tsun':{'enabled': False},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof'}}}
@pytest.fixture
def config_tsun_inv1():
Config.config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof'}}}
Config.act_config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof'}}}
@pytest.fixture
def config_no_modbus_poll():
Config.config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': False, 'suggested_area':'roof'}}}
Config.act_config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': False, 'suggested_area':'roof'}}}
@pytest.fixture
def msg_ota_req(): # Over the air update request from tsun cloud
@@ -540,7 +541,7 @@ def test_read_message(msg_contact_info):
m.close()
def test_read_message_twice(config_no_tsun_inv1, msg_inverter_ind):
config_no_tsun_inv1
_ = config_no_tsun_inv1
m = MemoryStream(msg_inverter_ind, (0,))
m.append_msg(msg_inverter_ind)
m.read() # read complete msg, and dispatch msg
@@ -621,7 +622,7 @@ def test_read_message_in_chunks2(msg_contact_info):
m.close()
def test_read_two_messages(config_tsun_allow_all, msg2_contact_info,msg_contact_rsp,msg_contact_rsp2):
config_tsun_allow_all
_ = config_tsun_allow_all
m = MemoryStream(msg2_contact_info, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -651,7 +652,7 @@ def test_read_two_messages(config_tsun_allow_all, msg2_contact_info,msg_contact_
m.close()
def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_contact_rsp, (0,), False)
m.await_conn_resp_cnt = 1
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -671,7 +672,7 @@ def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp):
m.close()
def test_msg_contact_resp_2(config_tsun_inv1, msg_contact_rsp):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_contact_rsp, (0,), False)
m.await_conn_resp_cnt = 0
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -691,7 +692,7 @@ def test_msg_contact_resp_2(config_tsun_inv1, msg_contact_rsp):
m.close()
def test_msg_contact_resp_3(config_tsun_inv1, msg_contact_rsp):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_contact_rsp, (0,), True)
m.await_conn_resp_cnt = 0
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -711,7 +712,7 @@ def test_msg_contact_resp_3(config_tsun_inv1, msg_contact_rsp):
m.close()
def test_msg_contact_invalid(config_tsun_inv1, msg_contact_invalid):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_contact_invalid, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -729,7 +730,7 @@ def test_msg_contact_invalid(config_tsun_inv1, msg_contact_invalid):
m.close()
def test_msg_get_time(config_tsun_inv1, msg_get_time):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_get_time, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -748,7 +749,7 @@ def test_msg_get_time(config_tsun_inv1, msg_get_time):
m.close()
def test_msg_get_time_autark(config_no_tsun_inv1, msg_get_time):
config_no_tsun_inv1
_ = config_no_tsun_inv1
m = MemoryStream(msg_get_time, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -767,7 +768,7 @@ def test_msg_get_time_autark(config_no_tsun_inv1, msg_get_time):
m.close()
def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_time_rsp, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -786,7 +787,7 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
m.close()
def test_msg_time_resp_autark(config_no_tsun_inv1, msg_time_rsp):
config_no_tsun_inv1
_ = config_no_tsun_inv1
m = MemoryStream(msg_time_rsp, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -805,7 +806,7 @@ def test_msg_time_resp_autark(config_no_tsun_inv1, msg_time_rsp):
m.close()
def test_msg_time_inv_resp(config_tsun_inv1, msg_time_rsp_inv):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_time_rsp_inv, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -824,7 +825,7 @@ def test_msg_time_inv_resp(config_tsun_inv1, msg_time_rsp_inv):
m.close()
def test_msg_time_invalid(config_tsun_inv1, msg_time_invalid):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_time_invalid, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -843,7 +844,7 @@ def test_msg_time_invalid(config_tsun_inv1, msg_time_invalid):
m.close()
def test_msg_time_invalid_autark(config_no_tsun_inv1, msg_time_invalid):
config_no_tsun_inv1
_ = config_no_tsun_inv1
m = MemoryStream(msg_time_invalid, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -862,7 +863,7 @@ def test_msg_time_invalid_autark(config_no_tsun_inv1, msg_time_invalid):
m.close()
def test_msg_cntrl_ind(config_tsun_inv1, msg_controller_ind, msg_controller_ind_ts_offs, msg_controller_ack):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_controller_ind, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -885,7 +886,7 @@ def test_msg_cntrl_ind(config_tsun_inv1, msg_controller_ind, msg_controller_ind_
m.close()
def test_msg_cntrl_ack(config_tsun_inv1, msg_controller_ack):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_controller_ack, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -903,7 +904,7 @@ def test_msg_cntrl_ack(config_tsun_inv1, msg_controller_ack):
m.close()
def test_msg_cntrl_invalid(config_tsun_inv1, msg_controller_invalid):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_controller_invalid, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -926,7 +927,7 @@ def test_msg_cntrl_invalid(config_tsun_inv1, msg_controller_invalid):
m.close()
def test_msg_inv_ind(config_tsun_inv1, msg_inverter_ind, msg_inverter_ind_ts_offs, msg_inverter_ack):
config_tsun_inv1
_ = config_tsun_inv1
tracer.setLevel(logging.DEBUG)
m = MemoryStream(msg_inverter_ind, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -950,7 +951,7 @@ def test_msg_inv_ind(config_tsun_inv1, msg_inverter_ind, msg_inverter_ind_ts_off
m.close()
def test_msg_inv_ind1(config_tsun_inv1, msg_inverter_ind2, msg_inverter_ind_ts_offs, msg_inverter_ack):
config_tsun_inv1
_ = config_tsun_inv1
tracer.setLevel(logging.DEBUG)
m = MemoryStream(msg_inverter_ind2, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -974,7 +975,7 @@ def test_msg_inv_ind1(config_tsun_inv1, msg_inverter_ind2, msg_inverter_ind_ts_o
m.close()
def test_msg_inv_ind2(config_tsun_inv1, msg_inverter_ind_new, msg_inverter_ind_ts_offs, msg_inverter_ack):
config_tsun_inv1
_ = config_tsun_inv1
tracer.setLevel(logging.DEBUG)
m = MemoryStream(msg_inverter_ind_new, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -1002,7 +1003,7 @@ def test_msg_inv_ind2(config_tsun_inv1, msg_inverter_ind_new, msg_inverter_ind_t
def test_msg_inv_ind3(config_tsun_inv1, msg_inverter_ind_0w, msg_inverter_ack):
'''test that after close the invert_status will be resetted if the grid power is <2W'''
config_tsun_inv1
_ = config_tsun_inv1
tracer.setLevel(logging.DEBUG)
m = MemoryStream(msg_inverter_ind_0w, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -1023,13 +1024,13 @@ def test_msg_inv_ind3(config_tsun_inv1, msg_inverter_ind_0w, msg_inverter_ack):
assert m._forward_buffer==msg_inverter_ind_0w
assert m._send_buffer==msg_inverter_ack
assert m.db.get_db_value(Register.INVERTER_STATUS) == None
assert m.db.db['grid']['Output_Power'] == 0.5
assert isclose(m.db.db['grid']['Output_Power'], 0.5)
m.close()
assert m.db.get_db_value(Register.INVERTER_STATUS) == 0
def test_msg_inv_ack(config_tsun_inv1, msg_inverter_ack):
config_tsun_inv1
_ = config_tsun_inv1
tracer.setLevel(logging.ERROR)
m = MemoryStream(msg_inverter_ack, (0,), False)
@@ -1049,7 +1050,7 @@ def test_msg_inv_ack(config_tsun_inv1, msg_inverter_ack):
m.close()
def test_msg_inv_invalid(config_tsun_inv1, msg_inverter_invalid):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_inverter_invalid, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
@@ -1072,7 +1073,7 @@ def test_msg_inv_invalid(config_tsun_inv1, msg_inverter_invalid):
m.close()
def test_msg_ota_req(config_tsun_inv1, msg_ota_req):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_ota_req, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.db.stat['proxy']['OTA_Start_Msg'] = 0
@@ -1097,7 +1098,7 @@ def test_msg_ota_req(config_tsun_inv1, msg_ota_req):
m.close()
def test_msg_ota_ack(config_tsun_inv1, msg_ota_ack):
config_tsun_inv1
_ = config_tsun_inv1
tracer.setLevel(logging.ERROR)
m = MemoryStream(msg_ota_ack, (0,), False)
@@ -1124,7 +1125,7 @@ def test_msg_ota_ack(config_tsun_inv1, msg_ota_ack):
m.close()
def test_msg_ota_invalid(config_tsun_inv1, msg_ota_invalid):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_ota_invalid, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.db.stat['proxy']['OTA_Start_Msg'] = 0
@@ -1206,15 +1207,15 @@ def test_timestamp_cnv():
m = MemoryStream(b'')
ts = 1722645998453 # Saturday, 3. August 2024 00:46:38.453 (GMT+2:00)
utc =1722638798.453 # GMT: Friday, 2. August 2024 22:46:38.453
assert utc == m._utcfromts(ts)
assert isclose(utc, m._utcfromts(ts))
ts = 1691246944000 # Saturday, 5. August 2023 14:49:04 (GMT+2:00)
utc =1691239744.0 # GMT: Saturday, 5. August 2023 12:49:04
assert utc == m._utcfromts(ts)
assert isclose(utc, m._utcfromts(ts))
ts = 1704152544000 # Monday, 1. January 2024 23:42:24 (GMT+1:00)
utc =1704148944.0 # GMT: Monday, 1. January 2024 22:42:24
assert utc == m._utcfromts(ts)
assert isclose(utc, m._utcfromts(ts))
m.close()
@@ -1267,7 +1268,7 @@ def test_proxy_counter():
m.close()
def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(b'')
m.id_str = b"R170000000000001"
m.state = State.up
@@ -1298,7 +1299,7 @@ def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd):
m.close()
def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(b'')
m.id_str = b"R170000000000001"
@@ -1328,7 +1329,7 @@ def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd):
m.close()
def test_msg_modbus_req3(config_tsun_inv1, msg_modbus_cmd_crc_err):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(b'')
m.id_str = b"R170000000000001"
c = m.createClientStream(msg_modbus_cmd_crc_err)
@@ -1357,7 +1358,7 @@ def test_msg_modbus_req3(config_tsun_inv1, msg_modbus_cmd_crc_err):
def test_msg_modbus_rsp1(config_tsun_inv1, msg_modbus_rsp):
'''Modbus response without a valid Modbus request must be dropped'''
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_modbus_rsp)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.db.stat['proxy']['Modbus_Command'] = 0
@@ -1378,7 +1379,7 @@ def test_msg_modbus_rsp1(config_tsun_inv1, msg_modbus_rsp):
def test_msg_modbus_cloud_rsp(config_tsun_inv1, msg_modbus_rsp):
'''Modbus response from TSUN without a valid Modbus request must be dropped'''
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_modbus_rsp, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.db.stat['proxy']['Unknown_Msg'] = 0
@@ -1401,7 +1402,7 @@ def test_msg_modbus_cloud_rsp(config_tsun_inv1, msg_modbus_rsp):
def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp20):
'''Modbus response with a valid Modbus request must be forwarded'''
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_modbus_rsp20)
m.append_msg(msg_modbus_rsp20)
@@ -1431,7 +1432,7 @@ def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp20):
def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp21):
'''Modbus response with a valid Modbus request must be forwarded'''
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_modbus_rsp21)
m.append_msg(msg_modbus_rsp21)
@@ -1460,7 +1461,7 @@ def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp21):
m.close()
def test_msg_modbus_invalid(config_tsun_inv1, msg_modbus_inv):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_modbus_inv, (0,), False)
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.db.stat['proxy']['Modbus_Command'] = 0
@@ -1480,7 +1481,7 @@ def test_msg_modbus_invalid(config_tsun_inv1, msg_modbus_inv):
m.close()
def test_msg_modbus_fragment(config_tsun_inv1, msg_modbus_rsp20):
config_tsun_inv1
_ = config_tsun_inv1
# receive more bytes than expected (7 bytes from the next msg)
m = MemoryStream(msg_modbus_rsp20+b'\x00\x00\x00\x45\x10\x52\x31', (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
@@ -1512,7 +1513,7 @@ def test_msg_modbus_fragment(config_tsun_inv1, msg_modbus_rsp20):
@pytest.mark.asyncio
async def test_msg_build_modbus_req(config_tsun_inv1, msg_modbus_cmd):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(b'', (0,), True)
m.id_str = b"R170000000000001"
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
@@ -1538,7 +1539,7 @@ async def test_msg_build_modbus_req(config_tsun_inv1, msg_modbus_cmd):
m.close()
def test_modbus_no_polling(config_no_modbus_poll, msg_get_time):
config_no_modbus_poll
_ = config_no_modbus_poll
m = MemoryStream(msg_get_time, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.modbus_polling = False
@@ -1559,7 +1560,7 @@ def test_modbus_no_polling(config_no_modbus_poll, msg_get_time):
@pytest.mark.asyncio
async def test_modbus_polling(config_tsun_inv1, msg_inverter_ind):
config_tsun_inv1
_ = config_tsun_inv1
assert asyncio.get_running_loop()
m = MemoryStream(msg_inverter_ind, (0,))
@@ -1581,7 +1582,7 @@ async def test_modbus_polling(config_tsun_inv1, msg_inverter_ind):
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m._send_buffer = bytearray(0) # clear send buffer for next test
assert m.mb_timeout == 0.5
assert isclose(m.mb_timeout, 0.5)
assert next(m.mb_timer.exp_count) == 0
await asyncio.sleep(0.5)
@@ -1599,7 +1600,7 @@ async def test_modbus_polling(config_tsun_inv1, msg_inverter_ind):
m.close()
def test_broken_recv_buf(config_tsun_allow_all, broken_recv_buf):
config_tsun_allow_all
_ = config_tsun_allow_all
m = MemoryStream(broken_recv_buf, (0,))
m.db.stat['proxy']['Unknown_Ctrl'] = 0
assert m.db.stat['proxy']['Invalid_Data_Type'] == 0