mirror of
				https://github.com/mcMMO-Dev/mcMMO.git
				synced 2025-11-04 11:03:43 +01:00 
			
		
		
		
	
							
								
								
									
										308
									
								
								src/test/java/ChunkStoreTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								src/test/java/ChunkStoreTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,308 @@
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.*;
 | 
			
		||||
import com.google.common.io.Files;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.junit.*;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.mockito.Mockito;
 | 
			
		||||
import org.powermock.api.mockito.PowerMockito;
 | 
			
		||||
import org.powermock.core.classloader.annotations.PrepareForTest;
 | 
			
		||||
import org.powermock.modules.junit4.PowerMockRunner;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Could be alot better.  But some tests are better than none!  Tests the major things, still kinda unit-testy.  Verifies that the serialization isn't completely broken.
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(PowerMockRunner.class)
 | 
			
		||||
@PrepareForTest(Bukkit.class)
 | 
			
		||||
public class ChunkStoreTest {
 | 
			
		||||
    private static File tempDir;
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
    public static void setUpClass() {
 | 
			
		||||
        tempDir = Files.createTempDir();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @AfterClass
 | 
			
		||||
    public static void tearDownClass() {
 | 
			
		||||
        recursiveDelete(tempDir);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private World mockWorld;
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUpMock(){
 | 
			
		||||
        UUID worldUUID = UUID.randomUUID();
 | 
			
		||||
        mockWorld = mock(World.class);
 | 
			
		||||
        Mockito.when(mockWorld.getUID()).thenReturn(worldUUID);
 | 
			
		||||
        Mockito.when(mockWorld.getMaxHeight()).thenReturn(256);
 | 
			
		||||
        Mockito.when(mockWorld.getWorldFolder()).thenReturn(tempDir);
 | 
			
		||||
        PowerMockito.mockStatic(Bukkit.class);
 | 
			
		||||
        Mockito.when(Bukkit.getWorld(worldUUID)).thenReturn(mockWorld);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testSetValue() {
 | 
			
		||||
        BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0);
 | 
			
		||||
        original.setTrue(0, 0, 0);
 | 
			
		||||
        Assert.assertTrue(original.isTrue(0, 0, 0));
 | 
			
		||||
        original.setFalse(0, 0, 0);
 | 
			
		||||
        Assert.assertFalse(original.isTrue(0, 0, 0));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testIsEmpty() {
 | 
			
		||||
        BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0);
 | 
			
		||||
        Assert.assertTrue(original.isEmpty());
 | 
			
		||||
        original.setTrue(0, 0, 0);
 | 
			
		||||
        original.setFalse(0, 0, 0);
 | 
			
		||||
        Assert.assertTrue(original.isEmpty());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testRoundTrip() throws IOException {
 | 
			
		||||
        BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2);
 | 
			
		||||
        original.setTrue(14, 89, 12);
 | 
			
		||||
        original.setTrue(14, 90, 12);
 | 
			
		||||
        original.setTrue(13, 89, 12);
 | 
			
		||||
        byte[] serializedBytes = serializeChunkstore(original);
 | 
			
		||||
        ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
 | 
			
		||||
        assertEqual(original, deserialized);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testChunkCoords() throws IOException {
 | 
			
		||||
        for (int x = -96; x < 0; x++) {
 | 
			
		||||
                int cx = x >> 4;
 | 
			
		||||
                int ix = Math.abs(x) % 16;
 | 
			
		||||
                System.out.print(cx + ":" + ix + "  ");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testUpgrade() throws IOException {
 | 
			
		||||
        LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 32);
 | 
			
		||||
        original.setTrue(14, 89, 12);
 | 
			
		||||
        original.setTrue(14, 90, 12);
 | 
			
		||||
        original.setTrue(13, 89, 12);
 | 
			
		||||
        byte[] serializedBytes = serializeChunkstore(original);
 | 
			
		||||
        ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
 | 
			
		||||
        assertEqual(original, deserialized);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testSimpleRegionRoundtrip() throws IOException {
 | 
			
		||||
        LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 12);
 | 
			
		||||
        original.setTrue(14, 89, 12);
 | 
			
		||||
        original.setTrue(14, 90, 12);
 | 
			
		||||
        original.setTrue(13, 89, 12);
 | 
			
		||||
        File file = new File(tempDir, "SimpleRegionRoundTrip.region");
 | 
			
		||||
        McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0);
 | 
			
		||||
        try (DataOutputStream outputStream = region.getOutputStream(12, 12)){
 | 
			
		||||
            outputStream.write(serializeChunkstore(original));
 | 
			
		||||
        }
 | 
			
		||||
        region.close();
 | 
			
		||||
        region = new McMMOSimpleRegionFile(file, 0, 0);
 | 
			
		||||
        try (DataInputStream is = region.getInputStream(original.getChunkX(), original.getChunkZ()))
 | 
			
		||||
        {
 | 
			
		||||
            Assert.assertNotNull(is);
 | 
			
		||||
            ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(is);
 | 
			
		||||
            assertEqual(original, deserialized);
 | 
			
		||||
        }
 | 
			
		||||
        region.close();
 | 
			
		||||
        file.delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testSimpleRegionRejectsOutOfBounds() {
 | 
			
		||||
        File file = new File(tempDir, "SimpleRegionRoundTrip.region");
 | 
			
		||||
        McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0);
 | 
			
		||||
        assertThrows(() -> region.getOutputStream(-1, 0), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> region.getOutputStream(0, -1), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> region.getOutputStream(32, 0), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> region.getOutputStream(0, 32), IndexOutOfBoundsException.class);
 | 
			
		||||
        region.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testChunkStoreRejectsOutOfBounds() {
 | 
			
		||||
        ChunkStore chunkStore = new BitSetChunkStore(mockWorld, 0, 0);
 | 
			
		||||
        assertThrows(() -> chunkStore.setTrue(-1, 0, 0), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> chunkStore.setTrue(0, -1, 0), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> chunkStore.setTrue(0, 0, -1), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> chunkStore.setTrue(16, 0, 0), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> chunkStore.setTrue(0, mockWorld.getMaxHeight(), 0), IndexOutOfBoundsException.class);
 | 
			
		||||
        assertThrows(() -> chunkStore.setTrue(0, 0, 16), IndexOutOfBoundsException.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testRegressionChunkMirrorBug() {
 | 
			
		||||
        ChunkManager chunkManager = new HashChunkManager();
 | 
			
		||||
        chunkManager.setTrue(15,0,15, mockWorld);
 | 
			
		||||
        chunkManager.setFalse(-15, 0, -15, mockWorld);
 | 
			
		||||
        Assert.assertTrue(chunkManager.isTrue(15, 0, 15, mockWorld));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private interface Delegate {
 | 
			
		||||
        void run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void assertThrows(Delegate delegate, Class<?> clazz) {
 | 
			
		||||
        try {
 | 
			
		||||
            delegate.run();
 | 
			
		||||
            Assert.fail(); // We didn't throw
 | 
			
		||||
        }
 | 
			
		||||
        catch (Throwable t) {
 | 
			
		||||
            Assert.assertTrue(t.getClass().equals(clazz));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void assertEqual(ChunkStore expected, ChunkStore actual)
 | 
			
		||||
    {
 | 
			
		||||
        Assert.assertEquals(expected.getChunkX(), actual.getChunkX());
 | 
			
		||||
        Assert.assertEquals(expected.getChunkZ(), actual.getChunkZ());
 | 
			
		||||
        Assert.assertEquals(expected.getWorldId(), actual.getWorldId());
 | 
			
		||||
        for (int y = 0; y < 256; y++)
 | 
			
		||||
            for (int x = 0; x < 16; x++)
 | 
			
		||||
                for (int z = 0; z < 16; z++)
 | 
			
		||||
                    Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void recursiveDelete(File directoryToBeDeleted) {
 | 
			
		||||
        if (directoryToBeDeleted.isDirectory()) {
 | 
			
		||||
            for (File file : directoryToBeDeleted.listFiles()) {
 | 
			
		||||
                recursiveDelete(file);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        directoryToBeDeleted.delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static byte[] serializeChunkstore(ChunkStore chunkStore) throws IOException {
 | 
			
		||||
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 | 
			
		||||
        if (chunkStore instanceof BitSetChunkStore)
 | 
			
		||||
            BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore);
 | 
			
		||||
        else
 | 
			
		||||
            new UnitTestObjectOutputStream(byteArrayOutputStream).writeObject(chunkStore); // Serializes the class as if it were the old PrimitiveChunkStore
 | 
			
		||||
        return byteArrayOutputStream.toByteArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static class LegacyChunkStore implements ChunkStore, Serializable {
 | 
			
		||||
        private static final long serialVersionUID = -1L;
 | 
			
		||||
        transient private boolean dirty = false;
 | 
			
		||||
        public boolean[][][] store;
 | 
			
		||||
        private static final int CURRENT_VERSION = 7;
 | 
			
		||||
        private static final int MAGIC_NUMBER = 0xEA5EDEBB;
 | 
			
		||||
        private int cx;
 | 
			
		||||
        private int cz;
 | 
			
		||||
        private UUID worldUid;
 | 
			
		||||
 | 
			
		||||
        public LegacyChunkStore(World world, int cx, int cz) {
 | 
			
		||||
            this.cx = cx;
 | 
			
		||||
            this.cz = cz;
 | 
			
		||||
            this.worldUid = world.getUID();
 | 
			
		||||
            this.store = new boolean[16][16][world.getMaxHeight()];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isDirty() {
 | 
			
		||||
            return dirty;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void setDirty(boolean dirty) {
 | 
			
		||||
            this.dirty = dirty;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int getChunkX() {
 | 
			
		||||
            return cx;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int getChunkZ() {
 | 
			
		||||
            return cz;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public UUID getWorldId() {
 | 
			
		||||
            return worldUid;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isTrue(int x, int y, int z) {
 | 
			
		||||
            return store[x][z][y];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void setTrue(int x, int y, int z) {
 | 
			
		||||
            if (y >= store[0][0].length || y < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            store[x][z][y] = true;
 | 
			
		||||
            dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void setFalse(int x, int y, int z) {
 | 
			
		||||
            if (y >= store[0][0].length || y < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            store[x][z][y] = false;
 | 
			
		||||
            dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void set(int x, int y, int z, boolean value) {
 | 
			
		||||
            if (y >= store[0][0].length || y < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            store[x][z][y] = value;
 | 
			
		||||
            dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean isEmpty() {
 | 
			
		||||
            for (int x = 0; x < 16; x++) {
 | 
			
		||||
                for (int z = 0; z < 16; z++) {
 | 
			
		||||
                    for (int y = 0; y < store[0][0].length; y++) {
 | 
			
		||||
                        if (store[x][z][y]) {
 | 
			
		||||
                            return false;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void writeObject(ObjectOutputStream out) throws IOException {
 | 
			
		||||
            out.writeInt(MAGIC_NUMBER);
 | 
			
		||||
            out.writeInt(CURRENT_VERSION);
 | 
			
		||||
 | 
			
		||||
            out.writeLong(worldUid.getLeastSignificantBits());
 | 
			
		||||
            out.writeLong(worldUid.getMostSignificantBits());
 | 
			
		||||
            out.writeInt(cx);
 | 
			
		||||
            out.writeInt(cz);
 | 
			
		||||
            out.writeObject(store);
 | 
			
		||||
 | 
			
		||||
            dirty = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 | 
			
		||||
            throw new UnsupportedOperationException();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class UnitTestObjectOutputStream extends ObjectOutputStream {
 | 
			
		||||
        public UnitTestObjectOutputStream(OutputStream outputStream) throws IOException {
 | 
			
		||||
            super(outputStream);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void writeUTF(String str) throws IOException {
 | 
			
		||||
            // Pretend to be the old class
 | 
			
		||||
            if (str.equals(LegacyChunkStore.class.getName()))
 | 
			
		||||
                str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore";
 | 
			
		||||
            super.writeUTF(str);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user