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.
Dieser Commit ist enthalten in:
Ursprung
47ffa7f62d
Commit
09cc024a3f
@ -91,6 +91,23 @@
|
||||
</configuration>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
|
@ -126,7 +126,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// Updater
|
||||
private Updater updater;
|
||||
private boolean updateDisabled;
|
||||
private static boolean UPDATES_DISABLED;
|
||||
|
||||
// Logger
|
||||
private Logger logger;
|
||||
@ -479,7 +479,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
manager.sendProcessedPackets(tickCounter++, true);
|
||||
|
||||
// Check for updates too
|
||||
if (!updateDisabled) {
|
||||
if (!UPDATES_DISABLED) {
|
||||
checkUpdates();
|
||||
}
|
||||
}
|
||||
@ -511,7 +511,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
|
||||
updateDisabled = true;
|
||||
UPDATES_DISABLED = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
In neuem Issue referenzieren
Einen Benutzer sperren