add conformance test plugin and fix command registration panic

- new patchbukkit-test-plugin gradle subproject with 69 tests across
  10 categories (server, plugin_manager, command_system, event_system,
  permissions, registry, entity, console_sender, unsafe_values, stubs)
- test framework with reflection-based discovery, ansi-colored output,
  and ci-parseable [PBTEST_SUMMARY] line
- /pbtest [category|all] command for manual test execution
- fix .unwrap() panic in command_manager.rs register_permission that
  crashed the jvm worker thread when plugins declared commands
This commit is contained in:
Purdze 2026-02-12 18:11:30 +00:00 committed by Bjorn Beishline
parent 5a4b495a25
commit 338717143a
22 changed files with 1176 additions and 2 deletions

View File

@ -0,0 +1,27 @@
plugins {
java
}
repositories {
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
tasks.withType<JavaCompile> {
options.compilerArgs.addAll(listOf(
"-Xlint:-removal",
"-Xlint:-deprecation"
))
}

View File

@ -0,0 +1,14 @@
package org.patchbukkit.testplugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ConformanceTest {
String name();
TestCategory category();
TestExpectation expectation() default TestExpectation.SHOULD_WORK;
}

View File

@ -0,0 +1,39 @@
package org.patchbukkit.testplugin;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
import org.patchbukkit.testplugin.tests.*;
public final class PatchBukkitTestPlugin extends JavaPlugin {
@Override
public void onEnable() {
TestFramework framework = new TestFramework(getLogger());
framework.registerSuite(new ServerTests(this));
framework.registerSuite(new PluginManagerTests(this));
framework.registerSuite(new CommandSystemTests(this));
framework.registerSuite(new EventSystemTests(this));
framework.registerSuite(new PermissionsTests(this));
framework.registerSuite(new RegistryTests(this));
framework.registerSuite(new EntityTests(this));
framework.registerSuite(new ConsoleSenderTests(this));
framework.registerSuite(new UnsafeValuesTests(this));
framework.registerSuite(new StubTests(this));
// Set executor on the PluginCommand created by PatchBukkit's Rust side
PbTestCommand cmd = new PbTestCommand(framework);
Command existing = Bukkit.getServer().getCommandMap().getCommand("pbtest");
if (existing instanceof PluginCommand pluginCmd) {
pluginCmd.setExecutor(cmd);
pluginCmd.setTabCompleter(cmd);
} else {
// Fallback: register directly on CommandMap
Bukkit.getServer().getCommandMap().register("patchbukkittest", new PbTestBukkitCommand(cmd));
}
getLogger().info("PatchBukkit Test Plugin loaded. Run /pbtest [category|all] to execute tests.");
}
}

View File

@ -0,0 +1,26 @@
package org.patchbukkit.testplugin;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import java.util.List;
public final class PbTestBukkitCommand extends Command {
private final PbTestCommand handler;
public PbTestBukkitCommand(PbTestCommand handler) {
super("pbtest", "Run PatchBukkit conformance tests", "/pbtest [category|all]", List.of());
this.handler = handler;
}
@Override
public boolean execute(CommandSender sender, String label, String[] args) {
return handler.onCommand(sender, this, label, args);
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) {
return handler.onTabComplete(sender, this, alias, args);
}
}

View File

@ -0,0 +1,56 @@
package org.patchbukkit.testplugin;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public final class PbTestCommand implements CommandExecutor, TabCompleter {
private final TestFramework framework;
public PbTestCommand(TestFramework framework) {
this.framework = framework;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
String target = (args.length > 0) ? args[0].toUpperCase(Locale.ROOT) : "ALL";
List<TestResult> results;
if ("ALL".equals(target)) {
results = framework.runAll();
} else {
TestCategory category;
try {
category = TestCategory.valueOf(target);
} catch (IllegalArgumentException e) {
sender.sendMessage("Unknown category: " + target + ". Use /pbtest [category|all]");
return true;
}
results = framework.runCategory(category);
}
framework.reportResults(results, sender);
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length != 1) return List.of();
String prefix = args[0].toUpperCase(Locale.ROOT);
List<String> completions = new ArrayList<>();
completions.add("all");
for (TestCategory cat : TestCategory.values()) {
completions.add(cat.name().toLowerCase(Locale.ROOT));
}
completions.removeIf(s -> !s.toUpperCase(Locale.ROOT).startsWith(prefix));
return completions;
}
}

View File

@ -0,0 +1,14 @@
package org.patchbukkit.testplugin;
public enum TestCategory {
SERVER,
PLUGIN_MANAGER,
COMMAND_SYSTEM,
EVENT_SYSTEM,
PERMISSIONS,
REGISTRY,
ENTITY,
CONSOLE_SENDER,
UNSAFE_VALUES,
STUBS
}

