Esempio n. 1
0
    def test_protocol_equality(self):
        # Set up a protocol.
        p1 = Protocol()
        p1.add_instrument('A', 'p200')
        p1.add_container('C1', 'tiprack.p200')
        p1.add_container('A1', 'microplate.96')
        p1.calibrate('A1', x=1, y=2, z=3)
        p1.calibrate_instrument('A', top=0, blowout=10)
        p1.transfer('A1:A1', 'A1:A2', ul=100)
        p1.transfer('A1:A2', 'A1:A3', ul=80)

        # And a copy.
        p2 = Protocol()
        p2.add_container('A1', 'microplate.96')
        p2.add_container('C1', 'tiprack.p200')
        p2.add_instrument('A', 'p200')
        p2.calibrate('A1', x=1, y=2, z=3)
        p2.calibrate_instrument('A', top=0, blowout=10)
        p2.transfer('A1:A1', 'A1:A2', ul=100)
        p2.transfer('A1:A2', 'A1:A3', ul=80)

        # They're identical.
        self.assertEqual(p1, p2)

        # Make a change.
        p2.add_instrument('B', 'p10')

        # No longer identical.
        self.assertNotEqual(p1, p2)
Esempio n. 2
0
class ProtocolTest(unittest.TestCase):

    def setUp(self):
        self.protocol = Protocol()
        self.maxDiff = None

    @property
    def instructions(self):
        return self.protocol._commands

    def test_normalize_address(self):
        self.protocol.add_container('A1', 'microplate.96', label="Output")
        label = self.protocol._normalize_address('Output:A1')
        self.assertEqual(label, ((0, 0), (0, 0)))
        slot = self.protocol._normalize_address('A1:A1')
        self.assertEqual(slot, ((0, 0), (0, 0)))

    def test_info(self):
        name = "Foo Bar"
        desc = "Lorem ipsum dolor set amet."
        auth = "Jane Doe"
        self.protocol.set_info(name=name, description=desc, author=auth)
        i = self.protocol.info
        self.assertEqual(i['name'], name)
        self.assertEqual(i['description'], desc)
        self.assertEqual(i['author'], auth)
        self.assertTrue('created' in i)
        self.assertTrue('updated' in i)

    def test_humanize_address(self):
        self.protocol.add_container("A1", 'microplate.96', label="LaBeL")
        with self.assertRaises(x.ContainerConflict):
            self.protocol.add_container("A2", 'microplate.96', label="label")
        self.protocol.add_container("A2", 'microplate.96', label="stuff")
        lA1 = self.protocol.humanize_address(('label', 'A1'))
        sA1 = self.protocol.humanize_address(('STUFF', 'A1'))
        self.assertEqual(lA1, 'LaBeL:A1')
        self.assertEqual(sA1, 'stuff:A1')

    def test_transfer(self):
        """ Basic transfer. """
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('B1', 'microplate.96')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_instrument('B', 'p20')
        self.protocol.transfer('A1:A1', 'B1:B1', ul=100, tool='p200')
        expected = [{
            'command': 'transfer',
            'tool': 'p200',
            'volume': 100,
            'start': ((0, 0), (0, 0)),
            'end': ((1, 0), (1, 0)),
            'blowout': True,
            'touchtip': True
        }]
        self.assertEqual(self.instructions, expected)

    def test_transfer_without_pipette(self):
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(x.InstrumentMissing):
            self.protocol.transfer('A1:A1', 'A1:A2', ul=10)

    def test_transfer_without_volume(self):
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.transfer("A1:A1", "A1:A1")

    def test_transfer_zero_volume(self):
        with self.assertRaises(ValueError):
            self.protocol.transfer("A1:A1", "A1:A1", ul=0)
        with self.assertRaises(ValueError):
            self.protocol.transfer("A1:A1", "A1:A1", ml=0)

    def test_transfer_conflicting_volume(self):
        with self.assertRaises(ValueError):
            self.protocol.transfer("A1:A1", "A1:A1", ul=1, ml=1)

    def test_transfer_group(self):
        """ Transfer group. """
        expected = [{
            'command': 'transfer_group',
            'tool': 'p20',
            'transfers': [
                {
                    'volume': 15,
                    'start': ((0, 0), (0, 0)),  # A1:A1
                    'end': ((0, 0), (1, 0)),  # A1:B1
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 10,
                    'start': ((0, 0), (0, 1)),  # A1:A2
                    'end': ((0, 0), (1, 1)),  # A1:B2
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 12,
                    'start': ((0, 0), (0, 2)),  # A1:A3
                    'end': ((0, 0), (1, 2)),  # A1:B3
                    'blowout': False,
                    'touchtip': True
                },
                {
                    'volume': 12,
                    'start': ((0, 0), (0, 3)),  # A1:A4
                    'end': ((0, 0), (1, 3)),  # A1:B4
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 12,
                    'start': ((0, 0), (0, 4)),  # A1:A5
                    'end': ((0, 0), (1, 4)),  # A1:B5
                    'blowout': True,
                    'touchtip': True
                }
            ]
        }]
        self.protocol.add_container('A1', 'microplate.96', label="Label")
        self.protocol.add_instrument('A', 'p20')
        self.protocol.transfer_group(
            ('A1:A1', 'A1:B1', {'ul': 15}),
            ('A1:A2', 'A1:B2', {'ul': 10}),
            ('A1:A3', 'A1:B3', {'blowout': False}),
            ('A1:A4', 'A1:B4'),
            ('A1:A5', 'A1:B5'),
            ul=12,
            tool='p20'
        )
        self.assertEqual(self.instructions, expected)

    def test_transfer_group_without_pipette(self):
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(x.InstrumentMissing):
            self.protocol.transfer_group(
                ('A1:A1', 'A1:B1', {'ul': 15}),
                ('A1:A2', 'A1:B2', {'ml': 1}),
                ('A1:A3', 'A1:B3', {'blowout': False}),
                ('A1:A4', 'A1:B4'),
                ('A1:A5', 'A1:B5'),
                ul=12,
                tool='p10'
            )

    def test_transfer_group_without_volume(self):
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.add_instrument('A', 'p10')
            self.protocol.transfer_group(
                ('A1:A1', 'A1:B1'),
                ('A1:A2', 'A1:B2'),
                ('A1:A3', 'A1:B3', {'blowout': False}),
                ('A1:A4', 'A1:B4'),
                ('A1:A5', 'A1:B5'),
                tool='p10'
            )

    def test_transfer_group_zero_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.transfer_group(
                ('A1:A1', 'A1:B1'),
                ('A1:A2', 'A1:B2'),
                ('A1:A3', 'A1:B3', {'blowout': False}),
                ('A1:A4', 'A1:B4'),
                ('A1:A5', 'A1:B5'),
                ul=0,
                tool='p10'
            )
        with self.assertRaises(ValueError):
            self.protocol.transfer_group(
                ('A1:A1', 'A1:B1'),
                ('A1:A2', 'A1:B2'),
                ('A1:A3', 'A1:B3', {'blowout': False}),
                ('A1:A4', 'A1:B4'),
                ('A1:A5', 'A1:B5'),
                ml=0,
                tool='p10'
            )

    def test_transfer_group_conflicting_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.transfer_group(
                ('A1:A1', 'A1:B1'),
                ('A1:A2', 'A1:B2'),
                ('A1:A3', 'A1:B3', {'blowout': False}),
                ('A1:A4', 'A1:B4'),
                ('A1:A5', 'A1:B5'),
                ul=5,
                ml=4,
                tool='p10'
            )

    def test_distribute(self):
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.distribute(
            'A1:A1',
            ('A1:B1', {'ul': 50}),
            ('A1:C1'),
            ('A1:D1', {'ul': 30}),
            ul=20
        )
        expected = [{
            'command': 'distribute',
            'tool': 'p200',
            'start': ((0, 0), (0, 0)),
            'transfers': [
                {
                    'volume': 50,
                    'end': ((0, 0), (1, 0)),  # A1:B1
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 20,  # Default
                    'end': ((0, 0), (2, 0)),  # A1:C1
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 30,
                    'end': ((0, 0), (3, 0)),  # A1:D1
                    'blowout': True,
                    'touchtip': True
                }
            ]
        }]
        self.assertEqual(self.instructions, expected)

    def test_distribute_without_pipette(self):
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(x.InstrumentMissing):
            self.protocol.distribute(
                'A1:A1',
                ('A1:B1', {'ul': 50}),
                ('A1:C1', {'ul': 5}),
                ('A1:D1', {'ul': 10})
            )

    def test_distribute_zero_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.distribute(
                'A1:A1',
                ('A1:B1', {'ul': 4}),
                ('A1:C1', {'ul': 5}),
                ('A1:D1', {'ul': 0})
            )

    def test_distribute_conflicting_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.distribute(
                'A1:A1',
                ('A1:B1'),
                ('A1:C1'),
                ('A1:D1', {'ul': 10, 'ml': 5}),
                ul=5
            )

    def test_consolidate(self):
        """ Consolidate. """
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.consolidate(
            'A1:A1',
            ('A1:B1', {'ul': 50}),
            ('A1:C1', {'ul': 25}),
            ('A1:D1'),
            ul=30
        )
        expected = [{
            'command': 'consolidate',
            'tool': 'p200',
            'end': ((0, 0), (0, 0)),
            'transfers': [
                {
                    'volume': 50,
                    'start': ((0, 0), (1, 0)),  # A1:B1
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 25,
                    'start': ((0, 0), (2, 0)),  # A1:C1
                    'blowout': True,
                    'touchtip': True
                },
                {
                    'volume': 30,
                    'start': ((0, 0), (3, 0)), # A1:D1
                    'blowout': True,
                    'touchtip': True
                }
            ]
        }]
        self.assertEqual(self.instructions, expected)

    def test_consolidate_without_pipette(self):
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(x.InstrumentMissing):
            self.protocol.consolidate(
                'A1:A1',
                ('A1:B1', {'ul': 50}),
                ('A1:C1', {'ul': 5}),
                ('A1:D1', {'ul': 10})
            )

    def test_consolidate_zero_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.consolidate(
                'A1:A1',
                ('A1:B1', {'ul': 4}),
                ('A1:C1', {'ul': 5}),
                ('A1:D1', {'ul': 0})
            )

    def test_consolidate_conflicting_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.consolidate(
                'A1:A1',
                ('A1:B1', {'ul': 5}),
                ('A1:C1', {'ul': 5}),
                ('A1:D1', {'ul': 10, 'ml': 5})
            )

    def test_mix(self):
        """ Mix. """
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.mix('A1:A1', ul=50, repetitions=10)
        expected = [{
            'command': 'mix',
            'tool': 'p200',
            'start': ((0, 0), (0, 0)),  # A1:A1
            'blowout': True,
            'touchtip': True,
            'volume': 50,
            'reps': 10
        }]
        self.assertEqual(self.instructions, expected)

    def test_mix_without_pipette(self):
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(x.InstrumentMissing):
            self.protocol.mix('A1:A1', ul=50, repetitions=10, tool='p200')

    def test_mix_without_volume(self):
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_instrument('A', 'p10')
        with self.assertRaises(ValueError):
            self.protocol.mix('A1:A1', repetitions=10, tool='p10')

    def test_mix_zero_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.mix('A1:A1', ul=0, repetitions=10, tool='p10')
        with self.assertRaises(ValueError):
            self.protocol.mix('A1:A1', ml=0, repetitions=10, tool='p10')

    def test_mix_conflicting_volume(self):
        self.protocol.add_instrument('A', 'p10')
        self.protocol.add_container('A1', 'microplate.96')
        with self.assertRaises(ValueError):
            self.protocol.mix('A1:A1', ul=1, ml=1, repetitions=10, tool='p10')

    def test_protocol_run_twice(self):
        """ Run a protocol twice without error. """
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.calibrate('A1', x=1, y=2, z=3)
        self.protocol.calibrate_instrument('A', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        self.protocol.run_all()
        self.protocol.run_all()

    def test_protocol_equality(self):
        # Set up a protocol.
        p1 = Protocol()
        p1.add_instrument('A', 'p200')
        p1.add_container('C1', 'tiprack.p200')
        p1.add_container('A1', 'microplate.96')
        p1.calibrate('A1', x=1, y=2, z=3)
        p1.calibrate_instrument('A', top=0, blowout=10)
        p1.transfer('A1:A1', 'A1:A2', ul=100)
        p1.transfer('A1:A2', 'A1:A3', ul=80)

        # And a copy.
        p2 = Protocol()
        p2.add_container('A1', 'microplate.96')
        p2.add_container('C1', 'tiprack.p200')
        p2.add_instrument('A', 'p200')
        p2.calibrate('A1', x=1, y=2, z=3)
        p2.calibrate_instrument('A', top=0, blowout=10)
        p2.transfer('A1:A1', 'A1:A2', ul=100)
        p2.transfer('A1:A2', 'A1:A3', ul=80)

        # They're identical.
        self.assertEqual(p1, p2)

        # Make a change.
        p2.add_instrument('B', 'p10')

        # No longer identical.
        self.assertNotEqual(p1, p2)

    def test_protocol_version(self):
        # Set up a protocol.
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.calibrate('A1', x=1, y=2, z=3)
        self.protocol.calibrate_instrument('A', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)

        # First version bump.
        v1 = self.protocol.bump_version()
        self.assertEqual(v1, '0.0.1')

        # No changes, version will stay the same.
        v2 = self.protocol.bump_version()
        self.assertEqual(v1, v2)

        # Make a change, bump the version.
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        v3 = self.protocol.bump_version()
        self.assertEqual(v3, '0.0.2')

        # Make a change, bump the version.
        self.protocol.transfer('A1:A1', 'A1:A1', ul=20)
        v4 = self.protocol.bump_version('feature')
        self.assertEqual(v4, '0.1.0')

        # Make a change, bump the version.
        self.protocol.transfer('A1:A1', 'A1:A1', ul=20)
        v5 = self.protocol.bump_version('major')
        self.assertEqual(v5, '1.0.0')

    def test_partial_protocol(self):
        p = Protocol.partial()
        p.transfer('A1:A1', 'A1:A3', ul=1)
        # As long as it doesn't throw an Exception, we're good.

    def test_partial_protocol_run(self):
        p = Protocol.partial()
        p.transfer('A1:A1', 'A1:A3', ul=1)
        with self.assertRaises(x.PartialProtocolException):
            # This shouldn't run because it's not valid.
            p.run_all()

    def test_valid_partial_protocol_run(self):
        p = Protocol.partial()
        p.add_instrument('A', 'p10')
        p.add_container("A1", "microplate.96")
        p.transfer('A1:A1', 'A1:A3', ul=1)
        # This should run because there are no Partial problems.
        p.run_all()

    def test_partial_protocol_export(self):
        p = Protocol.partial()
        p.transfer('A1:A1', 'A1:A3', ul=1)
        with self.assertRaises(x.PartialProtocolException):
            # This shouldn't export because it's not valid.
            p.export(JSONFormatter)

    def test_valid_partial_protocol_export(self):
        p = Protocol.partial()
        p.add_instrument('A', 'p10')
        p.add_container("A1", "microplate.96")
        p.transfer('A1:A1', 'A1:A3', ul=1)
        # This should export because there are no Partial problems.
        p.export(JSONFormatter)

    def test_protocol_addition_of_partial(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p20')
        p1.add_container('A1', 'microplate.96')
        p1.transfer('A1:A1', 'A1:A2', ul=10)

        p2 = Protocol.partial()
        p2.add_container('A2', 'microplate.96')
        p2.transfer('A1:A1', 'A1:A3', ul=20)
        p2.transfer('A2:A1', 'A2:A4', ul=15)

        p3 = Protocol()
        p3.add_instrument('A', 'p20')
        p3.add_container('A1', 'microplate.96')
        p3.add_container('A2', 'microplate.96')
        p3.transfer('A1:A1', 'A1:A2', ul=10)
        p3.transfer('A1:A1', 'A1:A3', ul=20)
        p3.transfer('A2:A1', 'A2:A4', ul=15)

        self.assertEqual(p3, p1 + p2)

    def test_protocol_addition_of_partials(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p20')
        p1.add_container('A1', 'microplate.96')
        p1.transfer('A1:A1', 'A1:A2', ul=10)

        p2 = Protocol.partial()
        p2.add_container('A2', 'microplate.96')
        p2.transfer('A1:A1', 'A1:A3', ul=20)
        p2.transfer('A2:A1', 'A2:A4', ul=15)

        p3 = Protocol.partial()
        p3.add_container('A3', 'microplate.96')
        p3.transfer('A1:A1', 'A1:A3', ul=20)
        p3.transfer('A1:A1', 'A3:A4', ul=15)

        p4 = Protocol()
        p4.add_instrument('A', 'p20')
        p4.add_container('A1', 'microplate.96')
        p4.add_container('A2', 'microplate.96')
        p4.add_container('A3', 'microplate.96')
        p4.transfer('A1:A1', 'A1:A2', ul=10)
        p4.transfer('A1:A1', 'A1:A3', ul=20)
        p4.transfer('A2:A1', 'A2:A4', ul=15)
        p4.transfer('A1:A1', 'A1:A3', ul=20)
        p4.transfer('A1:A1', 'A3:A4', ul=15)

        self.assertEqual(p4, p1 + p2 + p3)

    def test_protocol_added_to_partial(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p20')
        p1.add_container('A1', 'microplate.96')
        p1.transfer('A1:A1', 'A1:A2', ul=10)

        p2 = Protocol.partial()
        p2.add_container('A2', 'microplate.96')
        p2.transfer('A1:A1', 'A1:A3', ul=20)
        p2.transfer('A2:A1', 'A2:A4', ul=15)

        with self.assertRaises(TypeError):
            p2 + p1

    def test_protocol_addition(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p10')
        p1.add_container('A1', 'microplate.96')
        p1.transfer('A1:A1', 'A1:A1', ul=10)

        p2 = Protocol()
        p2.add_instrument('A', 'p10')  # Same definition; no conflict.
        p2.add_instrument('B', 'p20')  # New instrument.
        p2.add_container('A1', 'microplate.96')  # No conflict.
        p2.add_container('A2', 'microplate.96')  # New container.
        p2.transfer('A1:A1', 'A1:A1', ul=12)
        p2.transfer('A1:A1', 'A2:A2', ul=20)

        p3 = Protocol()
        p3.add_instrument('A', 'p10')
        p3.add_instrument('B', 'p20')
        p3.add_container('A1', 'microplate.96')
        p3.add_container('A2', 'microplate.96')
        p3.transfer('A1:A1', 'A1:A1', ul=10)
        p3.transfer('A1:A1', 'A1:A1', ul=12)
        p3.transfer('A1:A1', 'A2:A2', ul=20)

        self.assertEqual(p3, p1 + p2)

    def test_protocol_label_addition(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p10')
        p1.add_container('A1', 'microplate.96', label="Input")
        p1.transfer('A1:A1', 'A1:A1', ul=10)

        p2 = Protocol()
        p2.add_instrument('A', 'p10')  # Same definition; no conflict.
        p2.add_instrument('B', 'p20')  # New instrument.
        p2.add_container('A1', 'microplate.96', label="Input")  # No conflict.
        p2.add_container('A2', 'microplate.96')  # New container.
        p2.transfer('A1:A1', 'A1:A1', ul=9)
        p2.transfer('Input:A1', 'A2:A2', ul=20)

        p3 = Protocol()
        p3.add_instrument('A', 'p10')
        p3.add_instrument('B', 'p20')
        p3.add_container('A1', 'microplate.96', label="Input")
        p3.add_container('A2', 'microplate.96')
        p3.transfer('A1:A1', 'A1:A1', ul=10)
        p3.transfer('A1:A1', 'A1:A1', ul=9)
        p3.transfer('A1:A1', 'A2:A2', ul=20)

        self.assertEqual(p3, p1 + p2)

    def test_protocol_addition_label_conflict(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p10')
        p1.add_container('A1', 'microplate.96', label="Input")
        p1.transfer('A1:A1', 'A1:A1', ul=10)

        p2 = Protocol()
        p2.add_instrument('A', 'p10')  # Same definition; no conflict.
        p2.add_instrument('B', 'p20')  # New instrument.
        p2.add_container('A1', 'microplate.96', label="Output")  # Conflict.
        p2.add_container('A2', 'microplate.96')  # New container.
        p2.transfer('A1:A1', 'A1:A1', ul=12)
        p2.transfer('Output:A1', 'A2:A2', ul=20)

        with self.assertRaises(x.ContainerConflict):
            p1 + p2

    def test_protocol_addition_label_case(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p10')
        p1.add_container('A1', 'microplate.96', label="Input")

        p2 = Protocol()
        p2.add_instrument('A', 'p10')
        p2.add_container('A1', 'microplate.96', label="INPUT")

        p3 = Protocol()
        p3.add_instrument('A', 'p10')
        p3.add_container('A1', 'microplate.96', label="INPUT")

        self.assertEqual((p1 + p2), p3)

    def test_protocol_addition_info(self):
        p1 = Protocol()
        p1.set_info(author="John Doe", name="Lorem Ipsum")

        p2 = Protocol()
        p2.set_info(author="Jane Doe")

        p3 = p1 + p2

        self.assertEqual('Jane Doe', p3.info['author'])
        self.assertEqual('Lorem Ipsum', p3.info['name'])

    def test_protocol_addition_container_conflict(self):
        p1 = Protocol()
        p1.add_instrument('A', 'p10')
        p1.add_container('A1', 'microplate.96')
        p1.transfer('A1:A1', 'A1:A2', ul=10)

        p2 = Protocol()
        p2.add_container('A1', 'tiprack.p20')

        with self.assertRaises(x.ContainerConflict):
            p1 + p2
Esempio n. 3
0
class ProtocolRequirementsTest(unittest.TestCase):
    def setUp(self):
        self.protocol = Protocol()

    def assertRequirements(self, expected, reqs=None):
        reqs = reqs or self.protocol.run_requirements
        ouch = []
        extra = deepcopy(reqs)
        missing = deepcopy(expected)
        for ei, e in enumerate(expected):
            for ri, r in enumerate(reqs):
                try:
                    self.assertEqual(e, r)
                except AssertionError:
                    continue
                extra[ri] = None
                missing[ei] = None
        extra = [x for x in extra if x is not None]
        missing = [x for x in missing if x is not None]
        if len(extra) > 0:
            ouch.append("Found {} extra items: {}".format(len(extra), extra))
        if len(missing) > 0:
            ouch.append("Missing items: {}".format(missing))
        if len(ouch) > 0:
            assert False, "\n".join(ouch)

    def test_requirements_assertion(self):
        self.assertRequirements([{
            'foo': 'bar'
        }, {
            'bizz': 'buzz'
        }], [{
            'foo': 'bar'
        }, {
            'bizz': 'buzz'
        }])
        with self.assertRaises(AssertionError):
            self.assertRequirements([{
                'foo': 'bar'
            }], [{
                'foo': 'bar'
            }, {
                'bizz': 'buzz'
            }])
        with self.assertRaises(AssertionError):
            self.assertRequirements([{
                'foo': 'bar'
            }, {
                'bizz': 'buzz'
            }], [{
                'foo': 'bar'
            }])

    def test_require_instrument_calibration(self):
        self.protocol.add_instrument('A', 'p20')
        self.protocol.add_container('A1', 'microplate')
        self.protocol.add_container('A2', 'tiprack.p20')
        self.protocol.transfer('A1:A1', 'A1:A2', ul=10)
        reqs = [{
            'type': 'calibrate_instrument',
            'axis': 'A',
            'instrument_name': 'p20'
        }, {
            'type': 'calibrate_container',
            'axis': 'A',
            'address': (0, 0),
            'container_name': 'microplate',
            'instrument_name': 'p20'
        }, {
            'type': 'calibrate_container',
            'axis': 'A',
            'address': (0, 1),
            'container_name': 'tiprack.p20',
            'instrument_name': 'p20'
        }]
        self.assertRequirements(reqs)
        self.protocol.calibrate_instrument(axis='A',
                                           top=10,
                                           bottom=10,
                                           blowout=10)
        reqs.pop(0)
        self.assertRequirements(reqs)
        self.protocol.calibrate('A1', axis='A')
        reqs.pop(0)
        self.assertRequirements(reqs)
        self.protocol.calibrate('A2', axis='A')
        reqs.pop(0)
        self.assertRequirements(reqs)

    def test_requirements_calibration_multiple_racks(self):
        self.protocol.add_instrument('A', 'p20')
        self.protocol.add_container('A1', 'microplate')
        self.protocol.add_container('A2', 'tiprack.p20')
        self.protocol.add_container('A3', 'tiprack.p20')
        for _ in range(50):
            self.protocol.transfer('A1:A1', 'A1:A2', ul=10)
            self.protocol.transfer('A1:A2', 'A1:A1', ul=10)
        reqs = [{
            'type': 'calibrate_instrument',
            'axis': 'A',
            'instrument_name': 'p20'
        }, {
            'type': 'calibrate_container',
            'axis': 'A',
            'address': (0, 0),
            'container_name': 'microplate',
            'instrument_name': 'p20'
        }, {
            'type': 'calibrate_container',
            'axis': 'A',
            'address': (0, 1),
            'container_name': 'tiprack.p20',
            'instrument_name': 'p20'
        }, {
            'type': 'calibrate_container',
            'axis': 'A',
            'address': (0, 2),
            'container_name': 'tiprack.p20',
            'instrument_name': 'p20'
        }]
        self.assertRequirements(reqs)

    def test_no_errors_from_commands(self):
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.add_container('C1', 'point.trash')
        self.protocol.calibrate('A1')
        self.protocol.calibrate('B1')
        self.protocol.calibrate('C1')
        self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13)
        self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer_group(('A1:A1', 'A1:A2', {'ul': 100}))
        self.protocol.consolidate('A1:A1', ('A1:A2', {
            'ul': 100
        }), ('A1:A2', {
            'ul': 150
        }))
        self.protocol.distribute('A1:A1', ('A1:A2', {
            'ul': 100
        }), ('A1:A2', {
            'ul': 150
        }))
        self.protocol.mix('A1:A2', ul=100, repetitions=5)
        self.protocol.run_requirements
Esempio n. 4
0
class ProtocolTest(unittest.TestCase):

    def setUp(self):
        self.protocol = Protocol()

    @property
    def instructions(self):
        return self.protocol._commands

    def test_normalize_address(self):
        self.protocol.add_container('A1', 'microplate.96', label="Output")
        label = self.protocol._normalize_address('Output:A1')
        self.assertEqual(label, ('output', (0, 0)))
        slot = self.protocol._normalize_address('A1:A1')
        self.assertEqual(slot, ((0, 0), (0, 0)))

    def test_transfer(self):
        """ Basic transfer. """
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('B1', 'microplate.96')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_instrument('B', 'p20')
        self.protocol.transfer('A1:A1', 'B1:B1', ul=100, tool='p20')
        expected = [{
            'transfer': {
                'tool': 'p20',
                'volume': 100,
                'start': ((0, 0), (0, 0)),
                'end': ((1, 0), (1, 0)),
                'blowout': True,
                'touchtip': True
            }
        }]
        self.assertEqual(self.instructions, expected)

    def test_transfer_group(self):
        """ Transfer group. """
        expected = [{
            'transfer_group': {
                'tool': 'p10',
                'transfers': [
                    {
                        'volume': 15,
                        'start': ((0, 0), (0, 0)),  # A1:A1
                        'end': ((1, 0), (1, 0)),  # B1:B1
                        'blowout': True,
                        'touchtip': True
                    },
                    {
                        'volume': 1000,
                        'start': ((0, 1), (0, 1)),  # A2:A2
                        'end': ((1, 1), (1, 1)),  # B2:B2
                        'blowout': True,
                        'touchtip': True
                    },
                    {
                        'volume': 12,
                        'start': ((0, 2), (0, 2)),  # A3:A3
                        'end': ((1, 2), (1, 2)),  # B3:B3
                        'blowout': False,
                        'touchtip': True
                    },
                    {
                        'volume': 12,
                        'start': ((0, 3), (0, 3)),  # A4:A4
                        'end': ((1, 3), (1, 3)),  # B4:B4
                        'blowout': True,
                        'touchtip': True
                    },
                    {
                        'volume': 12,
                        'start': ('label', (0, 4)),  # label:A5
                        'end': ((1, 4), (2, 0)),  # B5:C1
                        'blowout': True,
                        'touchtip': True
                    }
                ]
            }
        }]
        self.protocol.add_container('A1', 'microplate.96', label="Label")
        self.protocol.transfer_group(
            ('A1:A1', 'B1:B1', {'ul': 15}),
            ('A2:A2', 'B2:B2', {'ml': 1}),
            ('A3:A3', 'B3:B3', {'blowout': False}),
            ('A4:A4', 'B4:B4'),
            ('Label:A5', 'B5:C1'),
            ul=12,
            tool='p10'
        )
        self.assertEqual(self.instructions, expected)

    def test_distribute(self):
        self.protocol.distribute(
            'A1:A1',
            ('B1:B1', 50),
            ('C1:C1', 5),
            ('D1:D1', 10)
        )
        expected = [{
            'distribute': {
                'tool': 'p10',
                'blowout': True,
                'start': ((0, 0), (0, 0)),
                'transfers': [
                    {
                        'volume': 50,
                        'end': ((1, 0), (1, 0)),  # B1:B1
                    },
                    {
                        'volume': 5,
                        'end': ((2, 0), (2, 0)),  # C1:C1
                    },
                    {
                        'volume': 10,
                        'end': ((3, 0), (3, 0))  # D1:D1
                    }
                ]
            }
        }]
        self.assertEqual(self.instructions, expected)

    def test_consolidate(self):
        """ Consolidate. """
        self.protocol.consolidate(
            'A1:A1',
            ('B1:B1', 50),
            ('C1:C1', 5),
            ('D1:D1', 10)
        )
        expected = [{
            'consolidate': {
                'tool': 'p10',
                'blowout': True,
                'end': ((0, 0), (0, 0)),
                'transfers': [
                    {
                        'volume': 50,
                        'start': ((1, 0), (1, 0)),  # B1:B1
                    },
                    {
                        'volume': 5,
                        'start': ((2, 0), (2, 0)),  # C1:C1
                    },
                    {
                        'volume': 10,
                        'start': ((3, 0), (3, 0))  # D1:D1
                    }
                ]
            }
        }]
        self.assertEqual(self.instructions, expected)

    def test_mix(self):
        """ Mix. """
        self.protocol.mix(
            'A1:A1',
            volume=50,
            repetitions=10
        )
        expected = [{'mix': {
            'tool': 'p10',
            'start': ((0, 0), (0, 0)),  # A1:A1
            'blowout': True,
            'volume': 50,
            'reps': 10
        }}]
        self.assertEqual(self.instructions, expected)

    def test_protocol_run_twice(self):
        """ Run a protocol twice without error. """
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.calibrate('A1', x=1, y=2, z=3)
        self.protocol.calibrate_instrument('A', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        self.protocol.run_all()
        self.protocol.run_all()
Esempio n. 5
0
class ProtocolRequirementsTest(unittest.TestCase):

    def setUp(self):
        self.protocol = Protocol()

    def assertRequirements(self, expected, reqs=None):
        reqs = reqs or self.protocol.run_requirements
        ouch = []
        extra = deepcopy(reqs)
        missing = deepcopy(expected)
        for ei, e in enumerate(expected):
            for ri, r in enumerate(reqs):
                try:
                    self.assertEqual(e, r)
                except AssertionError:
                    continue
                extra[ri] = None
                missing[ei] = None
        extra = [x for x in extra if x is not None]
        missing = [x for x in missing if x is not None]
        if len(extra) > 0:
            ouch.append("Found {} extra items: {}".format(len(extra), extra))
        if len(missing) > 0:
            ouch.append("Missing items: {}".format(missing))
        if len(ouch) > 0:
            assert False, "\n".join(ouch)

    def test_requirements_assertion(self):
        self.assertRequirements(
            [{'foo': 'bar'}, {'bizz': 'buzz'}],
            [{'foo': 'bar'}, {'bizz': 'buzz'}]
        )
        with self.assertRaises(AssertionError):
            self.assertRequirements(
                [{'foo': 'bar'}],
                [{'foo': 'bar'}, {'bizz': 'buzz'}]
            )
        with self.assertRaises(AssertionError):
            self.assertRequirements(
                [{'foo': 'bar'}, {'bizz': 'buzz'}],
                [{'foo': 'bar'}]
            )

    def test_require_instrument_calibration(self):
        self.protocol.add_instrument('A', 'p20')
        self.protocol.add_container('A1', 'microplate')
        self.protocol.add_container('A2', 'tiprack.p20')
        self.protocol.transfer('A1:A1', 'A1:A2', ul=10)
        reqs = [
            {'type': 'calibrate_instrument', 'axis': 'A', 'instrument_name': 'p20'},
            {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 0),
             'container_name': 'microplate', 'instrument_name': 'p20'},
            {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 1),
             'container_name': 'tiprack.p20', 'instrument_name': 'p20'}
        ]
        self.assertRequirements(reqs)
        self.protocol.calibrate_instrument(axis='A', top=10, bottom=10, blowout=10)
        reqs.pop(0)
        self.assertRequirements(reqs)
        self.protocol.calibrate('A1', axis='A')
        reqs.pop(0)
        self.assertRequirements(reqs)
        self.protocol.calibrate('A2', axis='A')
        reqs.pop(0)
        self.assertRequirements(reqs)

    def test_requirements_calibration_multiple_racks(self):
        self.protocol.add_instrument('A', 'p20')
        self.protocol.add_container('A1', 'microplate')
        self.protocol.add_container('A2', 'tiprack.p20')
        self.protocol.add_container('A3', 'tiprack.p20')
        for _ in range(50):
            self.protocol.transfer('A1:A1', 'A1:A2', ul=10)
            self.protocol.transfer('A1:A2', 'A1:A1', ul=10)
        reqs = [
            {'type': 'calibrate_instrument', 'axis': 'A', 'instrument_name': 'p20'},
            {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 0),
             'container_name': 'microplate', 'instrument_name': 'p20'},
            {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 1),
             'container_name': 'tiprack.p20', 'instrument_name': 'p20'},
             {'type': 'calibrate_container', 'axis': 'A', 'address': (0, 2),
             'container_name': 'tiprack.p20', 'instrument_name': 'p20'}
        ]
        self.assertRequirements(reqs)

    def test_no_errors_from_commands(self):
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.add_container('C1', 'point.trash')
        self.protocol.calibrate('A1')
        self.protocol.calibrate('B1')
        self.protocol.calibrate('C1')
        self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13)
        self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer_group(
            ('A1:A1', 'A1:A2', {'ul': 100})
        )
        self.protocol.consolidate(
            'A1:A1',
            ('A1:A2', {'ul':100}),
            ('A1:A2', {'ul':150})
        )
        self.protocol.distribute(
            'A1:A1',
            ('A1:A2', {'ul':100}),
            ('A1:A2', {'ul':150})
        )
        self.protocol.mix('A1:A2', ul=100, repetitions=5)
        self.protocol.run_requirements
Esempio n. 6
0
from flask_socketio import SocketIO, emit

import logging
import math

protocol = Protocol()
motor_handler = protocol.attach_motor()
protocol.add_instrument('B', 'p200')
protocol.add_container('A1', 'microplate.96')
protocol.add_container('C1', 'tiprack.p200')
protocol.add_container('B2', 'point.trash')
protocol.calibrate('A1', x=1, y=2, top=3, bottom=13)
protocol.calibrate('A1:A2', bottom=5)
protocol.calibrate('C1', x=100, y=100, top=50)
protocol.calibrate('B2', x=200, y=250, top=15)
protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25)
protocol.transfer('A1:A1', 'A1:A2', ul=100)
protocol.transfer('A1:A2', 'A1:A3', ul=80)

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s %(levelname)-8s %(message)s',
    datefmt='%d-%m-%y %H:%M:%S'
)

app = Flask(__name__)
socketio = SocketIO(app, async_mode='gevent')


@app.route('/')
def index():
Esempio n. 7
0
class MotorHandlerTest(unittest.TestCase):

    def setUp(self):
        self.protocol = Protocol()

    def test_basic_transfer(self):
        """ Basic transfer. """
        motor = self.protocol.attach_motor()
        output_log = motor._driver
        self.protocol.add_instrument('A', 'p20')
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_container('B1', 'tiprack.p20')
        self.protocol.add_container('B2', 'point.trash')
        self.protocol.calibrate('A1', axis="A", x=1, y=2, top=3, bottom=10)
        self.protocol.calibrate('B1', axis="A", x=4, y=5, top=6, bottom=15)
        self.protocol.calibrate('B2', axis="A", x=50, y=60, top=70)
        self.protocol.calibrate('A1:A2', axis="B", bottom=5)
        self.protocol.calibrate('C1', axis="B", x=100, y=100, top=50)
        self.protocol.calibrate('B2', axis="B", x=200, y=250, top=15)
        self.protocol.calibrate('A1', axis="B", x=1, y=2, top=3, bottom=13)
        self.protocol.calibrate_instrument('A', top=0, blowout=1, droptip=2)
        self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=10)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        prog_out = []
        for progress in self.protocol.run():
            prog_out.append(progress)
        expected = [
            # Transfer 1.
            {'x': 4, 'y': 5},  # Pickup tip.
            {'z': 6},
            {'z': 0},  # Move to well.
            {'x': 1, 'y': 2},
            {'z': 3},
            {'a': 0.5},  # Plunge.
            {'x': 1, 'y': 2},
            {'z': 10},  # Move into well.
            {'a': 0},  # Release.
            {'z': 0},  # Move up.
            {'x': 1, 'y': 11},  # Move to well.
            {'z': 3},
            {'x': 1, 'y': 11},
            {'z': 10},  # Move into well.
            {'a': 1},  # Blowout.
            {'z': 0},  # Move up.
            {'a': 0},  # Release.
            {'x': 50, 'y': 60},  # Dispose tip.
            {'z': 70},
            {'a': 2},
            {'a': 0},
            # Transfer 2.
            {'x': 100, 'y': 100},
            {'z': 50},
            {'z': 0},
            {'x': 1, 'y': 11},
            {'z': 3},
            {'b': 4.0},
            {'x': 1, 'y': 11},
            {'z': 5},
            {'b': 0},
            {'z': 0},
            {'x': 1, 'y': 20},
            {'z': 3},
            {'x': 1, 'y': 20},
            {'z': 13},
            {'b': 10},
            {'z': 0},
            {'b': 0},
            {'x': 200, 'y': 250},
            {'z': 15},
            {'b': 25},
            {'b': 0}
        ]
        self.assertEqual(expected, output_log.movements)
        self.assertEqual([(0, 2), (1, 2), (2, 2)], prog_out)

    def test_calibrate_without_axis(self):
        self.protocol.add_instrument('A', 'p20')
        self.protocol.add_instrument('B', 'p200')
        with self.assertRaises(x.DataMissing):
            self.protocol.calibrate('A1', top=0, bottom=0)

    def test_transfer_without_tiprack(self):
        """ Raise error when no tiprack found. """
        self.protocol.attach_motor()
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.calibrate('A1', top=0, bottom=0)
        self.protocol.calibrate_instrument('B', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        with self.assertRaises(x.ContainerMissing):
            self.protocol.run_all()

    def test_transfer_without_dispose_point(self):
        """ Raise when no dispose point set. """
        self.protocol.attach_motor()
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.calibrate('A1')
        self.protocol.calibrate('C1')
        self.protocol.calibrate_instrument('B', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)

        with self.assertRaises(x.ContainerMissing):
            self.protocol.run_all()

    def test_instrument_missing(self):
        with self.assertRaises(x.InstrumentMissing):
            m = self.protocol.attach_motor()
            m.get_pipette(has_volume=1000)

    def test_no_errors_from_commands(self):
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.add_container('C1', 'point.trash')
        self.protocol.calibrate('A1')
        self.protocol.calibrate('B1')
        self.protocol.calibrate('C1')
        self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13)
        self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer_group(
            ('A1:A1', 'A1:A2', {'ul': 100})
        )
        self.protocol.consolidate(
            'A1:A1',
            ('A1:A2', {'ul':100}),
            ('A1:A2', {'ul':150})
        )
        self.protocol.distribute(
            'A1:A1',
            ('A1:A2', {'ul':100}),
            ('A1:A2', {'ul':150})
        )
        self.protocol.mix('A1:A2', ul=100, repetitions=5)
        motor = self.protocol.attach_motor()
        output_log = motor._driver
        movements = output_log.movements

        # We're not really testing anything except that it runs without
        # errors.
        self.protocol.run_all()
