S allius/pytest (#211)

* - fix pytest setup that can be startet from the rootdir
  - support python venv environment
  - add pytest.ini
  - move common settings from .vscode/settings.json into pytest.ini
  - add missing requirements
  - fix import paths for pytests

* - support python venv environment

* initial version

* - add missing requirements python-dotenv

* fix import paths for pytests

* fix pytest warnings

* initial version

* report 5 slowest test durations

* add more vscode settings for python
This commit is contained in:
Stefan Allius
2024-11-24 22:07:43 +01:00
committed by GitHub
parent 84231c034c
commit 3bada76516
40 changed files with 187 additions and 248 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
__pycache__
.pytest_cache
.venv/**
bin/**
mosquitto/**
homeassistant/**

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12.7

15
.vscode/settings.json vendored
View File

@@ -1,10 +1,12 @@
{
"python.analysis.extraPaths": [
"app/src",
".venv/lib" ],
"python.testing.pytestArgs": [
"-vv",
"app",
"-v",
"--cov=app/src",
"--cov-report=xml",
"--cov-report=html",
"app",
"system_tests"
],
"python.testing.unittestEnabled": false,
@@ -18,5 +20,10 @@
},
"files.exclude": {
"**/*.pyi": true
}
},
"python.analysis.typeEvaluation.deprecateTypingAliases": true,
"python.autoComplete.extraPaths": [
".venv/lib"
],
"coverage-gutters.coverageBaseDir": "tsun"
}

View File

@@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.11.1] - 2024-11-20
- fix pytest setup that can be startet from the rootdir
- support python venv environment
- add pytest.ini
- move common settings from .vscode/settings.json into pytest.ini
- add missing requirements
- fix import paths for pytests
- Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.5 to 3.10.11.
## [0.11.0] - 2024-10-13

View File

@@ -2,5 +2,6 @@
pytest
pytest-asyncio
pytest-cov
python-dotenv
mock
coverage

View File

@@ -6,12 +6,6 @@ from asyncio import StreamReader, StreamWriter
from typing import Self
from itertools import count
if __name__ == "app.src.async_stream":
from app.src.proxy import Proxy
from app.src.byte_fifo import ByteFifo
from app.src.async_ifc import AsyncIfc
from app.src.infos import Infos
else: # pragma: no cover
from proxy import Proxy
from byte_fifo import ByteFifo
from async_ifc import AsyncIfc

View File

@@ -1,7 +1,3 @@
if __name__ == "app.src.byte_fifo":
from app.src.messages import hex_dump_str, hex_dump_memory
else: # pragma: no cover
from messages import hex_dump_str, hex_dump_memory

View File

@@ -3,9 +3,6 @@ import struct
import logging
from typing import Generator
if __name__ == "app.src.gen3.infos_g3":
from app.src.infos import Infos, Register
else: # pragma: no cover
from infos import Infos, Register

View File

@@ -1,8 +1,5 @@
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.gen3.inverter_g3":
from app.src.inverter_base import InverterBase
from app.src.gen3.talent import Talent
else: # pragma: no cover
from inverter_base import InverterBase
from gen3.talent import Talent

View File

@@ -4,14 +4,6 @@ from zoneinfo import ZoneInfo
from datetime import datetime
from tzlocal import get_localzone
if __name__ == "app.src.gen3.talent":
from app.src.async_ifc import AsyncIfc
from app.src.messages import Message, State
from app.src.modbus import Modbus
from app.src.config import Config
from app.src.gen3.infos_g3 import InfosG3
from app.src.infos import Register
else: # pragma: no cover
from async_ifc import AsyncIfc
from messages import Message, State
from modbus import Modbus

View File

@@ -1,9 +1,6 @@
from typing import Generator
if __name__ == "app.src.gen3plus.infos_g3p":
from app.src.infos import Infos, Register, ProxyMode, Fmt
else: # pragma: no cover
from infos import Infos, Register, ProxyMode, Fmt

View File

@@ -1,10 +1,5 @@
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.gen3plus.inverter_g3p":
from app.src.inverter_base import InverterBase
from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.gen3plus.solarman_emu import SolarmanEmu
else: # pragma: no cover
from inverter_base import InverterBase
from gen3plus.solarman_v5 import SolarmanV5
from gen3plus.solarman_emu import SolarmanEmu

