Added a simple integration test to ensure ProtocolLib actually works.

We do this by running a CraftBukkit server in target/server, and 
copying ProtocolLib to its plugin folder.
This commit is contained in:
Kristian S. Stangeland 2013-10-07 04:29:21 +02:00
parent 47ffa7f62d
commit 09cc024a3f
5 changed files with 340 additions and 4 deletions

View File

@ -91,6 +91,23 @@
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<workingDirectory>${basedir}/target/server/</workingDirectory>
<argLine>-Xmx1024m -Xms1024M -Dnojline=true</argLine>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>

View File

@ -126,7 +126,7 @@ public class ProtocolLibrary extends JavaPlugin {
// Updater // Updater
private Updater updater; private Updater updater;
private boolean updateDisabled; private static boolean UPDATES_DISABLED;
// Logger // Logger
private Logger logger; private Logger logger;
@ -479,7 +479,7 @@ public class ProtocolLibrary extends JavaPlugin {
manager.sendProcessedPackets(tickCounter++, true); manager.sendProcessedPackets(tickCounter++, true);
// Check for updates too // Check for updates too
if (!updateDisabled) { if (!UPDATES_DISABLED) {
checkUpdates(); checkUpdates();
} }
} }
@ -511,7 +511,7 @@ public class ProtocolLibrary extends JavaPlugin {
} }
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e)); reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
updateDisabled = true; UPDATES_DISABLED = true;
} }
} }

View File

@ -0,0 +1,119 @@
package com.comphenix.integration.protocol;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.Callable;
import org.apache.commons.io.FileUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoadOrder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.FieldUtils;
import com.google.common.collect.Lists;
// Damn final classes ...
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
@PrepareForTest(PluginDescriptionFile.class)
public class SimpleCraftBukkitITCase {
// The fake plugin
private static volatile Plugin FAKE_PLUGIN = null;
/**
* Setup the CraftBukkit server for all the tests.
* @throws IOException Unable to setup server.
* @throws InterruptedException Thread interrupted.
*/
@BeforeClass
public static void setupCraftBukkit() throws Exception {
setupPlugins();
org.bukkit.craftbukkit.Main.main(new String[0]);
// We need to wait until the server object is ready
while (Bukkit.getServer() == null)
Thread.sleep(1);
// Make it clear this plugin doesn't exist
FAKE_PLUGIN = createPlugin("FakeTestPluginIntegration");
// No need to look for updates
FieldUtils.writeStaticField(ProtocolLibrary.class, "UPDATES_DISABLED", Boolean.TRUE, true);
// Wait until the server and all the plugins have loaded
Bukkit.getScheduler().callSyncMethod(FAKE_PLUGIN, new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
}).get();
// Plugins are now ready
}
/**
* Close the CraftBukkit server when they're done.
*/
@AfterClass
public static void shutdownCraftBukkit() {
Bukkit.shutdown();
}
@Test
public void testPingPacket() throws Throwable {
TestPingPacket.newTest().startTest(FAKE_PLUGIN);
}
/**
* Copy ProtocolLib into the plugins folder.
* @throws IOException If anything went wrong.
*/
private static void setupPlugins() throws IOException {
File pluginDirectory = new File("plugins/");
File bestFile = null;
int bestLength = Integer.MAX_VALUE;
// Copy the ProtocolLib plugin to the server
FileUtils.cleanDirectory(pluginDirectory);
for (File file : new File("../").listFiles()) {
String name = file.getName();
if (name.startsWith("ProtocolLib") && name.length() < bestLength) {
bestLength = name.length();
bestFile = file;
}
}
FileUtils.copyFile(bestFile, new File(pluginDirectory, bestFile.getName()));
}
/**
* Create a mockable plugin for all the tests.
* @param fakePluginName - the fake plugin name.
* @return The plugin.
*/
private static Plugin createPlugin(String fakePluginName) {
Plugin plugin = mock(Plugin.class);
PluginDescriptionFile description = mock(PluginDescriptionFile.class);
when(description.getDepend()).thenReturn(Lists.newArrayList("ProtocolLib"));
when(description.getSoftDepend()).thenReturn(Collections.<String>emptyList());
when(description.getLoadBefore()).thenReturn(Collections.<String>emptyList());
when(description.getLoad()).thenReturn(PluginLoadOrder.POSTWORLD);
when(plugin.getName()).thenReturn(fakePluginName);
when(plugin.getServer()).thenReturn(Bukkit.getServer());
when(plugin.isEnabled()).thenReturn(true);
when(plugin.getDescription()).thenReturn(description);
return plugin;
}
}