View File

@ -0,0 +1,6 @@
package org.patchbukkit.testplugin;
public enum TestExpectation {
SHOULD_WORK,
EXPECT_UNSUPPORTED
}

View File

@ -0,0 +1,172 @@
package org.patchbukkit.testplugin;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.command.CommandSender;
public final class TestFramework {
private final Logger logger;
private final List<Object> suites = new ArrayList<>();
public TestFramework(Logger logger) {
this.logger = logger;
}
public void registerSuite(Object suite) {
suites.add(suite);
}
public List<TestResult> runAll() {
List<TestResult> results = new ArrayList<>();
for (Object suite : suites) {
results.addAll(runSuite(suite));
}
return results;
}
public List<TestResult> runCategory(TestCategory category) {
List<TestResult> results = new ArrayList<>();
for (Object suite : suites) {
for (Method method : suite.getClass().getDeclaredMethods()) {
ConformanceTest ann = method.getAnnotation(ConformanceTest.class);
if (ann != null && ann.category() == category) {
results.add(runTest(suite, method, ann));
}
}
}
return results;
}
private List<TestResult> runSuite(Object suite) {
List<TestResult> results = new ArrayList<>();
for (Method method : suite.getClass().getDeclaredMethods()) {
ConformanceTest ann = method.getAnnotation(ConformanceTest.class);
if (ann != null) {
results.add(runTest(suite, method, ann));
}
}
return results;
}
private TestResult runTest(Object suite, Method method, ConformanceTest ann) {
try {
method.setAccessible(true);
method.invoke(suite);
if (ann.expectation() == TestExpectation.SHOULD_WORK) {
return new TestResult(ann.name(), ann.category(), ann.expectation(), true, null);
} else {
return new TestResult(ann.name(), ann.category(), ann.expectation(), false,
"Expected UnsupportedOperationException but method succeeded");
}
} catch (Exception e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
if (ann.expectation() == TestExpectation.EXPECT_UNSUPPORTED) {
if (cause instanceof UnsupportedOperationException) {
return new TestResult(ann.name(), ann.category(), ann.expectation(), true, null);
} else {
return new TestResult(ann.name(), ann.category(), ann.expectation(), false,
"Expected UnsupportedOperationException but got " + cause.getClass().getSimpleName() + ": " + cause.getMessage());
}
} else {
return new TestResult(ann.name(), ann.category(), ann.expectation(), false,
cause.getClass().getSimpleName() + ": " + cause.getMessage());
}
}
}
// ANSI color codes
private static final String RESET = "\u001B[0m";
private static final String BOLD = "\u001B[1m";
private static final String RED = "\u001B[31m";
private static final String GREEN = "\u001B[32m";
private static final String YELLOW = "\u001B[33m";
private static final String CYAN = "\u001B[36m";
private static final String WHITE = "\u001B[37m";
public void reportResults(List<TestResult> results) {
Map<TestCategory, List<TestResult>> grouped = new EnumMap<>(TestCategory.class);
for (TestResult r : results) {
grouped.computeIfAbsent(r.category(), k -> new ArrayList<>()).add(r);
}
int total = results.size();
int passed = 0;
for (TestResult r : results) {
if (r.passed()) passed++;
}
int failed = total - passed;
logger.info(CYAN + BOLD + "========================================" + RESET);
logger.info(CYAN + BOLD + " PatchBukkit Conformance Test Results" + RESET);
logger.info(CYAN + BOLD + "========================================" + RESET);
for (TestCategory cat : TestCategory.values()) {
List<TestResult> catResults = grouped.get(cat);
if (catResults == null || catResults.isEmpty()) continue;
logger.info(YELLOW + BOLD + "--- " + cat.name() + " ---" + RESET);
for (TestResult r : catResults) {
if (r.passed()) {
logger.info(GREEN + " PASS" + RESET + " [" + r.tag() + "] " + r.name());
} else {
logger.info(RED + " FAIL" + RESET + " [" + r.tag() + "] " + r.name() + RED + " -- " + r.detail() + RESET);
}
}
}
logger.info(CYAN + BOLD + "========================================" + RESET);
String summaryColor = failed == 0 ? GREEN : RED;
logger.info(BOLD + " Total: " + WHITE + total + RESET
+ BOLD + " Passed: " + GREEN + passed + RESET
+ BOLD + " Failed: " + summaryColor + failed + RESET);
logger.info(CYAN + BOLD + "========================================" + RESET);
logger.info("[PBTEST_SUMMARY] total=" + total + " passed=" + passed + " failed=" + failed);
}
public void reportResults(List<TestResult> results, CommandSender sender) {
Map<TestCategory, List<TestResult>> grouped = new EnumMap<>(TestCategory.class);
for (TestResult r : results) {
grouped.computeIfAbsent(r.category(), k -> new ArrayList<>()).add(r);
}
int total = results.size();
int passed = 0;
for (TestResult r : results) {
if (r.passed()) passed++;
}
int failed = total - passed;
sender.sendMessage(CYAN + BOLD + "========================================" + RESET);
sender.sendMessage(CYAN + BOLD + " PatchBukkit Conformance Test Results" + RESET);
sender.sendMessage(CYAN + BOLD + "========================================" + RESET);
for (TestCategory cat : TestCategory.values()) {
List<TestResult> catResults = grouped.get(cat);
if (catResults == null || catResults.isEmpty()) continue;
sender.sendMessage(YELLOW + BOLD + "--- " + cat.name() + " ---" + RESET);
for (TestResult r : catResults) {
if (r.passed()) {
sender.sendMessage(GREEN + " PASS" + RESET + " [" + r.tag() + "] " + r.name());
} else {
sender.sendMessage(RED + " FAIL" + RESET + " [" + r.tag() + "] " + r.name() + RED + " -- " + r.detail() + RESET);
}
}
}
sender.sendMessage(CYAN + BOLD + "========================================" + RESET);
String summaryColor = failed == 0 ? GREEN : RED;
sender.sendMessage(BOLD + " Total: " + WHITE + total + RESET
+ BOLD + " Passed: " + GREEN + passed + RESET
+ BOLD + " Failed: " + summaryColor + failed + RESET);
sender.sendMessage(CYAN + BOLD + "========================================" + RESET);
}
}

