From 18d99a769cf9df8ef1e8c61779b65576f1e776fd Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 16 Feb 2024 13:49:56 -0500 Subject: [PATCH] Pr fixes --- .../robot_server/errors/error_mappers.py | 10 ++ .../protocols/protocol_analyzer.py | 33 ++++++- .../tests/protocols/test_protocol_analyzer.py | 99 +++++++++++++++++++ 3 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 robot-server/robot_server/errors/error_mappers.py diff --git a/robot-server/robot_server/errors/error_mappers.py b/robot-server/robot_server/errors/error_mappers.py new file mode 100644 index 00000000000..70aa815bf70 --- /dev/null +++ b/robot-server/robot_server/errors/error_mappers.py @@ -0,0 +1,10 @@ +"""Map errors to Exceptions.""" +from opentrons_shared_data.errors import EnumeratedError, PythonException + + +def map_unexpected_error(error: BaseException) -> EnumeratedError: + """Map an unhandled Exception to a known exception.""" + if isinstance(error, EnumeratedError): + return error + else: + return PythonException(error) diff --git a/robot-server/robot_server/protocols/protocol_analyzer.py b/robot-server/robot_server/protocols/protocol_analyzer.py index 542ece91284..49457d864f9 100644 --- a/robot-server/robot_server/protocols/protocol_analyzer.py +++ b/robot-server/robot_server/protocols/protocol_analyzer.py @@ -2,11 +2,14 @@ import logging from opentrons import protocol_runner +from opentrons.protocol_engine.errors import ErrorOccurrence +import opentrons.util.helpers as datetime_helper + +import robot_server.errors.error_mappers as em from .protocol_store import ProtocolResource from .analysis_store import AnalysisStore - log = logging.getLogger(__name__) @@ -30,9 +33,31 @@ async def analyze( robot_type=protocol_resource.source.robot_type, protocol_config=protocol_resource.source.config, ) - result = await runner.run( - protocol_source=protocol_resource.source, deck_configuration=[] - ) + try: + result = await runner.run( + protocol_source=protocol_resource.source, deck_configuration=[] + ) + except BaseException as error: + internal_error = em.map_unexpected_error(error=error) + await self._analysis_store.update( + analysis_id=analysis_id, + robot_type=protocol_resource.source.robot_type, + commands=[], + labware=[], + modules=[], + pipettes=[], + errors=[ + ErrorOccurrence.from_failed( + # TODO(tz, 2-15-24): replace with a different error type + # when we are able to support different errors. + id="internal-error", + createdAt=datetime_helper.utc_now(), + error=internal_error, + ) + ], + liquids=[], + ) + return log.info(f'Completed analysis "{analysis_id}".') diff --git a/robot-server/tests/protocols/test_protocol_analyzer.py b/robot-server/tests/protocols/test_protocol_analyzer.py index 03784e62c8e..5f53452b7a2 100644 --- a/robot-server/tests/protocols/test_protocol_analyzer.py +++ b/robot-server/tests/protocols/test_protocol_analyzer.py @@ -17,10 +17,30 @@ ) import opentrons.protocol_runner as protocol_runner from opentrons.protocol_reader import ProtocolSource, JsonProtocolConfig +import opentrons.util.helpers as datetime_helper from robot_server.protocols.analysis_store import AnalysisStore from robot_server.protocols.protocol_store import ProtocolResource from robot_server.protocols.protocol_analyzer import ProtocolAnalyzer +import robot_server.errors.error_mappers as em + +from opentrons_shared_data.errors import EnumeratedError, ErrorCodes + + +@pytest.fixture(autouse=True) +def patch_mock_map_unexpected_error( + decoy: Decoy, monkeypatch: pytest.MonkeyPatch +) -> None: + """Replace map_unexpected_error with a mock.""" + mock_map_unexpected_error = decoy.mock(func=em.map_unexpected_error) + monkeypatch.setattr(em, "map_unexpected_error", mock_map_unexpected_error) + + +@pytest.fixture(autouse=True) +def patch_mock_get_utc_datetime(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: + """Replace utc_now with a mock.""" + mock_get_utc_datetime = decoy.mock(func=datetime_helper.utc_now) + monkeypatch.setattr(datetime_helper, "utc_now", mock_get_utc_datetime) @pytest.fixture(autouse=True) @@ -146,3 +166,82 @@ async def test_analyze( liquids=[], ), ) + + +async def test_analyze_updates_pending_on_error( + decoy: Decoy, + analysis_store: AnalysisStore, + subject: ProtocolAnalyzer, +) -> None: + """It should update pending analysis with an internal error.""" + robot_type: RobotType = "OT-3 Standard" + + protocol_resource = ProtocolResource( + protocol_id="protocol-id", + created_at=datetime(year=2021, month=1, day=1), + source=ProtocolSource( + directory=Path("/dev/null"), + main_file=Path("/dev/null/abc.json"), + config=JsonProtocolConfig(schema_version=123), + files=[], + metadata={}, + robot_type=robot_type, + content_hash="abc123", + ), + protocol_key="dummy-data-111", + ) + + raised_exception = Exception("You got me!!") + + error_occurrence = pe_errors.ErrorOccurrence.construct( + id="internal-error", + createdAt=datetime(year=2023, month=3, day=3), + errorType="EnumeratedError", + detail="You got me!!", + ) + + enumerated_error = EnumeratedError( + code=ErrorCodes.GENERAL_ERROR, + message="You got me!!", + ) + + json_runner = decoy.mock(cls=protocol_runner.JsonRunner) + + decoy.when( + await protocol_runner.create_simulating_runner( + robot_type=robot_type, + protocol_config=JsonProtocolConfig(schema_version=123), + ) + ).then_return(json_runner) + + decoy.when( + await json_runner.run( + deck_configuration=[], protocol_source=protocol_resource.source + ) + ).then_raise(raised_exception) + + decoy.when(em.map_unexpected_error(error=raised_exception)).then_return( + enumerated_error + ) + + decoy.when(datetime_helper.utc_now()).then_return( + datetime(year=2023, month=3, day=3) + ) + + await subject.analyze( + protocol_resource=protocol_resource, + analysis_id="analysis-id", + ) + + decoy.verify( + await analysis_store.update( + analysis_id="analysis-id", + robot_type=robot_type, + commands=[], + labware=[], + modules=[], + pipettes=[], + errors=[error_occurrence], + liquids=[], + ), + )