From 832b7831c13634296bbfcca5942a8c62784f6dac Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 9 Feb 2024 16:44:06 -0500 Subject: [PATCH] initial thoughts progress update on fail added completed_with_error to update signature added tests and fixed linting fixed linting added EnumeratedError to errors list and removed completed_with_error create an ErrorOccurence from the internal error monkeypatching not going so well progress with mocking. still not complete fixed mocking!need fo figure out datetime failing fixed bug with datetime compare remove unnecessary test reverted changes and restructured removed print reverted changes Update robot-server/robot_server/protocols/protocol_analyzer.py Co-authored-by: Seth Foster 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=[], + ), + )