mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2025-07-23 23:55:33 +02: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