View File

@@ -1,12 +1,6 @@
import logging
import struct
if __name__ == "app.src.gen3plus.solarman_emu":
from app.src.async_ifc import AsyncIfc
from app.src.gen3plus.solarman_v5 import SolarmanBase
from app.src.my_timer import Timer
from app.src.infos import Register
else: # pragma: no cover
from async_ifc import AsyncIfc
from gen3plus.solarman_v5 import SolarmanBase
from my_timer import Timer

View File

@@ -4,14 +4,6 @@ import time
import asyncio
from datetime import datetime
if __name__ == "app.src.gen3plus.solarman_v5":
from app.src.async_ifc import AsyncIfc
from app.src.messages import hex_dump_memory, Message, State
from app.src.modbus import Modbus
from app.src.config import Config
from app.src.gen3plus.infos_g3p import InfosG3P
from app.src.infos import Register, Fmt
else: # pragma: no cover
from async_ifc import AsyncIfc
from messages import hex_dump_memory, Message, State
from config import Config

View File

@@ -7,15 +7,6 @@ import gc
from aiomqtt import MqttCodeError
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.inverter_base":
from app.src.inverter_ifc import InverterIfc
from app.src.proxy import Proxy
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStreamClient
from app.src.async_stream import AsyncStreamServer
from app.src.config import Config
from app.src.infos import Infos
else: # pragma: no cover
from inverter_ifc import InverterIfc
from proxy import Proxy
from async_stream import StreamPtr

View File

@@ -2,9 +2,6 @@ from abc import abstractmethod
import logging
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.inverter_ifc":
from app.src.iter_registry import AbstractIterMeta
else: # pragma: no cover
from iter_registry import AbstractIterMeta
logger_mqtt = logging.getLogger('mqtt')

View File

@@ -3,14 +3,6 @@ import weakref
from typing import Callable
from enum import Enum
if __name__ == "app.src.messages":
from app.src.async_ifc import AsyncIfc
from app.src.protocol_ifc import ProtocolIfc
from app.src.infos import Infos, Register
from app.src.modbus import Modbus
from app.src.my_timer import Timer
else: # pragma: no cover
from async_ifc import AsyncIfc
from protocol_ifc import ProtocolIfc
from infos import Infos, Register

View File

@@ -16,9 +16,6 @@ import logging
import asyncio
from typing import Generator, Callable
if __name__ == "app.src.modbus":
from app.src.infos import Register, Fmt
else: # pragma: no cover
from infos import Register, Fmt
logger = logging.getLogger('data')

View File

@@ -2,11 +2,6 @@ import logging
import traceback
import asyncio
if __name__ == "app.src.modbus_tcp":
from app.src.config import Config
from app.src.gen3plus.inverter_g3p import InverterG3P
from app.src.infos import Infos
else: # pragma: no cover
from config import Config
from gen3plus.inverter_g3p import InverterG3P
from infos import Infos

View File

@@ -2,12 +2,7 @@ import asyncio
import logging
import aiomqtt
import traceback
if __name__ == "app.src.mqtt":
from app.src.modbus import Modbus
from app.src.messages import Message
from app.src.config import Config
from app.src.singleton import Singleton
else: # pragma: no cover
from modbus import Modbus
from messages import Message
from config import Config

View File

@@ -1,11 +1,7 @@
from abc import abstractmethod
if __name__ == "app.src.protocol_ifc":
from app.src.iter_registry import AbstractIterMeta
from app.src.async_ifc import AsyncIfc
else: # pragma: no cover
from iter_registry import AbstractIterMeta
from async_ifc import AsyncIfc
from iter_registry import AbstractIterMeta
class ProtocolIfc(metaclass=AbstractIterMeta):

View File

@@ -2,11 +2,6 @@ import asyncio
import logging
import json
if __name__ == "app.src.proxy":
from app.src.config import Config
from app.src.mqtt import Mqtt
from app.src.infos import Infos
else: # pragma: no cover
from config import Config
from mqtt import Mqtt
from infos import Infos

View File