View File

@ -0,0 +1,13 @@
package org.patchbukkit.testplugin;
public record TestResult(
String name,
TestCategory category,
TestExpectation expectation,
boolean passed,
String detail
) {
public String tag() {
return expectation == TestExpectation.SHOULD_WORK ? "IMPL" : "STUB";
}
}

View File

@ -0,0 +1,106 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Arrays;
import java.util.List;
public final class CommandSystemTests {
private final JavaPlugin plugin;
public CommandSystemTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "CommandMap.register() accepts a command", category = TestCategory.COMMAND_SYSTEM)
public void testRegister() {
CommandMap map = Bukkit.getServer().getCommandMap();
Command testCmd = new BukkitCommand("pbtestdummy", "test", "/pbtestdummy", List.of()) {
@Override
public boolean execute(CommandSender sender, String label, String[] args) {
return true;
}
};
boolean registered = map.register("patchbukkittest", testCmd);
// registered is true if direct label was available, false if only fallback was used
// Either way it should not throw
}
@ConformanceTest(name = "CommandMap.getCommand() retrieves registered command", category = TestCategory.COMMAND_SYSTEM)
public void testGetCommand() {
CommandMap map = Bukkit.getServer().getCommandMap();
// "pbtest" was registered by the plugin
Command cmd = map.getCommand("pbtest");
assertNotNull(cmd, "CommandMap.getCommand(\"pbtest\")");
}
@ConformanceTest(name = "CommandMap.getKnownCommands() returns map", category = TestCategory.COMMAND_SYSTEM)
public void testGetKnownCommands() {
CommandMap map = Bukkit.getServer().getCommandMap();
var known = map.getKnownCommands();
assertNotNull(known, "CommandMap.getKnownCommands()");
assertTrue(!known.isEmpty(), "getKnownCommands() should not be empty");
}
@ConformanceTest(name = "CommandMap.dispatch() executes a command", category = TestCategory.COMMAND_SYSTEM)
public void testDispatch() {
CommandMap map = Bukkit.getServer().getCommandMap();
// Register a command that sets a flag
final boolean[] executed = {false};
Command testCmd = new BukkitCommand("pbtestexec", "test", "/pbtestexec", List.of()) {
@Override
public boolean execute(CommandSender sender, String label, String[] args) {
executed[0] = true;
return true;
}
};
map.register("patchbukkittest", testCmd);
CommandSender sender = Bukkit.getConsoleSender();
boolean result = map.dispatch(sender, "pbtestexec");
assertTrue(result, "dispatch() should return true");
assertTrue(executed[0], "command should have been executed");
}
@ConformanceTest(name = "CommandMap.tabComplete() returns completions", category = TestCategory.COMMAND_SYSTEM)
public void testTabComplete() {
CommandMap map = Bukkit.getServer().getCommandMap();
CommandSender sender = Bukkit.getConsoleSender();
List<String> completions = map.tabComplete(sender, "pb");
assertNotNull(completions, "CommandMap.tabComplete()");
// Should include "pbtest" at minimum
}
@ConformanceTest(name = "CommandMap.register() with fallback prefix creates alias", category = TestCategory.COMMAND_SYSTEM)
public void testFallbackAlias() {
CommandMap map = Bukkit.getServer().getCommandMap();
Command testCmd = new BukkitCommand("pbtestalias", "test", "/pbtestalias", List.of("pbta")) {
@Override
public boolean execute(CommandSender sender, String label, String[] args) {
return true;
}
};
map.register("myprefix", testCmd);
// Should be accessible via fallback prefix
Command found = map.getCommand("myprefix:pbtestalias");
assertNotNull(found, "CommandMap.getCommand(\"myprefix:pbtestalias\")");
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
private static void assertTrue(boolean condition, String what) {
if (!condition) throw new AssertionError(what);
}
}

