diff --git a/api/src/opentrons/protocol_runner/run_orchestrator.py b/api/src/opentrons/protocol_runner/run_orchestrator.py index 4b046f95d0e8..af3e61437d5d 100644 --- a/api/src/opentrons/protocol_runner/run_orchestrator.py +++ b/api/src/opentrons/protocol_runner/run_orchestrator.py @@ -19,6 +19,7 @@ StateSummary, CommandPointer, CommandSlice, + DeckType, ) from ..protocol_engine.types import ( PostRunHardwareState, @@ -329,3 +330,7 @@ def prepare(self) -> None: def get_robot_type(self) -> RobotType: """Get engine robot type.""" return self._protocol_engine.state_view.config.robot_type + + def get_deck_type(self) -> DeckType: + """Get engine deck type.""" + return self._protocol_engine.state_view.config.deck_type diff --git a/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py b/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py index c70d2a1dd070..6c4c5c31cd6e 100644 --- a/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py +++ b/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py @@ -2,14 +2,26 @@ import asyncio import logging from datetime import datetime -from typing import List, NamedTuple, Optional, Callable +from typing import List, Optional, Callable from opentrons.protocol_engine.errors.exceptions import EStopActivatedError -from opentrons.protocol_engine.types import PostRunHardwareState -from opentrons_shared_data.robot.dev_types import RobotType -from opentrons_shared_data.robot.dev_types import RobotTypeEnum +from opentrons.protocol_engine.types import PostRunHardwareState, DeckConfigurationType +from opentrons.protocol_engine import ( + Config as ProtocolEngineConfig, + DeckType, + LabwareOffsetCreate, + StateSummary, + create_protocol_engine, + CommandSlice, + CommandPointer, + Command, + CommandCreate, + LabwareOffset, +) +from opentrons.protocol_runner import RunResult, RunOrchestrator from opentrons.config import feature_flags + from opentrons.hardware_control import HardwareControlAPI from opentrons.hardware_control.types import ( EstopState, @@ -17,18 +29,10 @@ EstopStateNotification, HardwareEventHandler, ) -from opentrons.protocol_runner import LiveRunner, RunResult -from opentrons.protocol_engine import ( - Config as ProtocolEngineConfig, - DeckType, - LabwareOffsetCreate, - ProtocolEngine, - StateSummary, - create_protocol_engine, -) - -from opentrons.protocol_engine.types import DeckConfigurationType +from opentrons_shared_data.robot.dev_types import RobotType, RobotTypeEnum +from opentrons_shared_data.labware.dev_types import LabwareUri +from opentrons_shared_data.labware.labware_definition import LabwareDefinition _log = logging.getLogger(__name__) @@ -45,13 +49,8 @@ class NoRunnerEnginePairError(RuntimeError): """Raised if you try to get the current engine or runner while there is none.""" -class RunnerEnginePair(NamedTuple): - """A stored ProtocolRunner/ProtocolEngine pair.""" - - run_id: str - created_at: datetime - runner: LiveRunner - engine: ProtocolEngine +class NoRunOrchestrator(RuntimeError): + """Raised if you try to get the current run orchestrator while there is none.""" async def handle_estop_event( @@ -72,8 +71,8 @@ async def handle_estop_event( return # todo(mm, 2024-04-17): This estop teardown sequencing belongs in the # runner layer. - engine_store.engine.estop() - await engine_store.engine.finish(error=EStopActivatedError()) + engine_store.run_orchestrator.estop() + await engine_store.run_orchestrator.finish(error=EStopActivatedError()) except Exception: # This is a background task kicked off by a hardware event, # so there's no one to propagate this exception to. @@ -100,6 +99,9 @@ def run_handler_in_engine_thread_from_hardware_thread( class MaintenanceEngineStore: """Factory and in-memory storage for ProtocolEngine.""" + _run_orchestrator: Optional[RunOrchestrator] = None + _created_at: Optional[datetime] = None + def __init__( self, hardware_api: HardwareControlAPI, @@ -117,37 +119,27 @@ def __init__( self._hardware_api = hardware_api self._robot_type = robot_type self._deck_type = deck_type - self._runner_engine_pair: Optional[RunnerEnginePair] = None hardware_api.register_callback(_get_estop_listener(self)) @property - def engine(self) -> ProtocolEngine: - """Get the "current" ProtocolEngine.""" - if self._runner_engine_pair is None: - raise NoRunnerEnginePairError() - return self._runner_engine_pair.engine - - @property - def runner(self) -> LiveRunner: - """Get the "current" ProtocolRunner.""" - if self._runner_engine_pair is None: - raise NoRunnerEnginePairError() - return self._runner_engine_pair.runner + def run_orchestrator(self) -> RunOrchestrator: + """Get the "current" RunOrchestrator.""" + if self._run_orchestrator is None: + raise NoRunOrchestrator() + return self._run_orchestrator @property def current_run_id(self) -> Optional[str]: - """Get the run identifier associated with the current engine/runner pair.""" + """Get the run identifier associated with the current engine.""" return ( - self._runner_engine_pair.run_id - if self._runner_engine_pair is not None - else None + self.run_orchestrator.run_id if self._run_orchestrator is not None else None ) @property def current_run_created_at(self) -> datetime: """Get the run creation datetime.""" - assert self._runner_engine_pair is not None, "Run not yet created." - return self._runner_engine_pair.created_at + assert self._created_at is not None, "Run not yet created." + return self._created_at async def create( self, @@ -171,7 +163,7 @@ async def create( # Because we will be clearing engine store before creating a new one, # the runner-engine pair should be None at this point. assert ( - self._runner_engine_pair is None + self._run_orchestrator is None ), "There is an active maintenance run that was not cleared correctly." engine = await create_protocol_engine( hardware_api=self._hardware_api, @@ -186,23 +178,16 @@ async def create( notify_publishers=notify_publishers, ) - # Using LiveRunner as the runner to allow for future refactor of maintenance runs - # See https://opentrons.atlassian.net/browse/RSS-226 - runner = LiveRunner(protocol_engine=engine, hardware_api=self._hardware_api) - - # TODO (spp): set live runner start func - for offset in labware_offsets: engine.add_labware_offset(offset) - self._runner_engine_pair = RunnerEnginePair( - run_id=run_id, - created_at=created_at, - runner=runner, - engine=engine, + self._run_orchestrator = RunOrchestrator.build_orchestrator( + run_id=run_id, protocol_engine=engine, hardware_api=self._hardware_api ) - return engine.state_view.get_summary() + self._created_at = created_at + + return self._run_orchestrator.get_state_summary() async def clear(self) -> RunResult: """Remove the ProtocolEngine. @@ -211,11 +196,8 @@ async def clear(self) -> RunResult: EngineConflictError: The current runner/engine pair is not idle, so they cannot be cleared. """ - engine = self.engine - state_view = engine.state_view - - if state_view.commands.get_is_okay_to_clear(): - await engine.finish( + if self.run_orchestrator.get_is_okay_to_clear(): + await self.run_orchestrator.finish( drop_tips_after_run=False, set_run_status=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, @@ -223,8 +205,57 @@ async def clear(self) -> RunResult: else: raise EngineConflictError("Current run is not idle or stopped.") - run_data = state_view.get_summary() - commands = state_view.commands.get_all() - self._runner_engine_pair = None + run_data = self.run_orchestrator.get_state_summary() + commands = self.run_orchestrator.get_all_commands() + self._run_orchestrator = None + self._created_at = None return RunResult(state_summary=run_data, commands=commands, parameters=[]) + + def get_command_slice( + self, + cursor: Optional[int], + length: int, + ) -> CommandSlice: + """Get a slice of run commands. + + Args: + cursor: Requested index of first command in the returned slice. + length: Length of slice to return. + """ + return self.run_orchestrator.get_command_slice(cursor=cursor, length=length) + + def get_current_command(self) -> Optional[CommandPointer]: + """Get the current running command.""" + return self.run_orchestrator.get_current_command() + + def get_command_recovery_target(self) -> Optional[CommandPointer]: + """Get the current error recovery target.""" + return self.run_orchestrator.get_command_recovery_target() + + def get_command(self, command_id: str) -> Command: + """Get a run's command by ID.""" + return self.run_orchestrator.get_command(command_id=command_id) + + def get_state_summary(self) -> StateSummary: + """Get protocol run data.""" + return self.run_orchestrator.get_state_summary() + + async def add_command_and_wait_for_interval( + self, + request: CommandCreate, + wait_until_complete: bool = False, + timeout: Optional[int] = None, + ) -> Command: + """Add a new command to execute and wait for it to complete if needed.""" + return await self.run_orchestrator.add_command_and_wait_for_interval( + command=request, wait_until_complete=wait_until_complete, timeout=timeout + ) + + def add_labware_offset(self, request: LabwareOffsetCreate) -> LabwareOffset: + """Add a new labware offset to state.""" + return self.run_orchestrator.add_labware_offset(request) + + def add_labware_definition(self, definition: LabwareDefinition) -> LabwareUri: + """Add a new labware definition to state.""" + return self.run_orchestrator.add_labware_definition(definition) diff --git a/robot-server/robot_server/maintenance_runs/maintenance_run_data_manager.py b/robot-server/robot_server/maintenance_runs/maintenance_run_data_manager.py index 92b7eeb5cd12..f765165b4ab1 100644 --- a/robot-server/robot_server/maintenance_runs/maintenance_run_data_manager.py +++ b/robot-server/robot_server/maintenance_runs/maintenance_run_data_manager.py @@ -178,9 +178,7 @@ def get_commands_slice( """ if run_id != self._engine_store.current_run_id: raise MaintenanceRunNotFoundError(run_id=run_id) - the_slice = self._engine_store.engine.state_view.commands.get_slice( - cursor=cursor, length=length - ) + the_slice = self._engine_store.get_command_slice(cursor=cursor, length=length) return the_slice def get_current_command(self, run_id: str) -> Optional[CommandPointer]: @@ -193,7 +191,7 @@ def get_current_command(self, run_id: str) -> Optional[CommandPointer]: run_id: ID of the run. """ if self._engine_store.current_run_id == run_id: - return self._engine_store.engine.state_view.commands.get_current() + return self._engine_store.get_current_command() else: # todo(mm, 2024-05-20): # For historical runs to behave consistently with the current run, @@ -209,7 +207,7 @@ def get_recovery_target_command(self, run_id: str) -> Optional[CommandPointer]: run_id: ID of the run. """ if self._engine_store.current_run_id == run_id: - return self._engine_store.engine.state_view.commands.get_recovery_target() + return self._engine_store.get_command_recovery_target() else: # Historical runs can't have any ongoing error recovery. return None @@ -227,7 +225,7 @@ def get_command(self, run_id: str, command_id: str) -> Command: """ if run_id != self._engine_store.current_run_id: raise MaintenanceRunNotFoundError(run_id=run_id) - return self._engine_store.engine.state_view.commands.get(command_id=command_id) + return self._engine_store.get_command(command_id=command_id) def _get_state_summary(self, run_id: str) -> Optional[StateSummary]: - return self._engine_store.engine.state_view.get_summary() + return self._engine_store.get_state_summary() diff --git a/robot-server/robot_server/maintenance_runs/router/commands_router.py b/robot-server/robot_server/maintenance_runs/router/commands_router.py index f64f96ee7263..af71a3cd1b81 100644 --- a/robot-server/robot_server/maintenance_runs/router/commands_router.py +++ b/robot-server/robot_server/maintenance_runs/router/commands_router.py @@ -3,12 +3,10 @@ from typing import Optional, Union from typing_extensions import Final, Literal -from anyio import move_on_after from fastapi import APIRouter, Depends, Query, status from opentrons.protocol_engine import ( CommandPointer, - ProtocolEngine, commands as pe_commands, ) from opentrons.protocol_engine.errors import CommandDoesNotExistError @@ -60,11 +58,11 @@ class CommandNotAllowed(ErrorDetails): title: str = "Setup Command Not Allowed" -async def get_current_run_engine_from_url( +async def get_current_run_from_url( runId: str, engine_store: MaintenanceEngineStore = Depends(get_maintenance_engine_store), -) -> ProtocolEngine: - """Get current run protocol engine. +) -> str: + """Get run from url. Args: runId: Run ID to associate the command with. @@ -76,7 +74,7 @@ async def get_current_run_engine_from_url( f"Note that only one maintenance run can exist at a time." ).as_error(status.HTTP_404_NOT_FOUND) - return engine_store.engine + return runId @PydanticResponse.wrap_route( @@ -110,6 +108,7 @@ async def create_run_command( " or when the timeout is reached. See the `timeout` query parameter." ), ), + engine_store: MaintenanceEngineStore = Depends(get_maintenance_engine_store), timeout: Optional[int] = Query( default=None, gt=0, @@ -130,7 +129,7 @@ async def create_run_command( " the default was 30 seconds, not infinite." ), ), - protocol_engine: ProtocolEngine = Depends(get_current_run_engine_from_url), + run_id: str = Depends(get_current_run_from_url), check_estop: bool = Depends(require_estop_in_good_state), ) -> PydanticResponse[SimpleBody[pe_commands.Command]]: """Enqueue a protocol command. @@ -142,9 +141,10 @@ async def create_run_command( Else, return immediately. Comes from a query parameter in the URL. timeout: The maximum time, in seconds, to wait before returning. Comes from a query parameter in the URL. - protocol_engine: The run's `ProtocolEngine` on which the new + engine_store: The run's `EngineStore` on which the new command will be enqueued. check_estop: Dependency to verify the estop is in a valid state. + run_id: Run identification to attach command to. """ # TODO(mc, 2022-05-26): increment the HTTP API version so that default # behavior is to pass through `command_intent` without overriding it @@ -153,14 +153,11 @@ async def create_run_command( # TODO (spp): re-add `RunStoppedError` exception catching if/when maintenance runs # have actions. - command = protocol_engine.add_command(command_create) - - if waitUntilComplete: - timeout_sec = None if timeout is None else timeout / 1000.0 - with move_on_after(timeout_sec): - await protocol_engine.wait_for_command(command.id) + command = await engine_store.add_command_and_wait_for_interval( + request=command_create, wait_until_complete=waitUntilComplete, timeout=timeout + ) - response_data = protocol_engine.state_view.commands.get(command.id) + response_data = engine_store.get_command(command.id) return await PydanticResponse.create( content=SimpleBody.construct(data=response_data), diff --git a/robot-server/robot_server/maintenance_runs/router/labware_router.py b/robot-server/robot_server/maintenance_runs/router/labware_router.py index 95e1c01f9bc7..3a44b36a9522 100644 --- a/robot-server/robot_server/maintenance_runs/router/labware_router.py +++ b/robot-server/robot_server/maintenance_runs/router/labware_router.py @@ -47,7 +47,7 @@ async def add_labware_offset( engine_store: Engine storage interface. run: Run response data by ID from URL; ensures 404 if run not found. """ - added_offset = engine_store.engine.add_labware_offset(request_body.data) + added_offset = engine_store.add_labware_offset(request_body.data) log.info(f'Added labware offset "{added_offset.id}"' f' to run "{run.id}".') return await PydanticResponse.create( @@ -84,7 +84,7 @@ async def add_labware_definition( engine_store: Engine storage interface. run: Run response data by ID from URL; ensures 404 if run not found. """ - uri = engine_store.engine.add_labware_definition(request_body.data) + uri = engine_store.add_labware_definition(request_body.data) log.info(f'Added labware definition "{uri}"' f' to run "{run.id}".') return PydanticResponse( diff --git a/robot-server/robot_server/runs/engine_store.py b/robot-server/robot_server/runs/engine_store.py index 6daaafe59f38..84ad43972fa0 100644 --- a/robot-server/robot_server/runs/engine_store.py +++ b/robot-server/robot_server/runs/engine_store.py @@ -138,7 +138,7 @@ def run_orchestrator(self) -> RunOrchestrator: @property def current_run_id(self) -> Optional[str]: - """Get the run identifier associated with the current engine/runner pair.""" + """Get the run identifier associated with the current engine.""" return ( self.run_orchestrator.run_id if self._run_orchestrator is not None else None ) diff --git a/robot-server/tests/maintenance_runs/router/test_commands_router.py b/robot-server/tests/maintenance_runs/router/test_commands_router.py index 5d5e66abff57..038d6c9a612c 100644 --- a/robot-server/tests/maintenance_runs/router/test_commands_router.py +++ b/robot-server/tests/maintenance_runs/router/test_commands_router.py @@ -7,7 +7,6 @@ from opentrons.protocol_engine import ( CommandSlice, CommandPointer, - ProtocolEngine, commands as pe_commands, errors as pe_errors, ) @@ -30,7 +29,7 @@ create_run_command, get_run_command, get_run_commands, - get_current_run_engine_from_url, + get_current_run_from_url, ) from robot_server.runs.command_models import ( RequestModelWithCommandCreate, @@ -40,22 +39,22 @@ ) -async def test_get_current_run_engine_from_url( +async def test_get_current_run_from_url( decoy: Decoy, mock_maintenance_engine_store: MaintenanceEngineStore, ) -> None: """Should get an instance of a maintenance run protocol engine.""" decoy.when(mock_maintenance_engine_store.current_run_id).then_return("run-id") - result = await get_current_run_engine_from_url( + result = await get_current_run_from_url( runId="run-id", engine_store=mock_maintenance_engine_store, ) - assert result is mock_maintenance_engine_store.engine + assert result == "run-id" -async def test_get_current_run_engine_from_url_not_current( +async def test_get_current_run_from_url_not_current( decoy: Decoy, mock_maintenance_engine_store: MaintenanceEngineStore, ) -> None: @@ -65,7 +64,7 @@ async def test_get_current_run_engine_from_url_not_current( ) with pytest.raises(ApiError) as exc_info: - await get_current_run_engine_from_url( + await get_current_run_from_url( runId="run-id", engine_store=mock_maintenance_engine_store, ) @@ -76,7 +75,7 @@ async def test_get_current_run_engine_from_url_not_current( async def test_create_run_command( decoy: Decoy, - mock_protocol_engine: ProtocolEngine, + mock_maintenance_engine_store: MaintenanceEngineStore, ) -> None: """It should add the requested command to the ProtocolEngine and return it.""" command_request = pe_commands.WaitForResumeCreate( @@ -91,35 +90,35 @@ async def test_create_run_command( params=pe_commands.WaitForResumeParams(message="Hello"), ) - def _stub_queued_command_state(*_a: object, **_k: object) -> pe_commands.Command: - decoy.when( - mock_protocol_engine.state_view.commands.get("command-id") - ).then_return(command_once_added) - return command_once_added - decoy.when( - mock_protocol_engine.add_command( - pe_commands.WaitForResumeCreate( + await mock_maintenance_engine_store.add_command_and_wait_for_interval( + request=pe_commands.WaitForResumeCreate( params=pe_commands.WaitForResumeParams(message="Hello"), intent=pe_commands.CommandIntent.SETUP, - ) + ), + wait_until_complete=False, + timeout=None, ) - ).then_do(_stub_queued_command_state) + ).then_return(command_once_added) + + decoy.when(mock_maintenance_engine_store.get_command("command-id")).then_return( + command_once_added + ) result = await create_run_command( request_body=RequestModelWithCommandCreate(data=command_request), waitUntilComplete=False, - protocol_engine=mock_protocol_engine, + engine_store=mock_maintenance_engine_store, + timeout=None, ) assert result.content.data == command_once_added assert result.status_code == 201 - decoy.verify(await mock_protocol_engine.wait_for_command("command-id"), times=0) async def test_create_run_command_blocking_completion( decoy: Decoy, - mock_protocol_engine: ProtocolEngine, + mock_maintenance_engine_store: MaintenanceEngineStore, ) -> None: """It should be able to create a command and wait for it to execute.""" command_request = pe_commands.WaitForResumeCreate( @@ -127,14 +126,6 @@ async def test_create_run_command_blocking_completion( intent=pe_commands.CommandIntent.SETUP, ) - command_once_added = pe_commands.WaitForResume( - id="command-id", - key="command-key", - createdAt=datetime(year=2021, month=1, day=1), - status=pe_commands.CommandStatus.QUEUED, - params=pe_commands.WaitForResumeParams(message="Hello"), - ) - command_once_completed = pe_commands.WaitForResume( id="command-id", key="command-key", @@ -144,31 +135,21 @@ async def test_create_run_command_blocking_completion( result=pe_commands.WaitForResumeResult(), ) - def _stub_queued_command_state(*_a: object, **_k: object) -> pe_commands.Command: - decoy.when( - mock_protocol_engine.state_view.commands.get("command-id") - ).then_return(command_once_added) - - return command_once_added - - def _stub_completed_command_state(*_a: object, **_k: object) -> None: - decoy.when( - mock_protocol_engine.state_view.commands.get("command-id") - ).then_return(command_once_completed) - - decoy.when(mock_protocol_engine.add_command(command_request)).then_do( - _stub_queued_command_state - ) + decoy.when( + await mock_maintenance_engine_store.add_command_and_wait_for_interval( + request=command_request, wait_until_complete=True, timeout=999 + ) + ).then_return(command_once_completed) - decoy.when(await mock_protocol_engine.wait_for_command("command-id")).then_do( - _stub_completed_command_state + decoy.when(mock_maintenance_engine_store.get_command("command-id")).then_return( + command_once_completed ) result = await create_run_command( request_body=RequestModelWithCommandCreate(data=command_request), waitUntilComplete=True, timeout=999, - protocol_engine=mock_protocol_engine, + engine_store=mock_maintenance_engine_store, ) assert result.content.data == command_once_completed diff --git a/robot-server/tests/maintenance_runs/router/test_labware_router.py b/robot-server/tests/maintenance_runs/router/test_labware_router.py index 41bf67a76be9..383ad9ebb67b 100644 --- a/robot-server/tests/maintenance_runs/router/test_labware_router.py +++ b/robot-server/tests/maintenance_runs/router/test_labware_router.py @@ -68,7 +68,7 @@ async def test_add_labware_offset( ) decoy.when( - mock_maintenance_engine_store.engine.add_labware_offset(labware_offset_request) + mock_maintenance_engine_store.add_labware_offset(labware_offset_request) ).then_return(labware_offset) result = await add_labware_offset( @@ -91,7 +91,7 @@ async def test_add_labware_definition( uri = pe_types.LabwareUri("some/definition/uri") decoy.when( - mock_maintenance_engine_store.engine.add_labware_definition(labware_definition) + mock_maintenance_engine_store.add_labware_definition(labware_definition) ).then_return(uri) result = await add_labware_definition( diff --git a/robot-server/tests/maintenance_runs/test_engine_store.py b/robot-server/tests/maintenance_runs/test_engine_store.py index 948705572ce0..a4f62815b410 100644 --- a/robot-server/tests/maintenance_runs/test_engine_store.py +++ b/robot-server/tests/maintenance_runs/test_engine_store.py @@ -11,17 +11,16 @@ from opentrons.hardware_control import API from opentrons.hardware_control.types import EstopStateNotification, EstopState from opentrons.protocol_engine import ( - ProtocolEngine, StateSummary, types as pe_types, ) -from opentrons.protocol_runner import LiveRunner, RunResult +from opentrons.protocol_runner import RunResult from robot_server.maintenance_runs.maintenance_engine_store import ( MaintenanceEngineStore, EngineConflictError, - NoRunnerEnginePairError, handle_estop_event, + NoRunOrchestrator, ) @@ -55,9 +54,15 @@ async def test_create_engine(subject: MaintenanceEngineStore) -> None: ) assert subject.current_run_id == "run-id" + assert subject.current_run_created_at is not None assert isinstance(result, StateSummary) - assert isinstance(subject.runner, LiveRunner) - assert isinstance(subject.engine, ProtocolEngine) + assert subject.run_orchestrator.get_protocol_runner() is None + + +def test_run_created_at_raises(subject: MaintenanceEngineStore) -> None: + """Should raise that the run has not yet created.""" + with pytest.raises(AssertionError): + subject.current_run_created_at @pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) @@ -82,8 +87,8 @@ async def test_create_engine_uses_robot_and_deck_type( notify_publishers=mock_notify_publishers, ) - assert subject.engine.state_view.config.robot_type == robot_type - assert subject.engine.state_view.config.deck_type == deck_type + assert subject.run_orchestrator.get_robot_type() == robot_type + assert subject.run_orchestrator.get_deck_type() == deck_type async def test_create_engine_with_labware_offsets( @@ -122,17 +127,15 @@ async def test_clear_engine(subject: MaintenanceEngineStore) -> None: created_at=datetime(2023, 5, 1), notify_publishers=mock_notify_publishers, ) - await subject.runner.run(deck_configuration=[]) + await subject.run_orchestrator.run(deck_configuration=[]) result = await subject.clear() assert subject.current_run_id is None + assert subject._created_at is None assert isinstance(result, RunResult) - with pytest.raises(NoRunnerEnginePairError): - subject.engine - - with pytest.raises(NoRunnerEnginePairError): - subject.runner + with pytest.raises(NoRunOrchestrator): + subject.run_orchestrator async def test_clear_engine_not_stopped_or_idle( @@ -145,7 +148,7 @@ async def test_clear_engine_not_stopped_or_idle( created_at=datetime(2023, 6, 1), notify_publishers=mock_notify_publishers, ) - subject.runner.play() + subject.run_orchestrator.play() with pytest.raises(EngineConflictError): await subject.clear() @@ -159,16 +162,13 @@ async def test_clear_idle_engine(subject: MaintenanceEngineStore) -> None: created_at=datetime(2023, 7, 1), notify_publishers=mock_notify_publishers, ) - assert subject.engine is not None - assert subject.runner is not None + assert subject._run_orchestrator is not None await subject.clear() # TODO: test engine finish is called - with pytest.raises(NoRunnerEnginePairError): - subject.engine - with pytest.raises(NoRunnerEnginePairError): - subject.runner + with pytest.raises(NoRunOrchestrator): + subject.run_orchestrator async def test_estop_callback( @@ -187,12 +187,12 @@ async def test_estop_callback( decoy.when(engine_store.current_run_id).then_return(None) await handle_estop_event(engine_store, disengage_event) decoy.verify( - engine_store.engine.estop(), + engine_store.run_orchestrator.estop(), ignore_extra_args=True, times=0, ) decoy.verify( - await engine_store.engine.finish(), + await engine_store.run_orchestrator.finish(), ignore_extra_args=True, times=0, ) @@ -200,7 +200,9 @@ async def test_estop_callback( decoy.when(engine_store.current_run_id).then_return("fake-run-id") await handle_estop_event(engine_store, engage_event) decoy.verify( - engine_store.engine.estop(), - await engine_store.engine.finish(error=matchers.IsA(EStopActivatedError)), + engine_store.run_orchestrator.estop(), + await engine_store.run_orchestrator.finish( + error=matchers.IsA(EStopActivatedError) + ), times=1, ) diff --git a/robot-server/tests/maintenance_runs/test_run_data_manager.py b/robot-server/tests/maintenance_runs/test_run_data_manager.py index 0046b3098db2..0bacc0311a99 100644 --- a/robot-server/tests/maintenance_runs/test_run_data_manager.py +++ b/robot-server/tests/maintenance_runs/test_run_data_manager.py @@ -232,9 +232,9 @@ async def test_get_current_run( run_id = "hello world" decoy.when(mock_maintenance_engine_store.current_run_id).then_return(run_id) - decoy.when( - mock_maintenance_engine_store.engine.state_view.get_summary() - ).then_return(engine_state_summary) + decoy.when(mock_maintenance_engine_store.get_state_summary()).then_return( + engine_state_summary + ) decoy.when(mock_maintenance_engine_store.current_run_created_at).then_return( datetime(2023, 1, 1) ) @@ -311,9 +311,9 @@ def test_get_commands_slice_current_run( commands=expected_commands_result, cursor=1, total_length=3 ) decoy.when(mock_maintenance_engine_store.current_run_id).then_return("run-id") - decoy.when( - mock_maintenance_engine_store.engine.state_view.commands.get_slice(1, 2) - ).then_return(expected_command_slice) + decoy.when(mock_maintenance_engine_store.get_command_slice(1, 2)).then_return( + expected_command_slice + ) result = subject.get_commands_slice("run-id", 1, 2)