@@ -4,12 +4,13 @@ import asyncio
import gc
import time
from app.src.infos import Infos
from app.src.inverter_base import InverterBase
from app.src.async_stream import AsyncStreamServer, AsyncStreamClient, StreamPtr
from app.src.messages import Message
from app.tests.test_modbus_tcp import FakeReader, FakeWriter
from app.tests.test_inverter_base import config_conn, patch_open_connection
from infos import Infos
from inverter_base import InverterBase
from async_stream import AsyncStreamServer, AsyncStreamClient, StreamPtr
from messages import Message
from test_modbus_tcp import FakeReader, FakeWriter
from test_inverter_base import config_conn, patch_open_connection
pytest_plugins = ('pytest_asyncio',)
@@ -541,7 +542,7 @@ async def test_forward_resp():
remote = StreamPtr(None)
cnt = 0
async def _close_cb():
def _close_cb():
nonlocal cnt, remote, ifc
cnt += 1
@@ -550,7 +551,7 @@ async def test_forward_resp():
create_remote(remote, TestType.FWD_NO_EXCPT)
ifc.fwd_add(b'test-forward_msg')
await ifc.client_loop('')
assert cnt == 0
assert cnt == 1
del ifc
@pytest.mark.asyncio
@@ -559,7 +560,7 @@ async def test_forward_resp2():
remote = StreamPtr(None)
cnt = 0
async def _close_cb():
def _close_cb():
nonlocal cnt, remote, ifc
cnt += 1
@@ -568,5 +569,5 @@ async def test_forward_resp2():
create_remote(remote, TestType.FWD_NO_EXCPT)
ifc.fwd_add(b'test-forward_msg')
await ifc.client_loop('')
assert cnt == 0
assert cnt == 1
del ifc

View File

@@ -1,6 +1,6 @@
# test_with_pytest.py
from app.src.byte_fifo import ByteFifo
from byte_fifo import ByteFifo
def test_fifo():
read = ByteFifo()

View File

@@ -1,7 +1,7 @@
# test_with_pytest.py
import tomllib
from schema import SchemaMissingKeyError
from app.src.config import Config
from config import Config
class TstConfig(Config):

View File

@@ -2,8 +2,8 @@
import pytest
import json, math
import logging
from app.src.infos import Register, ClrAtMidnight
from app.src.infos import Infos, Fmt
from infos import Register, ClrAtMidnight
from infos import Infos, Fmt
def test_statistic_counter():
i = Infos()

View File

@@ -1,7 +1,7 @@
# test_with_pytest.py
import pytest, json, math
from app.src.infos import Register
from app.src.gen3.infos_g3 import InfosG3, RegisterMap
from infos import Register
from gen3.infos_g3 import InfosG3, RegisterMap
@pytest.fixture
def contr_data_seq(): # Get Time Request message

View File

@@ -1,9 +1,9 @@
# test_with_pytest.py
import pytest, json, math, random
from app.src.infos import Register
from app.src.gen3plus.infos_g3p import InfosG3P
from app.src.gen3plus.infos_g3p import RegisterMap
from infos import Register
from gen3plus.infos_g3p import InfosG3P
from gen3plus.infos_g3p import RegisterMap
@pytest.fixture(scope="session")
def str_test_ip():

View File

@@ -5,14 +5,14 @@ import gc
from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.gen3.talent import Talent
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.async_stream import AsyncStream, AsyncStreamClient
from infos import Infos
from config import Config
from gen3.talent import Talent
from inverter_base import InverterBase
from singleton import Singleton
from async_stream import AsyncStream, AsyncStreamClient
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
from test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
pytest_plugins = ('pytest_asyncio',)
@@ -69,13 +69,13 @@ class FakeWriter():
async def wait_closed(self):
return
class TestType(Enum):
class MockType(Enum):
RD_TEST_0_BYTES = 1
RD_TEST_TIMEOUT = 2
RD_TEST_EXCEPT = 3
test = TestType.RD_TEST_0_BYTES
test = MockType.RD_TEST_0_BYTES
@pytest.fixture
def patch_open_connection():
@@ -85,9 +85,9 @@ def patch_open_connection():
def new_open(host: str, port: int):
global test
if test == TestType.RD_TEST_TIMEOUT:
if test == MockType.RD_TEST_TIMEOUT:
raise ConnectionRefusedError
elif test == TestType.RD_TEST_EXCEPT:
elif test == MockType.RD_TEST_EXCEPT:
raise ValueError("Value cannot be negative") # Compliant
return new_conn(None)

