|
1 | 1 | """Unit tests for the deck_conflict module."""
|
2 | 2 |
|
3 | 3 | import pytest
|
4 |
| -from typing import ContextManager, Any |
| 4 | +from typing import ContextManager, Any, NamedTuple, List |
5 | 5 | from decoy import Decoy
|
6 | 6 | from contextlib import nullcontext as does_not_raise
|
7 | 7 | from opentrons_shared_data.labware.dev_types import LabwareUri
|
|
22 | 22 | WellOrigin,
|
23 | 23 | WellOffset,
|
24 | 24 | TipGeometry,
|
| 25 | + OnDeckLabwareLocation, |
| 26 | + OnLabwareLocation, |
| 27 | + Dimensions, |
25 | 28 | )
|
26 | 29 |
|
27 | 30 |
|
@@ -473,3 +476,116 @@ def test_deck_conflict_raises_for_bad_partial_8_channel_move(
|
473 | 476 | well_name="A2",
|
474 | 477 | well_location=WellLocation(origin=WellOrigin.TOP, offset=WellOffset(z=10)),
|
475 | 478 | )
|
| 479 | + |
| 480 | + |
| 481 | +class PipetteMovementSpec(NamedTuple): |
| 482 | + """Spec data to test deck_conflict.check_safe_for_tip_pickup_and_return .""" |
| 483 | + |
| 484 | + tiprack_parent: OnDeckLabwareLocation |
| 485 | + tiprack_dim: Dimensions |
| 486 | + is_on_flex_adapter: bool |
| 487 | + is_partial_config: bool |
| 488 | + expected_raise: ContextManager[Any] |
| 489 | + |
| 490 | + |
| 491 | +pipette_movement_specs: List[PipetteMovementSpec] = [ |
| 492 | + PipetteMovementSpec( |
| 493 | + tiprack_parent=DeckSlotLocation(slotName=DeckSlotName.SLOT_5), |
| 494 | + tiprack_dim=Dimensions(x=0, y=0, z=50), |
| 495 | + is_on_flex_adapter=False, |
| 496 | + is_partial_config=False, |
| 497 | + expected_raise=pytest.raises( |
| 498 | + deck_conflict.UnsuitableTiprackForPipetteMotion, |
| 499 | + match="A cool tiprack must be on an Opentrons Flex 96 Tip Rack Adapter", |
| 500 | + ), |
| 501 | + ), |
| 502 | + PipetteMovementSpec( |
| 503 | + tiprack_parent=OnLabwareLocation(labwareId="adapter-id"), |
| 504 | + tiprack_dim=Dimensions(x=0, y=0, z=50), |
| 505 | + is_on_flex_adapter=True, |
| 506 | + is_partial_config=False, |
| 507 | + expected_raise=does_not_raise(), |
| 508 | + ), |
| 509 | + PipetteMovementSpec( |
| 510 | + tiprack_parent=OnLabwareLocation(labwareId="adapter-id"), |
| 511 | + tiprack_dim=Dimensions(x=0, y=0, z=50), |
| 512 | + is_on_flex_adapter=False, |
| 513 | + is_partial_config=False, |
| 514 | + expected_raise=pytest.raises( |
| 515 | + deck_conflict.UnsuitableTiprackForPipetteMotion, |
| 516 | + match="A cool tiprack must be on an Opentrons Flex 96 Tip Rack Adapter", |
| 517 | + ), |
| 518 | + ), |
| 519 | + PipetteMovementSpec( |
| 520 | + tiprack_parent=OnLabwareLocation(labwareId="adapter-id"), |
| 521 | + tiprack_dim=Dimensions(x=0, y=0, z=50), |
| 522 | + is_on_flex_adapter=True, |
| 523 | + is_partial_config=True, |
| 524 | + expected_raise=pytest.raises( |
| 525 | + deck_conflict.PartialTipMovementNotAllowedError, |
| 526 | + match="A cool tiprack cannot be on an adapter taller than the tip rack", |
| 527 | + ), |
| 528 | + ), |
| 529 | + PipetteMovementSpec( |
| 530 | + tiprack_parent=OnLabwareLocation(labwareId="adapter-id"), |
| 531 | + tiprack_dim=Dimensions(x=0, y=0, z=101), |
| 532 | + is_on_flex_adapter=True, |
| 533 | + is_partial_config=True, |
| 534 | + expected_raise=does_not_raise(), |
| 535 | + ), |
| 536 | + PipetteMovementSpec( |
| 537 | + tiprack_parent=DeckSlotLocation(slotName=DeckSlotName.SLOT_5), |
| 538 | + tiprack_dim=Dimensions(x=0, y=0, z=50), |
| 539 | + is_on_flex_adapter=True, # will be ignored |
| 540 | + is_partial_config=True, |
| 541 | + expected_raise=does_not_raise(), |
| 542 | + ), |
| 543 | +] |
| 544 | + |
| 545 | + |
| 546 | +@pytest.mark.parametrize( |
| 547 | + ("robot_type", "deck_type"), |
| 548 | + [("OT-3 Standard", DeckType.OT3_STANDARD)], |
| 549 | +) |
| 550 | +@pytest.mark.parametrize( |
| 551 | + argnames=PipetteMovementSpec._fields, |
| 552 | + argvalues=pipette_movement_specs, |
| 553 | +) |
| 554 | +def test_valid_96_pipette_movement_for_tiprack_and_adapter( |
| 555 | + decoy: Decoy, |
| 556 | + mock_state_view: StateView, |
| 557 | + tiprack_parent: OnDeckLabwareLocation, |
| 558 | + tiprack_dim: Dimensions, |
| 559 | + is_on_flex_adapter: bool, |
| 560 | + is_partial_config: bool, |
| 561 | + expected_raise: ContextManager[Any], |
| 562 | +) -> None: |
| 563 | + """It should raise appropriate error for unsuitable tiprack parent when moving 96 channel to it.""" |
| 564 | + decoy.when(mock_state_view.pipettes.get_channels("pipette-id")).then_return(96) |
| 565 | + decoy.when(mock_state_view.labware.get_dimensions("adapter-id")).then_return( |
| 566 | + Dimensions(x=0, y=0, z=100) |
| 567 | + ) |
| 568 | + decoy.when(mock_state_view.labware.get_display_name("labware-id")).then_return( |
| 569 | + "A cool tiprack" |
| 570 | + ) |
| 571 | + decoy.when( |
| 572 | + mock_state_view.pipettes.get_is_partially_configured("pipette-id") |
| 573 | + ).then_return(is_partial_config) |
| 574 | + decoy.when(mock_state_view.labware.get_location("labware-id")).then_return( |
| 575 | + tiprack_parent |
| 576 | + ) |
| 577 | + decoy.when(mock_state_view.labware.get_dimensions("labware-id")).then_return( |
| 578 | + tiprack_dim |
| 579 | + ) |
| 580 | + decoy.when( |
| 581 | + mock_state_view.labware.get_has_quirk( |
| 582 | + labware_id="labware-id", quirk="tiprackAdapterFor96Channel" |
| 583 | + ) |
| 584 | + ).then_return(is_on_flex_adapter) |
| 585 | + |
| 586 | + with expected_raise: |
| 587 | + deck_conflict.check_safe_for_tip_pickup_and_return( |
| 588 | + engine_state=mock_state_view, |
| 589 | + pipette_id="pipette-id", |
| 590 | + labware_id="labware-id", |
| 591 | + ) |
0 commit comments