mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-23 07:10:51 +00:00
dd
This commit is contained in:
@@ -16,6 +16,7 @@ String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
|
||||
dependencies {
|
||||
implementation(project(':core'))
|
||||
compileOnly(libs.spigot)
|
||||
testImplementation('junit:junit:4.13.2')
|
||||
compileOnly(volmLibCoordinate) {
|
||||
transitive = false
|
||||
}
|
||||
@@ -44,6 +45,10 @@ tasks.named('run', JavaExec).configure {
|
||||
dependsOn(':core:compileJava')
|
||||
}
|
||||
|
||||
tasks.named('test').configure {
|
||||
dependsOn(':core:compileJava')
|
||||
}
|
||||
|
||||
String probePack = providers.gradleProperty('probePack')
|
||||
.orElse('/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/instances/purpur-26.2/plugins/Iris/packs/overworld')
|
||||
.get()
|
||||
|
||||
@@ -18,15 +18,23 @@
|
||||
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ClassloadProbe {
|
||||
private static final String ALLOWLIST_RESOURCE = "/classload-allowlist.tsv";
|
||||
|
||||
private static final String[] CRITICAL_PREFIXES = {
|
||||
"art.arcane.iris.engine.",
|
||||
"art.arcane.iris.util.",
|
||||
@@ -34,26 +42,6 @@ public final class ClassloadProbe {
|
||||
"art.arcane.iris.spi.",
|
||||
};
|
||||
|
||||
private static final String[] EXEMPT_PREFIXES = {
|
||||
"art.arcane.iris.platform.bukkit.",
|
||||
"art.arcane.iris.engine.platform.",
|
||||
"art.arcane.iris.util.common.plugin.",
|
||||
"art.arcane.iris.util.common.inventorygui.",
|
||||
"art.arcane.iris.util.common.director.",
|
||||
"art.arcane.iris.util.common.data.registry.",
|
||||
};
|
||||
|
||||
private static final String[] EXEMPT_CLASSES = {
|
||||
"art.arcane.iris.util.common.misc.Bindings",
|
||||
"art.arcane.iris.util.common.misc.SlimJar",
|
||||
"art.arcane.iris.util.common.misc.ServerProperties",
|
||||
"art.arcane.iris.util.common.data.IrisCustomData",
|
||||
"art.arcane.iris.engine.IrisWorldManager",
|
||||
"art.arcane.iris.engine.framework.EngineAssignedWorldManager",
|
||||
"art.arcane.iris.engine.object.StudioMode",
|
||||
"art.arcane.iris.engine.framework.placer.WorldObjectPlacer",
|
||||
};
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
art.arcane.iris.spi.IrisPlatforms.bind(new StubPlatform());
|
||||
boolean bukkitPresent = true;
|
||||
@@ -63,39 +51,42 @@ public final class ClassloadProbe {
|
||||
bukkitPresent = false;
|
||||
}
|
||||
System.out.println("[probe] org.bukkit on classpath: " + bukkitPresent);
|
||||
|
||||
Path classesRoot = Path.of(args[0]);
|
||||
List<String> names = new ArrayList<>();
|
||||
try (Stream<Path> walk = Files.walk(classesRoot)) {
|
||||
walk.filter(p -> p.toString().endsWith(".class"))
|
||||
.forEach(p -> {
|
||||
String rel = classesRoot.relativize(p).toString();
|
||||
String name = rel.substring(0, rel.length() - 6).replace('/', '.');
|
||||
if (name.contains("$")) {
|
||||
return;
|
||||
}
|
||||
names.add(name);
|
||||
});
|
||||
if (bukkitPresent) {
|
||||
System.out.println("[probe] RESULT: FAIL - org.bukkit must be absent from the classload probe runtime");
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
names.sort(String::compareTo);
|
||||
|
||||
TreeMap<String, String> criticalFailures = new TreeMap<>();
|
||||
TreeMap<String, String> otherFailures = new TreeMap<>();
|
||||
TreeMap<String, Allowance> allowlist = loadAllowlist();
|
||||
Path classesRoot = Path.of(args[0]);
|
||||
List<String> names = scanClassNames(classesRoot);
|
||||
|
||||
TreeMap<String, Failure> criticalFailures = new TreeMap<>();
|
||||
TreeMap<String, Failure> otherFailures = new TreeMap<>();
|
||||
int loaded = 0;
|
||||
int nestedTotal = 0;
|
||||
int nestedLoaded = 0;
|
||||
int criticalTotal = 0;
|
||||
for (String name : names) {
|
||||
boolean critical = matchesAny(name, CRITICAL_PREFIXES) && !matchesAny(name, EXEMPT_PREFIXES) && !exactAny(name);
|
||||
boolean nested = name.contains("$");
|
||||
if (nested) {
|
||||
nestedTotal++;
|
||||
}
|
||||
boolean critical = matchesAny(name, CRITICAL_PREFIXES) && !allowlist.containsKey(name);
|
||||
if (critical) {
|
||||
criticalTotal++;
|
||||
}
|
||||
try {
|
||||
Class.forName(name, true, ClassloadProbe.class.getClassLoader());
|
||||
loaded++;
|
||||
if (nested) {
|
||||
nestedLoaded++;
|
||||
}
|
||||
} catch (Throwable failure) {
|
||||
if (System.getenv("PROBE_TRACE") != null && name.contains(System.getenv("PROBE_TRACE"))) {
|
||||
failure.printStackTrace(System.out);
|
||||
}
|
||||
String cause = rootCause(failure);
|
||||
Failure cause = Failure.from(failure);
|
||||
if (critical) {
|
||||
criticalFailures.put(name, cause);
|
||||
} else {
|
||||
@@ -105,25 +96,56 @@ public final class ClassloadProbe {
|
||||
}
|
||||
|
||||
System.out.println("[probe] classes scanned: " + names.size() + ", initialized OK: " + loaded);
|
||||
System.out.println("[probe] nested classes scanned: " + nestedTotal + ", initialized OK: " + nestedLoaded
|
||||
+ ", initialization failures: " + (nestedTotal - nestedLoaded));
|
||||
System.out.println("[probe] critical set: " + criticalTotal + ", critical failures: " + criticalFailures.size());
|
||||
criticalFailures.forEach((name, cause) -> System.out.println(" CRITICAL " + name + " -> " + cause));
|
||||
criticalFailures.forEach((name, cause) -> System.out.println(" CRITICAL " + name + " -> " + cause.display()));
|
||||
System.out.println("[probe] non-critical failures: " + otherFailures.size());
|
||||
otherFailures.forEach((name, cause) -> System.out.println(" other " + name + " -> " + cause));
|
||||
Review review = review(otherFailures, allowlist);
|
||||
System.out.println("[probe] reviewed allowances: " + allowlist.size() + ", matched failures: " + review.matched());
|
||||
otherFailures.forEach((name, cause) -> {
|
||||
Allowance allowance = allowlist.get(name);
|
||||
String status = allowance != null && allowance.matches(cause) ? "allowed " + allowance.category() : "UNEXPECTED";
|
||||
System.out.println(" " + status + " " + name + " -> " + cause.display());
|
||||
});
|
||||
System.out.println("[probe] unexpected non-critical failures: " + review.unexpected().size());
|
||||
System.out.println("[probe] stale allowances: " + review.staleAllowances().size());
|
||||
review.staleAllowances().forEach((name, allowance) -> System.out.println(" STALE " + name + " -> " + allowance));
|
||||
|
||||
if (!criticalFailures.isEmpty()) {
|
||||
if (!criticalFailures.isEmpty() || !review.passes()) {
|
||||
System.out.println("[probe] RESULT: FAIL");
|
||||
System.exit(1);
|
||||
}
|
||||
System.out.println("[probe] RESULT: PASS");
|
||||
}
|
||||
|
||||
private static boolean exactAny(String name) {
|
||||
for (String exempt : EXEMPT_CLASSES) {
|
||||
if (name.equals(exempt)) {
|
||||
return true;
|
||||
static Review review(Map<String, Failure> failures, Map<String, Allowance> allowlist) {
|
||||
TreeMap<String, Failure> unexpected = new TreeMap<>();
|
||||
TreeMap<String, Allowance> staleAllowances = new TreeMap<>(allowlist);
|
||||
int matched = 0;
|
||||
for (Map.Entry<String, Failure> entry : failures.entrySet()) {
|
||||
Allowance allowance = allowlist.get(entry.getKey());
|
||||
staleAllowances.remove(entry.getKey());
|
||||
if (allowance == null || !allowance.matches(entry.getValue())) {
|
||||
unexpected.put(entry.getKey(), entry.getValue());
|
||||
} else {
|
||||
matched++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return new Review(matched, unexpected, staleAllowances);
|
||||
}
|
||||
|
||||
static List<String> scanClassNames(Path classesRoot) throws IOException {
|
||||
List<String> names = new ArrayList<>();
|
||||
try (Stream<Path> walk = Files.walk(classesRoot)) {
|
||||
walk.filter(path -> path.toString().endsWith(".class"))
|
||||
.forEach(path -> {
|
||||
String relative = classesRoot.relativize(path).toString();
|
||||
names.add(relative.substring(0, relative.length() - 6).replace(File.separatorChar, '.'));
|
||||
});
|
||||
}
|
||||
names.sort(String::compareTo);
|
||||
return names;
|
||||
}
|
||||
|
||||
private static boolean matchesAny(String name, String[] prefixes) {
|
||||
@@ -135,11 +157,91 @@ public final class ClassloadProbe {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String rootCause(Throwable failure) {
|
||||
Throwable cause = failure;
|
||||
while (cause.getCause() != null && cause.getCause() != cause) {
|
||||
cause = cause.getCause();
|
||||
private static TreeMap<String, Allowance> loadAllowlist() throws IOException {
|
||||
InputStream resource = ClassloadProbe.class.getResourceAsStream(ALLOWLIST_RESOURCE);
|
||||
if (resource == null) {
|
||||
throw new IOException("Classload allowlist resource not found: " + ALLOWLIST_RESOURCE);
|
||||
}
|
||||
|
||||
TreeMap<String, Allowance> allowlist = new TreeMap<>();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
int lineNumber = 0;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lineNumber++;
|
||||
if (line.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
String[] fields = line.split("\\t", -1);
|
||||
if (fields.length != 3 || fields[0].isBlank() || fields[2].isBlank()) {
|
||||
throw new IOException("Invalid classload allowlist entry at line " + lineNumber);
|
||||
}
|
||||
Allowance allowance;
|
||||
try {
|
||||
allowance = new Allowance(AllowanceCategory.valueOf(fields[1]), fields[2]);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new IOException("Invalid classload allowlist category at line " + lineNumber + ": " + fields[1], exception);
|
||||
}
|
||||
if (allowlist.put(fields[0], allowance) != null) {
|
||||
throw new IOException("Duplicate classload allowlist class at line " + lineNumber + ": " + fields[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allowlist;
|
||||
}
|
||||
|
||||
enum AllowanceCategory {
|
||||
BUKKIT_API,
|
||||
PAPERLIB_API,
|
||||
ADVENTURE_API,
|
||||
MYTHICMOBS_API,
|
||||
SERVER_RUNTIME_FILE
|
||||
}
|
||||
|
||||
record Allowance(AllowanceCategory category, String detail) {
|
||||
boolean matches(Failure failure) {
|
||||
return switch (category) {
|
||||
case BUKKIT_API -> detail.startsWith("org.bukkit.") && failure.isMissingClassIn("org.bukkit.");
|
||||
case PAPERLIB_API -> detail.startsWith("io.papermc.lib.") && failure.isMissingClassIn("io.papermc.lib.");
|
||||
case ADVENTURE_API -> detail.startsWith("net.kyori.adventure.") && failure.isMissingClassIn("net.kyori.adventure.");
|
||||
case MYTHICMOBS_API -> detail.startsWith("io.lumine.mythic.") && failure.isMissingClassIn("io.lumine.mythic.");
|
||||
case SERVER_RUNTIME_FILE -> failure.exceptionClass().equals("java.io.FileNotFoundException")
|
||||
&& failure.message().startsWith(detail);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
record Failure(String exceptionClass, String message) {
|
||||
static Failure from(Throwable failure) {
|
||||
Throwable cause = failure;
|
||||
while (cause.getCause() != null && cause.getCause() != cause) {
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return new Failure(cause.getClass().getName(), String.valueOf(cause.getMessage()));
|
||||
}
|
||||
|
||||
boolean isMissingClassIn(String packagePrefix) {
|
||||
if (exceptionClass.equals("java.lang.ClassNotFoundException")) {
|
||||
return message.startsWith(packagePrefix);
|
||||
}
|
||||
String internalPrefix = packagePrefix.replace('.', '/');
|
||||
if (exceptionClass.equals("java.lang.NoClassDefFoundError")) {
|
||||
return message.startsWith(internalPrefix);
|
||||
}
|
||||
return exceptionClass.equals("java.lang.ExceptionInInitializerError")
|
||||
&& message.contains("NoClassDefFoundError: " + internalPrefix);
|
||||
}
|
||||
|
||||
String display() {
|
||||
int separator = exceptionClass.lastIndexOf('.');
|
||||
String simpleName = separator < 0 ? exceptionClass : exceptionClass.substring(separator + 1);
|
||||
return simpleName + ": " + message;
|
||||
}
|
||||
}
|
||||
|
||||
record Review(int matched, TreeMap<String, Failure> unexpected, TreeMap<String, Allowance> staleAllowances) {
|
||||
boolean passes() {
|
||||
return unexpected.isEmpty() && staleAllowances.isEmpty();
|
||||
}
|
||||
return cause.getClass().getSimpleName() + ": " + cause.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
art.arcane.iris.core.IrisWorlds BUKKIT_API org.bukkit.command.CommandSender
|
||||
art.arcane.iris.core.ServerConfigurator BUKKIT_API org.bukkit.command.CommandSender
|
||||
art.arcane.iris.core.edit.BlockSignal BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.core.edit.BlockSignal$1 BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.core.edit.DustRevealer ADVENTURE_API net.kyori.adventure.text.format.TextColor
|
||||
art.arcane.iris.core.events.IrisEngineEvent BUKKIT_API org.bukkit.event.Event
|
||||
art.arcane.iris.core.events.IrisEngineHotloadEvent BUKKIT_API org.bukkit.event.Event
|
||||
art.arcane.iris.core.events.IrisLootEvent BUKKIT_API org.bukkit.event.Event
|
||||
art.arcane.iris.core.events.IrisLootEvent$1 BUKKIT_API org.bukkit.loot.LootTable
|
||||
art.arcane.iris.core.events.IrisLootEvent$2 BUKKIT_API org.bukkit.inventory.InventoryHolder
|
||||
art.arcane.iris.core.lifecycle.PaperLibBootstrap PAPERLIB_API io.papermc.lib.environments.Environment
|
||||
art.arcane.iris.core.lifecycle.PaperLibBootstrap$ModernPaperEnvironment PAPERLIB_API io.papermc.lib.environments.PaperEnvironment
|
||||
art.arcane.iris.core.link.ExternalDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.CraftEngineDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.EcoItemsDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.ExecutableItemsDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.HMCLeavesDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.ItemAdderDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.KGeneratorsDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.MMOItemsDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.MythicCrucibleDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.MythicMobsDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.link.data.MythicMobsDataProvider$IrisBiomeCondition MYTHICMOBS_API io.lumine.mythic.api.skills.conditions.ILocationCondition
|
||||
art.arcane.iris.core.link.data.MythicMobsDataProvider$IrisRegionCondition MYTHICMOBS_API io.lumine.mythic.api.skills.conditions.ILocationCondition
|
||||
art.arcane.iris.core.link.data.NexoDataProvider BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.nms.INMS BUKKIT_API org.bukkit.Bukkit
|
||||
art.arcane.iris.core.project.IrisProject BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.core.runtime.WorldRuntimeControlService BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.core.service.BoardSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.BoardSVC$PlayerBoard BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.core.service.ExternalDataSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.GlobalCacheSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.LogFilterSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.ObjectSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.ObjectStudioSaveService BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.PreservationSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.StudioSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.service.TreeSVC BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.core.tools.IrisToolbelt BUKKIT_API org.bukkit.command.CommandSender
|
||||
art.arcane.iris.core.tools.IrisWorldCreator BUKKIT_API org.bukkit.generator.ChunkGenerator
|
||||
art.arcane.iris.core.tools.TreePlausibilizer BUKKIT_API org.bukkit.Keyed
|
||||
art.arcane.iris.engine.IrisWorldManager BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.engine.framework.EngineAssignedWorldManager BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.engine.framework.placer.WorldObjectPlacer BUKKIT_API org.bukkit.event.Event
|
||||
art.arcane.iris.engine.object.IrisDirection$11 BUKKIT_API org.bukkit.block.BlockFace
|
||||
art.arcane.iris.engine.object.IrisEffect$BukkitFx BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.engine.object.IrisEntity$BukkitOps BUKKIT_API org.bukkit.loot.LootTable
|
||||
art.arcane.iris.engine.object.IrisEntity$BukkitOps$1 BUKKIT_API org.bukkit.loot.LootTable
|
||||
art.arcane.iris.engine.object.IrisObjectRotation$1 BUKKIT_API org.bukkit.block.BlockFace
|
||||
art.arcane.iris.engine.object.IrisObjectRotation$Faces BUKKIT_API org.bukkit.block.BlockFace
|
||||
art.arcane.iris.engine.object.LegacyTileData$BannerHandler BUKKIT_API org.bukkit.Keyed
|
||||
art.arcane.iris.engine.object.LegacyTileData$Handlers BUKKIT_API org.bukkit.block.BlockState
|
||||
art.arcane.iris.engine.object.LegacyTileData$SignHandler BUKKIT_API org.bukkit.Keyed
|
||||
art.arcane.iris.engine.object.LegacyTileData$SignTags BUKKIT_API org.bukkit.Tag
|
||||
art.arcane.iris.engine.object.LegacyTileData$SignTags$1 BUKKIT_API org.bukkit.Tag
|
||||
art.arcane.iris.engine.platform.BukkitChunkGenerator BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.engine.platform.BukkitChunkGenerator$2 BUKKIT_API org.bukkit.HeightMap
|
||||
art.arcane.iris.engine.platform.DummyBiomeProvider BUKKIT_API org.bukkit.generator.BiomeProvider
|
||||
art.arcane.iris.engine.platform.DummyChunkGenerator BUKKIT_API org.bukkit.generator.ChunkGenerator
|
||||
art.arcane.iris.engine.platform.EngineBukkitOps BUKKIT_API org.bukkit.entity.Entity
|
||||
art.arcane.iris.engine.platform.studio.generators.ObjectStudioGenerator BUKKIT_API org.bukkit.Material
|
||||
art.arcane.iris.platform.bukkit.BukkitBlockResolution BUKKIT_API org.bukkit.Material
|
||||
art.arcane.iris.platform.bukkit.BukkitPlatform BUKKIT_API org.bukkit.command.CommandSender
|
||||
art.arcane.iris.util.common.data.IrisCustomData BUKKIT_API org.bukkit.block.data.BlockData
|
||||
art.arcane.iris.util.common.data.registry.Attributes BUKKIT_API org.bukkit.attribute.Attribute
|
||||
art.arcane.iris.util.common.data.registry.Materials BUKKIT_API org.bukkit.Material
|
||||
art.arcane.iris.util.common.data.registry.Particles BUKKIT_API org.bukkit.Particle
|
||||
art.arcane.iris.util.common.director.handlers.VectorHandler BUKKIT_API org.bukkit.util.Vector
|
||||
art.arcane.iris.util.common.format.C$DyeMaps BUKKIT_API org.bukkit.DyeColor
|
||||
art.arcane.iris.util.common.inventorygui.WindowResolution BUKKIT_API org.bukkit.event.inventory.InventoryType
|
||||
art.arcane.iris.util.common.misc.Bindings BUKKIT_API org.bukkit.plugin.Plugin
|
||||
art.arcane.iris.util.common.misc.ServerProperties SERVER_RUNTIME_FILE server.properties
|
||||
art.arcane.iris.util.common.misc.SlimJar BUKKIT_API org.bukkit.plugin.Plugin
|
||||
art.arcane.iris.util.common.plugin.CommandDummy BUKKIT_API org.bukkit.command.CommandSender
|
||||
art.arcane.iris.util.common.plugin.Controller BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.util.common.plugin.IController BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.util.common.plugin.IrisService BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.util.common.plugin.RouterCommand BUKKIT_API org.bukkit.command.Command
|
||||
art.arcane.iris.util.common.plugin.VolmitPlugin BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.util.common.plugin.VolmitSender BUKKIT_API org.bukkit.command.CommandSender
|
||||
art.arcane.iris.util.common.plugin.chunk.ChunkTickets BUKKIT_API org.bukkit.event.Listener
|
||||
art.arcane.iris.util.nbt.common.mca.NBTWorld$Holder BUKKIT_API org.bukkit.Material
|
||||
art.arcane.iris.util.project.sentry.ServerID$Holder BUKKIT_API org.bukkit.Bukkit
|
||||
|
@@ -0,0 +1,204 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class ClassloadProbeTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void scansNamedAndGeneratedNestedClasses() throws Exception {
|
||||
Path classesRoot = temporaryFolder.newFolder("classes").toPath();
|
||||
Path packageRoot = Files.createDirectories(classesRoot.resolve("art/arcane/iris"));
|
||||
Files.createFile(packageRoot.resolve("Outer.class"));
|
||||
Files.createFile(packageRoot.resolve("Outer$Nested.class"));
|
||||
Files.createFile(packageRoot.resolve("Outer$1.class"));
|
||||
Files.createFile(packageRoot.resolve("ignored.txt"));
|
||||
|
||||
List<String> names = ClassloadProbe.scanClassNames(classesRoot);
|
||||
|
||||
assertEquals(List.of(
|
||||
"art.arcane.iris.Outer",
|
||||
"art.arcane.iris.Outer$1",
|
||||
"art.arcane.iris.Outer$Nested"
|
||||
), names);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scansEmptyClassDirectory() throws Exception {
|
||||
Path classesRoot = temporaryFolder.newFolder("empty-classes").toPath();
|
||||
|
||||
assertTrue(ClassloadProbe.scanClassNames(classesRoot).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsMatchingReviewedFailure() {
|
||||
ClassloadProbe.Failure failure = new ClassloadProbe.Failure(
|
||||
"java.lang.ClassNotFoundException",
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
|
||||
ClassloadProbe.Review review = ClassloadProbe.review(
|
||||
Map.of("art.arcane.iris.Allowed", failure),
|
||||
Map.of("art.arcane.iris.Allowed", allowance)
|
||||
);
|
||||
|
||||
assertTrue(review.passes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsUnlistedFailure() {
|
||||
ClassloadProbe.Failure failure = new ClassloadProbe.Failure(
|
||||
"java.lang.ClassNotFoundException",
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
|
||||
ClassloadProbe.Review review = ClassloadProbe.review(
|
||||
Map.of("art.arcane.iris.Unlisted", failure),
|
||||
Map.of()
|
||||
);
|
||||
|
||||
assertFalse(review.passes());
|
||||
assertTrue(review.unexpected().containsKey("art.arcane.iris.Unlisted"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsDifferentMissingClassInReviewedNamespace() {
|
||||
ClassloadProbe.Failure changedFailure = new ClassloadProbe.Failure(
|
||||
"java.lang.ClassNotFoundException",
|
||||
"org.bukkit.Material"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
|
||||
ClassloadProbe.Review review = ClassloadProbe.review(
|
||||
Map.of("art.arcane.iris.Changed", changedFailure),
|
||||
Map.of("art.arcane.iris.Changed", allowance)
|
||||
);
|
||||
|
||||
assertTrue(review.passes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsChangedFailureNamespace() {
|
||||
ClassloadProbe.Failure changedFailure = new ClassloadProbe.Failure(
|
||||
"java.lang.ClassNotFoundException",
|
||||
"io.papermc.lib.environments.Environment"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
|
||||
ClassloadProbe.Review review = ClassloadProbe.review(
|
||||
Map.of("art.arcane.iris.Changed", changedFailure),
|
||||
Map.of("art.arcane.iris.Changed", allowance)
|
||||
);
|
||||
|
||||
assertFalse(review.passes());
|
||||
assertTrue(review.unexpected().containsKey("art.arcane.iris.Changed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsNonMissingClassFailure() {
|
||||
ClassloadProbe.Failure changedFailure = new ClassloadProbe.Failure(
|
||||
"java.lang.IllegalStateException",
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
|
||||
assertFalse(allowance.matches(changedFailure));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsBukkitFailureCachedByClassInitialization() {
|
||||
ClassloadProbe.Failure failure = new ClassloadProbe.Failure(
|
||||
"java.lang.ExceptionInInitializerError",
|
||||
"Exception java.lang.NoClassDefFoundError: org/bukkit/Material [in thread \"main\"]"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.Material"
|
||||
);
|
||||
|
||||
assertTrue(allowance.matches(failure));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsDifferentBukkitFailureCachedByClassInitialization() {
|
||||
ClassloadProbe.Failure failure = new ClassloadProbe.Failure(
|
||||
"java.lang.ExceptionInInitializerError",
|
||||
"Exception java.lang.NoClassDefFoundError: org/bukkit/World [in thread \"main\"]"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.Material"
|
||||
);
|
||||
|
||||
assertTrue(allowance.matches(failure));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsDifferentNamespaceCachedByClassInitialization() {
|
||||
ClassloadProbe.Failure failure = new ClassloadProbe.Failure(
|
||||
"java.lang.ExceptionInInitializerError",
|
||||
"Exception java.lang.NoClassDefFoundError: io/papermc/lib/environments/Environment [in thread \"main\"]"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.Material"
|
||||
);
|
||||
|
||||
assertFalse(allowance.matches(failure));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsMythicMobsApiNamespace() {
|
||||
ClassloadProbe.Failure failure = new ClassloadProbe.Failure(
|
||||
"java.lang.ClassNotFoundException",
|
||||
"io.lumine.mythic.api.skills.conditions.ILocationCondition"
|
||||
);
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.MYTHICMOBS_API,
|
||||
"io.lumine.mythic.api.skills.conditions.ILocationCondition"
|
||||
);
|
||||
|
||||
assertTrue(allowance.matches(failure));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsStaleAllowance() {
|
||||
ClassloadProbe.Allowance allowance = new ClassloadProbe.Allowance(
|
||||
ClassloadProbe.AllowanceCategory.BUKKIT_API,
|
||||
"org.bukkit.event.Listener"
|
||||
);
|
||||
TreeMap<String, ClassloadProbe.Allowance> allowlist = new TreeMap<>();
|
||||
allowlist.put("art.arcane.iris.Stale", allowance);
|
||||
|
||||
ClassloadProbe.Review review = ClassloadProbe.review(Map.of(), allowlist);
|
||||
|
||||
assertFalse(review.passes());
|
||||
assertTrue(review.staleAllowances().containsKey("art.arcane.iris.Stale"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user