Files
flucto-heisskleber/tests/udp/test_functional_udp.py
Felix Weiler 98099f5b00 Refactor heisskleber core, remove synchronous implementations (#156)
* #129 AsyncTcpSource enhancements
- retry connection on startup (behavior is configurable)
- reconnect if data receiving fails (EOF received)
- add Python logging
- add unit tests

* remove syncronous implementations.

* WIP: Refactor packer/unpacker

* Refactor type hints and topic handling in console sink.

* Remove comma from tcp config enum definitions

* Remove references to deleted synchronous classes.

* Hopefully stable interface for Packer and Unpacker.

* WIP: Working with protocols and generics

* Finalized Sink, Source definition.

* Rename mqtt source and sink files

* Rename mqtt publisher and subscriber.

* Fix start function to async.

* Update documentation.

* Remove recursion from udp source.

* rename unpack to unpacker, stay consistent.

* Renaming in tests.

* Make MqttSource generic.

* Configure pyproject.toml to move to uv

* Add nox support.

* Update documentation with myst-parser and sphinx.

* Mess with autogeneration of __call__ signatures.

* Add dynamic versioning to hatch

* Asyncio wrapper for pyserial.

* Add docstrings for serial sink and source.

* Refactor config handling (#171)

* Removes deprecated "verbose" and "print_std" parameters

* Adds class methods for config generation from dictionary or file (yaml or json at this point)

* Run-time type checking via __post_init__() function

* Add serial dependency.

* WIP

* Move broker to bin/

* Update docs.

* WIP: Need to update docstrings to make ruff happy.

* Move source files to src/

* Fix tests for TcpSource.

* WIP: Remove old tests.

* Fix docstrings in mqtt classes.

* Make default tcp unpacker json_unpacker.

* No failed tests if there are no tests

* Update test pipeline

* Update ruff pre-commit

* Updated ruff formatting

* Format bin/

* Fix type hints

* No type checking

* Make stop() async

* Only test on ubuntu for now

* Don't be so strict about sphinx warnings.

* Rename TestConf for pytest naming compability.

* Install package in editable mode for ci tests.

* Update dependencies for docs generation.

* Add keepalive and will to mqtt, fixes #112.

* Update readme to reflect changes in usage.

* Requested fixes for console adapters.

* Raise correct errors in unpacker and packer.

* Correct logger name for mqtt sink.

* Add config options for stopbits and parity to Serial.

* Remove exception logging call from yaml parser.

* Add comments to clear up very implicit test.

* Rename Sink -> Sender, Source -> Receiver.

* Rename sink and source in tests.

* Fix tests.

---------

Co-authored-by: Adrian Weiler <a.weiler@aldea.de>
2024-12-09 19:32:34 +01:00

120 lines
3.2 KiB
Python

import asyncio
import json
import pytest
from heisskleber.udp import UdpConf, UdpReceiver, UdpSender
class MockUdpReceiver:
"""Helper class to receive UDP messages for testing."""
transport: asyncio.DatagramTransport
protocol: asyncio.DatagramProtocol
def __init__(self):
self.received_data = []
class ReceiverProtocol(asyncio.DatagramProtocol):
def __init__(self, received_data):
self.received_data = received_data
def connection_made(self, transport):
pass
def datagram_received(self, data, addr):
self.received_data.append(data)
async def start(self, host: str, port: int):
"""Start the UDP receiver."""
loop = asyncio.get_running_loop()
self.transport, self.protocol = await loop.create_datagram_endpoint(
lambda: self.ReceiverProtocol(self.received_data),
local_addr=(host, port),
)
async def stop(self):
"""Stop the UDP receiver."""
if hasattr(self, "transport"):
self.transport.close()
class MockUdpSender:
"""Helper class to send UDP messages for testing."""
transport: asyncio.DatagramTransport
protocol: asyncio.DatagramProtocol
def __init__(self):
self.received_data = []
class SenderProtocol(asyncio.DatagramProtocol):
def connection_made(self, transport):
pass
async def start(self, host: str, port: int):
"""Start the UDP receiver."""
loop = asyncio.get_running_loop()
self.transport, self.protocol = await loop.create_datagram_endpoint(
lambda: self.SenderProtocol(),
remote_addr=(host, port),
)
async def stop(self):
"""Stop the UDP receiver."""
if hasattr(self, "transport"):
self.transport.close()
@pytest.mark.asyncio
async def test_udp_source() -> None:
receiver_host = "127.0.0.1"
receiver_port = 35699
receiver = UdpReceiver(UdpConf(host=receiver_host, port=receiver_port))
try:
await receiver.start()
sink = MockUdpSender()
try:
await sink.start(receiver_host, receiver_port)
sink.transport.sendto(data=json.dumps({"message": "hi there!"}).encode())
data, extra = await receiver.receive()
assert data == {"message": "hi there!"}
finally:
await sink.stop()
finally:
await receiver.stop()
@pytest.mark.asyncio
async def test_actual_udp_transport():
"""Test actual UDP communication between sender and receiver."""
mock_receiver = MockUdpReceiver()
receiver_host = "127.0.0.1"
receiver_port = 45678
try:
await mock_receiver.start(receiver_host, receiver_port)
config = UdpConf(host=receiver_host, port=receiver_port)
sink = UdpSender(config)
try:
await sink.start()
test_data = {"message": "Hello, UDP!"}
await sink.send(test_data)
await asyncio.sleep(0.1)
assert len(mock_receiver.received_data) == 1
received_bytes = mock_receiver.received_data[0]
assert b'"message": "Hello, UDP!"' in received_bytes
finally:
await sink.stop()
finally:
await mock_receiver.stop()