mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
fix
This commit is contained in:
@@ -15,6 +15,7 @@ dependencies {
|
||||
}
|
||||
compileOnly(libs.paper.api)
|
||||
testImplementation('junit:junit:4.13.2')
|
||||
testImplementation('org.mockito:mockito-core:5.23.0')
|
||||
testImplementation(libs.paper.api)
|
||||
testImplementation(libs.bstats)
|
||||
testImplementation(libs.sentry)
|
||||
@@ -66,5 +67,6 @@ tasks.named('test').configure {
|
||||
systemProperty('iris.terrainSvcSource', file('src/main/java/art/arcane/iris/core/service/IrisTerrainSVC.java').absolutePath)
|
||||
systemProperty('iris.apiEventSvcSource', file('src/main/java/art/arcane/iris/core/service/IrisApiEventSVC.java').absolutePath)
|
||||
systemProperty('iris.worldInfoFactorySource', file('src/main/java/art/arcane/iris/core/service/terrain/IrisWorldInfoFactory.java').absolutePath)
|
||||
systemProperty('iris.startupSource', file('src/main/java/art/arcane/iris/Iris.java').absolutePath)
|
||||
systemProperty('iris.readmeSource', rootProject.file('README.md').absolutePath)
|
||||
}
|
||||
|
||||
@@ -27,12 +27,15 @@ import art.arcane.iris.engine.framework.EnginePlatformHooks;
|
||||
import art.arcane.iris.engine.framework.EngineWorldManagerProvider;
|
||||
import art.arcane.iris.core.splash.IrisSplashComposer;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.IrisStartupValidation;
|
||||
import art.arcane.iris.core.IrisStartupAdmissionListener;
|
||||
import art.arcane.iris.core.BukkitWorldReconciler;
|
||||
import art.arcane.iris.core.IrisWorldGeneratorResolver;
|
||||
import art.arcane.iris.core.PendingWorldDeleteQueue;
|
||||
import art.arcane.iris.core.SettingsHotloadWatch;
|
||||
import art.arcane.iris.core.ServerConfigurator;
|
||||
import art.arcane.iris.core.datapack.DatapackIngestService;
|
||||
import art.arcane.iris.core.datapack.DatapackIngestService.StartupValidationOutcome;
|
||||
import art.arcane.iris.core.lifecycle.PaperLibBootstrap;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
||||
import art.arcane.iris.core.runtime.BukkitEnginePlatformHooks;
|
||||
@@ -558,7 +561,10 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
IrisServices.register(IrisCompat.class, compat);
|
||||
ServerConfigurator.configure();
|
||||
IrisToolbelt.applyPregenPerformanceProfile();
|
||||
generatorResolver.validateAllPacks();
|
||||
StartupValidationOutcome datapackValidation = DatapackIngestService.validateOnStartup();
|
||||
if (datapackValidation == StartupValidationOutcome.READY) {
|
||||
generatorResolver.validateAllPacks();
|
||||
}
|
||||
IrisSafeguard.execute();
|
||||
getSender().setTag(getTag());
|
||||
splash();
|
||||
@@ -593,7 +599,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
WorldLifecycleService.get();
|
||||
WorldRuntimeControlService.get();
|
||||
|
||||
if (J.isFolia()) {
|
||||
if (J.isFolia() && IrisStartupValidation.isReady()) {
|
||||
J.s(() -> worldReconciler.checkForBukkitWorlds(s -> true), 1);
|
||||
}
|
||||
|
||||
@@ -602,10 +608,11 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
J.ar(() -> settingsHotloadWatch.checkConfigHotload(configHotloadEngine), 60);
|
||||
J.sr(this::tickQueue, 0);
|
||||
J.s(this::setupPapi);
|
||||
J.a(DatapackIngestService::autoIngestOnStartup, 60);
|
||||
|
||||
autoStartStudio();
|
||||
if (!J.isFolia()) {
|
||||
if (IrisStartupValidation.isReady()) {
|
||||
J.a(DatapackIngestService::runPostStartupTasks, 60);
|
||||
autoStartStudio();
|
||||
}
|
||||
if (!J.isFolia() && IrisStartupValidation.isReady()) {
|
||||
worldReconciler.checkForBukkitWorlds(s -> true);
|
||||
}
|
||||
IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName());
|
||||
@@ -674,6 +681,8 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
|
||||
public void onEnable() {
|
||||
IrisPlatforms.bind(new BukkitPlatform());
|
||||
IrisStartupValidation.begin();
|
||||
Bukkit.getPluginManager().registerEvents(new IrisStartupAdmissionListener(), this);
|
||||
enable();
|
||||
BukkitGuiHost.install();
|
||||
super.onEnable();
|
||||
|
||||
@@ -22,6 +22,7 @@ import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.lifecycle.BukkitWorldConfiguration;
|
||||
import art.arcane.iris.core.lifecycle.LifecycleOperationCoordinator;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.platform.bukkit.BukkitEnvironment;
|
||||
@@ -204,6 +205,14 @@ public final class BukkitWorldReconciler {
|
||||
LifecycleOperationCoordinator.Lease lease
|
||||
) {
|
||||
|
||||
try {
|
||||
IrisStartupValidation.requireWorldCreationReady();
|
||||
backend.requireDimensionLoadable(worldKey, dimension);
|
||||
} catch (Throwable failure) {
|
||||
lease.close();
|
||||
return CompletableFuture.completedFuture(LoadResult.dimensionFailure(worldKey, failure));
|
||||
}
|
||||
|
||||
BukkitWorldConfiguration.Registration registration;
|
||||
String worldName = IrisWorldStorage.logicalName(worldKey);
|
||||
try {
|
||||
@@ -424,6 +433,8 @@ public final class BukkitWorldReconciler {
|
||||
boolean isIrisWorld(World world);
|
||||
|
||||
DimensionResolution resolveDimension(NamespacedKey worldKey);
|
||||
|
||||
void requireDimensionLoadable(NamespacedKey worldKey, String dimension);
|
||||
}
|
||||
|
||||
public enum ReconciliationStatus {
|
||||
@@ -727,5 +738,16 @@ public final class BukkitWorldReconciler {
|
||||
"Multiple dimension definitions were found without an exact registered dimension: "
|
||||
+ String.join(", ", dimensions)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireDimensionLoadable(NamespacedKey worldKey, String dimension) {
|
||||
String worldName = IrisWorldStorage.logicalName(worldKey);
|
||||
IrisDimension irisDimension = IrisWorldGeneratorResolver.loadDimension(worldName, dimension);
|
||||
if (irisDimension == null) {
|
||||
throw new IllegalStateException("Could not resolve the Iris dimension \"" + dimension + "\".");
|
||||
}
|
||||
PackValidationRegistry.requireLoadable(
|
||||
irisDimension.getLoader().getDataFolder().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
|
||||
public final class IrisStartupAdmissionListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
|
||||
IrisStartupValidation.denialReason().ifPresent(reason -> event.disallow(
|
||||
AsyncPlayerPreLoginEvent.Result.KICK_OTHER,
|
||||
Component.text(reason + " Check the server console, correct the reported Iris state, and restart.")));
|
||||
}
|
||||
}
|
||||
+78
-29
@@ -24,6 +24,7 @@ import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.pack.BrokenPackException;
|
||||
import art.arcane.iris.core.pack.PackDownloader;
|
||||
import art.arcane.iris.core.pack.PackDirectoryResolver;
|
||||
import art.arcane.iris.core.pack.PackValidationCache;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.core.pack.PackValidator;
|
||||
@@ -42,7 +43,11 @@ import org.bukkit.generator.ChunkGenerator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -60,31 +65,73 @@ public final class IrisWorldGeneratorResolver {
|
||||
File packsRoot = plugin.getDataFolder("packs");
|
||||
List<File> packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
|
||||
PackValidationRegistry.clear();
|
||||
if (packDirs.isEmpty()) {
|
||||
return;
|
||||
List<String> packNames = packDirs.stream().map(File::getName).sorted().toList();
|
||||
Path cacheFile = IrisPlatforms.get().dataFile("cache", "pack-validation.json").toPath();
|
||||
String contentFingerprint = "";
|
||||
String contextFingerprint = "";
|
||||
Optional<List<PackValidationResult>> cached = Optional.empty();
|
||||
try {
|
||||
contentFingerprint = PackValidationCache.contentFingerprint(packsRoot);
|
||||
contextFingerprint = PackValidationCache.contextFingerprint();
|
||||
cached = PackValidationCache.load(
|
||||
cacheFile,
|
||||
contentFingerprint,
|
||||
contextFingerprint,
|
||||
packNames);
|
||||
} catch (RuntimeException exception) {
|
||||
Iris.reportError("Could not evaluate the persisted pack-validation cache", exception);
|
||||
}
|
||||
for (File packDir : packDirs) {
|
||||
try {
|
||||
PackValidationResult result = PackValidator.validate(packDir);
|
||||
PackValidationRegistry.publish(result);
|
||||
if (!result.isLoadable()) {
|
||||
Iris.error("Pack '" + result.getPackName() + "' FAILED validation - world/studio creation will be refused. Reasons:");
|
||||
for (String reason : result.getBlockingErrors()) {
|
||||
Iris.error(" - " + reason);
|
||||
|
||||
List<PackValidationResult> results;
|
||||
if (cached.isPresent()) {
|
||||
results = cached.get();
|
||||
Iris.info("Reused persisted validation for " + results.size()
|
||||
+ " unchanged Iris pack(s); full pack parsing was skipped.");
|
||||
} else {
|
||||
results = new ArrayList<>(packDirs.size());
|
||||
for (File packDir : packDirs) {
|
||||
try {
|
||||
results.add(PackValidator.validate(packDir));
|
||||
} catch (Throwable exception) {
|
||||
Iris.reportError("Pack validation failed for '" + packDir.getName() + "'", exception);
|
||||
String detail = exception.getMessage();
|
||||
if (detail == null || detail.isBlank()) {
|
||||
detail = exception.getClass().getSimpleName();
|
||||
}
|
||||
} else if (!result.getWarnings().isEmpty()) {
|
||||
Iris.info("Pack '" + result.getPackName() + "' validated ("
|
||||
+ result.getWarnings().size() + " warning(s)).");
|
||||
for (String warning : result.getWarnings()) {
|
||||
Iris.warn(" [" + result.getPackName() + "] " + warning);
|
||||
}
|
||||
} else {
|
||||
Iris.success("Pack '" + result.getPackName() + "' validated.");
|
||||
results.add(new PackValidationResult(
|
||||
packDir.getName(),
|
||||
List.of("Pack validation failed with " + exception.getClass().getSimpleName()
|
||||
+ ": " + detail),
|
||||
List.of(),
|
||||
System.currentTimeMillis()));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("Pack validation failed for '" + packDir.getName() + "'", e);
|
||||
}
|
||||
try {
|
||||
PackValidationCache.save(cacheFile, contentFingerprint, contextFingerprint, results);
|
||||
} catch (IOException exception) {
|
||||
Iris.reportError("Could not persist Iris pack-validation results", exception);
|
||||
}
|
||||
}
|
||||
|
||||
for (PackValidationResult result : results) {
|
||||
PackValidationRegistry.publish(result);
|
||||
if (!result.isLoadable()) {
|
||||
Iris.error("Pack '" + result.getPackName()
|
||||
+ "' FAILED validation - world and Studio creation with this pack will be refused. Reasons:");
|
||||
for (String reason : result.getBlockingErrors()) {
|
||||
Iris.error(" - " + reason);
|
||||
}
|
||||
} else if (!result.getWarnings().isEmpty()) {
|
||||
Iris.info("Pack '" + result.getPackName() + "' validated ("
|
||||
+ result.getWarnings().size() + " warning(s)).");
|
||||
for (String warning : result.getWarnings()) {
|
||||
Iris.warn(" [" + result.getPackName() + "] " + warning);
|
||||
}
|
||||
} else if (cached.isEmpty()) {
|
||||
Iris.success("Pack '" + result.getPackName() + "' validated.");
|
||||
}
|
||||
}
|
||||
IrisStartupValidation.markPacksReady();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -127,6 +174,7 @@ public final class IrisWorldGeneratorResolver {
|
||||
}
|
||||
|
||||
public ChunkGenerator resolveDefaultWorldGenerator(String worldName, String id) {
|
||||
IrisStartupValidation.requireWorldCreationReady();
|
||||
ChunkGenerator stagedGenerator = WorldLifecycleStaging.consumeGenerator(worldName);
|
||||
if (stagedGenerator != null) {
|
||||
Iris.debug("Using staged runtime generator for " + worldName);
|
||||
@@ -136,19 +184,20 @@ public final class IrisWorldGeneratorResolver {
|
||||
if (id == null || id.isEmpty()) id = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||
Iris.debug("Generator ID: " + id + " requested by bukkit/plugin");
|
||||
|
||||
PackValidationResult validation = PackValidationRegistry.get(id);
|
||||
if (validation != null && !validation.isLoadable()) {
|
||||
Iris.error("Refusing to create world '" + worldName + "' using broken pack '" + id + "':");
|
||||
for (String reason : validation.getBlockingErrors()) {
|
||||
Iris.error(" - " + reason);
|
||||
}
|
||||
throw new BrokenPackException(id, validation.getBlockingErrors());
|
||||
}
|
||||
|
||||
IrisDimension dim = loadDimension(worldName, id);
|
||||
if (dim == null) {
|
||||
throw new RuntimeException("Can't find dimension " + id + "!");
|
||||
}
|
||||
String packName = dim.getLoader().getDataFolder().getName();
|
||||
try {
|
||||
PackValidationRegistry.requireLoadable(packName);
|
||||
} catch (BrokenPackException exception) {
|
||||
Iris.error("Refusing to create world '" + worldName + "' using broken pack '" + packName + "':");
|
||||
for (String reason : exception.getReasons()) {
|
||||
Iris.error(" - " + reason);
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
|
||||
Iris.debug("Assuming IrisDimension: " + dim.getName());
|
||||
NamespacedKey worldKey = IrisWorldStorage.keyFromName(worldName);
|
||||
|
||||
@@ -22,6 +22,7 @@ import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.BukkitWorldReconciler;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.IrisStartupValidation;
|
||||
import art.arcane.iris.core.DatapackInstallResult;
|
||||
import art.arcane.iris.core.IrisWorldStorage;
|
||||
import art.arcane.iris.core.IrisWorlds;
|
||||
@@ -34,6 +35,7 @@ import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.pack.AtomicDirectoryPublisher;
|
||||
import art.arcane.iris.core.pack.PackDownloader;
|
||||
import art.arcane.iris.core.pack.PackDirectoryResolver;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
@@ -394,6 +396,14 @@ public class CommandIris implements DirectorExecutor {
|
||||
}
|
||||
|
||||
private boolean stageFoliaWorldCreation(String name, IrisDimension dimension, long seed, boolean main) {
|
||||
try {
|
||||
IrisStartupValidation.requireWorldCreationReady();
|
||||
PackValidationRegistry.requireLoadable(
|
||||
dimension.getLoader().getDataFolder().getName());
|
||||
} catch (RuntimeException exception) {
|
||||
sender().sendMessage(C.RED + exception.getMessage());
|
||||
return false;
|
||||
}
|
||||
NamespacedKey worldKey = IrisWorldStorage.managedKeyFromName(name);
|
||||
LifecycleOperationCoordinator.Lease worldLease = null;
|
||||
File worldFolder = IrisWorldStorage.requireSafeManagedDimensionRoot(worldKey);
|
||||
|
||||
+39
-2
@@ -21,6 +21,7 @@ package art.arcane.iris.core.commands;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.pack.PackDirectoryResolver;
|
||||
import art.arcane.iris.core.pack.PackResourceCleanup;
|
||||
import art.arcane.iris.core.pack.PackValidationCache;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.core.pack.PackValidator;
|
||||
@@ -31,6 +32,8 @@ import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.volmlib.util.localization.TextKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
@@ -64,6 +67,7 @@ public class CommandPack implements DirectorExecutor {
|
||||
broken++;
|
||||
}
|
||||
}
|
||||
persistValidationCache(packsRoot);
|
||||
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_VALIDATION_COMPLETE_BROKEN_PACKS, MessageArgument.untrusted("broken", String.valueOf(broken)), MessageArgument.untrusted("value", String.valueOf(dirs.size()))));
|
||||
return;
|
||||
}
|
||||
@@ -74,6 +78,7 @@ public class CommandPack implements DirectorExecutor {
|
||||
return;
|
||||
}
|
||||
runValidate(s, target);
|
||||
persistValidationCache(packsRoot);
|
||||
}
|
||||
|
||||
@Director(description = "Preview or apply unused-resource cleanup", descriptionKey = "iris.director.commandpack.director.preview_apply_unused_resource_cleanup", aliases = {"c"})
|
||||
@@ -102,6 +107,7 @@ public class CommandPack implements DirectorExecutor {
|
||||
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NO_CLEANUP_CANDIDATES_FOUND_PACK, MessageArgument.untrusted("pack", String.valueOf(pack))));
|
||||
return;
|
||||
}
|
||||
PackValidationRegistry.remove(packFolder.getName());
|
||||
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_QUARANTINED_CLEANUP_CANDIDATE_S_UNDER, MessageArgument.untrusted("size", String.valueOf(result.quarantinedPaths().size())), MessageArgument.untrusted("quarantinePath", String.valueOf(result.quarantinePath()))));
|
||||
reportPaths(s, result.quarantinedPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_QUARANTINED);
|
||||
return;
|
||||
@@ -157,6 +163,7 @@ public class CommandPack implements DirectorExecutor {
|
||||
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_NOTHING_RESTORE_PACK, MessageArgument.untrusted("pack", String.valueOf(pack))));
|
||||
return;
|
||||
}
|
||||
PackValidationRegistry.remove(packFolder.getName());
|
||||
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_RESTORED_FILE_S_FROM, MessageArgument.untrusted("size", String.valueOf(result.restoredPaths().size())), MessageArgument.untrusted("dumpPath", String.valueOf(result.dumpPath()))));
|
||||
reportPaths(s, result.restoredPaths(), BukkitRuntimeMessages.COMMAND_PACK_PATH_RESTORED);
|
||||
return;
|
||||
@@ -227,8 +234,38 @@ public class CommandPack implements DirectorExecutor {
|
||||
return result;
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError("Pack validation failed for '" + packFolder.getName() + "'", e);
|
||||
s.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_VALIDATION_FAILED, MessageArgument.untrusted("name", String.valueOf(packFolder.getName())), MessageArgument.untrusted("error", String.valueOf(e.getMessage()))));
|
||||
return null;
|
||||
String detail = e.getMessage() == null || e.getMessage().isBlank()
|
||||
? e.getClass().getSimpleName()
|
||||
: e.getMessage();
|
||||
PackValidationResult result = new PackValidationResult(
|
||||
packFolder.getName(),
|
||||
List.of("Pack validation failed with " + e.getClass().getSimpleName() + ": " + detail),
|
||||
List.of(),
|
||||
System.currentTimeMillis());
|
||||
PackValidationRegistry.publish(result);
|
||||
reportResult(s, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void persistValidationCache(File packsRoot) {
|
||||
List<File> packDirectories = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
|
||||
List<PackValidationResult> results = new ArrayList<>(packDirectories.size());
|
||||
for (File packDirectory : packDirectories) {
|
||||
PackValidationResult result = PackValidationRegistry.get(packDirectory.getName());
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
results.add(result);
|
||||
}
|
||||
try {
|
||||
PackValidationCache.save(
|
||||
Iris.instance.getDataFile("cache", "pack-validation.json").toPath(),
|
||||
PackValidationCache.contentFingerprint(packsRoot),
|
||||
PackValidationCache.contextFingerprint(),
|
||||
results);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
Iris.reportError("Could not persist refreshed pack-validation results", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
@@ -5,6 +5,7 @@ import art.arcane.iris.core.lifecycle.LifecycleOperationCoordinator;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -29,6 +30,11 @@ public class BukkitWorldReconcilerTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@After
|
||||
public void disableStartupValidation() {
|
||||
IrisStartupValidation.disable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadLeaseCoversRegistrationThroughExactWorldCompletion() throws Exception {
|
||||
File configuration = temporaryFolder.newFile("bukkit.yml");
|
||||
@@ -221,6 +227,38 @@ public class BukkitWorldReconcilerTest {
|
||||
assertEquals(0L, configuration.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pendingStartupValidationRefusesWorldLoadBeforeRegistration() throws Exception {
|
||||
File configuration = temporaryFolder.newFile("startup-pending.yml");
|
||||
FakeBackend backend = new FakeBackend();
|
||||
BukkitWorldReconciler reconciler = new BukkitWorldReconciler(backend, coordinator());
|
||||
IrisStartupValidation.begin();
|
||||
|
||||
BukkitWorldReconciler.LoadResult result = reconciler
|
||||
.loadWorld(configuration, backend.worldKey.toString())
|
||||
.join();
|
||||
|
||||
assertEquals(BukkitWorldReconciler.ReconciliationStatus.DIMENSION_UNRESOLVED, result.status());
|
||||
assertEquals(0, backend.createCount.get());
|
||||
assertEquals(0L, configuration.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidDimensionRefusesWorldLoadBeforeRegistration() throws Exception {
|
||||
File configuration = temporaryFolder.newFile("invalid-dimension.yml");
|
||||
FakeBackend backend = new FakeBackend();
|
||||
backend.validationFailure = new IllegalStateException("pack is invalid");
|
||||
BukkitWorldReconciler reconciler = new BukkitWorldReconciler(backend, coordinator());
|
||||
|
||||
BukkitWorldReconciler.LoadResult result = reconciler
|
||||
.loadWorld(configuration, backend.worldKey.toString())
|
||||
.join();
|
||||
|
||||
assertEquals(BukkitWorldReconciler.ReconciliationStatus.DIMENSION_UNRESOLVED, result.status());
|
||||
assertEquals(0, backend.createCount.get());
|
||||
assertEquals(0L, configuration.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void terminalCreateTimeoutWinsOverLateWorldCompletion() {
|
||||
NamespacedKey worldKey = new NamespacedKey("iris", "probe");
|
||||
@@ -317,6 +355,7 @@ public class BukkitWorldReconcilerTest {
|
||||
private boolean irisWorld;
|
||||
private Long configuredSeed;
|
||||
private BukkitWorldReconciler.DimensionResolution dimensionResolution;
|
||||
private RuntimeException validationFailure;
|
||||
|
||||
private FakeBackend() {
|
||||
worldKey = new NamespacedKey("iris", "probe");
|
||||
@@ -326,6 +365,7 @@ public class BukkitWorldReconcilerTest {
|
||||
irisWorld = true;
|
||||
configuredSeed = null;
|
||||
dimensionResolution = BukkitWorldReconciler.DimensionResolution.resolved("overworld");
|
||||
validationFailure = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -358,5 +398,12 @@ public class BukkitWorldReconcilerTest {
|
||||
public BukkitWorldReconciler.DimensionResolution resolveDimension(NamespacedKey requestedWorldKey) {
|
||||
return dimensionResolution;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireDimensionLoadable(NamespacedKey requestedWorldKey, String dimension) {
|
||||
if (validationFailure != null) {
|
||||
throw validationFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import com.destroystokyo.paper.profile.PlayerProfile;
|
||||
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisStartupAdmissionListenerTest {
|
||||
private final IrisStartupAdmissionListener listener = new IrisStartupAdmissionListener();
|
||||
|
||||
@After
|
||||
public void disableValidation() {
|
||||
IrisStartupValidation.disable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pendingStartupValidationDeniesLogin() throws Exception {
|
||||
IrisStartupValidation.begin();
|
||||
AsyncPlayerPreLoginEvent event = event();
|
||||
|
||||
listener.onAsyncPlayerPreLogin(event);
|
||||
|
||||
assertEquals(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, event.getLoginResult());
|
||||
assertNotNull(event.kickMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidDatapacksDenyLogin() throws Exception {
|
||||
IrisStartupValidation.begin();
|
||||
IrisStartupValidation.markDatapacksInvalid("datapack failure");
|
||||
IrisStartupValidation.markPacksReady();
|
||||
AsyncPlayerPreLoginEvent event = event();
|
||||
|
||||
listener.onAsyncPlayerPreLogin(event);
|
||||
|
||||
assertEquals(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, event.getLoginResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartRequiredDeniesLogin() throws Exception {
|
||||
IrisStartupValidation.begin();
|
||||
IrisStartupValidation.requireRestart("restart required");
|
||||
IrisStartupValidation.markPacksReady();
|
||||
AsyncPlayerPreLoginEvent event = event();
|
||||
|
||||
listener.onAsyncPlayerPreLogin(event);
|
||||
|
||||
assertEquals(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, event.getLoginResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readyValidationAllowsLogin() throws Exception {
|
||||
IrisStartupValidation.begin();
|
||||
IrisStartupValidation.markDatapacksReady();
|
||||
IrisStartupValidation.markPacksReady();
|
||||
AsyncPlayerPreLoginEvent event = event();
|
||||
|
||||
listener.onAsyncPlayerPreLogin(event);
|
||||
|
||||
assertEquals(AsyncPlayerPreLoginEvent.Result.ALLOWED, event.getLoginResult());
|
||||
}
|
||||
|
||||
private AsyncPlayerPreLoginEvent event() throws Exception {
|
||||
return new AsyncPlayerPreLoginEvent(
|
||||
"ValidationTest",
|
||||
InetAddress.getLoopbackAddress(),
|
||||
UUID.randomUUID(),
|
||||
false,
|
||||
mock(PlayerProfile.class));
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisStartupOrderingTest {
|
||||
@Test
|
||||
public void admissionGateIsRegisteredBeforeStartupValidationBegins() throws Exception {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.startupSource")));
|
||||
String onEnable = section(source, "public void onEnable()", "public void onDisable()");
|
||||
|
||||
assertOrdered(onEnable,
|
||||
"IrisStartupValidation.begin();",
|
||||
"registerEvents(new IrisStartupAdmissionListener(), this);",
|
||||
"enable();");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalDatapacksValidateBeforeDimensionPacks() throws Exception {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.startupSource")));
|
||||
String enable = section(source, "private void enable()", "public void addShutdownHook()");
|
||||
|
||||
assertOrdered(enable,
|
||||
"DatapackIngestService.validateOnStartup();",
|
||||
"generatorResolver.validateAllPacks();");
|
||||
}
|
||||
|
||||
private static String section(String source, String startMarker, String endMarker) {
|
||||
int start = source.indexOf(startMarker);
|
||||
int end = source.indexOf(endMarker, start);
|
||||
assertTrue("Missing source section starting with " + startMarker, start >= 0);
|
||||
assertTrue("Missing source section ending with " + endMarker, end > start);
|
||||
return source.substring(start, end);
|
||||
}
|
||||
|
||||
private static void assertOrdered(String source, String... markers) {
|
||||
int previous = -1;
|
||||
for (String marker : markers) {
|
||||
int current = source.indexOf(marker);
|
||||
assertTrue("Missing source marker " + marker, current >= 0);
|
||||
assertTrue("Source marker is out of order: " + marker, current > previous);
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user