Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration tests #153

Merged
merged 14 commits into from
Jul 21, 2023
27 changes: 27 additions & 0 deletions tests/system_tests/configs/nested.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
- type: tickit.devices.source.Source
name: external_source
inputs: {}
value: 42
- type: tickit.core.components.system_simulation.SystemSimulation
name: internal_tickit
inputs:
input_1:
component: external_source
port: value
components:
- type: tickit.devices.sink.Sink
name: internal_sink
inputs:
sink_1:
component: external
port: input_1
expose:
output_1:
component: external
port: input_1
- type: tickit.devices.sink.Sink
name: external_sink
inputs:
sink_1:
component: internal_tickit
port: output_1
10 changes: 10 additions & 0 deletions tests/system_tests/configs/test_with_master.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- type: tickit.devices.source.Source
name: source
inputs: {}
value: 42
- type: tickit.devices.sink.Sink
name: sink
inputs:
input:
component: source
port: value
91 changes: 91 additions & 0 deletions tests/system_tests/test_with_master.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import asyncio
from asyncio import AbstractEventLoop
from pathlib import Path
from typing import List, cast

import mock
import pytest
import pytest_asyncio
from mock import create_autospec

from tickit.core.components.component import Component, ComponentConfig
from tickit.core.components.device_simulation import DeviceSimulation
from tickit.core.management.event_router import InverseWiring
from tickit.core.management.schedulers.master import MasterScheduler
from tickit.core.state_interfaces.state_interface import get_interface
from tickit.core.typedefs import ComponentException, ComponentID, PortID
from tickit.devices.sink import SinkDevice
from tickit.utils.configuration.loading import read_configs


@pytest.fixture
def configs_from_yaml() -> List[ComponentConfig]:
path_to_yaml = Path(__file__).parent / "configs" / "test_with_master.yaml"
return read_configs(path_to_yaml)


@pytest.fixture
def wiring(configs_from_yaml: List[ComponentConfig]) -> InverseWiring:
return InverseWiring.from_component_configs(configs_from_yaml)


@pytest.fixture
def components(configs_from_yaml) -> List[Component]:
return [config() for config in configs_from_yaml]


@pytest_asyncio.fixture
async def master_scheduler(
wiring: InverseWiring,
components: List[Component],
event_loop: AbstractEventLoop,
):
with mock.patch(
"tickit.devices.sink.SinkDevice.update",
return_value=create_autospec(SinkDevice.update),
):
scheduler = MasterScheduler(wiring, *get_interface("internal"))

assert scheduler.running.is_set() is False
run_task = event_loop.create_task(
asyncio.wait(
[
event_loop.create_task(
component.run_forever(*get_interface("internal"))
)
for component in components
]
+ [event_loop.create_task(scheduler.run_forever())]
)
)

yield scheduler

exception_task = event_loop.create_task(
scheduler.handle_component_exception(
ComponentException(
source=ComponentID("sim"), error=Exception(), traceback=""
)
)
)

await asyncio.gather(run_task, exception_task)
assert scheduler.running.is_set() is False


@pytest.mark.asyncio
async def test_sink_has_captured_value(
components: List[Component],
master_scheduler: MasterScheduler,
):
await asyncio.wait_for(master_scheduler.running.wait(), timeout=2.0)

sink = cast(DeviceSimulation, components[1])

await asyncio.wait_for(master_scheduler.ticker.finished.wait(), timeout=2.0)

sunk_value = master_scheduler.ticker.inputs[ComponentID("sink")]

mocked_update = cast(mock.MagicMock, sink.device.update)
mocked_update.assert_called_once_with(0, sunk_value)
assert sunk_value.get(PortID("input")) == 42
94 changes: 94 additions & 0 deletions tests/system_tests/test_with_master_and_slave.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import asyncio
from asyncio import AbstractEventLoop
from pathlib import Path
from typing import List, cast

import pytest
import pytest_asyncio

from tickit.core.components.component import Component, ComponentConfig
from tickit.core.components.device_simulation import DeviceSimulation
from tickit.core.components.system_simulation import SystemSimulationComponent
from tickit.core.management.event_router import InverseWiring
from tickit.core.management.schedulers.master import MasterScheduler
from tickit.core.state_interfaces.state_interface import get_interface
from tickit.core.typedefs import ComponentException, ComponentID, PortID
from tickit.utils.configuration.loading import read_configs


@pytest_asyncio.fixture
def configs_from_yaml() -> List[ComponentConfig]:
path_to_yaml = Path(__file__).parent / "configs" / "nested.yaml"
return read_configs(path_to_yaml)


@pytest_asyncio.fixture
def wiring(configs_from_yaml) -> InverseWiring:
return InverseWiring.from_component_configs(configs_from_yaml)


@pytest_asyncio.fixture
def components(configs_from_yaml) -> List[Component]:
return [config() for config in configs_from_yaml]


@pytest_asyncio.fixture
async def master_scheduler(
wiring: InverseWiring,
components: List[Component],
event_loop: AbstractEventLoop,
):
scheduler = MasterScheduler(wiring, *get_interface("internal"))

assert scheduler.running.is_set() is False
run_task = event_loop.create_task(
asyncio.wait(
[
event_loop.create_task(
component.run_forever(*get_interface("internal"))
)
for component in components
]
+ [event_loop.create_task(scheduler.run_forever())]
)
)

yield scheduler

exception_task = event_loop.create_task(
scheduler.handle_component_exception(
ComponentException(
source=ComponentID("internal_tickit"), error=Exception(), traceback=""
),
)
)
interrupt_task = event_loop.create_task(
scheduler.schedule_interrupt(ComponentID("external_sink"))
)

await asyncio.wait([run_task, exception_task, interrupt_task])

assert scheduler.running.is_set() is False


@pytest.mark.asyncio
async def test_sink_has_captured_value(
components: List[Component],
master_scheduler: MasterScheduler,
):
source = cast(DeviceSimulation, components[0])
sim = cast(SystemSimulationComponent, components[1])
sink = cast(DeviceSimulation, components[2])

assert sink.device_inputs == {}
assert source.last_outputs == {}

await asyncio.wait_for(master_scheduler.running.wait(), timeout=2.0)
await asyncio.wait_for(master_scheduler.ticker.finished.wait(), timeout=3.0)

assert (
source.last_outputs[PortID("value")]
== sim.scheduler.input_changes[PortID("input_1")]
== 42
)
assert sink.device_inputs == {"sink_1": 42}