Cover untested contract surfaces

- Add a test source set to spi with bind, service registry and platform value contracts
- Prove the DataType probe contract, the ExternalDataProvider defaults and the string-only provider namespace claims
- Prove Identifier parsing and rendering
- Cover the data version, primitive and vector director handlers
- Cover the vanilla export identifier, block state, orientation, request and settings values
- Cover the structure adoption limits, diagnostics and result invariants
- Cover the pregen mantle backpressure, heap reclaim urgency, empty cache, listener defaults and diagnostic descriptions
This commit is contained in:
Brian Neumann-Fopiano
2026-09-08 05:27:49 -04:00
parent 71e3af727f
commit 290412b62a
21 changed files with 2030 additions and 0 deletions
@@ -0,0 +1,48 @@
package art.arcane.iris.core.link;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IdentifierTest {
@Test
public void abareKeyIsQualifiedIntoTheMinecraftNamespace() {
Identifier identifier = Identifier.fromString("stone");
assertEquals("minecraft", identifier.namespace());
assertEquals("stone", identifier.key());
}
@Test
public void aQualifiedKeyKeepsBothHalves() {
Identifier identifier = Identifier.fromString("oraxen:ruby_ore");
assertEquals("oraxen", identifier.namespace());
assertEquals("ruby_ore", identifier.key());
}
@Test
public void onlyTheFirstSeparatorSplitsTheIdentifier() {
Identifier identifier = Identifier.fromString("itemsadder:rocks/ruby:ore");
assertEquals("itemsadder", identifier.namespace());
assertEquals("rocks/ruby:ore", identifier.key());
}
@Test
public void aLeadingSeparatorProducesAnEmptyNamespaceRatherThanADefault() {
Identifier identifier = Identifier.fromString(":ruby_ore");
assertEquals("", identifier.namespace());
assertEquals("ruby_ore", identifier.key());
}
@Test
public void theRenderedFormRoundTripsThroughTheParser() {
Identifier identifier = new Identifier("nexo", "village/lamp");
assertEquals("nexo:village/lamp", identifier.toString());
assertEquals(identifier, Identifier.fromString(identifier.toString()));
assertEquals(Identifier.fromString("stone"), Identifier.fromString("minecraft:stone"));
}
}
@@ -0,0 +1,156 @@
package art.arcane.iris.core.link.data;
import art.arcane.iris.core.link.ExternalDataProvider;
import art.arcane.iris.core.link.Identifier;
import art.arcane.volmlib.util.collection.KMap;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.MissingResourceException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class DataTypeProbeContractTest {
private static final Identifier RUBY = new Identifier("testplugin", "ruby_ore");
@Test
public void anUnclaimedIdentifierIsRejectedWithoutProbingTheProvider() {
RecordingProvider provider = new RecordingProvider(false, true, true);
assertFalse(DataType.BLOCK.test(provider, RUBY));
assertFalse(DataType.ITEM.test(provider, RUBY));
assertFalse(DataType.ENTITY.test(provider, RUBY));
assertEquals(List.of(), provider.probes);
}
@Test
public void aClaimedBlockIsProbedThroughTheStatelessBlockLookup() {
RecordingProvider provider = new RecordingProvider(true, true, true);
assertTrue(DataType.BLOCK.test(provider, RUBY));
assertEquals(List.of("block"), provider.probes);
}
@Test
public void aClaimedItemIsProbedThroughTheStatelessItemLookup() {
RecordingProvider provider = new RecordingProvider(true, true, true);
assertTrue(DataType.ITEM.test(provider, RUBY));
assertEquals(List.of("item"), provider.probes);
}
@Test
public void aClaimedEntityIsAcceptedOnTheClaimAlone() {
RecordingProvider provider = new RecordingProvider(true, false, false);
assertTrue(DataType.ENTITY.test(provider, RUBY));
assertEquals(List.of(), provider.probes);
}
@Test
public void aMissingResourceDowngradesTheClaimInsteadOfFailing() {
RecordingProvider provider = new RecordingProvider(true, false, false);
assertFalse(DataType.BLOCK.test(provider, RUBY));
assertFalse(DataType.ITEM.test(provider, RUBY));
assertEquals(List.of("block", "item"), provider.probes);
}
@Test
public void anUnexpectedProviderFailureIsNotSwallowed() {
ExplodingProvider provider = new ExplodingProvider();
assertThrows(IllegalStateException.class, () -> DataType.BLOCK.test(provider, RUBY));
}
@Test
public void thePredicateFormFiltersEveryElementThroughTheSameProvider() {
RecordingProvider provider = new RecordingProvider(true, true, true);
List<Identifier> claimed = List.of(RUBY, new Identifier("other", "quartz"));
List<Identifier> filtered = claimed.stream().filter(DataType.BLOCK.asPredicate(provider)).toList();
assertEquals(claimed, filtered);
assertEquals(List.of("block", "block"), provider.probes);
}
private static final class RecordingProvider extends ExternalDataProvider {
private final List<String> probes = new ArrayList<>();
private final boolean claims;
private final boolean resolvesBlocks;
private final boolean resolvesItems;
private RecordingProvider(boolean claims, boolean resolvesBlocks, boolean resolvesItems) {
super("TestPlugin");
this.claims = claims;
this.resolvesBlocks = resolvesBlocks;
this.resolvesItems = resolvesItems;
}
@Override
public void init() {
}
@Override
public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) {
probes.add("block");
if (!resolvesBlocks) {
throw new MissingResourceException("no block", blockId.namespace(), blockId.key());
}
return mock(BlockData.class);
}
@Override
public @NotNull ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) {
probes.add("item");
if (!resolvesItems) {
throw new MissingResourceException("no item", itemId.namespace(), itemId.key());
}
return mock(ItemStack.class);
}
@Override
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
return List.of();
}
@Override
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
return claims;
}
}
private static final class ExplodingProvider extends ExternalDataProvider {
private ExplodingProvider() {
super("TestPlugin");
}
@Override
public void init() {
}
@Override
public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) {
throw new IllegalStateException("the provider backend is broken");
}
@Override
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
return List.of();
}
@Override
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
return true;
}
}
}
@@ -0,0 +1,110 @@
package art.arcane.iris.core.link.data;
import art.arcane.iris.core.link.ExternalDataProvider;
import art.arcane.iris.core.link.Identifier;
import art.arcane.volmlib.util.collection.KMap;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.MissingResourceException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class ExternalDataProviderDefaultsTest {
private static final Identifier RUBY = new Identifier("testplugin", "ruby_ore");
@Test
public void theProviderRemembersThePluginItSpeaksFor() {
assertEquals("TestPlugin", new StatelessProvider().getPluginId());
}
@Test
public void aProviderWithoutAPluginIdIsRejected() {
assertThrows(NullPointerException.class, () -> new StatelessProvider(null));
}
@Test
public void anUnimplementedProviderRefusesEveryResourceKind() {
StatelessProvider provider = new StatelessProvider();
MissingResourceException blockFailure =
assertThrows(MissingResourceException.class, () -> provider.getBlockData(RUBY));
MissingResourceException itemFailure =
assertThrows(MissingResourceException.class, () -> provider.getItemStack(RUBY));
assertEquals("testplugin", blockFailure.getClassName());
assertEquals("ruby_ore", blockFailure.getKey());
assertEquals("testplugin", itemFailure.getClassName());
assertEquals("ruby_ore", itemFailure.getKey());
assertThrows(MissingResourceException.class, () -> provider.spawnMob(mock(Location.class), RUBY));
}
@Test
public void anUnimplementedProviderExposesNoBlockProperties() {
assertTrue(new StatelessProvider().getBlockProperties(RUBY).isEmpty());
}
@Test
public void theStatelessLookupsDelegateWithAnEmptyStateMap() {
DelegationProvider provider = new DelegationProvider();
provider.getBlockData(RUBY);
provider.getItemStack(RUBY);
assertEquals(List.of("block:0", "item:0"), provider.calls);
}
@Test
public void deferredPlacementIsANoOpUntilAProviderImplementsIt() {
new StatelessProvider().processUpdate(null, null, RUBY);
}
private static class StatelessProvider extends ExternalDataProvider {
private StatelessProvider() {
super("TestPlugin");
}
private StatelessProvider(String pluginId) {
super(pluginId);
}
@Override
public void init() {
}
@Override
public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) {
return List.of();
}
@Override
public boolean isValidProvider(@NotNull Identifier id, DataType dataType) {
return false;
}
}
private static final class DelegationProvider extends StatelessProvider {
private final List<String> calls = new ArrayList<>();
@Override
public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap<String, String> state) {
calls.add("block:" + state.size());
return mock(BlockData.class);
}
@Override
public @NotNull ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap<String, Object> customNbt) {
calls.add("item:" + customNbt.size());
return mock(ItemStack.class);
}
}
}
@@ -0,0 +1,90 @@
package art.arcane.iris.core.link.data;
import art.arcane.iris.core.link.Identifier;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ProviderNamespaceClaimTest {
@Test
public void ecoItemsClaimsOnlyItemsInItsOwnNamespace() {
EcoItemsDataProvider provider = new EcoItemsDataProvider();
assertTrue(provider.isValidProvider(new Identifier("ecoitems", "ruby"), DataType.ITEM));
assertTrue(provider.isValidProvider(new Identifier("EcoItems", "ruby"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("ecoitems", "ruby"), DataType.BLOCK));
assertFalse(provider.isValidProvider(new Identifier("ecoitems", "ruby"), DataType.ENTITY));
assertFalse(provider.isValidProvider(new Identifier("oraxen", "ruby"), DataType.ITEM));
}
@Test
public void executableItemsClaimsOnlyItemsInItsOwnNamespace() {
ExecutableItemsDataProvider provider = new ExecutableItemsDataProvider();
assertTrue(provider.isValidProvider(new Identifier("executable_items", "wand"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("executable_items", "wand"), DataType.BLOCK));
assertFalse(provider.isValidProvider(new Identifier("executableitems", "wand"), DataType.ITEM));
}
@Test
public void kgeneratorsClaimsItemsWithoutConsultingTheGeneratorRegistry() {
KGeneratorsDataProvider provider = new KGeneratorsDataProvider();
assertTrue(provider.isValidProvider(new Identifier("kgenerators", "cobble"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("kgenerators", "cobble"), DataType.ENTITY));
assertFalse(provider.isValidProvider(new Identifier("oraxen", "cobble"), DataType.ITEM));
}
@Test
public void mmoItemsEncodesItsItemTypeInTheNamespace() {
MMOItemsDataProvider provider = new MMOItemsDataProvider();
assertTrue(provider.isValidProvider(new Identifier("mmoitems_sword", "excalibur"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("mmoitems", "excalibur"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("mmoitems_sword", "excalibur"), DataType.ENTITY));
assertFalse(provider.isValidProvider(new Identifier("MMOITEMS", "1"), DataType.BLOCK));
}
@Test
public void mythicCrucibleClaimsItemsWithoutConsultingItsItemManager() {
MythicCrucibleDataProvider provider = new MythicCrucibleDataProvider();
assertTrue(provider.isValidProvider(new Identifier("crucible", "ruby"), DataType.ITEM));
assertTrue(provider.isValidProvider(new Identifier("Crucible", "ruby"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("crucible", "ruby"), DataType.ENTITY));
assertFalse(provider.isValidProvider(new Identifier("oraxen", "ruby"), DataType.ITEM));
}
@Test
public void oraxenRefusesForeignNamespacesBeforeTouchingItsApi() {
OraxenDataProvider provider = new OraxenDataProvider();
assertFalse(provider.isValidProvider(new Identifier("nexo", "ruby_ore"), DataType.BLOCK));
assertFalse(provider.isValidProvider(new Identifier("nexo", "ruby_ore"), DataType.ITEM));
assertEquals(List.of(), provider.getTypes(DataType.ENTITY));
}
@Test
public void nexoRefusesForeignNamespacesBeforeTouchingItsApi() {
NexoDataProvider provider = new NexoDataProvider();
assertFalse(provider.isValidProvider(new Identifier("oraxen", "lamp"), DataType.BLOCK));
assertFalse(provider.isValidProvider(new Identifier("oraxen", "lamp"), DataType.ITEM));
assertEquals(List.of(), provider.getTypes(DataType.ENTITY));
}
@Test
public void itemsAdderClaimsNothingBeforeItsRegistryHasLoaded() {
ItemAdderDataProvider provider = new ItemAdderDataProvider();
assertFalse(provider.isValidProvider(new Identifier("rocks", "ruby_ore"), DataType.ITEM));
assertFalse(provider.isValidProvider(new Identifier("rocks", "ruby_ore"), DataType.BLOCK));
assertFalse(provider.isValidProvider(new Identifier("rocks", "ruby_ore"), DataType.ENTITY));
assertTrue(provider.getTypes(DataType.ITEM).isEmpty());
assertTrue(provider.getTypes(DataType.BLOCK).isEmpty());
}
}
@@ -0,0 +1,47 @@
package art.arcane.iris.core.pregenerator;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class MantleHeapPressureUrgencyTest {
@Test
public void headroomBelowTheLowWaterMarkAsksForNothing() {
assertEquals(0D, MantleHeapPressure.reclaimUrgency(0D), 0D);
assertEquals(0D, MantleHeapPressure.reclaimUrgency(0.5D), 0D);
assertEquals(0D, MantleHeapPressure.reclaimUrgency(0.82D), 0D);
}
@Test
public void pressureAtOrAboveTheHighWaterMarkIsFullyUrgent() {
assertEquals(1D, MantleHeapPressure.reclaimUrgency(0.92D), 0D);
assertEquals(1D, MantleHeapPressure.reclaimUrgency(0.99D), 0D);
assertEquals(1D, MantleHeapPressure.reclaimUrgency(4D), 0D);
}
@Test
public void pressureBetweenTheMarksRampsLinearly() {
assertEquals(0.25D, MantleHeapPressure.reclaimUrgency(0.845D), 1.0E-9D);
assertEquals(0.5D, MantleHeapPressure.reclaimUrgency(0.87D), 1.0E-9D);
assertEquals(0.75D, MantleHeapPressure.reclaimUrgency(0.895D), 1.0E-9D);
}
@Test
public void urgencyNeverDecreasesAsPressureRises() {
double previous = -1D;
for (int step = 0; step <= 100; step++) {
double urgency = MantleHeapPressure.reclaimUrgency(step / 100D);
assertTrue("urgency dropped at " + step, urgency >= previous);
previous = urgency;
}
}
@Test
public void unmeasurableHeapPressureAsksForNothing() {
assertEquals(0D, MantleHeapPressure.reclaimUrgency(Double.NaN), 0D);
assertEquals(0D, MantleHeapPressure.reclaimUrgency(Double.POSITIVE_INFINITY), 0D);
assertEquals(0D, MantleHeapPressure.reclaimUrgency(Double.NEGATIVE_INFINITY), 0D);
}
}
@@ -0,0 +1,40 @@
package art.arcane.iris.core.pregenerator;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class PregenDiagnosticsDescriptionTest {
@Test
public void anUnnamedProbeStillReadsAsAProbe() {
assertEquals("unnamed", PregenDiagnostics.describeProbe(null));
assertEquals("unnamed", PregenDiagnostics.describeProbe(""));
assertEquals("unnamed", PregenDiagnostics.describeProbe(" "));
}
@Test
public void aNamedProbeIsReportedVerbatim() {
assertEquals("mantle handle for backpressure",
PregenDiagnostics.describeProbe("mantle handle for backpressure"));
assertEquals("%s throughput", PregenDiagnostics.describeProbe("%s throughput"));
}
@Test
public void anAbsentThrowableIsCalledOutInsteadOfPrintingNull() {
assertEquals("no throwable reported", PregenDiagnostics.describeFailure(null));
}
@Test
public void aThrowableWithoutAMessageIsIdentifiedByItsType() {
assertEquals("java.lang.IllegalStateException",
PregenDiagnostics.describeFailure(new IllegalStateException()));
}
@Test
public void aThrowableWithAMessageCarriesBothTypeAndMessage() {
assertEquals("java.lang.IllegalStateException: mantle is gone",
PregenDiagnostics.describeFailure(new IllegalStateException("mantle is gone")));
assertEquals("java.lang.RuntimeException: ",
PregenDiagnostics.describeFailure(new RuntimeException("")));
}
}
@@ -0,0 +1,104 @@
package art.arcane.iris.core.pregenerator;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class PregenListenerDefaultsTest {
@Test
public void anUnqualifiedChunkIsReportedAsFreshlyGenerated() {
RecordingListener listener = new RecordingListener();
listener.onChunkGenerated(4, -9);
assertEquals(List.of("4/-9/false"), listener.generated);
}
@Test
public void anExplicitCacheFlagIsPassedThroughUnchanged() {
RecordingListener listener = new RecordingListener();
listener.onChunkGenerated(1, 2, true);
listener.onChunkGenerated(3, 4, false);
assertEquals(List.of("1/2/true", "3/4/false"), listener.generated);
}
@Test
public void aFailedChunkIsIgnoredByListenersThatDoNotCare() {
RecordingListener listener = new RecordingListener();
listener.onChunkFailed(7, 7);
assertTrue(listener.generated.isEmpty());
}
private static final class RecordingListener implements PregenListener {
private final List<String> generated = new ArrayList<>();
@Override
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generatedChunks, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
}
@Override
public void onChunkGenerating(int x, int z) {
}
@Override
public void onChunkGenerated(int x, int z, boolean cached) {
generated.add(x + "/" + z + "/" + cached);
}
@Override
public void onRegionGenerated(int x, int z) {
}
@Override
public void onRegionGenerating(int x, int z) {
}
@Override
public void onChunkCleaned(int x, int z) {
}
@Override
public void onRegionSkipped(int x, int z) {
}
@Override
public void onNetworkStarted(int x, int z) {
}
@Override
public void onNetworkFailed(int x, int z) {
}
@Override
public void onNetworkReclaim(int revert) {
}
@Override
public void onNetworkGeneratedChunk(int x, int z) {
}
@Override
public void onNetworkDownloaded(int x, int z) {
}
@Override
public void onClose() {
}
@Override
public void onSaving() {
}
@Override
public void onChunkExistsInRegionGen(int x, int z) {
}
}
}
@@ -0,0 +1,152 @@
package art.arcane.iris.core.pregenerator;
import art.arcane.iris.testsupport.Await;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import org.junit.Test;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class PregenMantleBackpressureTest {
@Test
public void aDisabledPlateCapNeverAsksForAMantle() {
AtomicInteger supplierCalls = new AtomicInteger();
Supplier<Mantle> supplier = () -> {
supplierCalls.incrementAndGet();
return null;
};
backpressure(supplier, 0, 1_000L, () -> {
}).enforceMantleBudget();
assertEquals(0, supplierCalls.get());
}
@Test
public void aFailingMantleProbeDoesNotPropagate() {
AtomicInteger timeouts = new AtomicInteger();
backpressure(() -> {
throw new IllegalStateException("no mantle here");
}, 4, 1_000L, timeouts::incrementAndGet).enforceMantleBudget();
assertEquals(0, timeouts.get());
}
@Test
public void residencyAtTwiceThePlateCapIsNotTrimmed() {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(8);
backpressure(() -> mantle, 4, 1_000L, () -> {
}).enforceMantleBudget();
verify(mantle, never()).trim(0L, 0);
verify(mantle, never()).unloadTectonicPlate(0);
}
@Test
public void residencyOverTheHardCapIsTrimmedUntilItFits() {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(20, 20, 3);
backpressure(() -> mantle, 4, 1_000L, () -> {
}).enforceMantleBudget();
verify(mantle, times(1)).trim(0L, 0);
verify(mantle, times(1)).unloadTectonicPlate(0);
}
@Test
public void aTrimFailureEndsTheWaitInsteadOfSpinning() {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(20);
doThrow(new IllegalStateException("mantle is gone")).when(mantle).trim(0L, 0);
AtomicInteger timeouts = new AtomicInteger();
backpressure(() -> mantle, 4, 1_000L, timeouts::incrementAndGet).enforceMantleBudget();
verify(mantle, never()).unloadTectonicPlate(0);
assertEquals(0, timeouts.get());
}
@Test
public void anExhaustedBudgetReportsTheTimeoutOnceAndProceeds() {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(20);
AtomicInteger timeouts = new AtomicInteger();
backpressure(() -> mantle, 4, 0L, timeouts::incrementAndGet).enforceMantleBudget();
assertEquals(1, timeouts.get());
}
@Test
public void cancellationStopsTheWaitWithoutReportingATimeout() {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(20);
AtomicInteger timeouts = new AtomicInteger();
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(
() -> mantle, 4, 5, 0L, timeouts::incrementAndGet, () -> "cancelled", () -> true);
backpressure.enforceMantleBudget();
verify(mantle, never()).trim(0L, 0);
verify(mantle, never()).unloadTectonicPlate(0);
assertEquals(0, timeouts.get());
}
@Test
public void aFailingCancellationProbeIsTreatedAsRunning() {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(20);
AtomicInteger timeouts = new AtomicInteger();
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(
() -> mantle, 4, 5, 0L, timeouts::incrementAndGet, () -> "probing", () -> {
throw new IllegalStateException("cancellation probe exploded");
});
backpressure.enforceMantleBudget();
assertEquals(1, timeouts.get());
}
@Test
public void signalledProgressReleasesTheWaitAheadOfThePollInterval() throws Exception {
Mantle mantle = mock(Mantle.class);
when(mantle.getLoadedRegionCount()).thenReturn(20, 20, 20, 3);
PregenMantleBackpressure backpressure = new PregenMantleBackpressure(
() -> mantle, 4, 600_000, 600_000L, () -> {
}, () -> "waiting");
Thread waiter = new Thread(backpressure::enforceMantleBudget, "pregen-backpressure-waiter");
waiter.start();
Await.until("the parked backpressure wait to be released", Duration.ofSeconds(10L), () -> {
backpressure.signalProgress();
return !waiter.isAlive();
});
waiter.join(1_000L);
assertFalse(waiter.isAlive());
}
private static PregenMantleBackpressure backpressure(
Supplier<Mantle> mantleSupplier,
int maxResidentTectonicPlates,
long timeoutMs,
Runnable onBudgetTimeout
) {
return new PregenMantleBackpressure(
mantleSupplier, maxResidentTectonicPlates, 5, timeoutMs, onBudgetTimeout, () -> "test");
}
}
@@ -0,0 +1,43 @@
package art.arcane.iris.core.pregenerator.cache;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
public class PregenCacheDefaultsTest {
@Test
public void anAbsentDirectoryResolvesToTheSharedEmptyCache() {
assertSame(PregenCache.EMPTY, PregenCache.create(null));
}
@Test
public void theEmptyCacheNeverClaimsAChunkItWasToldToCache() {
PregenCache.EMPTY.cacheChunk(3, -7);
PregenCache.EMPTY.cacheChunk(0, 0);
assertFalse(PregenCache.EMPTY.isChunkCached(3, -7));
assertFalse(PregenCache.EMPTY.isChunkCached(0, 0));
}
@Test
public void theEmptyCacheNeverClaimsARegionItWasToldToCache() {
PregenCache.EMPTY.cacheRegion(-2, 5);
assertFalse(PregenCache.EMPTY.isRegionCached(-2, 5));
}
@Test
public void theEmptyCacheSurvivesMaintenanceCalls() {
PregenCache.EMPTY.write();
PregenCache.EMPTY.trim(0L);
PregenCache.EMPTY.trim(60_000L);
assertFalse(PregenCache.EMPTY.isChunkCached(0, 0));
}
@Test
public void aCacheIsItsOwnSynchronousView() {
assertSame(PregenCache.EMPTY, PregenCache.EMPTY.sync());
}
}
@@ -0,0 +1,187 @@
package art.arcane.iris.core.structure.conversion;
import org.junit.Test;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class IrisStructureAdoptionValueTest {
@Test
public void theDefaultLimitsAreTheOnesTheServiceShipsWith() {
IrisStructureAdoptionLimits limits = IrisStructureAdoptionLimits.defaults();
assertEquals(10_000, limits.maxResources());
assertEquals(8 * 1024 * 1024, limits.maxJsonBytes());
assertEquals(64 * 1024 * 1024, limits.maxBinaryBytes());
assertEquals(1024L * 1024L * 1024L, limits.maxTotalBytes());
assertEquals(10_000, limits.maxStructuresScanned());
assertEquals(1_000, limits.maxDiagnostics());
assertEquals(1_024, limits.maxActivePlans());
assertEquals(Duration.ofMinutes(15L), limits.planTtl());
}
@Test
public void everyLimitRejectsBothEndsOfItsRange() {
assertThrows(IllegalArgumentException.class, () -> limits(0, 1, 1, 1L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(100_001, 1, 1, 1L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 0, 1, 1L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 67_108_865, 1, 1L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 0, 1L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 268_435_457, 1L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 0L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1_073_741_825L, 1, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 0, 1, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 1, 0, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 1, 10_001, 1, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 1, 1, 0, Duration.ofMinutes(1L)));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 1, 1, 10_001, Duration.ofMinutes(1L)));
}
@Test
public void thePlanTtlMustBePositiveAndWithinADay() {
assertThrows(NullPointerException.class, () -> limits(1, 1, 1, 1L, 1, 1, 1, null));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 1, 1, 1, Duration.ZERO));
assertThrows(IllegalArgumentException.class, () -> limits(1, 1, 1, 1L, 1, 1, 1, Duration.ofSeconds(-1L)));
assertThrows(IllegalArgumentException.class,
() -> limits(1, 1, 1, 1L, 1, 1, 1, Duration.ofHours(24L).plusNanos(1L)));
assertEquals(Duration.ofHours(24L), limits(1, 1, 1, 1L, 1, 1, 1, Duration.ofHours(24L)).planTtl());
assertEquals(Duration.ofNanos(1L), limits(1, 1, 1, 1L, 1, 1, 1, Duration.ofNanos(1L)).planTtl());
}
@Test
public void aDiagnosticRendersItsSeverityCodeResourceAndAdvice() {
IrisStructureAdoptionDiagnostic diagnostic = new IrisStructureAdoptionDiagnostic(
IrisStructureAdoptionDiagnostic.Severity.ERROR,
IrisStructureAdoptionDiagnostic.Code.TARGET_RESOURCE_EXISTS,
" structures/village.json ",
"the target already exists",
" pick another target ");
assertEquals("structures/village.json", diagnostic.resource());
assertEquals("ERROR TARGET_RESOURCE_EXISTS [structures/village.json]: "
+ "the target already exists pick another target", diagnostic.summary());
assertTrue(diagnostic.blocking());
}
@Test
public void aDiagnosticWithoutAResourceOrAdviceStillReadsCleanly() {
IrisStructureAdoptionDiagnostic diagnostic = new IrisStructureAdoptionDiagnostic(
IrisStructureAdoptionDiagnostic.Severity.INFO,
IrisStructureAdoptionDiagnostic.Code.IN_PLACE_AVAILABLE,
null,
"the source can be claimed in place",
null);
assertEquals("INFO IN_PLACE_AVAILABLE: the source can be claimed in place", diagnostic.summary());
assertFalse(diagnostic.blocking());
}
@Test
public void aDiagnosticWithoutADetailIsRejected() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> new IrisStructureAdoptionDiagnostic(
IrisStructureAdoptionDiagnostic.Severity.WARNING,
IrisStructureAdoptionDiagnostic.Code.SHARED_DEPENDENCY,
"structures/village.json",
" ",
null));
assertTrue(failure.getMessage(), failure.getMessage().contains("detail cannot be blank"));
assertThrows(NullPointerException.class, () -> new IrisStructureAdoptionDiagnostic(
null,
IrisStructureAdoptionDiagnostic.Code.SHARED_DEPENDENCY,
null,
"detail",
null));
}
@Test
public void diagnosticsSortErrorsAheadOfWarningsAndInfo() {
IrisStructureAdoptionDiagnostic error = diagnostic(
IrisStructureAdoptionDiagnostic.Severity.ERROR,
IrisStructureAdoptionDiagnostic.Code.PLAN_BLOCKED, "b", "second");
IrisStructureAdoptionDiagnostic warning = diagnostic(
IrisStructureAdoptionDiagnostic.Severity.WARNING,
IrisStructureAdoptionDiagnostic.Code.SHARED_DEPENDENCY, "a", "first");
IrisStructureAdoptionDiagnostic sameSeverityEarlierCode = diagnostic(
IrisStructureAdoptionDiagnostic.Severity.ERROR,
IrisStructureAdoptionDiagnostic.Code.SOURCE_GRAPH_INVALID, "z", "third");
assertTrue(error.compareTo(warning) < 0);
assertTrue(sameSeverityEarlierCode.compareTo(error) < 0);
assertTrue(diagnostic(IrisStructureAdoptionDiagnostic.Severity.ERROR,
IrisStructureAdoptionDiagnostic.Code.PLAN_BLOCKED, "a", "x")
.compareTo(error) < 0);
assertEquals(0, error.compareTo(diagnostic(
IrisStructureAdoptionDiagnostic.Severity.ERROR,
IrisStructureAdoptionDiagnostic.Code.PLAN_BLOCKED, "b", "second")));
}
@Test
public void anUnappliedResultCarriesItsDiagnosticsAndNoReceipt() {
UUID planId = UUID.randomUUID();
IrisStructureAdoptionResult result = new IrisStructureAdoptionResult(
IrisStructureAdoptionResult.Status.BLOCKED,
planId,
List.of(diagnostic(IrisStructureAdoptionDiagnostic.Severity.ERROR,
IrisStructureAdoptionDiagnostic.Code.PLAN_BLOCKED, "structures/village.json", "blocked")),
Optional.empty(),
Optional.empty());
assertFalse(result.successful());
assertEquals(List.of(
"Adoption plan " + planId + ": BLOCKED",
"ERROR PLAN_BLOCKED [structures/village.json]: blocked"), result.summaryLines());
}
@Test
public void anAppliedResultWithoutAReceiptIsRejected() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> new IrisStructureAdoptionResult(
IrisStructureAdoptionResult.Status.APPLIED,
UUID.randomUUID(),
List.of(),
Optional.empty(),
Optional.empty()));
assertTrue(failure.getMessage(), failure.getMessage().contains("requires a receipt"));
}
private static IrisStructureAdoptionDiagnostic diagnostic(
IrisStructureAdoptionDiagnostic.Severity severity,
IrisStructureAdoptionDiagnostic.Code code,
String resource,
String detail
) {
return new IrisStructureAdoptionDiagnostic(severity, code, resource, detail, null);
}
private static IrisStructureAdoptionLimits limits(
int maxResources,
int maxJsonBytes,
int maxBinaryBytes,
long maxTotalBytes,
int maxStructuresScanned,
int maxDiagnostics,
int maxActivePlans,
Duration planTtl
) {
return new IrisStructureAdoptionLimits(
maxResources,
maxJsonBytes,
maxBinaryBytes,
maxTotalBytes,
maxStructuresScanned,
maxDiagnostics,
maxActivePlans,
planTtl);
}
}
@@ -0,0 +1,73 @@
package art.arcane.iris.core.structure.export;
import art.arcane.volmlib.util.nbt.tag.CompoundTag;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class VanillaBlockStateTest {
@Test
public void aBareBlockKeepsItsNameAndNoProperties() {
VanillaBlockState state = VanillaBlockState.parse(" minecraft:stone ");
assertEquals("minecraft:stone", state.name());
assertTrue(state.properties().isEmpty());
assertEquals("minecraft:stone", state.canonical());
}
@Test
public void propertiesAreCanonicalizedIntoAlphabeticalOrder() {
VanillaBlockState state = VanillaBlockState.parse("minecraft:oak_stairs[waterlogged=false,facing=north,half=top]");
assertEquals("minecraft:oak_stairs[facing=north,half=top,waterlogged=false]", state.canonical());
assertEquals("north", state.properties().get("facing"));
}
@Test
public void theCanonicalFormRoundTripsThroughTheParser() {
String canonical = VanillaBlockState.parse("minecraft:oak_log[axis=z]").canonical();
assertEquals(canonical, VanillaBlockState.parse(canonical).canonical());
}
@Test
public void theNbtFormCarriesTheNameAndOnlyRealProperties() {
CompoundTag bare = VanillaBlockState.parse("minecraft:air").toNbt();
CompoundTag stairs = VanillaBlockState.parse("minecraft:oak_stairs[facing=north]").toNbt();
assertEquals("minecraft:air", bare.getString("Name"));
assertNull(bare.get("Properties"));
assertEquals("minecraft:oak_stairs", stairs.getString("Name"));
assertEquals("north", stairs.getCompoundTag("Properties").getString("facing"));
}
@Test
public void nonMinecraftBlocksAreRejectedBeforeAnythingIsWritten() {
IllegalArgumentException failure =
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("iris:custom_block"));
assertTrue(failure.getMessage(), failure.getMessage().contains("non-Minecraft block"));
}
@Test
public void malformedPropertyBlocksAreRejected() {
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse(null));
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("minecraft:stone[facing=north"));
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("minecraft:stone[]"));
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("minecraft:stone[facing]"));
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("minecraft:stone[facing=]"));
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("minecraft:stone[=north]"));
assertThrows(IllegalArgumentException.class, () -> VanillaBlockState.parse("minecraft:stone[a=1][b=2]"));
}
@Test
public void aRepeatedPropertyIsRejectedInsteadOfSilentlyWinning() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> VanillaBlockState.parse("minecraft:stone[facing=north,facing=south]"));
assertTrue(failure.getMessage(), failure.getMessage().contains("Duplicate block property"));
}
}
@@ -0,0 +1,124 @@
package art.arcane.iris.core.structure.export;
import art.arcane.iris.engine.framework.structure.StructureGraphResolver;
import art.arcane.iris.engine.object.IrisStructure;
import org.junit.Test;
import java.nio.file.Path;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class VanillaJigsawExportRequestTest {
@Test
public void aStructureSourceKeepsTheSuppliedGraphInsteadOfLoadingOne() {
IrisStructure structure = mock(IrisStructure.class);
StructureGraphResolver resolver = mock(StructureGraphResolver.class);
VanillaJigsawExportSource source = VanillaJigsawExportSource.forStructure(" village ", structure, resolver);
assertEquals("village", source.structureKey());
assertSame(structure, source.loadStructure());
assertSame(resolver, source.resolver());
}
@Test
public void aBlankStructureKeyIsRejected() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> VanillaJigsawExportSource.forStructure(" ", mock(IrisStructure.class), mock(StructureGraphResolver.class)));
assertTrue(failure.getMessage(), failure.getMessage().contains("must not be blank"));
assertThrows(NullPointerException.class,
() -> VanillaJigsawExportSource.forStructure(null, mock(IrisStructure.class), mock(StructureGraphResolver.class)));
}
@Test
public void theResourcePathDefaultsToTheStructureKey() {
VanillaJigsawExportRequest request = request().build();
assertEquals("village", request.resourcePath());
assertEquals("iris", request.namespace());
assertEquals(VanillaJigsawExportFormat.DIRECTORY, request.format());
assertFalse(request.replaceExisting());
}
@Test
public void theOutputPathIsStoredAbsoluteAndNormalized() {
VanillaJigsawExportRequest request = VanillaJigsawExportRequest
.builder(source(), Path.of("build", "exports", "..", "datapack"))
.build();
assertTrue(request.output().isAbsolute());
assertEquals("datapack", request.output().getFileName().toString());
assertFalse(request.output().toString().contains(".."));
}
@Test
public void identityFieldsAreTrimmedButTheDescriptionIsNot() {
VanillaJigsawExportRequest request = request()
.namespace(" mypack ")
.resourcePath(" village/plains ")
.description(" a description ")
.format(VanillaJigsawExportFormat.ZIP)
.replaceExisting(true)
.build();
assertEquals("mypack", request.namespace());
assertEquals("village/plains", request.resourcePath());
assertEquals(" a description ", request.description());
assertEquals(VanillaJigsawExportFormat.ZIP, request.format());
assertTrue(request.replaceExisting());
}
@Test
public void theDefaultSettingsDescribeAVanillaSurfaceStructure() {
VanillaJigsawExportSettings settings = VanillaJigsawExportSettings.defaults();
assertEquals(List.of("minecraft:plains"), settings.biomes());
assertEquals(0, settings.startHeight());
assertEquals(4064, settings.maxDistanceVertical());
assertEquals(32, settings.spacing());
assertEquals(8, settings.separation());
assertEquals(0, settings.salt());
assertEquals(1.0F, settings.frequency(), 0F);
assertFalse(settings.expansionHack());
assertEquals(VanillaJigsawExportSettings.ProjectHeightmap.WORLD_SURFACE_WG, settings.projectHeightmap());
assertEquals(VanillaJigsawExportSettings.GenerationStep.SURFACE_STRUCTURES, settings.generationStep());
assertEquals(VanillaJigsawExportSettings.TerrainAdaptation.NONE, settings.terrainAdaptation());
assertEquals(VanillaJigsawExportSettings.SpreadType.LINEAR, settings.spreadType());
assertNull(VanillaJigsawExportSettings.ProjectHeightmap.NONE.serializedName());
}
@Test
public void theSettingsBuilderReplacesTheBiomeListRatherThanAppendingToIt() {
VanillaJigsawExportSettings settings = VanillaJigsawExportSettings.builder()
.biomes(List.of("minecraft:desert", "minecraft:savanna"))
.biomes(List.of("minecraft:taiga"))
.startHeight(64)
.randomSpread(48, 12, 7)
.frequency(0.5F)
.build();
assertEquals(List.of("minecraft:taiga"), settings.biomes());
assertEquals(64, settings.startHeight());
assertEquals(48, settings.spacing());
assertEquals(12, settings.separation());
assertEquals(7, settings.salt());
assertEquals(0.5F, settings.frequency(), 0F);
}
private static VanillaJigsawExportSource source() {
return VanillaJigsawExportSource.forStructure(
"village", mock(IrisStructure.class), mock(StructureGraphResolver.class));
}
private static VanillaJigsawExportRequest.Builder request() {
return VanillaJigsawExportRequest.builder(source(), Path.of("build", "datapack"));
}
}
@@ -0,0 +1,66 @@
package art.arcane.iris.core.structure.export;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class VanillaResourceIdentifierTest {
@Test
public void namespacesAcceptOnlyTheVanillaCharacterSet() {
assertTrue(VanillaResourceIdentifier.validNamespace("minecraft"));
assertTrue(VanillaResourceIdentifier.validNamespace("iris_pack-1.0"));
assertFalse(VanillaResourceIdentifier.validNamespace(null));
assertFalse(VanillaResourceIdentifier.validNamespace(""));
assertFalse(VanillaResourceIdentifier.validNamespace("Minecraft"));
assertFalse(VanillaResourceIdentifier.validNamespace("iris/pack"));
}
@Test
public void pathsAllowNestingButNeverTraversal() {
assertTrue(VanillaResourceIdentifier.validPath("village/plains/houses"));
assertTrue(VanillaResourceIdentifier.validPath("empty"));
assertFalse(VanillaResourceIdentifier.validPath("/village"));
assertFalse(VanillaResourceIdentifier.validPath("village/"));
assertFalse(VanillaResourceIdentifier.validPath("village//houses"));
assertFalse(VanillaResourceIdentifier.validPath("village/../secrets"));
assertFalse(VanillaResourceIdentifier.validPath("village/./houses"));
assertFalse(VanillaResourceIdentifier.validPath("Village"));
}
@Test
public void identifiersRequireExactlyOneSeparatorWithBothHalvesPresent() {
assertTrue(VanillaResourceIdentifier.validIdentifier("minecraft:village/plains"));
assertFalse(VanillaResourceIdentifier.validIdentifier("village"));
assertFalse(VanillaResourceIdentifier.validIdentifier(":village"));
assertFalse(VanillaResourceIdentifier.validIdentifier("minecraft:"));
assertFalse(VanillaResourceIdentifier.validIdentifier("minecraft:village:plains"));
assertFalse(VanillaResourceIdentifier.validIdentifier(null));
}
@Test
public void anAbsentConnectorTargetBecomesTheVanillaEmptyPool() {
assertEquals("minecraft:empty", VanillaResourceIdentifier.normalizeConnectorIdentifier(null));
assertEquals("minecraft:empty", VanillaResourceIdentifier.normalizeConnectorIdentifier(""));
assertEquals("minecraft:empty", VanillaResourceIdentifier.normalizeConnectorIdentifier(" "));
}
@Test
public void abareConnectorNameIsQualifiedIntoTheMinecraftNamespace() {
assertEquals("minecraft:bottom", VanillaResourceIdentifier.normalizeConnectorIdentifier("bottom"));
assertEquals("minecraft:bottom", VanillaResourceIdentifier.normalizeConnectorIdentifier(" bottom "));
assertEquals("iris:side", VanillaResourceIdentifier.normalizeConnectorIdentifier("iris:side"));
}
@Test
public void aMalformedConnectorTargetIsRejectedWithItsOriginalText() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> VanillaResourceIdentifier.normalizeConnectorIdentifier("Iris:Side"));
assertTrue(failure.getMessage(), failure.getMessage().contains("Iris:Side"));
assertThrows(IllegalArgumentException.class,
() -> VanillaResourceIdentifier.normalizeConnectorIdentifier("iris:../escape"));
}
}
@@ -0,0 +1,50 @@
package art.arcane.iris.core.structure.export;
import art.arcane.iris.engine.object.IrisDirection;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class VanillaStructureTemplateOrientationTest {
@Test
public void aHorizontalFrontIsRenderedAgainstAnUpwardTop() {
assertEquals("north_up", VanillaStructureTemplateEncoder.orientation(
IrisDirection.NORTH_NEGATIVE_Z, IrisDirection.UP_POSITIVE_Y));
assertEquals("south_up", VanillaStructureTemplateEncoder.orientation(
IrisDirection.SOUTH_POSITIVE_Z, IrisDirection.UP_POSITIVE_Y));
assertEquals("east_up", VanillaStructureTemplateEncoder.orientation(
IrisDirection.EAST_POSITIVE_X, IrisDirection.UP_POSITIVE_Y));
assertEquals("west_up", VanillaStructureTemplateEncoder.orientation(
IrisDirection.WEST_NEGATIVE_X, IrisDirection.UP_POSITIVE_Y));
}
@Test
public void averticalFrontIsRenderedAgainstItsHorizontalTop() {
assertEquals("up_north", VanillaStructureTemplateEncoder.orientation(
IrisDirection.UP_POSITIVE_Y, IrisDirection.NORTH_NEGATIVE_Z));
assertEquals("down_east", VanillaStructureTemplateEncoder.orientation(
IrisDirection.DOWN_NEGATIVE_Y, IrisDirection.EAST_POSITIVE_X));
}
@Test
public void averticalFrontCannotTakeAVerticalTop() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> VanillaStructureTemplateEncoder.orientation(
IrisDirection.UP_POSITIVE_Y, IrisDirection.UP_POSITIVE_Y));
assertTrue(failure.getMessage(), failure.getMessage().contains("horizontal top direction"));
}
@Test
public void aHorizontalFrontCannotTakeANonUpwardTop() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> VanillaStructureTemplateEncoder.orientation(
IrisDirection.NORTH_NEGATIVE_Z, IrisDirection.SOUTH_POSITIVE_Z));
assertTrue(failure.getMessage(), failure.getMessage().contains("upward top direction"));
assertThrows(IllegalArgumentException.class, () -> VanillaStructureTemplateEncoder.orientation(
IrisDirection.EAST_POSITIVE_X, IrisDirection.DOWN_NEGATIVE_Y));
}
}
@@ -0,0 +1,56 @@
package art.arcane.iris.util.common.director.handlers;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class DataVersionHandlerTest {
private final DataVersionHandler handler = new DataVersionHandler();
@Test
public void latestResolvesToTheNewestShippedDatapackVersion() throws DirectorParsingException {
assertSame(DataVersion.getLatest(), handler.parse("latest", false));
assertSame(DataVersion.getLatest(), handler.parse("LATEST", false));
}
@Test
public void anExactVersionStringResolvesRegardlessOfCase() throws DirectorParsingException {
assertSame(DataVersion.V26_2, handler.parse("26.2", false));
assertSame(DataVersion.V26_1_2, handler.parse("26.1.2", false));
}
@Test
public void theUnsupportedSentinelIsHiddenFromSuggestionsButStillParses() throws DirectorParsingException {
KList<DataVersion> possibilities = handler.getPossibilities();
assertFalse(possibilities.contains(DataVersion.UNSUPPORTED));
assertTrue(possibilities.contains(DataVersion.getLatest()));
assertSame(DataVersion.UNSUPPORTED, handler.parse(DataVersion.UNSUPPORTED.getVersion(), false));
}
@Test
public void anUnknownVersionNamesTheOffendingInput() {
DirectorParsingException failure =
assertThrows(DirectorParsingException.class, () -> handler.parse("nope", false));
assertTrue(failure.getMessage(), failure.getMessage().contains("nope"));
}
@Test
public void theRenderedFormIsTheVersionString() {
assertSame(DataVersion.V26_2.getVersion(), handler.toString(DataVersion.V26_2));
}
@Test
public void onlyDataVersionParametersAreClaimed() {
assertTrue(handler.supports(DataVersion.class));
assertFalse(handler.supports(String.class));
assertFalse(handler.supports(Integer.class));
}
}
@@ -0,0 +1,94 @@
package art.arcane.iris.util.common.director.handlers;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class PrimitiveHandlerParsingTest {
@Test
public void integerSuffixesScaleTheParsedValue() throws DirectorParsingException {
IntegerHandler handler = new IntegerHandler();
assertEquals(Integer.valueOf(5), handler.parse("5", false));
assertEquals(Integer.valueOf(64), handler.parse("4c", false));
assertEquals(Integer.valueOf(1_000), handler.parse("1k", false));
assertEquals(Integer.valueOf(200), handler.parse("2h", false));
assertEquals(Integer.valueOf(512), handler.parse("1r", false));
}
@Test
public void stackedIntegerSuffixesMultiplyTogether() throws DirectorParsingException {
assertEquals(Integer.valueOf(2_000_000), new IntegerHandler().parse("2kk", false));
}
@Test
public void anUnparseableIntegerNamesTheOffendingInput() {
DirectorParsingException failure =
assertThrows(DirectorParsingException.class, () -> new IntegerHandler().parse("abc", false));
assertTrue(failure.getMessage(), failure.getMessage().contains("abc"));
}
@Test
public void anUnscaledLongKeepsFullPrecision() throws DirectorParsingException {
LongHandler handler = new LongHandler();
assertEquals(Long.valueOf(9_007_199_254_740_993L), handler.parse("9007199254740993", false));
assertEquals(Long.valueOf(1_000L), handler.parse("1k", false));
}
@Test
public void shortAndFloatingPointHandlersShareTheSuffixTable() throws DirectorParsingException {
assertEquals(Short.valueOf((short) 32), new ShortHandler().parse("2c", false));
assertEquals(1_500F, new FloatHandler().parse("1.5k", false), 0F);
assertEquals(1_500D, new DoubleHandler().parse("1.5k", false), 0D);
}
@Test
public void byteParsingHasNoSuffixTable() throws DirectorParsingException {
ByteHandler handler = new ByteHandler();
assertEquals(Byte.valueOf((byte) 12), handler.parse("12", false));
assertThrows(DirectorParsingException.class, () -> handler.parse("1k", false));
}
@Test
public void booleanSentinelsAreCaseSensitiveAndEverythingElseFallsBackToFalse() throws DirectorParsingException {
BooleanHandler handler = new BooleanHandler();
assertNull(handler.parse("null", false));
assertNull(handler.parse("other", false));
assertNull(handler.parse("flip", false));
assertEquals(Boolean.TRUE, handler.parse("TRUE", false));
assertEquals(Boolean.FALSE, handler.parse("yes", false));
assertEquals(Boolean.FALSE, handler.parse("NULL", false));
}
@Test
public void stringParsingIsVerbatim() throws DirectorParsingException {
StringHandler handler = new StringHandler();
assertEquals(" spaced ", handler.parse(" spaced ", false));
assertEquals("", handler.parse("", false));
}
@Test
public void eachHandlerClaimsOnlyItsOwnParameterType() {
assertTrue(new IntegerHandler().supports(int.class));
assertTrue(new IntegerHandler().supports(Integer.class));
assertFalse(new IntegerHandler().supports(Long.class));
assertTrue(new LongHandler().supports(long.class));
assertTrue(new ShortHandler().supports(short.class));
assertTrue(new ByteHandler().supports(byte.class));
assertTrue(new FloatHandler().supports(float.class));
assertTrue(new DoubleHandler().supports(double.class));
assertTrue(new BooleanHandler().supports(boolean.class));
assertTrue(new StringHandler().supports(String.class));
assertFalse(new StringHandler().supports(Object.class));
}
}
@@ -0,0 +1,144 @@
package art.arcane.iris.util.common.director.handlers;
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class VectorHandlerParsingTest {
@Test
public void threeComponentsMapToTheirAxes() throws DirectorParsingException {
Vector parsed = console().parse("1, 2 ,3", false);
assertEquals(1D, parsed.getX(), 0D);
assertEquals(2D, parsed.getY(), 0D);
assertEquals(3D, parsed.getZ(), 0D);
}
@Test
public void twoComponentsAreTreatedAsAHorizontalPair() throws DirectorParsingException {
Vector parsed = console().parse("4,6", false);
assertEquals(4D, parsed.getX(), 0D);
assertEquals(0D, parsed.getY(), 0D);
assertEquals(6D, parsed.getZ(), 0D);
}
@Test
public void anyOtherComponentCountIsRejectedWithItsArity() {
DirectorParsingException failure =
assertThrows(DirectorParsingException.class, () -> console().parse("1,2,3,4", false));
assertTrue(failure.getMessage(), failure.getMessage().contains("4 components"));
assertTrue(failure.getMessage(), failure.getMessage().contains("Expected 2 or 3"));
}
@Test
public void positionalKeywordsAreRefusedForAConsoleSender() {
VectorHandler handler = console();
assertTrue(assertThrows(DirectorParsingException.class, () -> handler.parse("here", false))
.getMessage().contains("as a console"));
assertTrue(assertThrows(DirectorParsingException.class, () -> handler.parse("SELF", false))
.getMessage().contains("as a console"));
assertTrue(assertThrows(DirectorParsingException.class, () -> handler.parse("look", false))
.getMessage().contains("as a console"));
assertTrue(assertThrows(DirectorParsingException.class, () -> handler.parse("Crosshair", false))
.getMessage().contains("as a console"));
}
@Test
public void positionalKeywordsResolveForAPlayerSender() throws DirectorParsingException {
VectorHandler handler = new TestVectorHandler(true, new Vector(10, 20, 30), new Vector(-1, -2, -3), List.of());
assertEquals(new Vector(10, 20, 30), handler.parse("me", false));
assertEquals(new Vector(-1, -2, -3), handler.parse("cursor", false));
}
@Test
public void aNamedPlayerResolvesToThatPlayersPosition() throws DirectorParsingException {
Player player = mock(Player.class);
when(player.getLocation()).thenReturn(new Location(null, 7D, 8D, 9D));
VectorHandler handler = new TestVectorHandler(false, null, null, List.of(player));
assertEquals(new Vector(7, 8, 9), handler.parse("player:steve", false));
}
@Test
public void anUnmatchedPlayerQueryNamesTheQuery() {
VectorHandler handler = new TestVectorHandler(false, null, null, List.of());
DirectorParsingException failure =
assertThrows(DirectorParsingException.class, () -> handler.parse("player:steve", false));
assertTrue(failure.getMessage(), failure.getMessage().contains("steve"));
}
@Test
public void anUnrecognizedTokenResolvesToNothingRatherThanFailing() throws DirectorParsingException {
assertNull(console().parse("garbage", false));
assertNull(console().parse("", false));
}
@Test
public void theRenderedFormOmitsAZeroHeight() {
VectorHandler handler = console();
assertEquals(2, handler.toString(new Vector(1, 0, 3)).split(",").length);
assertEquals(3, handler.toString(new Vector(1, 2, 3)).split(",").length);
}
@Test
public void onlyVectorParametersAreClaimed() {
assertTrue(console().supports(Vector.class));
assertEquals(false, console().supports(Location.class));
}
private static VectorHandler console() {
return new TestVectorHandler(false, null, null, List.of());
}
private static final class TestVectorHandler extends VectorHandler {
private final boolean senderIsPlayer;
private final Vector senderVector;
private final Vector lookVector;
private final List<?> players;
private TestVectorHandler(boolean senderIsPlayer, Vector senderVector, Vector lookVector, List<?> players) {
this.senderIsPlayer = senderIsPlayer;
this.senderVector = senderVector;
this.lookVector = lookVector;
this.players = players;
}
@Override
protected boolean isSenderPlayer() {
return senderIsPlayer;
}
@Override
protected Vector getSenderVector() {
return senderVector;
}
@Override
protected Vector getLookVector() {
return lookVector;
}
@Override
protected List<?> playerPossibilities(String query) {
return players;
}
}
}
+4
View File
@@ -11,6 +11,10 @@ java {
}
}
dependencies {
testImplementation('junit:junit:4.13.2')
}
tasks.named('jar', Jar).configure {
archiveBaseName.set('iris-spi')
}
@@ -0,0 +1,172 @@
package art.arcane.iris.spi;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class IrisPlatformsBindContractTest {
@Before
public void requireUnbound() {
IrisPlatforms.unbind();
}
@After
public void releaseBinding() {
IrisPlatforms.unbind();
}
@Test
public void unboundLookupsReportAbsenceInsteadOfImprovising() {
assertFalse(IrisPlatforms.isBound());
assertNull(IrisPlatforms.getOrNull());
IllegalStateException failure = assertThrows(IllegalStateException.class, IrisPlatforms::get);
assertTrue(failure.getMessage(), failure.getMessage().contains("No Iris platform is bound"));
}
@Test
public void bindingIsVisibleThroughEveryAccessor() {
IrisPlatform platform = new StubPlatform();
IrisPlatforms.bind(platform);
assertTrue(IrisPlatforms.isBound());
assertSame(platform, IrisPlatforms.get());
assertSame(platform, IrisPlatforms.getOrNull());
}
@Test
public void rebindingTheSameInstanceIsAccepted() {
IrisPlatform platform = new StubPlatform();
IrisPlatforms.bind(platform);
IrisPlatforms.bind(platform);
assertSame(platform, IrisPlatforms.get());
}
@Test
public void aSecondPlatformCannotDisplaceTheBoundOne() {
IrisPlatform first = new StubPlatform();
IrisPlatforms.bind(first);
IllegalStateException failure = assertThrows(
IllegalStateException.class, () -> IrisPlatforms.bind(new StubPlatform()));
assertTrue(failure.getMessage(), failure.getMessage().contains("already bound"));
assertSame(first, IrisPlatforms.get());
}
@Test
public void unbindIsIdempotentAndClearsTheBinding() {
IrisPlatforms.bind(new StubPlatform());
IrisPlatforms.unbind();
IrisPlatforms.unbind();
assertFalse(IrisPlatforms.isBound());
assertNull(IrisPlatforms.getOrNull());
}
@Test
public void unbindingClearsTheOnceKeysSoLaterRunsWarnAgain() {
IrisPlatforms.bind(new StubPlatform());
assertTrue(IrisLogging.warnOnce("spi-contract-once", "first"));
assertFalse(IrisLogging.warnOnce("spi-contract-once", "second"));
IrisPlatforms.unbind();
assertTrue(IrisLogging.warnOnce("spi-contract-once", "third"));
}
private static final class StubPlatform implements IrisPlatform {
@Override
public String platformName() {
return "stub";
}
@Override
public String minecraftVersion() {
return "26.2";
}
@Override
public PlatformRegistries registries() {
throw new UnsupportedOperationException();
}
@Override
public PlatformScheduler scheduler() {
throw new UnsupportedOperationException();
}
@Override
public PlatformStructureHooks structureHooks() {
throw new UnsupportedOperationException();
}
@Override
public PlatformBiomeWriter biomeWriter() {
throw new UnsupportedOperationException();
}
@Override
public File dataFolder() {
throw new UnsupportedOperationException();
}
@Override
public File dataFile(String... path) {
throw new UnsupportedOperationException();
}
@Override
public File pluginJar() {
throw new UnsupportedOperationException();
}
@Override
public int irisVersionNumber() {
return 0;
}
@Override
public int minecraftVersionNumber() {
return 0;
}
@Override
public void callEvent(Object event) {
}
@Override
public void dispatchConsoleCommand(String command) {
}
@Override
public boolean spawnEntity(PlatformWorld world, String entityKey, double x, double y, double z) {
return false;
}
@Override
public void log(LogLevel level, String message) {
}
@Override
public void msg(String message) {
}
@Override
public void reportError(Throwable error) {
}
}
}
@@ -0,0 +1,93 @@
package art.arcane.iris.spi;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class IrisServicesRegistryContractTest {
@Before
public void requireEmptyRegistry() {
IrisServices.clear();
}
@After
public void releaseRegistry() {
IrisServices.clear();
}
@Test
public void registeredServicesResolveThroughBothAccessors() {
Greeter greeter = new Greeter();
IrisServices.register(Greeter.class, greeter);
assertSame(greeter, IrisServices.get(Greeter.class));
assertSame(greeter, IrisServices.getOrNull(Greeter.class));
}
@Test
public void anAbsentServiceNamesItselfInTheFailure() {
assertNull(IrisServices.getOrNull(Greeter.class));
IllegalStateException failure = assertThrows(IllegalStateException.class, () -> IrisServices.get(Greeter.class));
assertTrue(failure.getMessage(), failure.getMessage().contains(Greeter.class.getName()));
}
@Test
public void reregisteringATypeReplacesThePreviousImplementation() {
Greeter first = new Greeter();
Greeter second = new Greeter();
IrisServices.register(Greeter.class, first);
IrisServices.register(Greeter.class, second);
assertSame(second, IrisServices.get(Greeter.class));
}
@Test
public void removingAServiceLeavesTheRegistryUsable() {
IrisServices.register(Greeter.class, new Greeter());
IrisServices.remove(Greeter.class);
IrisServices.remove(Greeter.class);
assertNull(IrisServices.getOrNull(Greeter.class));
}
@Test
public void clearDropsEveryRegistration() {
IrisServices.register(Greeter.class, new Greeter());
IrisServices.register(Counter.class, new Counter());
IrisServices.clear();
assertNull(IrisServices.getOrNull(Greeter.class));
assertNull(IrisServices.getOrNull(Counter.class));
}
@Test
public void aMismatchedImplementationIsRejectedBeforeItEntersTheRegistry() {
assertThrows(ClassCastException.class, () -> IrisServices.register(Greeter.class, new Counter()));
assertNull(IrisServices.getOrNull(Greeter.class));
}
@Test
public void aNullImplementationIsRejectedBeforeItEntersTheRegistry() {
assertThrows(NullPointerException.class, () -> IrisServices.register(Greeter.class, null));
assertNull(IrisServices.getOrNull(Greeter.class));
}
private static final class Greeter {
}
private static final class Counter {
}
}
@@ -0,0 +1,177 @@
package art.arcane.iris.spi;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class PlatformValueContractTest {
private static final String FINGERPRINT = "a".repeat(64);
@Test
public void jigsawMetadataAcceptsTheWholeDocumentedRange() {
PlatformStructureHooks.JigsawSourceMetadata smallest =
new PlatformStructureHooks.JigsawSourceMetadata(1, 0, 0);
PlatformStructureHooks.JigsawSourceMetadata largest =
new PlatformStructureHooks.JigsawSourceMetadata(128, 7, 96);
assertEquals(1, smallest.maxDistanceHorizontal());
assertEquals(128, largest.maxDistanceHorizontal());
assertEquals(7, largest.referenceExpansion());
assertEquals(96, largest.maxStartElementHorizontalSpan());
}
@Test
public void jigsawMetadataDefaultsTheStartElementSpanToZero() {
PlatformStructureHooks.JigsawSourceMetadata metadata =
new PlatformStructureHooks.JigsawSourceMetadata(64, 3);
assertEquals(0, metadata.maxStartElementHorizontalSpan());
}
@Test
public void jigsawMetadataRejectsOutOfRangeGeometry() {
assertThrows(IllegalArgumentException.class,
() -> new PlatformStructureHooks.JigsawSourceMetadata(0, 0, 0));
assertThrows(IllegalArgumentException.class,
() -> new PlatformStructureHooks.JigsawSourceMetadata(129, 0, 0));
assertThrows(IllegalArgumentException.class,
() -> new PlatformStructureHooks.JigsawSourceMetadata(64, -1, 0));
assertThrows(IllegalArgumentException.class,
() -> new PlatformStructureHooks.JigsawSourceMetadata(64, 0, -1));
}
@Test
public void unimplementedStructureHookDefaultsRefuseInsteadOfReturningNothing() {
PlatformStructureHooks hooks = new MinimalStructureHooks();
assertEquals(List.of(), hooks.jigsawStructureKeys());
assertEquals(List.of(), hooks.templatePoolKeys());
assertThrows(UnsupportedOperationException.class, () -> hooks.jigsawSourceMetadata("iris:village"));
assertThrows(UnsupportedOperationException.class, () -> hooks.templatePoolHorizontalSpan("iris:pool"));
assertThrows(UnsupportedOperationException.class,
() -> hooks.jigsawStartPoolHorizontalSpan("iris:village", "iris:pool"));
}
@Test
public void definitionFactoriesCarryTheirEncoding() {
PlatformGenerationRegistry.Definition json = PlatformGenerationRegistry.Definition.exactJson("{}");
PlatformGenerationRegistry.Definition identity =
PlatformGenerationRegistry.Definition.conservativeIdentity("abc");
assertEquals(PlatformGenerationRegistry.Encoding.JSON, json.encoding());
assertEquals("{}", json.value());
assertEquals(PlatformGenerationRegistry.Encoding.CONSERVATIVE_IDENTITY, identity.encoding());
assertEquals("abc", identity.value());
}
@Test
public void resourceIdentityTrimsItsPartsIntoAStableKey() {
PlatformGenerationRegistry.Definition definition =
PlatformGenerationRegistry.Definition.resourceIdentity(" worldgen/biome ", " iris:plains ");
assertEquals(PlatformGenerationRegistry.Encoding.CONSERVATIVE_IDENTITY, definition.encoding());
assertEquals("registry-resource-key-v1|worldgen/biome|iris:plains", definition.value());
}
@Test
public void definitionsRejectMissingAndBlankValues() {
assertThrows(NullPointerException.class,
() -> new PlatformGenerationRegistry.Definition(null, "value"));
assertThrows(NullPointerException.class,
() -> new PlatformGenerationRegistry.Definition(PlatformGenerationRegistry.Encoding.JSON, null));
assertThrows(IllegalArgumentException.class,
() -> new PlatformGenerationRegistry.Definition(PlatformGenerationRegistry.Encoding.JSON, " "));
assertThrows(IllegalArgumentException.class,
() -> PlatformGenerationRegistry.Definition.resourceIdentity(" ", "iris:plains"));
assertThrows(IllegalArgumentException.class,
() -> PlatformGenerationRegistry.Definition.resourceIdentity("worldgen/biome", " "));
}
@Test
public void customBiomeResourceKeysAreContentAddressed() {
assertEquals("iris:biomes/" + FINGERPRINT,
PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(FINGERPRINT));
}
@Test
public void customBiomeResourceKeysRejectAnythingButALowercaseSha256() {
assertThrows(NullPointerException.class,
() -> PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(null));
assertThrows(IllegalArgumentException.class,
() -> PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(FINGERPRINT.toUpperCase()));
assertThrows(IllegalArgumentException.class,
() -> PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey("a".repeat(63)));
assertThrows(IllegalArgumentException.class,
() -> PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey("z".repeat(64)));
}
@Test
public void blockPropertiesReportWhetherTheyCarryANumericRange() {
PlatformNumericRange range = new PlatformNumericRange(0D, 15D, false, true);
PlatformBlockProperty numeric =
new PlatformBlockProperty("level", "integer", 0, List.of(0, 15), range);
PlatformBlockProperty enumerated =
new PlatformBlockProperty("axis", "string", "y", List.of("x", "y", "z"), null);
assertTrue(numeric.hasNumericRange());
assertEquals(range, numeric.numericRange());
assertEquals(0D, range.minimum(), 0D);
assertEquals(15D, range.maximum(), 0D);
assertFalse(range.exclusiveMinimum());
assertTrue(range.exclusiveMaximum());
assertFalse(enumerated.hasNumericRange());
assertEquals(List.of("x", "y", "z"), enumerated.allowedValues());
}
private static final class MinimalStructureHooks implements PlatformStructureHooks {
@Override
public List<String> structureKeys() {
return List.of();
}
@Override
public List<String> structureSetKeys() {
return List.of();
}
@Override
public List<String> structureBiomeKeys(String structureKey) {
return List.of();
}
@Override
public List<String> objectFeatureKeys() {
return List.of();
}
@Override
public List<String> reachableStructureKeys(PlatformWorld world) {
return List.of();
}
@Override
public List<String> possibleBiomeKeys(PlatformWorld world) {
return List.of();
}
@Override
public boolean placeFeature(PlatformWorld world, int x, int y, int z, String featureKey, long seed) {
return false;
}
@Override
public int[] placeStructure(PlatformWorld world, int chunkX, int chunkZ, String structureKey, long seed, int maxSpan) {
return new int[0];
}
@Override
public boolean supportsStructurePlacement() {
return false;
}
}
}