1
1
import asyncio
2
2
import pytest
3
3
import mock
4
- from typing import AsyncGenerator
4
+ from contextlib import nullcontext as does_not_raise
5
+ from typing import AsyncGenerator , ContextManager , Any
5
6
from opentrons .drivers .flex_stacker .simulator import SimulatingDriver
6
7
from opentrons .drivers .flex_stacker .types import (
7
8
Direction ,
26
27
from opentrons .hardware_control .modules .types import PlatformState
27
28
from opentrons .hardware_control .poller import Poller
28
29
from opentrons .hardware_control .types import StatusBarState , StatusBarUpdateEvent
30
+ from opentrons_shared_data .errors .exceptions import (
31
+ FlexStackerShuttleLabwareError ,
32
+ FlexStackerHopperLabwareError ,
33
+ FlexStackerShuttleNotEmptyError ,
34
+ )
29
35
30
36
31
37
@pytest .fixture
@@ -401,31 +407,38 @@ async def test_dispense_labware_motion_sequence(
401
407
subject , "_move_and_home_axis" , mock .AsyncMock ()
402
408
) as _move_and_home_axis ,
403
409
mock .patch .object (
404
- subject , "verify_shuttle_labware_presence" , mock .AsyncMock ()
410
+ subject , "labware_detected" , mock .AsyncMock ()
411
+ ) as labware_detected ,
412
+ mock .patch .object (
413
+ subject ,
414
+ "verify_shuttle_labware_presence" ,
415
+ autospec = True ,
416
+ side_effect = subject .verify_shuttle_labware_presence ,
405
417
) as verify_shuttle_labware_presence ,
406
418
mock .patch .object (
407
- subject , "verify_hopper_labware_presence" , mock . AsyncMock ()
419
+ subject , "verify_hopper_labware_presence" , autospec = True
408
420
) as verify_hopper_labware_presence ,
409
421
mock .patch .object (subject , "move_axis" , mock .AsyncMock ()) as move_axis ,
410
422
mock .patch .object (subject , "home_axis" , mock .AsyncMock ()) as home_axis ,
411
423
mock .patch .object (subject , "open_latch" , mock .AsyncMock ()) as open_latch ,
412
424
mock .patch .object (subject , "close_latch" , mock .AsyncMock ()) as close_latch ,
413
425
):
426
+ labware_detected .side_effect = [True , False , True ]
414
427
# Test valid labware height
415
428
await subject .dispense_labware (
416
429
labware_height = labware_height ,
417
430
)
418
431
419
432
# We need to verify the move sequence
420
- verify_hopper_labware_presence .assert_called_once_with (Direction .EXTEND , True )
433
+ # verify_hopper_labware_presence.assert_called_once_with(Direction.EXTEND, True)
434
+ verify_hopper_labware_presence .assert_not_called ()
421
435
_prepare_for_action .assert_called ()
422
436
423
437
_move_and_home_axis .assert_any_call (
424
438
StackerAxis .X , Direction .RETRACT , HOME_OFFSET_MD
425
439
)
426
440
# Verify labware presence
427
441
verify_shuttle_labware_presence .assert_any_call (Direction .RETRACT , False )
428
-
429
442
_move_and_home_axis .assert_any_call (
430
443
StackerAxis .Z , Direction .EXTEND , HOME_OFFSET_SM
431
444
)
@@ -452,9 +465,77 @@ async def test_dispense_labware_motion_sequence(
452
465
)
453
466
454
467
# Make sure labware presense check on the X retract was called twice
468
+ labware_detected .assert_has_calls (
469
+ [
470
+ mock .call (StackerAxis .Z , Direction .EXTEND ),
471
+ mock .call (StackerAxis .X , Direction .RETRACT ),
472
+ mock .call (StackerAxis .X , Direction .RETRACT ),
473
+ ]
474
+ )
455
475
assert verify_shuttle_labware_presence .call_count == 2
456
476
457
477
478
+ @pytest .mark .parametrize ("hopper_lw_detected" , [True , False ])
479
+ @pytest .mark .parametrize ("shuttle_lw_detected_before" , [True , False ])
480
+ @pytest .mark .parametrize ("shuttle_lw_detected_after" , [True , False ])
481
+ async def test_dispense_labware_error_handling (
482
+ subject : modules .FlexStacker ,
483
+ hopper_lw_detected : bool ,
484
+ shuttle_lw_detected_before : bool ,
485
+ shuttle_lw_detected_after : bool ,
486
+ ) -> None :
487
+ """
488
+ Test different error handling scenarios when dispensing labware.
489
+ """
490
+ expected_raise : ContextManager [Any ]
491
+ if shuttle_lw_detected_before is True :
492
+ # If the shuttle is occupied, it should always raise an error
493
+ expected_raise = pytest .raises (FlexStackerShuttleNotEmptyError )
494
+ elif shuttle_lw_detected_after is True :
495
+ # if the shuttle has labware after dispensing, no need to raise any error
496
+ expected_raise = does_not_raise ()
497
+ elif hopper_lw_detected is False :
498
+ # if the shuttle has no labware after dispensing, and the hopper has no labware,
499
+ # it should raise a FlexStackerHopperLabwareError
500
+ expected_raise = pytest .raises (FlexStackerHopperLabwareError )
501
+ else :
502
+ # if the shuttle has no labware after dispensing, but the hopper has labware,
503
+ # it should raise a FlexStackerShuttleLabwareError letting user know
504
+ # the labware latch is properly jammed
505
+ expected_raise = pytest .raises (FlexStackerShuttleLabwareError )
506
+
507
+ with (
508
+ mock .patch .object (
509
+ subject ,
510
+ "labware_detected" ,
511
+ side_effect = [
512
+ hopper_lw_detected ,
513
+ shuttle_lw_detected_before ,
514
+ shuttle_lw_detected_after ,
515
+ ],
516
+ autospec = True ,
517
+ ) as labware_detected ,
518
+ ):
519
+
520
+ with expected_raise :
521
+ # Test valid labware height
522
+ await subject .dispense_labware (
523
+ labware_height = 100.0 ,
524
+ )
525
+
526
+ expected_calls = [
527
+ mock .call (StackerAxis .Z , Direction .EXTEND ),
528
+ mock .call (StackerAxis .X , Direction .RETRACT ),
529
+ mock .call (StackerAxis .X , Direction .RETRACT ),
530
+ ]
531
+ if shuttle_lw_detected_before :
532
+ assert labware_detected .call_count == 2
533
+ labware_detected .assert_has_calls (expected_calls [:2 ])
534
+ else :
535
+ assert labware_detected .call_count == 3
536
+ labware_detected .assert_has_calls (expected_calls )
537
+
538
+
458
539
@pytest .mark .parametrize (
459
540
("labware_height" ),
460
541
[(0 ), (- 10 ), (200 )],
0 commit comments