update to 1.0.2
This commit is contained in:
361
source/tests/final_result.py
Normal file
361
source/tests/final_result.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""The formatted register values."""
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
altered_input_register_values = [
|
||||
236.4,
|
||||
4.0,
|
||||
1243.0,
|
||||
629.5,
|
||||
737.1,
|
||||
1.4,
|
||||
0.6,
|
||||
49.96,
|
||||
44.0,
|
||||
2.0,
|
||||
940.0,
|
||||
484.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
428.1,
|
||||
0.2,
|
||||
128.0,
|
||||
1.0,
|
||||
29.0,
|
||||
1.0,
|
||||
0.0,
|
||||
2.0,
|
||||
98.0,
|
||||
3914.1,
|
||||
0.4,
|
||||
4535.7,
|
||||
8.2,
|
||||
5.2,
|
||||
30.0,
|
||||
12288.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
20792.6,
|
||||
3776.9,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
56.9,
|
||||
33161.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
235.9,
|
||||
4.0,
|
||||
937.0,
|
||||
49.97,
|
||||
232.3,
|
||||
1.3,
|
||||
45.0,
|
||||
49.98,
|
||||
235.4,
|
||||
1.5,
|
||||
261.0,
|
||||
49.98,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2.0,
|
||||
2.0,
|
||||
13476.8,
|
||||
107.5,
|
||||
69.1,
|
||||
0.0,
|
||||
0.0,
|
||||
0.2,
|
||||
35229.0,
|
||||
68.0,
|
||||
41.3,
|
||||
1.75,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
21.3,
|
||||
19.0,
|
||||
3.344,
|
||||
3.329,
|
||||
0.0,
|
||||
94.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1247.0,
|
||||
-1.0,
|
||||
2338.0,
|
||||
-9973.0,
|
||||
5000.0,
|
||||
-5000.0,
|
||||
0.0,
|
||||
0.0,
|
||||
245.0,
|
||||
12042.0,
|
||||
0.0,
|
||||
0.0,
|
||||
100.0,
|
||||
40.0,
|
||||
4.0,
|
||||
0.0,
|
||||
1.0,
|
||||
]
|
||||
altered_holding_register_values = [
|
||||
"H34T15H9022043",
|
||||
"Solax ",
|
||||
" ",
|
||||
900.0,
|
||||
15.0,
|
||||
900.0,
|
||||
195.5,
|
||||
264.5,
|
||||
49.5,
|
||||
50.5,
|
||||
1.0,
|
||||
0.0,
|
||||
259.9,
|
||||
195.5,
|
||||
264.5,
|
||||
47.5,
|
||||
51.5,
|
||||
100.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.94,
|
||||
0.9,
|
||||
20.0,
|
||||
50.0,
|
||||
80.0,
|
||||
100.0,
|
||||
1.05,
|
||||
1.0,
|
||||
6.0,
|
||||
0.0,
|
||||
0.0,
|
||||
213.9,
|
||||
223.1,
|
||||
236.9,
|
||||
246.1,
|
||||
0.0,
|
||||
10.0,
|
||||
0.0,
|
||||
0.0,
|
||||
20.0,
|
||||
5.0,
|
||||
0.0,
|
||||
7500.0,
|
||||
-7500.0,
|
||||
47.55,
|
||||
50.05,
|
||||
195.5,
|
||||
253.0,
|
||||
0.0,
|
||||
0.0,
|
||||
47.55,
|
||||
50.05,
|
||||
195.5,
|
||||
253.0,
|
||||
0.0,
|
||||
1.0,
|
||||
900.0,
|
||||
29.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
6.0,
|
||||
27.0,
|
||||
8.0,
|
||||
datetime.datetime(2024, 3, 19, 14, 58, 43),
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
20.0,
|
||||
21.0,
|
||||
0.0,
|
||||
30.0,
|
||||
0.0,
|
||||
30.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
245.0,
|
||||
0.0,
|
||||
"SPNXLAQHWX",
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
60000.0,
|
||||
1500.0,
|
||||
0.0,
|
||||
20.0,
|
||||
0.0,
|
||||
15000.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.3,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.3,
|
||||
0.01,
|
||||
0.01,
|
||||
0.01,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
32.0,
|
||||
1.0,
|
||||
50.2,
|
||||
5.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2014.0,
|
||||
49.8,
|
||||
2.0,
|
||||
0.005,
|
||||
0.0,
|
||||
30.0,
|
||||
50.2,
|
||||
49.8,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
3000.0,
|
||||
900.0,
|
||||
900.0,
|
||||
3000.0,
|
||||
3000.0,
|
||||
1.0,
|
||||
0.09,
|
||||
220.0,
|
||||
253.0,
|
||||
257.6,
|
||||
207.0,
|
||||
0.1,
|
||||
0.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
3.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
2.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
266.8,
|
||||
181.8,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
30.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2000.0,
|
||||
80.0,
|
||||
1000.0,
|
||||
40.0,
|
||||
30.0,
|
||||
800.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
50.0,
|
||||
0.0,
|
||||
]
|
||||
0
source/tests/mock_packages/mysql/__init__.py
Normal file
0
source/tests/mock_packages/mysql/__init__.py
Normal file
51
source/tests/mock_packages/mysql/connection.py
Normal file
51
source/tests/mock_packages/mysql/connection.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Module containing a connection class."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from .cursor import Cursor
|
||||
from .error import Error
|
||||
|
||||
open_connections_file = Path(__file__).parent / "open_connections.txt"
|
||||
|
||||
|
||||
class Connection:
|
||||
"""Class mocking a connection to a MySQL server."""
|
||||
|
||||
def __init__(self, *args, consider_open=True, **kwargs):
|
||||
self.open_connections += consider_open
|
||||
self.open: bool = consider_open
|
||||
|
||||
def cursor(self) -> Cursor:
|
||||
"""Return the cursor for the connection."""
|
||||
|
||||
self._cursor = Cursor()
|
||||
return self._cursor
|
||||
|
||||
def close(self):
|
||||
"""Close connection."""
|
||||
|
||||
if not self.open:
|
||||
raise Error("Connection already closed")
|
||||
|
||||
self.open = False
|
||||
self.open_connections -= 1
|
||||
|
||||
def commit(self):
|
||||
"""Save all changes to the database."""
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""Return if the connection is open."""
|
||||
|
||||
return self.open
|
||||
|
||||
@property
|
||||
def open_connections(self):
|
||||
with open(open_connections_file, "r", encoding="utf-8") as f:
|
||||
contents = f.read()
|
||||
return int(contents)
|
||||
|
||||
@open_connections.setter
|
||||
def open_connections(self, value: int):
|
||||
value = value if value > 0 else 0
|
||||
with open(open_connections_file, "w", encoding="utf-8") as f:
|
||||
f.write(str(value))
|
||||
10
source/tests/mock_packages/mysql/connector.py
Normal file
10
source/tests/mock_packages/mysql/connector.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Module with mock implementation of interacting with a MySQL server."""
|
||||
|
||||
from .connection import Connection
|
||||
from .error import Error
|
||||
|
||||
|
||||
def connect(*args, **kwargs) -> Connection:
|
||||
"""Mock connect to a MySQL server."""
|
||||
|
||||
return Connection()
|
||||
56
source/tests/mock_packages/mysql/cursor.py
Normal file
56
source/tests/mock_packages/mysql/cursor.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Module with cursor implementation."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from .error import Error
|
||||
|
||||
|
||||
config_file = Path(__file__).parent / "cursor_config.json"
|
||||
|
||||
|
||||
class Cursor:
|
||||
"""Cursor of a MySQL connection."""
|
||||
|
||||
def __init__(self, raise_err: bool = True):
|
||||
if self._raise_error_in_init and raise_err:
|
||||
raise Error
|
||||
|
||||
def execute(self, *args, **kwargs) -> None:
|
||||
"""Mock execute a query."""
|
||||
|
||||
if self._raise_error:
|
||||
raise Error
|
||||
|
||||
def executemany(self, *args, **kwargs) -> None:
|
||||
"""Mock execute a query multiple times with different data."""
|
||||
|
||||
if self._raise_error:
|
||||
raise Error
|
||||
|
||||
@property
|
||||
def _raise_error(self):
|
||||
return self._read_configs()["raise_error"]
|
||||
|
||||
@property
|
||||
def _raise_error_in_init(self):
|
||||
return self._read_configs()["raise_error_in_init"]
|
||||
|
||||
@_raise_error.setter
|
||||
def _raise_error(self, value: int):
|
||||
data = self._read_configs()
|
||||
data["raise_error"] = value
|
||||
self._save_configs(data)
|
||||
|
||||
@_raise_error_in_init.setter
|
||||
def _raise_error_in_init(self, value: int):
|
||||
data = self._read_configs()
|
||||
data["raise_error_in_init"] = value
|
||||
self._save_configs(data)
|
||||
|
||||
def _read_configs(self):
|
||||
with open(config_file, "r", encoding="utf-8") as f:
|
||||
return json.loads(f.read())
|
||||
|
||||
def _save_configs(self, data):
|
||||
with open(config_file, "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(data, indent=4))
|
||||
4
source/tests/mock_packages/mysql/cursor_config.json
Normal file
4
source/tests/mock_packages/mysql/cursor_config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"raise_error_in_init": false,
|
||||
"raise_error": false
|
||||
}
|
||||
5
source/tests/mock_packages/mysql/error.py
Normal file
5
source/tests/mock_packages/mysql/error.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Module containing the basic error class of this package."""
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Basic error class of this package."""
|
||||
1
source/tests/mock_packages/mysql/open_connections.txt
Normal file
1
source/tests/mock_packages/mysql/open_connections.txt
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
0
source/tests/mock_packages/pymodbus/__init__.py
Normal file
0
source/tests/mock_packages/pymodbus/__init__.py
Normal file
30
source/tests/mock_packages/pymodbus/client.py
Normal file
30
source/tests/mock_packages/pymodbus/client.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Class to mimick `pymodbus.client`. It is used when running the tests."""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from .registers_output import raw_holding_register_values, raw_input_register_values
|
||||
from .utils import getnext
|
||||
|
||||
Registers = namedtuple("Registers", ["registers"])
|
||||
|
||||
|
||||
class ModbusSerialClient:
|
||||
"""Class mimicking `pymodbus.client.ModbusSerialClient`."""
|
||||
|
||||
def __init__(self, *_, **__) -> None:
|
||||
self._holding_registers_gen = getnext(raw_holding_register_values)
|
||||
self._input_registers_gen = getnext(raw_input_register_values)
|
||||
|
||||
def connect(self):
|
||||
"""Mimick connecting the inverter and return the success code."""
|
||||
|
||||
return True
|
||||
|
||||
def read_holding_registers(self, count, *_, **__):
|
||||
"""Read holding register values from inverter."""
|
||||
|
||||
return Registers([next(self._holding_registers_gen) for _ in range(count)])
|
||||
|
||||
def read_input_registers(self, count, *_, **__):
|
||||
"""Read input register values from inverter."""
|
||||
return Registers([next(self._input_registers_gen) for _ in range(count)])
|
||||
806
source/tests/mock_packages/pymodbus/registers_output.py
Normal file
806
source/tests/mock_packages/pymodbus/registers_output.py
Normal file
@@ -0,0 +1,806 @@
|
||||
"""Raw register values, just read from the inverter."""
|
||||
|
||||
raw_input_register_values = [
|
||||
2364,
|
||||
40,
|
||||
1243,
|
||||
6295,
|
||||
7371,
|
||||
14,
|
||||
6,
|
||||
4996,
|
||||
44,
|
||||
2,
|
||||
940,
|
||||
484,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4281,
|
||||
2,
|
||||
128,
|
||||
1,
|
||||
29,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
98,
|
||||
39141,
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
45357,
|
||||
0,
|
||||
82,
|
||||
52,
|
||||
300,
|
||||
12288,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
52,
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
47644,
|
||||
31,
|
||||
50010,
|
||||
5,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
569,
|
||||
0,
|
||||
3930,
|
||||
5,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2359,
|
||||
40,
|
||||
937,
|
||||
4997,
|
||||
2323,
|
||||
13,
|
||||
45,
|
||||
4998,
|
||||
2354,
|
||||
15,
|
||||
261,
|
||||
4998,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
3696,
|
||||
2,
|
||||
1075,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
691,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
24610,
|
||||
5,
|
||||
680,
|
||||
0,
|
||||
4130,
|
||||
0,
|
||||
175,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
213,
|
||||
190,
|
||||
3344,
|
||||
3329,
|
||||
0,
|
||||
94,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
64289,
|
||||
65535,
|
||||
65535,
|
||||
65535,
|
||||
2338,
|
||||
0,
|
||||
55563,
|
||||
65535,
|
||||
5000,
|
||||
0,
|
||||
60536,
|
||||
65535,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
245,
|
||||
0,
|
||||
12042,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
100,
|
||||
40,
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
raw_holding_register_values = [
|
||||
18483,
|
||||
13396,
|
||||
12597,
|
||||
18489,
|
||||
12338,
|
||||
12848,
|
||||
13363,
|
||||
21359,
|
||||
27745,
|
||||
30752,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
8224,
|
||||
1800,
|
||||
900,
|
||||
15,
|
||||
900,
|
||||
1955,
|
||||
2645,
|
||||
4950,
|
||||
5050,
|
||||
1,
|
||||
0,
|
||||
2599,
|
||||
1955,
|
||||
2645,
|
||||
4750,
|
||||
5150,
|
||||
275,
|
||||
100,
|
||||
0,
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
94,
|
||||
90,
|
||||
20,
|
||||
50,
|
||||
80,
|
||||
100,
|
||||
105,
|
||||
100,
|
||||
6,
|
||||
0,
|
||||
0,
|
||||
2139,
|
||||
2231,
|
||||
2369,
|
||||
2461,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
5,
|
||||
0,
|
||||
7500,
|
||||
58036,
|
||||
4755,
|
||||
5005,
|
||||
1955,
|
||||
2530,
|
||||
0,
|
||||
0,
|
||||
4755,
|
||||
5005,
|
||||
1955,
|
||||
2530,
|
||||
0,
|
||||
1,
|
||||
900,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
29,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
6,
|
||||
27,
|
||||
8,
|
||||
43,
|
||||
58,
|
||||
14,
|
||||
19,
|
||||
3,
|
||||
24,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
200,
|
||||
210,
|
||||
0,
|
||||
10240,
|
||||
30,
|
||||
25610,
|
||||
12830,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
15127,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2450,
|
||||
0,
|
||||
21328,
|
||||
20056,
|
||||
19521,
|
||||
20808,
|
||||
22360,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
60000,
|
||||
1500,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
15000,
|
||||
0,
|
||||
0,
|
||||
300,
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
300,
|
||||
10,
|
||||
10,
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
5020,
|
||||
5,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2014,
|
||||
4980,
|
||||
2,
|
||||
5,
|
||||
0,
|
||||
30,
|
||||
5020,
|
||||
4980,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
30000,
|
||||
900,
|
||||
900,
|
||||
30000,
|
||||
30000,
|
||||
1000,
|
||||
900,
|
||||
2200,
|
||||
2530,
|
||||
2576,
|
||||
2070,
|
||||
10,
|
||||
0,
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
0,
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
100,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2668,
|
||||
1818,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2000,
|
||||
80,
|
||||
1000,
|
||||
40,
|
||||
30,
|
||||
800,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
500,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
95,
|
||||
1000,
|
||||
30,
|
||||
60,
|
||||
0,
|
||||
15127,
|
||||
0,
|
||||
2000,
|
||||
0,
|
||||
0,
|
||||
2000,
|
||||
30,
|
||||
80,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
11
source/tests/mock_packages/pymodbus/utils.py
Normal file
11
source/tests/mock_packages/pymodbus/utils.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Mock `pymodbus` utils."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def getnext(iterable: list, default: Any = 0):
|
||||
"""Yield one item at a time and yield `default` when iterable is exhausted."""
|
||||
|
||||
yield from iterable
|
||||
while True:
|
||||
yield default
|
||||
68
source/tests/test_mysql_connector.py
Normal file
68
source/tests/test_mysql_connector.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import sys
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
|
||||
from tests.mock_packages.mysql.connection import Connection
|
||||
from tests.mock_packages.mysql.cursor import Cursor
|
||||
|
||||
environ["MYSQL_DB_USERNAME"] = ""
|
||||
environ["MYSQL_DB_HOST_IP"] = ""
|
||||
environ["MYSQL_DB_PASSWORD"] = ""
|
||||
environ["MYSQL_DB_DATABASE"] = ""
|
||||
current: Path = Path().resolve()
|
||||
paths_to_extend = [current / "tests" / "mock_packages", current / "src"]
|
||||
for path in paths_to_extend:
|
||||
path_as_string = str(path)
|
||||
if path_as_string not in sys.path:
|
||||
sys.path.append(path_as_string)
|
||||
|
||||
from database import read_and_save
|
||||
|
||||
|
||||
class MysqlConnectionLeakageTests(TestCase):
|
||||
"""Tests to see if the class leaves any connections open after its processes."""
|
||||
|
||||
def _open_connections(self):
|
||||
return Connection(consider_open=False).open_connections
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""Set up the data after each test case."""
|
||||
|
||||
Cursor(raise_err=False)._raise_error = False
|
||||
Cursor(raise_err=False)._raise_error_in_init = False
|
||||
Connection().open_connections = 0
|
||||
|
||||
def test_bulk_transfer_without_error(self):
|
||||
"""Test if `bulk_transfer` leaves any hanging connections in normal operations."""
|
||||
|
||||
for _ in range(3):
|
||||
read_and_save.bulk_save()
|
||||
|
||||
self.assertEqual(self._open_connections(), 0)
|
||||
|
||||
def test_bulk_transfer_with_error_in_cursor_init(self):
|
||||
"""Test if `bulk_transfer` leaves any hanging connections with errors when instantiating
|
||||
cursor."""
|
||||
|
||||
Cursor()._raise_error_in_init = True
|
||||
for _ in range(3):
|
||||
try:
|
||||
read_and_save.bulk_save()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.assertEqual(self._open_connections(), 0)
|
||||
|
||||
def test_bulk_transfer_with_error_in_cursor(self):
|
||||
"""Test if `bulk_transfer` leaves any hanging connections with errors when executing
|
||||
a query in the cursor."""
|
||||
|
||||
Cursor()._raise_error = True
|
||||
for _ in range(3):
|
||||
try:
|
||||
read_and_save.bulk_save()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.assertEqual(self._open_connections(), 0)
|
||||
254
source/tests/test_register_data.py
Normal file
254
source/tests/test_register_data.py
Normal file
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
Test scenarios:
|
||||
Report any of the following anomalies:
|
||||
1. Not valid dictionary syntax [done]
|
||||
2. Dictionary key duplicates [done]
|
||||
3. "address" duplicates
|
||||
4. The value field has different keys than: [done]
|
||||
* address
|
||||
* register_type
|
||||
* data_format
|
||||
* si_adj
|
||||
* signed
|
||||
* data_unit
|
||||
* data_length
|
||||
* description
|
||||
5. "dataformat" starts with 'uint' but "signed" is different than "False"
|
||||
6. "dataformat" starts with 'int' but "signed" is different than "True"
|
||||
7. If "data_format" contains "int" in the name,
|
||||
"data_length" != <the number inside "data_format"> / 16 or 1 if no number is found
|
||||
in "data_length"
|
||||
8. "data_unit" is not one of following:
|
||||
{%, A, C, Hx, KWh, N/A, V, VA, Var, W, Wh, hour, second}
|
||||
Note: values are case sensitive
|
||||
9. "register_type" is different than {input, holding}
|
||||
Note: no values are case sensitive
|
||||
10. the key name contains "volt" but the "data_unit" is different than "V"
|
||||
11. the key name contains "current" but the "data_unit" is different than "A"
|
||||
12. "address" overlapping
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
from itertools import filterfalse
|
||||
from pathlib import Path
|
||||
|
||||
from src.solaxx3.solax_registers_info import SolaxRegistersInfo
|
||||
|
||||
import_path = Path().resolve() / "tests" / "mock_packages"
|
||||
if str(import_path) not in sys.path:
|
||||
sys.path.append(str(import_path))
|
||||
|
||||
REGISTER_FILE = "src/solaxx3/solax_registers_info.py"
|
||||
|
||||
|
||||
class RegisterTests(unittest.TestCase):
|
||||
"""Tests for SolaxRegistersInfo."""
|
||||
|
||||
registers = SolaxRegistersInfo._registers
|
||||
|
||||
def test_tc1_valid_dictionary(self):
|
||||
"""Prints an error if `registers` is a dictionary."""
|
||||
|
||||
self.assertIsInstance(self.registers, dict)
|
||||
|
||||
def test_tc2_duplicate_keys(self):
|
||||
"""Prints an error if 'registers' has any duplicate keys."""
|
||||
|
||||
PATTERN = r"""
|
||||
"(?P<key>.*?)" # The key of the dictionary
|
||||
\s*: # colon
|
||||
\s*{ # start of value
|
||||
"""
|
||||
regex = re.compile(PATTERN, re.VERBOSE)
|
||||
|
||||
with open(REGISTER_FILE, "r", encoding="utf-8") as dictionary:
|
||||
list_keys = []
|
||||
|
||||
for line_number, line in filterfalse(
|
||||
lambda x: x[1].lstrip().startswith("#"), enumerate(dictionary, start=1)
|
||||
):
|
||||
re_match = regex.search(line)
|
||||
|
||||
if re_match is not None:
|
||||
key = re_match.group("key")
|
||||
self.assertNotIn(key, list_keys, f"line={line_number}")
|
||||
list_keys.append(key)
|
||||
|
||||
def test_tc4_correct_value_field(self):
|
||||
"""Prints an error if the keys do not have the following keys:
|
||||
- address, register_type, data_format, si_adj, signed, data_unit, data_length,
|
||||
description"""
|
||||
|
||||
VALID_SUBKEYS = [
|
||||
"address",
|
||||
"data_format",
|
||||
"data_length",
|
||||
"data_unit",
|
||||
"description",
|
||||
"register_type",
|
||||
"si_adj",
|
||||
"signed",
|
||||
]
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertListEqual(
|
||||
sorted(list(register_value.keys())), VALID_SUBKEYS, f"dict={register!r}"
|
||||
)
|
||||
|
||||
def test_tc5_correctly_unsigned(self):
|
||||
"""Prints an error if subkey `data_format` starts with `uint` but `signed` is
|
||||
different than False."""
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertFalse(
|
||||
register_value["data_format"].startswith("uint")
|
||||
and register_value["signed"] is not False,
|
||||
f"not correctly signed: dict='{register}'",
|
||||
)
|
||||
|
||||
def test_tc6_correctly_signed(self):
|
||||
"""Prints an error if subkey `data_format` starts with 'int' but `signed`
|
||||
is different than True."""
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertFalse(
|
||||
register_value["data_format"].startswith("int")
|
||||
and register_value["signed"] is not True,
|
||||
f"not correctly signed: dict='{register}'",
|
||||
)
|
||||
|
||||
def test_tc7_correct_data_length(self):
|
||||
"""Prints an error if 'data_format' does not match 'data_length'."""
|
||||
|
||||
PATTERN = r"int(?P<bits>\d+)"
|
||||
regex = re.compile(PATTERN)
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
match = regex.search(register_value["data_format"])
|
||||
|
||||
if match:
|
||||
bits = int(match.group("bits"))
|
||||
self.assertEqual(
|
||||
bits / 16, register_value["data_length"], f"dict={register!r}"
|
||||
)
|
||||
|
||||
def test_tc8_correct_data_unit_value(self):
|
||||
"""Prints an error if `data_unit` is not %, C, Hz, KWh, N/A, V, VA, Var,
|
||||
W, Wh, hour, second."""
|
||||
|
||||
POSSIBLE_DATA_UNITS = (
|
||||
"min",
|
||||
"bps",
|
||||
"%",
|
||||
"A",
|
||||
"C",
|
||||
"Hz",
|
||||
"KWh",
|
||||
"N/A",
|
||||
"V",
|
||||
"VA",
|
||||
"Var",
|
||||
"W",
|
||||
"Wh",
|
||||
"hour",
|
||||
"second",
|
||||
)
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertGreater(
|
||||
len(register_value["data_unit"]),
|
||||
0,
|
||||
f"empty 'data_unit': dict={register!r}",
|
||||
)
|
||||
self.assertIn(
|
||||
register_value["data_unit"],
|
||||
POSSIBLE_DATA_UNITS,
|
||||
f"invalid data_unit: '{register_value['data_unit']}'; 'dict='{register}'",
|
||||
)
|
||||
|
||||
def test_tc9_correct_register_type(self):
|
||||
"""Prints an error if `register_type` is not 'input' or 'holding'."""
|
||||
|
||||
POSSIBLE_REGISTER_TYPES = ("input", "holding")
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertIn(
|
||||
register_value["register_type"],
|
||||
POSSIBLE_REGISTER_TYPES,
|
||||
f"dict='{register}'",
|
||||
)
|
||||
|
||||
def test_tc10_correct_data_unit_with_volt(self):
|
||||
"""Prints an error if the key contains 'volt' but `data_unit` is differrent
|
||||
than 'V'."""
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertFalse(
|
||||
"volt" in register
|
||||
and register_value["data_unit"] != "V"
|
||||
and "percent" not in register
|
||||
and "ratio" not in register,
|
||||
f"'data_unit' does not match name: dict={register!r}",
|
||||
)
|
||||
|
||||
def test_tc11_correct_data_unit_with_current(self):
|
||||
"""Prints an error if the key contains 'current' but `data_unit` is
|
||||
differrent than 'A'."""
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
self.assertFalse(
|
||||
"current" in register and register_value["data_unit"] != "A",
|
||||
f"'data_unit' does not match name: dict='{register}'",
|
||||
)
|
||||
|
||||
def test_tc12_correct_addresses(self):
|
||||
"""Prints an error if there is any address overlapping."""
|
||||
|
||||
holding_addresses: list = []
|
||||
input_addresses: list = []
|
||||
|
||||
for register, register_value in self.registers.items():
|
||||
addresses = (
|
||||
holding_addresses
|
||||
if register_value["register_type"] == "holding"
|
||||
else input_addresses
|
||||
)
|
||||
data_length = register_value["data_length"]
|
||||
|
||||
for x in range(data_length):
|
||||
address = register_value["address"] + x
|
||||
self.assertNotIn(address, addresses, f"dict={register!r}")
|
||||
addresses.append(address)
|
||||
|
||||
def test_tc13_subkey_duplicates(self):
|
||||
"""Prints an error if there are any subkey duplicates."""
|
||||
|
||||
PATTERN = r"""
|
||||
"(?P<subkey>.*?)" # dictionary subkey
|
||||
\s*: # colon
|
||||
\s*?[^[]+$ # value different than a dict
|
||||
"""
|
||||
|
||||
regex = re.compile(PATTERN, re.VERBOSE)
|
||||
|
||||
with open(REGISTER_FILE, "r", encoding="utf-8") as dictionary:
|
||||
subkeys = []
|
||||
|
||||
for line_number, line in filterfalse(
|
||||
lambda x: x[1].lstrip().startswith("#"), enumerate(dictionary, start=1)
|
||||
):
|
||||
match = regex.search(line)
|
||||
|
||||
if match:
|
||||
subkey = match.group("subkey")
|
||||
|
||||
self.assertNotIn(subkey, subkeys, f"line={line_number}")
|
||||
subkeys.append(subkey)
|
||||
else:
|
||||
subkeys.clear()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
48
source/tests/test_solaxx3.py
Normal file
48
source/tests/test_solaxx3.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Module to test solaxx3."""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from src.solaxx3 import solaxx3
|
||||
from tests.final_result import (
|
||||
altered_holding_register_values,
|
||||
altered_input_register_values,
|
||||
)
|
||||
|
||||
import_path = Path().resolve() / "tests" / "mock_packages"
|
||||
if str(import_path) not in sys.path:
|
||||
sys.path.append(str(import_path))
|
||||
|
||||
|
||||
class Solaxx3Tests(unittest.TestCase):
|
||||
"""Tests for SolaxX3."""
|
||||
|
||||
def test_connection(self):
|
||||
"""Test if SolaxX3 connected."""
|
||||
|
||||
s = solaxx3.SolaxX3()
|
||||
s.connect()
|
||||
self.assertEqual(s.connected, True)
|
||||
|
||||
def test_format_registers(self):
|
||||
"""Test if the module correctly formats raw register values."""
|
||||
|
||||
result = []
|
||||
s = solaxx3.SolaxX3()
|
||||
s.read_all_registers()
|
||||
|
||||
regs = solaxx3.SolaxRegistersInfo().list_register_names()
|
||||
for reg in regs:
|
||||
result.append(s.read(reg)[0])
|
||||
|
||||
self.assertEqual(
|
||||
result, altered_input_register_values + altered_holding_register_values
|
||||
)
|
||||
|
||||
def test_are_registers_identical(self):
|
||||
"""Test if holding registers' values are not equal to input registers' values."""
|
||||
|
||||
s = solaxx3.SolaxX3()
|
||||
s.read_all_registers()
|
||||
self.assertFalse(s._holding_registers_values == s._input_registers_values)
|
||||
Reference in New Issue
Block a user