View File

@@ -5,15 +5,15 @@ import sys,gc
from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.proxy import Proxy
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.gen3.inverter_g3 import InverterG3
from app.src.async_stream import AsyncStream
from infos import Infos
from config import Config
from proxy import Proxy
from inverter_base import InverterBase
from singleton import Singleton
from gen3.inverter_g3 import InverterG3
from async_stream import AsyncStream
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
from test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
pytest_plugins = ('pytest_asyncio',)
@@ -70,13 +70,13 @@ class FakeWriter():
async def wait_closed(self):
return
class TestType(Enum):
class MockType(Enum):
RD_TEST_0_BYTES = 1
RD_TEST_TIMEOUT = 2
RD_TEST_EXCEPT = 3
test = TestType.RD_TEST_0_BYTES
test = MockType.RD_TEST_0_BYTES
@pytest.fixture
def patch_open_connection():
@@ -86,9 +86,9 @@ def patch_open_connection():
def new_open(host: str, port: int):
global test
if test == TestType.RD_TEST_TIMEOUT:
if test == MockType.RD_TEST_TIMEOUT:
raise ConnectionRefusedError
elif test == TestType.RD_TEST_EXCEPT:
elif test == MockType.RD_TEST_EXCEPT:
raise ValueError("Value cannot be negative") # Compliant
return new_conn(None)
@@ -144,14 +144,14 @@ async def test_remote_except(config_conn, patch_open_connection):
assert asyncio.get_running_loop()
global test
test = TestType.RD_TEST_TIMEOUT
test = MockType.RD_TEST_TIMEOUT
with InverterG3(FakeReader(), FakeWriter()) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
test = TestType.RD_TEST_EXCEPT
test = MockType.RD_TEST_EXCEPT
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None

View File

@@ -4,14 +4,14 @@ import asyncio
from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.proxy import Proxy
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.gen3plus.inverter_g3p import InverterG3P
from infos import Infos
from config import Config
from proxy import Proxy
from inverter_base import InverterBase
from singleton import Singleton
from gen3plus.inverter_g3p import InverterG3P
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
from test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
pytest_plugins = ('pytest_asyncio',)
@@ -69,13 +69,13 @@ class FakeWriter():
async def wait_closed(self):
return
class TestType(Enum):
class MockType(Enum):
RD_TEST_0_BYTES = 1
RD_TEST_TIMEOUT = 2
RD_TEST_EXCEPT = 3
test = TestType.RD_TEST_0_BYTES
test = MockType.RD_TEST_0_BYTES
@pytest.fixture
def patch_open_connection():
@@ -85,9 +85,9 @@ def patch_open_connection():
def new_open(host: str, port: int):
global test
if test == TestType.RD_TEST_TIMEOUT:
if test == MockType.RD_TEST_TIMEOUT:
raise ConnectionRefusedError
elif test == TestType.RD_TEST_EXCEPT:
elif test == MockType.RD_TEST_EXCEPT:
raise ValueError("Value cannot be negative") # Compliant
return new_conn(None)
@@ -121,14 +121,14 @@ async def test_remote_except(config_conn, patch_open_connection):
assert asyncio.get_running_loop()
global test
test = TestType.RD_TEST_TIMEOUT
test = MockType.RD_TEST_TIMEOUT
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None
test = TestType.RD_TEST_EXCEPT
test = MockType.RD_TEST_EXCEPT
await inverter.create_remote()
await asyncio.sleep(0)
assert inverter.remote.stream==None

View File

@@ -1,8 +1,8 @@
# test_with_pytest.py
import pytest
import asyncio
from app.src.modbus import Modbus
from app.src.infos import Infos, Register
from modbus import Modbus
from infos import Infos, Register
pytest_plugins = ('pytest_asyncio',)

View File