Esempio n. 8
0
class MotorHandlerTest(unittest.TestCase):

    def setUp(self):
        self.protocol = Protocol()

    def test_basic_transfer(self):
        """ Basic transfer. """
        motor = self.protocol.attach_motor()
        output_log = motor._driver
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_container('B2', 'point.trash')
        self.protocol.calibrate('A1', x=1, y=2, top=3, bottom=13)
        self.protocol.calibrate('A1:A2', bottom=5)
        self.protocol.calibrate('C1', x=100, y=100, top=50)
        self.protocol.calibrate('B2', x=200, y=250, top=15)
        self.protocol.calibrate_instrument('B', top=0, blowout=10, droptip=25)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        prog_out = []
        for progress in self.protocol.run():
            prog_out.append(progress)
        expected = [
            # Transfer 1.
            {'x': 100, 'y': 100},  # Pickup tip.
            {'z': 50},
            {'z': 0},  # Move to well.
            {'x': 1, 'y': 2},
            {'z': 3},
            {'b': 5.0},  # Plunge.
            {'x': 1, 'y': 2},
            {'z': 13},  # Move into well.
            {'b': 0},  # Release.
            {'z': 0},  # Move up.
            {'x': 1, 'y': 11},  # Move to well.
            {'z': 3},
            {'x': 1, 'y': 11},
            {'z': 5},  # Move into well.
            {'b': 10},  # Blowout.
            {'z': 0},  # Move up.
            {'b': 0},  # Release.
            {'x': 200, 'y': 250},  # Dispose tip.
            {'z': 15},
            {'b': 25},
            {'b': 0},
            # Transfer 2.
            {'x': 100, 'y': 109},
            {'z': 50},
            {'z': 0},
            {'x': 1, 'y': 11},
            {'z': 3},
            {'b': 4.0},
            {'x': 1, 'y': 11},
            {'z': 5},
            {'b': 0},
            {'z': 0},
            {'x': 1, 'y': 20},
            {'z': 3},
            {'x': 1, 'y': 20},
            {'z': 13},
            {'b': 10},
            {'z': 0},
            {'b': 0},
            {'x': 200, 'y': 250},
            {'z': 15},
            {'b': 25},
            {'b': 0}
        ]
        self.assertEqual(expected, output_log.movements)
        self.assertEqual([(0, 2), (1, 2), (2, 2)], prog_out)

    def test_transfer_without_tiprack(self):
        """ Raise error when no tiprack found. """
        self.protocol.attach_motor()
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.calibrate_instrument('B', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        with self.assertRaises(KeyError):
            for progress in self.protocol.run():
                continue

    def test_transfer_without_dispose_point(self):
        """ Raise when no dispose point set. """
        self.protocol.attach_motor()
        self.protocol.add_instrument('B', 'p200')
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.calibrate_instrument('B', top=0, blowout=10)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=80)
        with self.assertRaises(KeyError):
            for progress in self.protocol.run():
                continue