예제 #1
0
class ContextHandlerTest(unittest.TestCase):

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

    def assertVolume(self, well, volume):
        result = self.protocol._context_handler.get_volume(well)
        self.assertEqual(volume, result)

    def test_transfer(self):
        """ Maintain well volumes during transfers. """
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.calibrate('A1', x=1, y=2, z=3)
        self.assertVolume('A1:A2', 0)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.assertVolume('A1:A2', 100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=20)
        self.assertVolume('A1:A3', 20)
        self.assertVolume('A1:A2', 80)
        
        run = self.protocol.run()
        next(run)  # Yield to set progress.
        self.assertVolume('A1:A2', 0)
        next(run)  # transfer('A1:A1', 'A1:A2', ul=100)
        self.assertVolume('A1:A2', 100)
        next(run)  # transfer('A1:A2', 'A1:A3', ul=20)
        self.assertVolume('A1:A3', 20)
        self.assertVolume('A1:A2', 80)

    def test_distribute(self):
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.distribute(
            'A1:A1',
            ('A1:B1', {'ul': 50}),
            ('A1:C1', {'ul': 30}),
            ('A1:D1', {'ul': 40})
        )
        # Final volumes.
        self.assertVolume('A1:A1', -120)
        self.assertVolume('A1:B1', 50)
        self.assertVolume('A1:C1', 30)
        self.assertVolume('A1:D1', 40)
        
        # Try during a run.
        run = self.protocol.run()
        next(run)  # Yield to set progress.
        self.assertVolume('A1:A2', 0)
        next(run)  # Our command.

        # Final volumes
        self.assertVolume('A1:A1', -120)
        self.assertVolume('A1:B1', 50)
        self.assertVolume('A1:C1', 30)
        self.assertVolume('A1:D1', 40)

    def test_consolidate(self):
        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': 30}),
            ('A1:D1', {'ul': 40})
        )
        self.assertVolume('A1:A1', 120)
        self.assertVolume('A1:B1', -50)
        self.assertVolume('A1:C1', -30)
        self.assertVolume('A1:D1', -40)

    def test_transfer_group(self):
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.transfer_group(
            ('A1:A1', 'A1:B1', {'ul': 50}),
            ('A1:A1', 'A1:C1', {'ul': 50}),
            ('A1:A1', 'A1:D1', {'ul': 30}),
            tool='p200'
        )
        self.assertVolume('A1:A1', -130)
        self.assertVolume('A1:B1', 50)
        self.assertVolume('A1:C1', 50)
        self.assertVolume('A1:D1', 30)

    def test_find_instrument_by_volume(self):
        """ Find instrument by volume. """
        self.protocol.add_instrument('A', 'p10')
        i = self.protocol._context_handler.get_instrument(has_volume=6)
        self.assertEqual(i.supports_volume(6), True)
        j = self.protocol._context_handler.get_instrument(has_volume=50)
        self.assertEqual(j, None)
        self.protocol.add_instrument('B', 'p200')
        k = self.protocol._context_handler.get_instrument(has_volume=50)
        self.assertEqual(k.name, 'p200')

    def test_tip_coordinates(self):
        """ Return tip coordinates. """
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_instrument('B', 'p10')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.calibrate('B1', axis="A", x=100, y=150, top=60)
        self.protocol.add_container('A1', 'tiprack.p10')
        self.protocol.calibrate('A1', axis="B", x=200, y=250, top=160)

        p200 = context.get_instrument(axis='A')
        p10 = context.get_instrument(axis='B')

        c1 = context.get_next_tip_coordinates(p200)
        self.assertEqual(c1, {'x': 100, 'y': 150, 'top': 60, 'bottom': 0})
        c2 = context.get_next_tip_coordinates(p200)  # Next tip.
        self.assertEqual(c2, {'x': 100, 'y': 159, 'top': 60, 'bottom': 0})

        c3 = context.get_next_tip_coordinates(p10)
        self.assertEqual(c3, {'x': 200, 'y': 250, 'top': 160, 'bottom': 0})
        c4 = context.get_next_tip_coordinates(p10)  # Next tip.
        self.assertEqual(c4, {'x': 200, 'y': 259, 'top': 160, 'bottom': 0})

    def test_tiprack_switch(self):
        """ Return second tiprack when first is used up. """
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.calibrate('B1', axis="A", x=100, y=150, top=60)
        self.protocol.add_container('A1', 'tiprack.p200')
        self.protocol.calibrate('A1', axis="A", x=200, y=250, top=160)
        p200 = context.get_instrument(axis='A')
        rack = context.find_container(name='tiprack.p200', has_tips=True)
        self.assertEqual([(0, 0)], rack.address)
        rack.set_tips_used(95)  # We've used all but one tip from this rack.
        c1 = context.get_next_tip_coordinates(p200)  # Last tip.
        h12 = [(0, 0), (7, 11)]
        self.assertEqual(c1, context.get_coordinates(h12, axis="A"))
        rack = context.find_container(name='tiprack.p200', has_tips=True)
        self.assertEqual([(1, 0)], rack.address)

    def test_multichannel_search(self):
        """ Find a multichannel pipette. """
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_instrument('B', 'p20.12')
        i1 = context.get_instrument(axis='B')
        i2 = context.get_instrument(has_volume=20, channels=12)
        self.assertEqual(i1, i2)
        i3 = context.get_instrument(has_volume=200, channels=12)
        self.assertEqual(i3, None)

    def test_multichannel_tip_allocation(self):
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p20.12')
        self.protocol.add_instrument('B', 'p20.8')
        self.protocol.add_container('A1', 'tiprack.p20')
        a = context.get_instrument(axis='A')
        b = context.get_instrument(axis='B')
        self.protocol.calibrate('A1', axis="A")
        self.protocol.calibrate('A1', axis="B")
        # Get a row first.
        context.get_next_tip_coordinates(a)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(b)
        # Get a col first.
        context = self.protocol.initialize_context()
        context.get_next_tip_coordinates(b)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(a)
        # Add another tiprack, get both!
        self.protocol.add_container('A2', 'tiprack.p20')
        self.protocol.calibrate('A2', axis="A")
        self.protocol.calibrate('A2', axis="B")
        context = self.protocol.initialize_context()
        context.get_next_tip_coordinates(a)
        context.get_next_tip_coordinates(b)
        # Exhaust the supply.
        context = self.protocol.initialize_context()
        for _ in range(8):
            context.get_next_tip_coordinates(a)
        for _ in range(12):
            context.get_next_tip_coordinates(b)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(a)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(b)

    def test_multichannel_transfer_cols(self):
        """ Test multichannel transfer (cols). """
        self.protocol.add_instrument('A', 'p20.12')
        self.protocol.add_instrument('B', 'p20.8')
        self.protocol.add_container('A1', 'microplate')
        p = self.protocol._context_handler.find_container(name="microplate")
        self.protocol.transfer('A1:A1', 'A1:B1', ul=10, tool='p20.12')
        self.assertEqual(p.col('A').get_volume(), [-10 for n in range(12)])
        self.assertEqual(p.col('B').get_volume(), [ 10 for n in range(12)])

    def test_multichannel_transfer_rows(self):
        """ Test multichannel transfer (rows). """
        self.protocol.add_instrument('A', 'p20.12')
        self.protocol.add_instrument('B', 'p20.8')
        self.protocol.add_container('A1', 'microplate')
        p = self.protocol._context_handler.find_container(name="microplate")
        self.protocol.transfer('A1:A1', 'A1:A2', ul=15, tool='p20.8')
        self.assertEqual(p.row(0).get_volume(), [-15 for n in range(8)])
        self.assertEqual(p.row(1).get_volume(), [ 15 for n in range(8)])