@@ -5,14 +5,14 @@ from aiomqtt import MqttCodeError
from mock import patch
from enum import Enum
from app.src.singleton import Singleton
from app.src.config import Config
from app.src.infos import Infos
from app.src.mqtt import Mqtt
from app.src.inverter_base import InverterBase
from app.src.messages import Message, State
from app.src.proxy import Proxy
from app.src.modbus_tcp import ModbusConn, ModbusTcp
from singleton import Singleton
from 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',)

View File

@@ -5,12 +5,12 @@ import aiomqtt
import logging
from mock import patch, Mock
from app.src.async_stream import AsyncIfcImpl
from app.src.singleton import Singleton
from app.src.mqtt import Mqtt
from app.src.modbus import Modbus
from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.config import Config
from async_stream import AsyncIfcImpl
from singleton import Singleton
from mqtt import Mqtt
from modbus import Modbus
from gen3plus.solarman_v5 import SolarmanV5
from config import Config
pytest_plugins = ('pytest_asyncio',)

View File

@@ -5,11 +5,11 @@ import aiomqtt
import logging
from mock import patch, Mock
from app.src.singleton import Singleton
from app.src.proxy import Proxy
from app.src.mqtt import Mqtt
from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.config import Config
from singleton import Singleton
from proxy import Proxy
from mqtt import Mqtt
from gen3plus.solarman_v5 import SolarmanV5
from config import Config
pytest_plugins = ('pytest_asyncio',)

View File

@@ -1,16 +1,16 @@
# test_with_pytest.py
import pytest
from app.src.singleton import Singleton
from singleton import Singleton
class Test(metaclass=Singleton):
class Example(metaclass=Singleton):
def __init__(self):
pass # is a dummy test class
def test_singleton_metaclass():
Singleton._instances.clear()
a = Test()
a = Example()
assert 1 == len(Singleton._instances)
b = Test()
b = Example()
assert 1 == len(Singleton._instances)
assert a is b
del a

View File

@@ -5,12 +5,12 @@ import asyncio
import logging
import random
from math import isclose
from app.src.async_stream import AsyncIfcImpl, StreamPtr
from app.src.gen3plus.solarman_v5 import SolarmanV5, SolarmanBase
from app.src.config import Config
from app.src.infos import Infos, Register
from app.src.modbus import Modbus
from app.src.messages import State, Message
from async_stream import AsyncIfcImpl, StreamPtr
from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase
from config import Config
from infos import Infos, Register
from modbus import Modbus
from messages import State, Message
pytest_plugins = ('pytest_asyncio',)

View File

@@ -1,11 +1,13 @@
import pytest
import asyncio
from app.src.async_stream import AsyncIfcImpl, StreamPtr
from app.src.gen3plus.solarman_v5 import SolarmanV5, SolarmanBase
from app.src.gen3plus.solarman_emu import SolarmanEmu
from app.src.infos import Infos, Register
from app.tests.test_solarman import FakeIfc, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp
from app.tests.test_infos_g3p import str_test_ip, bytes_test_ip
from async_stream import AsyncIfcImpl, StreamPtr
from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase
from gen3plus.solarman_emu import SolarmanEmu
from infos import Infos, Register
from test_solarman import FakeIfc, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp
from test_infos_g3p import str_test_ip, bytes_test_ip
timestamp = 0x3224c8bc

View File

@@ -1,12 +1,12 @@
# test_with_pytest.py
import pytest, logging, asyncio
from math import isclose
from app.src.async_stream import AsyncIfcImpl, StreamPtr
from app.src.gen3.talent import Talent, Control
from app.src.config import Config
from app.src.infos import Infos, Register
from app.src.modbus import Modbus
from app.src.messages import State
from async_stream import AsyncIfcImpl, StreamPtr
from gen3.talent import Talent, Control
from config import Config
from infos import Infos, Register
from modbus import Modbus
from messages import State
pytest_plugins = ('pytest_asyncio',)

8
pytest.ini Normal file
View File

@@ -0,0 +1,8 @@
# pytest.ini or .pytest.ini
[pytest]
minversion = 8.0
addopts = -ra -q --durations=5
pythonpath = app/src
testpaths = app/tests
asyncio_default_fixture_loop_scope = function
asyncio_mode = strict