View File

@ -0,0 +1,64 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.ConsoleCommandSender;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.bukkit.plugin.java.JavaPlugin;
public final class ConsoleSenderTests {
private final JavaPlugin plugin;
public ConsoleSenderTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "ConsoleSender.getName() returns non-null", category = TestCategory.CONSOLE_SENDER)
public void testGetName() {
ConsoleCommandSender console = Bukkit.getConsoleSender();
String name = console.getName();
assertNotNull(name, "ConsoleSender.getName()");
}
@ConformanceTest(name = "ConsoleSender.sendMessage(String) does not throw", category = TestCategory.CONSOLE_SENDER)
public void testSendMessage() {
ConsoleCommandSender console = Bukkit.getConsoleSender();
console.sendMessage("[PBTest] Console sendMessage test");
}
@ConformanceTest(name = "ConsoleSender.sendMessage(String[]) does not throw", category = TestCategory.CONSOLE_SENDER)
public void testSendMessageArray() {
ConsoleCommandSender console = Bukkit.getConsoleSender();
console.sendMessage(new String[]{"[PBTest] Line 1", "[PBTest] Line 2"});
}
@ConformanceTest(name = "ConsoleSender.isOp() returns true", category = TestCategory.CONSOLE_SENDER)
public void testIsOp() {
ConsoleCommandSender console = Bukkit.getConsoleSender();
assertTrue(console.isOp(), "ConsoleSender.isOp() should be true");
}
@ConformanceTest(name = "ConsoleSender.getServer() returns non-null", category = TestCategory.CONSOLE_SENDER)
public void testGetServer() {
ConsoleCommandSender console = Bukkit.getConsoleSender();
Server server = console.getServer();
assertNotNull(server, "ConsoleSender.getServer()");
}
@ConformanceTest(name = "ConsoleSender.spigot() returns non-null", category = TestCategory.CONSOLE_SENDER)
public void testSpigot() {
ConsoleCommandSender console = Bukkit.getConsoleSender();
var spigot = console.spigot();
assertNotNull(spigot, "ConsoleSender.spigot()");
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
private static void assertTrue(boolean condition, String what) {
if (!condition) throw new AssertionError(what);
}
}

View File

