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

    def setUp(self):
        self.name = "unittest"
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "")
        self.bcp.set("world unittest", "serializer", "memory")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_request_chunk_identity(self):
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_request_chunk_cached_identity(self):
        # Turn on the cache and get a few chunks in there, then request a
        # chunk that is in the cache.
        yield self.w.enable_cache(1)
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), repeat=3):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded,
                              self.w.sync_get_metadata, (16, 0, 0))

        return d
예제 #2
0
파일: test_world.py 프로젝트: alucas/bravo
class TestWorldChunks(unittest.TestCase):
    def setUp(self):
        self.name = "unittest"
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "")
        self.bcp.set("world unittest", "serializer", "memory")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_request_chunk_identity(self):
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_request_chunk_cached_identity(self):
        # Turn on the cache and get a few chunks in there, then request a
        # chunk that is in the cache.
        yield self.w.enable_cache(1)
        first = yield self.w.request_chunk(0, 0)
        second = yield self.w.request_chunk(0, 0)
        self.assertIs(first, second)

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), repeat=3):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = array("B")
        chunk.metadata.fromstring(os.urandom(32768))

        # Evict the chunk and grab it again.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        yield self.w.save_chunk(chunk)
        del chunk
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = array("B")
        chunk.blocks.fromstring(os.urandom(32768))

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded, self.w.sync_get_metadata,
                              (16, 0, 0))

        return d
