Skip to content

Commit

Permalink
Add integration tests (#153)
Browse files Browse the repository at this point in the history
Check the master and slave scheduler in context with other components

Two test cases are set up; one for a regular simulation with a master
scheduler, sink and source. Another for a simulation with a system
simulation component. A fixture sets up the simulation and yields it
before the tests, tearing it down afterwards by throwing an exception
and device interrupt.

---------

Co-authored-by: Abigail Emery <[email protected]>
Co-authored-by: DiamondJoseph <[email protected]>
  • Loading branch information
3 people authored Jul 21, 2023
1 parent 6066902 commit aef4042
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
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}

0 comments on commit aef4042

Please sign in to comment.