View File

@ -0,0 +1,120 @@
package com.comphenix.integration.protocol;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Charsets;
public class SimpleMinecraftClient {
private static final int CONNECT_TIMEOUT = 2500;
private static final int READ_TIMEOUT = 15000;
// The version after which we must send a plugin message with the host name
private static final String PLUGIN_MESSAGE_VERSION = "1.6.0";
// Current Minecraft version
private final MinecraftVersion version;
private final int protocolVersion;
public SimpleMinecraftClient(MinecraftVersion version, int protocolVersion) {
this.version = version;
this.protocolVersion = protocolVersion;
}
/**
* Query the local server for ping information.
* @return The server information.
* @throws IOException
*/
public String queryLocalPing() throws IOException {
return queryServerPing(new InetSocketAddress("localhost", 25565));
}
/**
* Query the given server for its list ping information.
* @param address - the server hostname and port.
* @return The server information.
* @throws IOException
*/
public String queryServerPing(InetSocketAddress address) throws IOException {
Socket socket = null;
OutputStream output = null;
InputStream input = null;
InputStreamReader reader = null;
// UTF-16!
Charset charset = Charsets.UTF_16BE;
try {
socket = new Socket();
socket.connect(address, CONNECT_TIMEOUT);
// Shouldn't take that long
socket.setSoTimeout(READ_TIMEOUT);
// Retrieve sockets
output = socket.getOutputStream();
input = socket.getInputStream();
reader = new InputStreamReader(input, charset);
// Get the server to send a MOTD
output.write(new byte[] { (byte) 0xFE, (byte) 0x01 });
// For 1.6
if (version.compareTo(new MinecraftVersion(PLUGIN_MESSAGE_VERSION)) >= 0) {
DataOutputStream data = new DataOutputStream(output);
String host = address.getHostString();
data.writeByte(0xFA);
writeString(data, "MC|PingHost");
data.writeShort(3 + 2 * host.length() + 4);
data.writeByte(protocolVersion);
writeString(data, host);
data.writeInt(address.getPort());
data.flush();
}
int packetId = input.read();
int length = reader.read();
if (packetId != 255)
throw new IOException("Invalid packet ID: " + packetId);
if (length <= 0)
throw new IOException("Invalid string length.");
char[] chars = new char[length];
// Read all the characters
if (reader.read(chars, 0, length) != length) {
throw new IOException("Premature end of stream.");
}
return new String(chars);
} finally {
if (reader != null)
reader.close();
if (input != null)
input.close();
if (output != null)
output.close();
if (socket != null)
socket.close();
}
}
private void writeString(DataOutputStream output, String text) throws IOException {
if (text.length() > 32767)
throw new IOException("String too big: " + text.length());
output.writeShort(text.length());
output.writeChars(text);
}
}

View File

@ -0,0 +1,80 @@
package com.comphenix.integration.protocol;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.utility.MinecraftVersion;
public class TestPingPacket {
// Current versions
private static final String CRAFTBUKKIT_VERSION = "1.6.2";
private static final int PROTOCOL_VERSION = 74;
private volatile String source;
private TestPingPacket() {
// Prevent external constructors
}
/**
* Create a new test ping packet test.
* @return The new test.
*/
public static TestPingPacket newTest() {
return new TestPingPacket();
}
/**
* Invoked when the test should be started.
* @param plugin - the current plugin.
* @throws Throwable Anything went wrong.
*/
public void startTest(Plugin plugin) throws Throwable {
try {
String transmitted = testInterception(plugin).get();
// Make sure it's the same
System.out.println("Server string: " + transmitted);
assertEquals(transmitted, source);
} catch (ExecutionException e) {
throw e.getCause();
}
}
private Future<String> testInterception(Plugin test) {
ProtocolLibrary.getProtocolManager().addPacketListener(
new PacketAdapter(test, ConnectionSide.SERVER_SIDE, GamePhase.LOGIN, Packets.Server.KICK_DISCONNECT) {
@Override
public void onPacketSending(PacketEvent event) {
source = event.getPacket().getStrings().read(0);
}
});
// Invoke the client on a separate thread
return Executors.newSingleThreadExecutor().submit(new Callable<String>() {
@Override
public String call() throws Exception {
SimpleMinecraftClient client = new SimpleMinecraftClient(new MinecraftVersion(CRAFTBUKKIT_VERSION), PROTOCOL_VERSION);
String information = client.queryLocalPing();
// Wait for the listener to catch up
for (int i = 0; i < 1000 && (source == null); i++)
Thread.sleep(1);
return information;
}
});
}
}