예제 #2
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()
예제 #3
0
class ContextHandlerTest(unittest.TestCase):
    def setUp(self):
        self.protocol = Protocol()

    def assertVolume(self, well, volume):
        result = self.protocol._context_handler.get_volume(well)
        self.assertEqual(volume, result)

    def test_transfer(self):
        """ Maintain well volumes during transfers. """
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.calibrate('A1', x=1, y=2, z=3)
        self.assertVolume('A1:A2', 0)
        self.protocol.transfer('A1:A1', 'A1:A2', ul=100)
        self.assertVolume('A1:A2', 100)
        self.protocol.transfer('A1:A2', 'A1:A3', ul=20)
        self.assertVolume('A1:A3', 20)
        self.assertVolume('A1:A2', 80)

        run = self.protocol.run()
        next(run)  # Yield to set progress.
        self.assertVolume('A1:A2', 0)
        next(run)  # transfer('A1:A1', 'A1:A2', ul=100)
        self.assertVolume('A1:A2', 100)
        next(run)  # transfer('A1:A2', 'A1:A3', ul=20)
        self.assertVolume('A1:A3', 20)
        self.assertVolume('A1:A2', 80)

    def test_distribute(self):
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.distribute('A1:A1', ('A1:B1', {
            'ul': 50
        }), ('A1:C1', {
            'ul': 30
        }), ('A1:D1', {
            'ul': 40
        }))
        # Final volumes.
        self.assertVolume('A1:A1', -120)
        self.assertVolume('A1:B1', 50)
        self.assertVolume('A1:C1', 30)
        self.assertVolume('A1:D1', 40)

        # Try during a run.
        run = self.protocol.run()
        next(run)  # Yield to set progress.
        self.assertVolume('A1:A2', 0)
        next(run)  # Our command.

        # Final volumes
        self.assertVolume('A1:A1', -120)
        self.assertVolume('A1:B1', 50)
        self.assertVolume('A1:C1', 30)
        self.assertVolume('A1:D1', 40)

    def test_consolidate(self):
        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': 30
        }), ('A1:D1', {
            'ul': 40
        }))
        self.assertVolume('A1:A1', 120)
        self.assertVolume('A1:B1', -50)
        self.assertVolume('A1:C1', -30)
        self.assertVolume('A1:D1', -40)

    def test_transfer_group(self):
        self.protocol.add_container('A1', 'microplate.96')
        self.protocol.add_container('C1', 'tiprack.p200')
        self.protocol.add_instrument('A', 'p200')
        self.protocol.transfer_group(('A1:A1', 'A1:B1', {
            'ul': 50
        }), ('A1:A1', 'A1:C1', {
            'ul': 50
        }), ('A1:A1', 'A1:D1', {
            'ul': 30
        }),
                                     tool='p200')
        self.assertVolume('A1:A1', -130)
        self.assertVolume('A1:B1', 50)
        self.assertVolume('A1:C1', 50)
        self.assertVolume('A1:D1', 30)

    def test_find_instrument_by_volume(self):
        """ Find instrument by volume. """
        self.protocol.add_instrument('A', 'p10')
        i = self.protocol._context_handler.get_instrument(has_volume=6)
        self.assertEqual(i.supports_volume(6), True)
        j = self.protocol._context_handler.get_instrument(has_volume=50)
        self.assertEqual(j, None)
        self.protocol.add_instrument('B', 'p200')
        k = self.protocol._context_handler.get_instrument(has_volume=50)
        self.assertEqual(k.name, 'p200')

    def test_tip_coordinates(self):
        """ Return tip coordinates. """
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_instrument('B', 'p10')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.calibrate('B1', axis="A", x=100, y=150, top=60)
        self.protocol.add_container('A1', 'tiprack.p10')
        self.protocol.calibrate('A1', axis="B", x=200, y=250, top=160)

        p200 = context.get_instrument(axis='A')
        p10 = context.get_instrument(axis='B')

        c1 = context.get_next_tip_coordinates(p200)
        self.assertEqual(c1, {'x': 100, 'y': 150, 'top': 60, 'bottom': 0})
        c2 = context.get_next_tip_coordinates(p200)  # Next tip.
        self.assertEqual(c2, {'x': 100, 'y': 159, 'top': 60, 'bottom': 0})

        c3 = context.get_next_tip_coordinates(p10)
        self.assertEqual(c3, {'x': 200, 'y': 250, 'top': 160, 'bottom': 0})
        c4 = context.get_next_tip_coordinates(p10)  # Next tip.
        self.assertEqual(c4, {'x': 200, 'y': 259, 'top': 160, 'bottom': 0})

    def test_tiprack_switch(self):
        """ Return second tiprack when first is used up. """
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_container('B1', 'tiprack.p200')
        self.protocol.calibrate('B1', axis="A", x=100, y=150, top=60)
        self.protocol.add_container('A1', 'tiprack.p200')
        self.protocol.calibrate('A1', axis="A", x=200, y=250, top=160)
        p200 = context.get_instrument(axis='A')
        rack = context.find_container(name='tiprack.p200', has_tips=True)
        self.assertEqual([(0, 0)], rack.address)
        rack.set_tips_used(95)  # We've used all but one tip from this rack.
        c1 = context.get_next_tip_coordinates(p200)  # Last tip.
        h12 = [(0, 0), (7, 11)]
        self.assertEqual(c1, context.get_coordinates(h12, axis="A"))
        rack = context.find_container(name='tiprack.p200', has_tips=True)
        self.assertEqual([(1, 0)], rack.address)

    def test_multichannel_search(self):
        """ Find a multichannel pipette. """
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p200')
        self.protocol.add_instrument('B', 'p20.12')
        i1 = context.get_instrument(axis='B')
        i2 = context.get_instrument(has_volume=20, channels=12)
        self.assertEqual(i1, i2)
        i3 = context.get_instrument(has_volume=200, channels=12)
        self.assertEqual(i3, None)

    def test_multichannel_tip_allocation(self):
        context = self.protocol._context_handler
        self.protocol.add_instrument('A', 'p20.12')
        self.protocol.add_instrument('B', 'p20.8')
        self.protocol.add_container('A1', 'tiprack.p20')
        a = context.get_instrument(axis='A')
        b = context.get_instrument(axis='B')
        self.protocol.calibrate('A1', axis="A")
        self.protocol.calibrate('A1', axis="B")
        # Get a row first.
        context.get_next_tip_coordinates(a)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(b)
        # Get a col first.
        context = self.protocol.initialize_context()
        context.get_next_tip_coordinates(b)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(a)
        # Add another tiprack, get both!
        self.protocol.add_container('A2', 'tiprack.p20')
        self.protocol.calibrate('A2', axis="A")
        self.protocol.calibrate('A2', axis="B")
        context = self.protocol.initialize_context()
        context.get_next_tip_coordinates(a)
        context.get_next_tip_coordinates(b)
        # Exhaust the supply.
        context = self.protocol.initialize_context()
        for _ in range(8):
            context.get_next_tip_coordinates(a)
        for _ in range(12):
            context.get_next_tip_coordinates(b)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(a)
        with self.assertRaises(x.TipMissing):
            context.get_next_tip_coordinates(b)

    def test_multichannel_transfer_cols(self):
        """ Test multichannel transfer (cols). """
        self.protocol.add_instrument('A', 'p20.12')
        self.protocol.add_instrument('B', 'p20.8')
        self.protocol.add_container('A1', 'microplate')
        p = self.protocol._context_handler.find_container(name="microplate")
        self.protocol.transfer('A1:A1', 'A1:B1', ul=10, tool='p20.12')
        self.assertEqual(p.col('A').get_volume(), [-10 for n in range(12)])
        self.assertEqual(p.col('B').get_volume(), [10 for n in range(12)])

    def test_multichannel_transfer_rows(self):
        """ Test multichannel transfer (rows). """
        self.protocol.add_instrument('A', 'p20.12')
        self.protocol.add_instrument('B', 'p20.8')
        self.protocol.add_container('A1', 'microplate')
        p = self.protocol._context_handler.find_container(name="microplate")
        self.protocol.transfer('A1:A1', 'A1:A2', ul=15, tool='p20.8')
        self.assertEqual(p.row(0).get_volume(), [-15 for n in range(8)])
        self.assertEqual(p.row(1).get_volume(), [15 for n in range(8)])
예제 #4
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