예제 #3
0
class TestWater(TestCase):

    def setUp(self):
        # Set up world.
        self.name = "unittest"
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "")
        self.bcp.set("world unittest", "serializer", "memory")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

        # And finally the mock factory.
        self.f = PhysicsMockFactory()
        self.f.world = self.w

        # Using dig hook to grab the plugin since the build hook was nuked in
        # favor of the automaton interface.
        self.p = bravo.plugin.retrieve_plugins(IDigHook, factory=self.f)
        self.hook = self.p["water"]

    def tearDown(self):
        self.w.stop()
        self.hook.stop()

    def test_trivial(self):
        pass

    def test_update_fluid_negative(self):
        """
        update_fluid() should always return False for Y at the bottom of the
        world.
        """

        self.assertFalse(self.hook.update_fluid(self.w, (0, -1, 0), False))

    def test_update_fluid_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.hook.update_fluid, self.w,
            (0, 0, 0), False)

    def test_update_fluid(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertTrue(self.hook.update_fluid(self.w, (0, 0, 0), False))
            self.assertEqual(self.w.sync_get_block((0, 0, 0)),
                blocks["water"].slot)
            self.assertEqual(self.w.sync_get_metadata((0, 0, 0)), 0)

        return d

    def test_update_fluid_metadata(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertTrue(self.hook.update_fluid(self.w, (0, 0, 0), False,
                1))
            self.assertEqual(self.w.sync_get_metadata((0, 0, 0)), 1)

        return d

    def test_update_fluid_falling(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertTrue(self.hook.update_fluid(self.w, (0, 0, 0), True))
            self.assertEqual(self.w.sync_get_metadata((0, 0, 0)), 8)

        return d

    def test_zero_y(self):
        """
        Double-check that water placed on the very bottom of the world doesn't
        cause internal errors.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium; if any exceptions happen,
        # they will bubble up.
        while self.hook.tracked:
            self.hook.process()

    def test_spring_spread(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((1, 0, 1), blocks["spring"].slot)
            self.hook.tracked.add((1, 0, 1))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            for coords in ((2, 0, 1), (1, 0, 2), (0, 0, 1), (1, 0, 0)):
                self.assertEqual(chunk.get_block(coords),
                    blocks["water"].slot)
                self.assertEqual(chunk.get_metadata(coords), 0x0)

        return d

    def test_spring_spread_edge(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 0, 0), blocks["spring"].slot)
            self.hook.tracked.add((0, 0, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            for coords in ((1, 0, 0), (0, 0, 1)):
                self.assertEqual(chunk.get_block(coords),
                    blocks["water"].slot)
                self.assertEqual(chunk.get_metadata(coords), 0x0)

        return d

    def test_fluid_spread_edge(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 0, 0), blocks["spring"].slot)
            self.hook.tracked.add((0, 0, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            for coords in ((2, 0, 0), (1, 0, 1), (0, 0, 2)):
                self.assertEqual(chunk.get_block(coords),
                    blocks["water"].slot)
                self.assertEqual(chunk.get_metadata(coords), 0x1)

        return d

    @inlineCallbacks
    def test_spring_fall(self):
        """
        Falling water should appear below springs.
        """

        self.w.set_block((0, 1, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        block = yield self.w.get_block((0, 0, 0))
        metadata = yield self.w.get_metadata((0, 0, 0))
        self.assertEqual(block, blocks["water"].slot)
        self.assertEqual(metadata, 0x8)

    @inlineCallbacks
    def test_spring_fall_dig(self):
        """
        Destroying ground underneath spring should allow water to continue
        falling downwards.
        """

        self.w.set_block((0, 1, 0), blocks["spring"].slot)
        self.w.set_block((0, 0, 0), blocks["dirt"].slot)
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        #dig away dirt under spring
        self.w.destroy((0, 0, 0))
        self.hook.tracked.add((0, 1, 0))

        while self.hook.tracked:
            self.hook.process()

        block = yield self.w.get_block((0, 0, 0))
        self.assertEqual(block, blocks["water"].slot)

    def test_spring_fall_dig_offset(self):
        """
        Destroying ground next to a spring should cause a waterfall effect.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):

            chunk.set_block((1, 1, 0), blocks["spring"].slot)
            chunk.set_block((1, 0, 0), blocks["dirt"].slot)
            chunk.set_block((1, 0, 1), blocks["dirt"].slot)
            self.hook.tracked.add((1, 1, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            # Dig away the dirt next to the dirt under the spring, and simulate
            # the dig hook by adding the block above it.
            chunk.destroy((1, 0, 1))
            self.hook.tracked.add((1, 1, 1))

            while self.hook.tracked:
                self.hook.process()

            self.assertEqual(chunk.get_block((1, 0, 1)), blocks["water"].slot)

        return d

    def test_trench(self):
        """
        Fluid should not spread across the top of existing fluid.

        This test is for a specific kind of trench-digging pattern.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 2, 0), blocks["spring"].slot)
            chunk.set_block((0, 1, 0), blocks["dirt"].slot)
            self.hook.tracked.add((0, 2, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            # Dig the dirt.
            self.w.destroy((0, 1, 0))
            self.hook.tracked.add((0, 1, 1))
            self.hook.tracked.add((0, 2, 0))
            self.hook.tracked.add((1, 1, 0))

            while self.hook.tracked:
                self.hook.process()

            block = chunk.get_block((0, 2, 2))
            self.assertEqual(block, blocks["air"].slot)

    @inlineCallbacks
    def test_obstacle(self):
        """
        Test that obstacles are flowed around correctly.
        """

        yield self.w.set_block((0, 0, 0), blocks["spring"].slot)
        yield self.w.set_block((1, 0, 0), blocks["stone"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Make sure that the water level behind the stone is 0x3, not 0x0.
        metadata = yield self.w.get_metadata((2, 0, 0))
        self.assertEqual(metadata, 0x3)

    @inlineCallbacks
    def test_sponge(self):
        """
        Test that sponges prevent water from spreading near them.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.w.set_block((3, 0, 0), blocks["sponge"].slot)
        self.hook.tracked.add((0, 0, 0))
        self.hook.tracked.add((3, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Make sure that water did not spread near the sponge.
        block = yield self.w.get_block((1, 0, 0))
        self.assertNotEqual(block, blocks["water"].slot)

    def test_sponge_absorb_spring(self):
        """
        Test that sponges can absorb springs and will cause all of the
        surrounding water to dry up.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 0, 0), blocks["spring"].slot)
            self.hook.tracked.add((0, 0, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            self.w.set_block((1, 0, 0), blocks["sponge"].slot)
            self.hook.tracked.add((1, 0, 0))

            while self.hook.tracked:
                self.hook.process()

            for coords in ((0, 0, 0), (0, 0, 1)):
                block = yield self.w.get_block(coords)
                self.assertEqual(block, blocks["air"].slot)

            # Make sure that water did not spread near the sponge.
            block = yield self.w.get_block((1, 0, 0))
            self.assertNotEqual(block, blocks["water"].slot)

        return d

    @inlineCallbacks
    def test_sponge_salt(self):
        """
        Test that sponges don't "salt the earth" or have any kind of lasting
        effects after destruction.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        chunk = yield self.w.request_chunk(0, 0)

        # Take a snapshot at the base level, with a clever slice.
        before = chunk.sections[0].blocks[:256], chunk.sections[0].metadata[:256]

        self.w.set_block((3, 0, 0), blocks["sponge"].slot)
        self.hook.tracked.add((3, 0, 0))

        while self.hook.tracked:
            self.hook.process()

        self.w.destroy((3, 0, 0))
        self.hook.tracked.add((3, 0, 0))

        while self.hook.tracked:
            self.hook.process()

        # Make another snapshot, for comparison.
        after = chunk.sections[0].blocks[:256], chunk.sections[0].metadata[:256]

        # Make sure that the sponge didn't permanently change anything.
        self.assertEqual(before, after)

    @inlineCallbacks
    def test_spring_remove(self):
        """
        Test that water dries up if no spring is providing it.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Remove the spring.
        self.w.destroy((0, 0, 0))
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        for coords in ((1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)):
            block = yield self.w.get_block(coords)
            self.assertEqual(block, blocks["air"].slot)

    @inlineCallbacks
    def test_spring_underneath_keepalive(self):
        """
        Test that springs located at a lower altitude than stray water do not
        keep that stray water alive.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.w.set_block((0, 1, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Remove the upper spring.
        self.w.destroy((0, 1, 0))
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Check that the upper water blocks dried out. Don't care about the
        # lower ones in this test.
        for coords in ((1, 1, 0), (-1, 1, 0), (0, 1, 1), (0, 1, -1)):
            block = yield self.w.get_block(coords)
            self.assertEqual(block, blocks["air"].slot)
예제 #4
0
class TestWorldChunks(unittest.TestCase):

    def setUp(self):
        self.name = "unittest"
        self.d = tempfile.mkdtemp()

        bravo.config.configuration.add_section("world unittest")
        bravo.config.configuration.set("world unittest", "url", "file://%s" % self.d)
        bravo.config.configuration.set("world unittest", "serializer",
            "alpha")

        self.w = World(self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()
        del self.w

        shutil.rmtree(self.d)
        bravo.config.configuration.remove_section("world unittest")

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), xrange(2), xrange(2)):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
            dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded,
                              self.w.sync_get_metadata, (16, 0, 0))

        return d
예제 #5
0
파일: test_world.py 프로젝트: JDShu/bravo
class TestWorldChunks(unittest.TestCase):
    def setUp(self):
        self.name = "unittest"
        self.d = tempfile.mkdtemp()
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "file://%s" % self.d)
        self.bcp.set("world unittest", "serializer", "alpha")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

    def tearDown(self):
        self.w.stop()
        shutil.rmtree(self.d)

    def test_trivial(self):
        pass

    @inlineCallbacks
    def test_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(
            chunk.blocks.size),
                                          dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = yield self.w.get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_block_readback_negative(self):
        chunk = yield self.w.request_chunk(-1, -1)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(-1, -1)

        for x, y, z in product(xrange(2), repeat=3):
            block = yield self.w.get_block((x - 16, y, z - 16))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    @inlineCallbacks
    def test_get_metadata_readback(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.metadata = numpy.fromstring(numpy.random.bytes(
            chunk.blocks.size),
                                          dtype=numpy.uint8)
        chunk.metadata.shape = (16, 16, 128)

        # Evict the chunk and grab it again.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            metadata = yield self.w.get_metadata((x, y, z))
            self.assertEqual(metadata, chunk.get_metadata((x, y, z)))

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(0, 0)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((12, 64, 4))
        chunk = yield self.w.request_chunk(0, 0)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_world_level_mark_chunk_dirty_offset(self):
        chunk = yield self.w.request_chunk(1, 2)

        # Reload chunk.
        self.w.save_chunk(chunk)
        del chunk
        self.w.chunk_cache.clear()
        self.w.dirty_chunk_cache.clear()
        chunk = yield self.w.request_chunk(1, 2)

        self.assertFalse(chunk.dirty)
        self.w.mark_dirty((29, 64, 43))
        chunk = yield self.w.request_chunk(1, 2)
        self.assertTrue(chunk.dirty)

    @inlineCallbacks
    def test_sync_get_block(self):
        chunk = yield self.w.request_chunk(0, 0)

        # Fill the chunk with random stuff.
        chunk.blocks = numpy.fromstring(numpy.random.bytes(chunk.blocks.size),
                                        dtype=numpy.uint8)
        chunk.blocks.shape = (16, 16, 128)

        for x, y, z in product(xrange(2), repeat=3):
            # This works because the chunk is at (0, 0) so the coords don't
            # need to be adjusted.
            block = self.w.sync_get_block((x, y, z))
            self.assertEqual(block, chunk.get_block((x, y, z)))

    def test_sync_get_block_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.w.sync_get_block, (0, 0, 0))

    def test_sync_get_metadata_neighboring(self):
        """
        Even if a neighboring chunk is loaded, the target chunk could still be
        unloaded.

        Test with sync_get_metadata() to increase test coverage.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertRaises(ChunkNotLoaded, self.w.sync_get_metadata,
                              (16, 0, 0))

        return d
예제 #6
0
파일: test_physics.py 프로젝트: JDShu/bravo
class TestWater(unittest.TestCase):
    def setUp(self):
        # Set up world.
        self.name = "unittest"
        self.d = tempfile.mkdtemp()
        self.bcp = BravoConfigParser()

        self.bcp.add_section("world unittest")
        self.bcp.set("world unittest", "url", "file://%s" % self.d)
        self.bcp.set("world unittest", "serializer", "alpha")

        self.w = World(self.bcp, self.name)
        self.w.pipeline = []
        self.w.start()

        # And finally the mock factory.
        self.f = PhysicsMockFactory()
        self.f.world = self.w

        # Using dig hook to grab the plugin since the build hook was nuked in
        # favor of the automaton interface.
        pp = {"factory": self.f}
        self.p = bravo.plugin.retrieve_plugins(IDigHook, parameters=pp)

        if "water" not in self.p:
            raise unittest.SkipTest("Plugin not present")

        self.hook = self.p["water"]

    def tearDown(self):
        self.w.stop()
        self.hook.stop()
        shutil.rmtree(self.d)

    def test_trivial(self):
        pass

    def test_update_fluid_negative(self):
        """
        update_fluid() should always return False for Y at the bottom of the
        world.
        """

        self.assertFalse(self.hook.update_fluid(self.w, (0, -1, 0), False))

    def test_update_fluid_unloaded(self):
        self.assertRaises(ChunkNotLoaded, self.hook.update_fluid, self.w,
                          (0, 0, 0), False)

    def test_update_fluid(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertTrue(self.hook.update_fluid(self.w, (0, 0, 0), False))
            self.assertEqual(self.w.sync_get_block((0, 0, 0)),
                             blocks["water"].slot)
            self.assertEqual(self.w.sync_get_metadata((0, 0, 0)), 0)

        return d

    def test_update_fluid_metadata(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertTrue(self.hook.update_fluid(self.w, (0, 0, 0), False,
                                                   1))
            self.assertEqual(self.w.sync_get_metadata((0, 0, 0)), 1)

        return d

    def test_update_fluid_falling(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            self.assertTrue(self.hook.update_fluid(self.w, (0, 0, 0), True))
            self.assertEqual(self.w.sync_get_metadata((0, 0, 0)), 8)

        return d

    def test_zero_y(self):
        """
        Double-check that water placed on the very bottom of the world doesn't
        cause internal errors.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium; if any exceptions happen,
        # they will bubble up.
        while self.hook.tracked:
            self.hook.process()

    def test_spring_spread(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((1, 0, 1), blocks["spring"].slot)
            self.hook.tracked.add((1, 0, 1))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            for coords in ((2, 0, 1), (1, 0, 2), (0, 0, 1), (1, 0, 0)):
                self.assertEqual(chunk.get_block(coords), blocks["water"].slot)
                self.assertEqual(chunk.get_metadata(coords), 0x0)

        return d

    def test_spring_spread_edge(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 0, 0), blocks["spring"].slot)
            self.hook.tracked.add((0, 0, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            for coords in ((1, 0, 0), (0, 0, 1)):
                self.assertEqual(chunk.get_block(coords), blocks["water"].slot)
                self.assertEqual(chunk.get_metadata(coords), 0x0)

        return d

    def test_fluid_spread_edge(self):
        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 0, 0), blocks["spring"].slot)
            self.hook.tracked.add((0, 0, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            for coords in ((2, 0, 0), (1, 0, 1), (0, 0, 2)):
                self.assertEqual(chunk.get_block(coords), blocks["water"].slot)
                self.assertEqual(chunk.get_metadata(coords), 0x1)

        return d

    @inlineCallbacks
    def test_spring_fall(self):
        """
        Falling water should appear below springs.
        """

        self.w.set_block((0, 1, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        block = yield self.w.get_block((0, 0, 0))
        metadata = yield self.w.get_metadata((0, 0, 0))
        self.assertEqual(block, blocks["water"].slot)
        self.assertEqual(metadata, 0x8)

    @inlineCallbacks
    def test_spring_fall_dig(self):
        """
        Destroying ground underneath spring should allow water to continue
        falling downwards.
        """

        self.w.set_block((0, 1, 0), blocks["spring"].slot)
        self.w.set_block((0, 0, 0), blocks["dirt"].slot)
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        #dig away dirt under spring
        self.w.destroy((0, 0, 0))
        self.hook.tracked.add((0, 1, 0))

        while self.hook.tracked:
            self.hook.process()

        block = yield self.w.get_block((0, 0, 0))
        self.assertEqual(block, blocks["water"].slot)

    def test_spring_fall_dig_offset(self):
        """
        Destroying ground next to a spring should cause a waterfall effect.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):

            chunk.set_block((1, 1, 0), blocks["spring"].slot)
            chunk.set_block((1, 0, 0), blocks["dirt"].slot)
            chunk.set_block((1, 0, 1), blocks["dirt"].slot)
            self.hook.tracked.add((1, 1, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            # Dig away the dirt next to the dirt under the spring, and simulate
            # the dig hook by adding the block above it.
            chunk.destroy((1, 0, 1))
            self.hook.tracked.add((1, 1, 1))

            while self.hook.tracked:
                self.hook.process()

            self.assertEqual(chunk.get_block((1, 0, 1)), blocks["water"].slot)

        return d

    def test_trench(self):
        """
        Fluid should not spread across the top of existing fluid.

        This test is for a specific kind of trench-digging pattern.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 2, 0), blocks["spring"].slot)
            chunk.set_block((0, 1, 0), blocks["dirt"].slot)
            self.hook.tracked.add((0, 2, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            # Dig the dirt.
            self.w.destroy((0, 1, 0))
            self.hook.tracked.add((0, 1, 1))
            self.hook.tracked.add((0, 2, 0))
            self.hook.tracked.add((1, 1, 0))

            while self.hook.tracked:
                self.hook.process()

            block = chunk.get_block((0, 2, 2))
            self.assertEqual(block, blocks["air"].slot)

    @inlineCallbacks
    def test_obstacle(self):
        """
        Test that obstacles are flowed around correctly.
        """

        yield self.w.set_block((0, 0, 0), blocks["spring"].slot)
        yield self.w.set_block((1, 0, 0), blocks["stone"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Make sure that the water level behind the stone is 0x3, not 0x0.
        metadata = yield self.w.get_metadata((2, 0, 0))
        self.assertEqual(metadata, 0x3)

    @inlineCallbacks
    def test_sponge(self):
        """
        Test that sponges prevent water from spreading near them.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.w.set_block((3, 0, 0), blocks["sponge"].slot)
        self.hook.tracked.add((0, 0, 0))
        self.hook.tracked.add((3, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Make sure that water did not spread near the sponge.
        block = yield self.w.get_block((1, 0, 0))
        self.assertNotEqual(block, blocks["water"].slot)

    def test_sponge_absorb_spring(self):
        """
        Test that sponges can absorb springs and will cause all of the
        surrounding water to dry up.
        """

        d = self.w.request_chunk(0, 0)

        @d.addCallback
        def cb(chunk):
            chunk.set_block((0, 0, 0), blocks["spring"].slot)
            self.hook.tracked.add((0, 0, 0))

            # Tight-loop run the hook to equilibrium.
            while self.hook.tracked:
                self.hook.process()

            self.w.set_block((1, 0, 0), blocks["sponge"].slot)
            self.hook.tracked.add((1, 0, 0))

            while self.hook.tracked:
                self.hook.process()

            for coords in ((0, 0, 0), (0, 0, 1)):
                block = yield self.w.get_block(coords)
                self.assertEqual(block, blocks["air"].slot)

            # Make sure that water did not spread near the sponge.
            block = yield self.w.get_block((1, 0, 0))
            self.assertNotEqual(block, blocks["water"].slot)

        return d

    @inlineCallbacks
    def test_sponge_salt(self):
        """
        Test that sponges don't "salt the earth" or have any kind of lasting
        effects after destruction.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Take a snapshot.
        chunk = yield self.w.request_chunk(0, 0)
        before = chunk.blocks[:, :, 0], chunk.metadata[:, :, 0]

        self.w.set_block((3, 0, 0), blocks["sponge"].slot)
        self.hook.tracked.add((3, 0, 0))

        while self.hook.tracked:
            self.hook.process()

        self.w.destroy((3, 0, 0))
        self.hook.tracked.add((3, 0, 0))

        while self.hook.tracked:
            self.hook.process()

        after = chunk.blocks[:, :, 0], chunk.metadata[:, :, 0]

        # Make sure that the sponge didn't permanently change anything.
        assert_array_equal(before, after)

    @inlineCallbacks
    def test_spring_remove(self):
        """
        Test that water dries up if no spring is providing it.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Remove the spring.
        self.w.destroy((0, 0, 0))
        self.hook.tracked.add((0, 0, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        for coords in ((1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)):
            block = yield self.w.get_block(coords)
            self.assertEqual(block, blocks["air"].slot)

    @inlineCallbacks
    def test_spring_underneath_keepalive(self):
        """
        Test that springs located at a lower altitude than stray water do not
        keep that stray water alive.
        """

        self.w.set_block((0, 0, 0), blocks["spring"].slot)
        self.w.set_block((0, 1, 0), blocks["spring"].slot)
        self.hook.tracked.add((0, 0, 0))
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Remove the upper spring.
        self.w.destroy((0, 1, 0))
        self.hook.tracked.add((0, 1, 0))

        # Tight-loop run the hook to equilibrium.
        while self.hook.tracked:
            self.hook.process()

        # Check that the upper water blocks dried out. Don't care about the
        # lower ones in this test.
        for coords in ((1, 1, 0), (-1, 1, 0), (0, 1, 1), (0, 1, -1)):
            block = yield self.w.get_block(coords)
            self.assertEqual(block, blocks["air"].slot)