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>
|
</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>
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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