@ -0,0 +1,40 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.patchbukkit.testplugin.TestExpectation;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Collection;
import java.util.UUID;
public final class EntityTests {
private final JavaPlugin plugin;
public EntityTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "Server.getOnlinePlayers() returns iterable collection", category = TestCategory.ENTITY)
public void testOnlinePlayersIteration() {
Collection<? extends Player> players = Bukkit.getOnlinePlayers();
assertNotNull(players, "Bukkit.getOnlinePlayers()");
// Iterate without error
for (Player p : players) {
assertNotNull(p, "Player in online players collection");
}
}
@ConformanceTest(name = "Server.getEntity(UUID) stub", category = TestCategory.ENTITY,
expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetEntity() {
Bukkit.getServer().getEntity(UUID.randomUUID());
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
}

View File

@ -0,0 +1,64 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.plugin.EventExecutor;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.bukkit.plugin.java.JavaPlugin;
public final class EventSystemTests {
private final JavaPlugin plugin;
public EventSystemTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "registerEvents() with annotated listener", category = TestCategory.EVENT_SYSTEM)
public void testRegisterEvents() {
Listener listener = new Listener() {
@EventHandler
public void onPluginEnable(PluginEnableEvent event) {
// no-op, just verifying registration doesn't throw
}
};
Bukkit.getPluginManager().registerEvents(listener, plugin);
}
@ConformanceTest(name = "registerEvent() with explicit priority", category = TestCategory.EVENT_SYSTEM)
public void testRegisterEventWithPriority() {
EventExecutor executor = (listener, event) -> {
// no-op
};
Bukkit.getPluginManager().registerEvent(
PluginEnableEvent.class,
new Listener() {},
EventPriority.HIGH,
executor,
plugin
);
}
@ConformanceTest(name = "registerEvent() with ignoreCancelled flag", category = TestCategory.EVENT_SYSTEM)
public void testRegisterEventIgnoreCancelled() {
EventExecutor executor = (listener, event) -> {
// no-op
};
Bukkit.getPluginManager().registerEvent(
PluginEnableEvent.class,
new Listener() {},
EventPriority.NORMAL,
executor,
plugin,
true
);
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
}

View File

@ -0,0 +1,87 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Set;
public final class PermissionsTests {
private final JavaPlugin plugin;
public PermissionsTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "addPermission() and getPermission()", category = TestCategory.PERMISSIONS)
public void testAddAndGetPermission() {
PluginManager pm = Bukkit.getPluginManager();
// Clean up from previous runs
pm.removePermission("patchbukkit.test.add");
Permission perm = new Permission("patchbukkit.test.add", PermissionDefault.OP);
pm.addPermission(perm);
Permission found = pm.getPermission("patchbukkit.test.add");
assertNotNull(found, "getPermission() after addPermission()");
assertTrue(found == perm, "getPermission() should return the same instance");
pm.removePermission(perm);
}
@ConformanceTest(name = "removePermission() removes added permission", category = TestCategory.PERMISSIONS)
public void testRemovePermission() {
PluginManager pm = Bukkit.getPluginManager();
pm.removePermission("patchbukkit.test.remove");
Permission perm = new Permission("patchbukkit.test.remove", PermissionDefault.OP);
pm.addPermission(perm);
pm.removePermission(perm);
Permission found = pm.getPermission("patchbukkit.test.remove");
assertTrue(found == null, "getPermission() should return null after remove");
}
@ConformanceTest(name = "getPermission() returns null for unknown", category = TestCategory.PERMISSIONS)
public void testGetPermissionUnknown() {
Permission found = Bukkit.getPluginManager().getPermission("patchbukkit.test.nonexistent");
assertTrue(found == null, "getPermission(unknown) should return null");
}
@ConformanceTest(name = "getDefaultPermissions(op=true) returns set", category = TestCategory.PERMISSIONS)
public void testGetDefaultPermissionsOp() {
Set<Permission> defaults = Bukkit.getPluginManager().getDefaultPermissions(true);
assertNotNull(defaults, "getDefaultPermissions(true)");
}
@ConformanceTest(name = "getDefaultPermissions(op=false) returns set", category = TestCategory.PERMISSIONS)
public void testGetDefaultPermissionsNonOp() {
Set<Permission> defaults = Bukkit.getPluginManager().getDefaultPermissions(false);
assertNotNull(defaults, "getDefaultPermissions(false)");
}
@ConformanceTest(name = "Console sender has op permissions", category = TestCategory.PERMISSIONS)
public void testConsoleSenderHasOpPermissions() {
var console = Bukkit.getConsoleSender();
assertTrue(console.isOp(), "Console sender should be op");
// Add a permission with OP default
PluginManager pm = Bukkit.getPluginManager();
pm.removePermission("patchbukkit.test.oponly");
Permission perm = new Permission("patchbukkit.test.oponly", PermissionDefault.OP);
pm.addPermission(perm);
// Console (op=true) should have this permission
boolean hasPerm = console.hasPermission("patchbukkit.test.oponly");
pm.removePermission(perm);
assertTrue(hasPerm, "Console (op) should have OP-default permission");
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
private static void assertTrue(boolean condition, String what) {
if (!condition) throw new AssertionError(what);
}
}

View File

@ -0,0 +1,70 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.bukkit.plugin.java.JavaPlugin;
public final class PluginManagerTests {
private final JavaPlugin plugin;
public PluginManagerTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "PluginManager.getPlugins() contains this plugin", category = TestCategory.PLUGIN_MANAGER)
public void testGetPlugins() {
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
assertNotNull(plugins, "PluginManager.getPlugins()");
boolean found = false;
for (Plugin p : plugins) {
if (p == plugin) {
found = true;
break;
}
}
assertTrue(found, "getPlugins() should contain this plugin");
}
@ConformanceTest(name = "PluginManager.getPlugin() finds this plugin by name", category = TestCategory.PLUGIN_MANAGER)
public void testGetPlugin() {
Plugin found = Bukkit.getPluginManager().getPlugin("PatchBukkitTest");
assertNotNull(found, "PluginManager.getPlugin(\"PatchBukkitTest\")");
assertTrue(found == plugin, "getPlugin() should return this plugin instance");
}
@ConformanceTest(name = "PluginManager.getPlugin() returns null for unknown", category = TestCategory.PLUGIN_MANAGER)
public void testGetPluginUnknown() {
Plugin found = Bukkit.getPluginManager().getPlugin("NoSuchPlugin_XYZ_12345");
assertTrue(found == null, "getPlugin(unknown) should return null");
}
@ConformanceTest(name = "PluginManager.isPluginEnabled(String) for this plugin", category = TestCategory.PLUGIN_MANAGER)
public void testIsPluginEnabledByName() {
boolean enabled = Bukkit.getPluginManager().isPluginEnabled("PatchBukkitTest");
assertTrue(enabled, "isPluginEnabled(\"PatchBukkitTest\") should be true");
}
@ConformanceTest(name = "PluginManager.isPluginEnabled(Plugin) for this plugin", category = TestCategory.PLUGIN_MANAGER)
public void testIsPluginEnabledByInstance() {
boolean enabled = Bukkit.getPluginManager().isPluginEnabled(plugin);
assertTrue(enabled, "isPluginEnabled(plugin) should be true");
}
@ConformanceTest(name = "PluginManager.getPermissions() returns set", category = TestCategory.PLUGIN_MANAGER)
public void testGetPermissions() {
var perms = Bukkit.getPluginManager().getPermissions();
assertNotNull(perms, "PluginManager.getPermissions()");
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
private static void assertTrue(boolean condition, String what) {
if (!condition) throw new AssertionError(what);
}
}

View File

@ -0,0 +1,54 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.Registry;
import org.bukkit.Sound;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.patchbukkit.testplugin.TestExpectation;
import org.bukkit.plugin.java.JavaPlugin;
public final class RegistryTests {
private final JavaPlugin plugin;
public RegistryTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "Registry.SOUNDS is accessible", category = TestCategory.REGISTRY)
public void testSoundsRegistryExists() {
Registry<Sound> sounds = Registry.SOUNDS;
assertNotNull(sounds, "Registry.SOUNDS");
}
@ConformanceTest(name = "Registry.SOUNDS.iterator() works", category = TestCategory.REGISTRY)
public void testSoundsIteration() {
Registry<Sound> sounds = Registry.SOUNDS;
assertNotNull(sounds, "Registry.SOUNDS");
var iterator = sounds.iterator();
assertNotNull(iterator, "Registry.SOUNDS.iterator()");
}
@ConformanceTest(name = "Registry.SOUNDS.stream() works", category = TestCategory.REGISTRY)
public void testSoundsStream() {
Registry<Sound> sounds = Registry.SOUNDS;
assertNotNull(sounds, "Registry.SOUNDS");
var stream = sounds.stream();
assertNotNull(stream, "Registry.SOUNDS.stream()");
}
@ConformanceTest(name = "Server.getRegistry(Sound.class) returns registry", category = TestCategory.REGISTRY,
expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testServerGetRegistry() {
// Server.getRegistry() returns null in PatchBukkit — might throw or return null
Registry<Sound> reg = Bukkit.getServer().getRegistry(Sound.class);
if (reg == null) {
throw new UnsupportedOperationException("Server.getRegistry() returned null (not implemented)");
}
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
}

View File

@ -0,0 +1,115 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.CommandMap;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.patchbukkit.testplugin.TestExpectation;
import org.bukkit.plugin.java.JavaPlugin;
public final class ServerTests {
private final JavaPlugin plugin;
public ServerTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "Server.getName() returns non-null", category = TestCategory.SERVER)
public void testGetName() {
String name = Bukkit.getServer().getName();
assertNotNull(name, "Server.getName()");
}
@ConformanceTest(name = "Server.getVersion() returns non-null", category = TestCategory.SERVER)
public void testGetVersion() {
String version = Bukkit.getServer().getVersion();
assertNotNull(version, "Server.getVersion()");
}
@ConformanceTest(name = "Server.getBukkitVersion() returns non-null", category = TestCategory.SERVER)
public void testGetBukkitVersion() {
String version = Bukkit.getServer().getBukkitVersion();
assertNotNull(version, "Server.getBukkitVersion()");
}
@ConformanceTest(name = "Server.getPluginManager() returns non-null", category = TestCategory.SERVER)
public void testGetPluginManager() {
PluginManager pm = Bukkit.getServer().getPluginManager();
assertNotNull(pm, "Server.getPluginManager()");
}
@ConformanceTest(name = "Server.getConsoleSender() returns non-null", category = TestCategory.SERVER)
public void testGetConsoleSender() {
ConsoleCommandSender cs = Bukkit.getServer().getConsoleSender();
assertNotNull(cs, "Server.getConsoleSender()");
}
@ConformanceTest(name = "Server.getCommandMap() returns non-null", category = TestCategory.SERVER)
public void testGetCommandMap() {
CommandMap cm = Bukkit.getServer().getCommandMap();
assertNotNull(cm, "Server.getCommandMap()");
}
@ConformanceTest(name = "Server.getScheduler() returns non-null", category = TestCategory.SERVER)
public void testGetScheduler() {
BukkitScheduler sched = Bukkit.getServer().getScheduler();
assertNotNull(sched, "Server.getScheduler()");
}
@ConformanceTest(name = "Server.getOnlinePlayers() returns collection", category = TestCategory.SERVER)
public void testGetOnlinePlayers() {
var players = Bukkit.getServer().getOnlinePlayers();
assertNotNull(players, "Server.getOnlinePlayers()");
}
@ConformanceTest(name = "Server.getUnsafe() returns non-null", category = TestCategory.SERVER)
public void testGetUnsafe() {
Object unsafe = Bukkit.getUnsafe();
assertNotNull(unsafe, "Server.getUnsafe()");
}
@ConformanceTest(name = "Server.suggestPlayerNamesWhenNullTabCompletions() returns boolean", category = TestCategory.SERVER)
public void testSuggestPlayerNames() {
// Should return true according to implementation
boolean result = Bukkit.getServer().suggestPlayerNamesWhenNullTabCompletions();
assertTrue(result, "Server.suggestPlayerNamesWhenNullTabCompletions()");
}
@ConformanceTest(name = "Bukkit.getServer() matches plugin.getServer()", category = TestCategory.SERVER)
public void testServerConsistency() {
Server bukkit = Bukkit.getServer();
Server fromPlugin = plugin.getServer();
assertTrue(bukkit == fromPlugin, "Bukkit.getServer() == plugin.getServer()");
}
@ConformanceTest(name = "Server.getPluginCommand() returns null for unregistered command", category = TestCategory.SERVER)
public void testGetPluginCommand() {
// getPluginCommand only finds PluginCommands registered via plugin.yml;
// commands registered directly on the CommandMap are not PluginCommands
var cmd = Bukkit.getServer().getPluginCommand("nonexistent_abc");
assertTrue(cmd == null, "Server.getPluginCommand(unregistered) should be null");
}
@ConformanceTest(name = "Server.getPluginCommand() returns null for unknown", category = TestCategory.SERVER)
public void testGetPluginCommandUnknown() {
var cmd = Bukkit.getServer().getPluginCommand("nonexistent_command_xyz");
assertTrue(cmd == null, "Server.getPluginCommand(unknown) should be null");
}
private static void assertNotNull(Object value, String what) {
if (value == null) {
throw new AssertionError(what + " returned null");
}
}
private static void assertTrue(boolean condition, String what) {
if (!condition) {
throw new AssertionError(what);
}
}
}

View File

@ -0,0 +1,134 @@
package org.patchbukkit.testplugin.tests;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.patchbukkit.testplugin.TestExpectation;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.UUID;
public final class StubTests {
private final JavaPlugin plugin;
public StubTests(JavaPlugin plugin) {
this.plugin = plugin;
}
// --- Server stubs that should throw UnsupportedOperationException ---
@ConformanceTest(name = "Server.getWorlds() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetWorlds() {
Bukkit.getServer().getWorlds();
}
@ConformanceTest(name = "Server.getMaxPlayers() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetMaxPlayers() {
Bukkit.getServer().getMaxPlayers();
}
@ConformanceTest(name = "Server.getPort() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetPort() {
Bukkit.getServer().getPort();
}
@ConformanceTest(name = "Server.getIp() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetIp() {
Bukkit.getServer().getIp();
}
@ConformanceTest(name = "Server.getViewDistance() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetViewDistance() {
Bukkit.getServer().getViewDistance();
}
@ConformanceTest(name = "Server.getSimulationDistance() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetSimulationDistance() {
Bukkit.getServer().getSimulationDistance();
}
@ConformanceTest(name = "Server.getUpdateFolder() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetUpdateFolder() {
Bukkit.getServer().getUpdateFolder();
}
@ConformanceTest(name = "Server.getUpdateFolderFile() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetUpdateFolderFile() {
Bukkit.getServer().getUpdateFolderFile();
}
@ConformanceTest(name = "Server.getConnectionThrottle() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetConnectionThrottle() {
Bukkit.getServer().getConnectionThrottle();
}
@ConformanceTest(name = "Server.broadcastMessage() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
@SuppressWarnings("deprecation")
public void testBroadcastMessage() {
Bukkit.getServer().broadcastMessage("test");
}
@ConformanceTest(name = "Server.getOfflinePlayer(UUID) throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetOfflinePlayer() {
Bukkit.getServer().getOfflinePlayer(UUID.randomUUID());
}
@ConformanceTest(name = "Server.getBanList() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetBanList() {
Bukkit.getServer().getBanList(org.bukkit.BanList.Type.NAME);
}
@ConformanceTest(name = "Server.getOperators() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetOperators() {
Bukkit.getServer().getOperators();
}
@ConformanceTest(name = "Server.getWhitelistedPlayers() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetWhitelistedPlayers() {
Bukkit.getServer().getWhitelistedPlayers();
}
@ConformanceTest(name = "Server.reloadWhitelist() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testReloadWhitelist() {
Bukkit.getServer().reloadWhitelist();
}
@ConformanceTest(name = "Server.shutdown() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testShutdown() {
Bukkit.getServer().shutdown();
}
@ConformanceTest(name = "Server.getMotd() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetMotd() {
Bukkit.getServer().getMotd();
}
@ConformanceTest(name = "Server.getAllowNether() throws UnsupportedOperationException",
category = TestCategory.STUBS, expectation = TestExpectation.EXPECT_UNSUPPORTED)
public void testGetAllowNether() {
Bukkit.getServer().getAllowNether();
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
}

View File

@ -0,0 +1,61 @@
package org.patchbukkit.testplugin.tests;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import org.bukkit.Bukkit;
import org.bukkit.UnsafeValues;
import org.patchbukkit.testplugin.ConformanceTest;
import org.patchbukkit.testplugin.TestCategory;
import org.bukkit.plugin.java.JavaPlugin;
@SuppressWarnings("deprecation")
public final class UnsafeValuesTests {
private final JavaPlugin plugin;
public UnsafeValuesTests(JavaPlugin plugin) {
this.plugin = plugin;
}
@ConformanceTest(name = "UnsafeValues.getDataVersion() returns positive int", category = TestCategory.UNSAFE_VALUES)
public void testGetDataVersion() {
UnsafeValues unsafe = Bukkit.getUnsafe();
int version = unsafe.getDataVersion();
assertTrue(version > 0, "getDataVersion() should be > 0, got " + version);
}
@ConformanceTest(name = "UnsafeValues.isSupportedApiVersion(\"1.21\") returns true", category = TestCategory.UNSAFE_VALUES)
public void testIsSupportedApiVersion121() {
UnsafeValues unsafe = Bukkit.getUnsafe();
boolean supported = unsafe.isSupportedApiVersion("1.21");
assertTrue(supported, "isSupportedApiVersion(\"1.21\") should be true");
}
@ConformanceTest(name = "UnsafeValues.isSupportedApiVersion(\"1.20\") returns true", category = TestCategory.UNSAFE_VALUES)
public void testIsSupportedApiVersion120() {
UnsafeValues unsafe = Bukkit.getUnsafe();
boolean supported = unsafe.isSupportedApiVersion("1.20");
assertTrue(supported, "isSupportedApiVersion(\"1.20\") should be true");
}
@ConformanceTest(name = "UnsafeValues.isSupportedApiVersion(\"1.0\") returns false", category = TestCategory.UNSAFE_VALUES)
public void testIsSupportedApiVersionOld() {
UnsafeValues unsafe = Bukkit.getUnsafe();
boolean supported = unsafe.isSupportedApiVersion("1.0");
assertTrue(!supported, "isSupportedApiVersion(\"1.0\") should be false");
}
@ConformanceTest(name = "UnsafeValues.componentFlattener() returns non-null", category = TestCategory.UNSAFE_VALUES)
public void testComponentFlattener() {
UnsafeValues unsafe = Bukkit.getUnsafe();
ComponentFlattener flattener = unsafe.componentFlattener();
assertNotNull(flattener, "UnsafeValues.componentFlattener()");
}
private static void assertNotNull(Object value, String what) {
if (value == null) throw new AssertionError(what + " returned null");
}
private static void assertTrue(boolean condition, String what) {
if (!condition) throw new AssertionError(what);
}
}

View File

@ -0,0 +1,9 @@
name: PatchBukkitTest
version: "1.0.0"
main: org.patchbukkit.testplugin.PatchBukkitTestPlugin
api-version: "1.21"
description: Conformance test plugin for PatchBukkit API surface
commands:
pbtest:
description: Run PatchBukkit conformance tests
usage: /pbtest [category|all]

View File

@ -13,3 +13,4 @@ plugins {
rootProject.name = "patchbukkit"
include("patchbukkit")
include("protoc-gen-ffi")
include("patchbukkit-test-plugin")

View File

@ -198,14 +198,16 @@ impl CommandManager {
// };
let permission = format!("patchbukkit:{cmd_name}");
context
if let Err(e) = context
.register_permission(Permission::new(
&permission,
&permission,
PermissionDefault::Allow,
))
.await
.unwrap();
{
log::warn!("Failed to register permission for command {}: {:?}", cmd_name, e);
}
context.register_command(node, permission).await;