Prepared for playback engine
This commit is contained in:
+7
-9
@@ -30,13 +30,6 @@ dl_core = Core(config)
|
||||
dl_core.start()
|
||||
|
||||
|
||||
dl_core.start_recording()
|
||||
|
||||
for i in range(2):
|
||||
dl_core.wait(1)
|
||||
values = dl_core.get_all_live_values()
|
||||
log.debug(f"Core values: {values}")
|
||||
|
||||
unit_id = dl_core.bind_derive_unit(
|
||||
processing_str,
|
||||
[
|
||||
@@ -50,6 +43,13 @@ unit_id = dl_core.bind_derive_unit(
|
||||
SignalDescriptor(id=uuid4(), name="Dummy sum", type="number", timeout_ms=5000),
|
||||
)
|
||||
|
||||
for i in range(10):
|
||||
dl_core.wait(1)
|
||||
values = dl_core.get_all_live_values()
|
||||
log.debug(f"Core values: {values}")
|
||||
|
||||
dl_core.set_mode("playback")
|
||||
|
||||
|
||||
try:
|
||||
while True:
|
||||
@@ -58,6 +58,4 @@ try:
|
||||
log.debug(f"Core values: {values}")
|
||||
except KeyboardInterrupt:
|
||||
log.info("Received keyboard interrupt")
|
||||
dl_core.stop_recording()
|
||||
dl_core._processing_buffer.write("./", "output.dlpak")
|
||||
dl_core.stop()
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import logging
|
||||
import threading
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from dynalab_core import Core
|
||||
from dynalab_core.config import CoreConfig
|
||||
from dynalab_core.errors import CoreStateMismatchError
|
||||
from dynalab_core.protocols.endpoint import ConnectorEndpoint
|
||||
from dynalab_core.protocols.packets.data import ValueDescriptor
|
||||
from dynalab_core.protocols.packets.handshake import ConnectorHello
|
||||
from test.common import find_available_port
|
||||
|
||||
|
||||
@@ -32,3 +37,59 @@ def test_core_cannot_start_twice(caplog: pytest.LogCaptureFixture) -> None:
|
||||
)
|
||||
assert rejection.levelno == logging.WARNING
|
||||
assert rejection.core_state == "started"
|
||||
|
||||
|
||||
def test_core_input_worker_logs_failure(
|
||||
caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
core = Core(CoreConfig(port=find_available_port(8765)))
|
||||
|
||||
def fail_routing(message: ValueDescriptor) -> None:
|
||||
raise RuntimeError("routing failed")
|
||||
|
||||
monkeypatch.setattr(core._derive_registry, "put_data", fail_routing)
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="dynalab_core"):
|
||||
core.start()
|
||||
try:
|
||||
core._data_input_queue.put_nowait(
|
||||
ValueDescriptor(signal_id=uuid4(), value=1.0, timestamp=1)
|
||||
)
|
||||
assert core._input_worker_stopped_event.wait(1.0)
|
||||
finally:
|
||||
core.stop()
|
||||
|
||||
failures = [
|
||||
record
|
||||
for record in caplog.records
|
||||
if getattr(record, "event", None) == "core.input_worker_failed"
|
||||
]
|
||||
assert len(failures) == 1
|
||||
assert failures[0].exception_type == "RuntimeError"
|
||||
assert failures[0].message_type == "ValueDescriptor"
|
||||
assert failures[0].exc_info is not None
|
||||
|
||||
|
||||
def test_core_mode_is_shared_with_endpoints() -> None:
|
||||
core = Core(CoreConfig(port=find_available_port(8765)))
|
||||
endpoint = ConnectorEndpoint(
|
||||
ConnectorHello(
|
||||
connector_uuid=uuid4(),
|
||||
connector_name="test",
|
||||
connector_version="1.0",
|
||||
signals=[],
|
||||
),
|
||||
threading.Event(),
|
||||
threading.Event(),
|
||||
core._data_input_queue,
|
||||
core._connector_registry._core_mode,
|
||||
)
|
||||
|
||||
try:
|
||||
assert endpoint._core_mode is core._mode
|
||||
|
||||
core.set_mode("playback")
|
||||
|
||||
assert endpoint._core_mode.value == "playback"
|
||||
finally:
|
||||
core.stop()
|
||||
|
||||
+43
-3
@@ -1,7 +1,9 @@
|
||||
import logging
|
||||
from queue import Empty, Queue
|
||||
import time
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from dynalab_core.derive import DeriveRegistry
|
||||
from dynalab_core.protocols.packets.data import ValueDescriptor
|
||||
from dynalab_core.protocols.packets.handshake import SignalDescriptor
|
||||
@@ -19,6 +21,14 @@ def add(left: ValueDescriptor, right: ValueDescriptor, output: SignalDescriptor)
|
||||
)
|
||||
"""
|
||||
|
||||
FAILING_PROCESSING_FUNCTION = """
|
||||
from dynalab_core.protocols.packets.data import ValueDescriptor
|
||||
from dynalab_core.protocols.packets.handshake import SignalDescriptor
|
||||
|
||||
def fail(value: ValueDescriptor, output: SignalDescriptor) -> ValueDescriptor:
|
||||
raise RuntimeError("derive failed")
|
||||
"""
|
||||
|
||||
|
||||
def test_derive_registry_routes_with_live_values() -> None:
|
||||
output_queue: Queue[ValueDescriptor] = Queue()
|
||||
@@ -32,9 +42,7 @@ def test_derive_registry_routes_with_live_values() -> None:
|
||||
unit = registry.register(
|
||||
PROCESSING_FUNCTION, [left_signal, right_signal], output_signal
|
||||
)
|
||||
right_value = ValueDescriptor(
|
||||
signal_id=right_signal.id, value=2.0, timestamp=1
|
||||
)
|
||||
right_value = ValueDescriptor(signal_id=right_signal.id, value=2.0, timestamp=1)
|
||||
live_values[right_signal.id] = right_value
|
||||
left_value = ValueDescriptor(signal_id=left_signal.id, value=3.0, timestamp=2)
|
||||
|
||||
@@ -59,6 +67,7 @@ def test_derive_registry_routes_with_live_values() -> None:
|
||||
assert result.value == 5.0
|
||||
assert result.timestamp == left_value.timestamp
|
||||
|
||||
|
||||
def test_derive_registry_ignores_unknown_unit_on_unregister() -> None:
|
||||
registry = DeriveRegistry(Queue(), lambda signal_id: None)
|
||||
|
||||
@@ -66,3 +75,34 @@ def test_derive_registry_ignores_unknown_unit_on_unregister() -> None:
|
||||
registry.unregister(uuid4())
|
||||
finally:
|
||||
registry.stop()
|
||||
|
||||
|
||||
def test_derive_worker_logs_failure_and_stops(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
input_signal = SignalDescriptor(id=uuid4(), name="Input", type="number")
|
||||
output_signal = SignalDescriptor(id=uuid4(), name="Output", type="number")
|
||||
registry = DeriveRegistry(Queue(), lambda signal_id: None)
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="dynalab_core"):
|
||||
try:
|
||||
unit = registry.register(
|
||||
FAILING_PROCESSING_FUNCTION, [input_signal], output_signal
|
||||
)
|
||||
unit.put_data(
|
||||
[ValueDescriptor(signal_id=input_signal.id, value=1.0, timestamp=1)]
|
||||
)
|
||||
assert unit._stopped_event.wait(1.0)
|
||||
assert registry.get_all_signal_descriptors() == []
|
||||
finally:
|
||||
registry.stop()
|
||||
|
||||
failures = [
|
||||
record
|
||||
for record in caplog.records
|
||||
if getattr(record, "event", None) == "derive.worker_failed"
|
||||
]
|
||||
assert len(failures) == 1
|
||||
assert failures[0].derive_uuid == str(unit.uuid())
|
||||
assert failures[0].exception_type == "RuntimeError"
|
||||
assert failures[0].exc_info is not None
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from dynalab_core.buffer import ValueBuffer
|
||||
from dynalab_core.dlpak import DLPak
|
||||
from dynalab_core.protocols.packets.handshake import SignalDescriptor
|
||||
|
||||
|
||||
def test_dlpak_logs_written_archive(caplog: pytest.LogCaptureFixture, tmp_path) -> None:
|
||||
signal = SignalDescriptor(id=uuid4(), name="Signal", type="number")
|
||||
buffer = ValueBuffer()
|
||||
buffer.append(1, signal.id, 2.0)
|
||||
package = DLPak()
|
||||
package.set_data(buffer)
|
||||
package.set_manifest(datetime.now(timezone.utc), [signal])
|
||||
|
||||
with caplog.at_level(logging.INFO, logger="dynalab_core"):
|
||||
package.write(tmp_path, "recording")
|
||||
|
||||
record = next(
|
||||
record
|
||||
for record in caplog.records
|
||||
if getattr(record, "event", None) == "dlpak.written"
|
||||
)
|
||||
assert record.output_filename == "recording.dlpak"
|
||||
assert record.sample_count == 1
|
||||
assert record.signal_count == 1
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import socket
|
||||
import time
|
||||
@@ -7,6 +8,7 @@ import pytest
|
||||
from dynalab_core import Core
|
||||
from dynalab_core.config import CoreConfig
|
||||
from dynalab_core.protocols.constants import PROTOCOL_VERSION
|
||||
from dynalab_core.protocols.common import VersionDescriptor
|
||||
from dynalab_core.protocols.json.errors import (
|
||||
JsonServerStartupError,
|
||||
JsonServerTimeoutError,
|
||||
@@ -91,7 +93,7 @@ def test_json_server_logs_invalid_handshake(
|
||||
with socket.create_connection(("127.0.0.1", port), timeout=1) as peer:
|
||||
server_hello = peer.makefile("rb").readline()
|
||||
assert server_hello
|
||||
peer.sendall(b"not-json\n")
|
||||
peer.sendall(b'{"secret":"must-not-appear-in-logs"}\n')
|
||||
|
||||
rejection = _wait_for_event(caplog, "connector.handshake_rejected")
|
||||
finally:
|
||||
@@ -101,6 +103,69 @@ def test_json_server_logs_invalid_handshake(
|
||||
assert rejection.reason == "invalid_frame"
|
||||
assert rejection.connection_id
|
||||
assert rejection.peer_address
|
||||
assert "must-not-appear-in-logs" not in caplog.text
|
||||
|
||||
|
||||
def test_json_server_reports_unexpected_thread_startup_failure(
|
||||
caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
async def fail_start_server(*args: object, **kwargs: object) -> None:
|
||||
raise RuntimeError("unexpected startup failure")
|
||||
|
||||
monkeypatch.setattr(asyncio, "start_server", fail_start_server)
|
||||
core = Core(CoreConfig(port=find_available_port(8765)))
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="dynalab_core"):
|
||||
try:
|
||||
with pytest.raises(JsonServerStartupError):
|
||||
core.start()
|
||||
finally:
|
||||
core.stop()
|
||||
|
||||
failures = [
|
||||
record
|
||||
for record in caplog.records
|
||||
if getattr(record, "event", None) == "json_server.thread_failed"
|
||||
]
|
||||
assert len(failures) == 1
|
||||
assert failures[0].startup_complete is False
|
||||
assert failures[0].exception_type == "RuntimeError"
|
||||
assert failures[0].exc_info is not None
|
||||
|
||||
|
||||
def test_rejected_handshake_is_not_logged_as_connected(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
port = find_available_port(8765)
|
||||
core = Core(CoreConfig(port=port))
|
||||
connector_uuid = uuid4()
|
||||
connector_hello = ConnectorHello(
|
||||
connector_uuid=connector_uuid,
|
||||
protocol_version=VersionDescriptor(type="alpha", major=999, minor=0, patch=0),
|
||||
connector_name="Incompatible connector",
|
||||
connector_version="0.1.0-test",
|
||||
signals=[SignalDescriptor(id=uuid4(), name="Dummy signal", type="number")],
|
||||
)
|
||||
|
||||
with caplog.at_level(logging.DEBUG, logger="dynalab_core"):
|
||||
core.start()
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", port), timeout=1) as peer:
|
||||
peer_file = peer.makefile("rb")
|
||||
assert peer_file.readline()
|
||||
peer.sendall(connector_hello.model_dump_json().encode("utf-8") + b"\n")
|
||||
assert b'"type":"handshake_rejected"' in peer_file.readline()
|
||||
rejection = _wait_for_event(caplog, "endpoint.handshake_rejected")
|
||||
finally:
|
||||
core.stop()
|
||||
|
||||
assert rejection.reason == "protocol_version_mismatch"
|
||||
assert rejection.connector_uuid == str(connector_uuid)
|
||||
assert not any(
|
||||
getattr(record, "event", None) == "connector.connected"
|
||||
and getattr(record, "connector_uuid", None) == str(connector_uuid)
|
||||
for record in caplog.records
|
||||
)
|
||||
|
||||
|
||||
def test_json_server_waits_for_connection_handlers_on_stop(
|
||||
@@ -133,6 +198,8 @@ def test_json_server_waits_for_connection_handlers_on_stop(
|
||||
|
||||
events = [getattr(record, "event", None) for record in caplog.records]
|
||||
assert "json_server.stop_timeout" not in events
|
||||
assert "connector.connected" in events
|
||||
assert events.index("connector.registered") < events.index("connector.connected")
|
||||
assert core._json_server._stopped_event.is_set()
|
||||
assert not core._json_server._handler_tasks
|
||||
assert core._connector_registry.get(connector_uuid) is None
|
||||
|
||||
Reference in New Issue
Block a user