Parameterize template-identical tests

- Split PackValidatorCustomBiomeSpawnTest into parameterized accepted and rejected suites plus the resolver-failure case
- Drive IrisCarveWallPaletteScopeTest from its four generation-scope modes
- Parameterize the legacy bilerp parity samples in IrisComplexGridBoundsCacheTest behind Enclosed
- Parameterize the resolved bounds in IrisStructureYBandTest
- Parameterize the entity key normalization in IrisBiomeCustomSpawnTest
- Parameterize the platform identity in IrisWorldIdentityTest
- Parameterize the layer lock in IrisBiomeLayerSlopeSourceTest
This commit is contained in:
Brian Neumann-Fopiano
2026-09-08 05:27:49 -04:00
parent c3bc0f5d18
commit 71e3af727f
7 changed files with 381 additions and 331 deletions
@@ -2,178 +2,172 @@ package art.arcane.iris.core.pack;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(Enclosed.class)
public class PackValidatorCustomBiomeSpawnTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private static final Function<String, PackSpawnValidator.SpawnCategoryResolution> MONSTER =
key -> PackSpawnValidator.SpawnCategoryResolution.known("monster");
private static final Function<String, PackSpawnValidator.SpawnCategoryResolution> MISC =
key -> PackSpawnValidator.SpawnCategoryResolution.known("misc");
private static final Function<String, PackSpawnValidator.SpawnCategoryResolution> AXOLOTLS =
key -> PackSpawnValidator.SpawnCategoryResolution.known("axolotls");
private static final Function<String, PackSpawnValidator.SpawnCategoryResolution> UNKNOWN =
key -> PackSpawnValidator.SpawnCategoryResolution.unknown();
@Test
public void acceptsExplicitMatchingSpawnGroup() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MONSTER\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("monster"));
assertTrue(errors.isEmpty());
}
@Test
public void acceptsBiomeWithoutCustomSpawns() throws Exception {
File biomes = createBiomes("{\"name\":\"Plains\"}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("monster"));
assertTrue(errors.isEmpty());
}
@Test
public void rejectsMissingSpawnGroup() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("monster"));
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("must declare group 'MONSTER'"));
}
@Test
public void acceptsImplicitMiscSpawnGroup() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"effects\",\"spawns\":[{\"type\":\"minecraft:armor_stand\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("misc"));
assertTrue(errors.isEmpty());
}
@Test
public void rejectsSpawnGroupThatDisagreesWithRegistry() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MISC\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("monster"));
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("live entity registry requires 'MONSTER'"));
}
@Test
public void acceptsAxolotlSpawnCategory() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"cave\",\"spawns\":[{\"type\":\"minecraft:axolotl\",\"group\":\"AXOLOTLS\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("axolotls"));
assertTrue(errors.isEmpty());
}
@Test
public void rejectsUnknownSpawnEntity() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"missing:entity\",\"group\":\"MISC\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.unknown());
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("unknown entity type 'missing:entity'"));
}
@Test
public void rejectsCaseNormalizedGroupThatRuntimeWouldNotParse() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"monster\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(
biomes, key -> PackSpawnValidator.SpawnCategoryResolution.known("monster"));
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("unknown group 'monster'"));
}
@Test
public void rejectsWrongCustomDerivativeContainerType() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":{}}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, null);
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("customDerivitives must be an array"));
}
@Test
public void rejectsWrongSpawnContainerType() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":{}}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, null);
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("spawns must be an array"));
}
@Test
public void acceptsNullCustomDerivativeContainerAsAbsent() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":null}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, null);
assertTrue(errors.isEmpty());
}
@Test
public void acceptsNullSpawnContainerAsAbsent() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":null}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, null);
assertTrue(errors.isEmpty());
}
@Test
public void acceptsNamespacedCustomBiomeTags() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"tags\":[\"minecraft:allows_surface_slime_spawns\"]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, null);
assertTrue(errors.isEmpty());
}
@Test
public void rejectsUnsafeCustomBiomeTags() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"tags\":[\"minecraft:../outside\"]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, null);
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("invalid tag"));
}
@Test
public void convertsSpawnCategoryResolverFailureIntoBlockingError() throws Exception {
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MONSTER\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, key -> {
throw new IllegalStateException("registry unavailable");
});
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("spawn category lookup failed"));
assertTrue(errors.get(0).contains("registry unavailable"));
}
private File createBiomes(String json) throws Exception {
private static File createBiomes(TemporaryFolder temporaryFolder, String json) throws Exception {
File biomes = temporaryFolder.newFolder("biomes");
File biome = new File(biomes, "test.json");
Files.writeString(biome.toPath(), json, StandardCharsets.UTF_8);
return biomes;
}
@RunWith(Parameterized.class)
public static class Accepted {
@Parameters(name = "{0}")
public static Collection<Object[]> biomes() {
return List.of(
new Object[]{"explicitMatchingSpawnGroup",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MONSTER\"}]}]}",
MONSTER},
new Object[]{"biomeWithoutCustomSpawns",
"{\"name\":\"Plains\"}",
MONSTER},
new Object[]{"implicitMiscSpawnGroup",
"{\"customDerivitives\":[{\"id\":\"effects\",\"spawns\":[{\"type\":\"minecraft:armor_stand\"}]}]}",
MISC},
new Object[]{"axolotlSpawnCategory",
"{\"customDerivitives\":[{\"id\":\"cave\",\"spawns\":[{\"type\":\"minecraft:axolotl\",\"group\":\"AXOLOTLS\"}]}]}",
AXOLOTLS},
new Object[]{"nullCustomDerivativeContainerAsAbsent",
"{\"customDerivitives\":null}",
null},
new Object[]{"nullSpawnContainerAsAbsent",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":null}]}",
null},
new Object[]{"namespacedCustomBiomeTags",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"tags\":[\"minecraft:allows_surface_slime_spawns\"]}]}",
null}
);
}
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private final String scenario;
private final String json;
private final Function<String, PackSpawnValidator.SpawnCategoryResolution> categoryResolver;
public Accepted(
String scenario,
String json,
Function<String, PackSpawnValidator.SpawnCategoryResolution> categoryResolver
) {
this.scenario = scenario;
this.json = json;
this.categoryResolver = categoryResolver;
}
@Test
public void reportsNoSpawnErrors() throws Exception {
File biomes = createBiomes(temporaryFolder, json);
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, categoryResolver);
assertTrue(scenario + " " + errors, errors.isEmpty());
}
}
@RunWith(Parameterized.class)
public static class Rejected {
@Parameters(name = "{0}")
public static Collection<Object[]> biomes() {
return List.of(
new Object[]{"missingSpawnGroup",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\"}]}]}",
MONSTER, "must declare group 'MONSTER'"},
new Object[]{"spawnGroupThatDisagreesWithRegistry",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MISC\"}]}]}",
MONSTER, "live entity registry requires 'MONSTER'"},
new Object[]{"unknownSpawnEntity",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"missing:entity\",\"group\":\"MISC\"}]}]}",
UNKNOWN, "unknown entity type 'missing:entity'"},
new Object[]{"caseNormalizedGroupThatRuntimeWouldNotParse",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"monster\"}]}]}",
MONSTER, "unknown group 'monster'"},
new Object[]{"wrongCustomDerivativeContainerType",
"{\"customDerivitives\":{}}",
null, "customDerivitives must be an array"},
new Object[]{"wrongSpawnContainerType",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":{}}]}",
null, "spawns must be an array"},
new Object[]{"unsafeCustomBiomeTags",
"{\"customDerivitives\":[{\"id\":\"swamp\",\"tags\":[\"minecraft:../outside\"]}]}",
null, "invalid tag"}
);
}
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private final String scenario;
private final String json;
private final Function<String, PackSpawnValidator.SpawnCategoryResolution> categoryResolver;
private final String expectedError;
public Rejected(
String scenario,
String json,
Function<String, PackSpawnValidator.SpawnCategoryResolution> categoryResolver,
String expectedError
) {
this.scenario = scenario;
this.json = json;
this.categoryResolver = categoryResolver;
this.expectedError = expectedError;
}
@Test
public void reportsExactlyOneSpawnError() throws Exception {
File biomes = createBiomes(temporaryFolder, json);
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, categoryResolver);
assertEquals(scenario + " " + errors, 1, errors.size());
assertTrue(errors.get(0), errors.get(0).contains(expectedError));
}
}
public static class ResolverFailure {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void convertsSpawnCategoryResolverFailureIntoBlockingError() throws Exception {
File biomes = createBiomes(temporaryFolder,
"{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MONSTER\"}]}]}");
List<String> errors = PackSpawnValidator.validateCustomBiomeSpawns(biomes, key -> {
throw new IllegalStateException("registry unavailable");
});
assertEquals(1, errors.size());
assertTrue(errors.get(0).contains("spawn category lookup failed"));
assertTrue(errors.get(0).contains("registry unavailable"));
}
}
}
@@ -24,11 +24,16 @@ import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.MockedStatic;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
@@ -47,25 +52,27 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
@RunWith(Parameterized.class)
public class IrisCarveWallPaletteScopeTest {
@Test
public void naturalWallsUseOneSelectedDataLookup() throws Exception {
verifyWalls(Mode.NATURAL);
@Parameters(name = "{0}")
public static Collection<Object[]> scopes() {
return List.of(
new Object[]{Mode.NATURAL},
new Object[]{Mode.CONTENT},
new Object[]{Mode.DETACHED},
new Object[]{Mode.ASSEMBLY}
);
}
private final Mode mode;
public IrisCarveWallPaletteScopeTest(Mode mode) {
this.mode = mode;
}
@Test
public void contentWallsRestoreDataAfterNestedSampling() throws Exception {
verifyWalls(Mode.CONTENT);
}
@Test
public void detachedWallsUseDetachedData() throws Exception {
verifyWalls(Mode.DETACHED);
}
@Test
public void assemblyWallsUseUnpublishedData() throws Exception {
verifyWalls(Mode.ASSEMBLY);
public void wallsUseTheDataOfTheirGenerationScope() throws Exception {
verifyWalls(mode);
}
private static void verifyWalls(Mode mode) throws Exception {
@@ -159,7 +166,7 @@ public class IrisCarveWallPaletteScopeTest {
return runtime;
}
private enum Mode {
enum Mode {
NATURAL,
CONTENT,
DETACHED,
@@ -6,11 +6,17 @@ import art.arcane.iris.engine.object.IrisInterpolator;
import art.arcane.iris.util.project.interpolation.NoiseBounds;
import art.arcane.iris.util.project.interpolation.NoiseBoundsProvider;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
@@ -19,109 +25,102 @@ import static org.junit.Assert.assertNotEquals;
import static org.mockito.Answers.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
@RunWith(Enclosed.class)
public class IrisComplexGridBoundsCacheTest {
public static class CornerCache {
@Test
public void gridBoundsCacheIsIsolatedPerComplex() throws Exception {
IrisComplex first = createComplex();
IrisComplex second = createComplex();
Method cornerBounds = cornerBoundsMethod();
public void gridBoundsCacheIsIsolatedPerComplex() throws Exception {
IrisComplex first = createComplex();
IrisComplex second = createComplex();
Method cornerBounds = cornerBoundsMethod();
long firstPacked = invokeCornerBounds(first, cornerBounds, new CountingInterpolator(1.25D, 2.5D), 64, -32);
long secondPacked = invokeCornerBounds(second, cornerBounds, new CountingInterpolator(10.25D, 20.5D), 64, -32);
long firstPacked = invokeCornerBounds(first, cornerBounds, new CountingInterpolator(1.25D, 2.5D), 64, -32);
long secondPacked = invokeCornerBounds(second, cornerBounds, new CountingInterpolator(10.25D, 20.5D), 64, -32);
assertNotEquals(firstPacked, secondPacked);
assertEquals(1.25F, unpackLow(firstPacked), 0D);
assertEquals(2.5F, unpackHigh(firstPacked), 0D);
assertEquals(10.25F, unpackLow(secondPacked), 0D);
assertEquals(20.5F, unpackHigh(secondPacked), 0D);
}
assertNotEquals(firstPacked, secondPacked);
assertEquals(1.25F, unpackLow(firstPacked), 0D);
assertEquals(2.5F, unpackHigh(firstPacked), 0D);
assertEquals(10.25F, unpackLow(secondPacked), 0D);
assertEquals(20.5F, unpackHigh(secondPacked), 0D);
}
@Test
public void gridBoundsCacheReusesCornersWithinSameComplex() throws Exception {
IrisComplex complex = createComplex();
Method cornerBounds = cornerBoundsMethod();
CountingInterpolator interpolator = new CountingInterpolator(3.5D, 7.25D);
public void gridBoundsCacheReusesCornersWithinSameComplex() throws Exception {
IrisComplex complex = createComplex();
Method cornerBounds = cornerBoundsMethod();
CountingInterpolator interpolator = new CountingInterpolator(3.5D, 7.25D);
long firstPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
long secondPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
long firstPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
long secondPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
assertEquals(firstPacked, secondPacked);
assertEquals(1, interpolator.getInvocations());
}
assertEquals(firstPacked, secondPacked);
assertEquals(1, interpolator.getInvocations());
}
@Test
public void nonFiniteCornerBoundsAreNeverCached() throws Exception {
IrisComplex complex = createComplex();
Method cornerBounds = cornerBoundsMethod();
CountingInterpolator interpolator = new CountingInterpolator(Double.NaN, 7.25D);
public void nonFiniteCornerBoundsAreNeverCached() throws Exception {
IrisComplex complex = createComplex();
Method cornerBounds = cornerBoundsMethod();
CountingInterpolator interpolator = new CountingInterpolator(Double.NaN, 7.25D);
long firstPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
long secondPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
long firstPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
long secondPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
assertTrue(Double.isNaN(unpackLow(firstPacked)));
assertTrue(Double.isNaN(unpackLow(secondPacked)));
assertEquals(2, interpolator.getInvocations());
}
assertTrue(Double.isNaN(unpackLow(firstPacked)));
assertTrue(Double.isNaN(unpackLow(secondPacked)));
assertEquals(2, interpolator.getInvocations());
}
@Test
public void alignedGridSampleUsesOnlyTheContributingCorner() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CountingInterpolator interpolator = new CountingInterpolator(3.5D, 7.25D);
public void alignedGridSampleUsesOnlyTheContributingCorner() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CountingInterpolator interpolator = new CountingInterpolator(3.5D, 7.25D);
NoiseBounds bounds = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, 64D, -32D);
NoiseBounds bounds = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, 64D, -32D);
assertEquals(3.5F, bounds.min(), 0D);
assertEquals(7.25F, bounds.max(), 0D);
assertEquals(1, interpolator.getInvocations());
assertEquals(3.5F, bounds.min(), 0D);
assertEquals(7.25F, bounds.max(), 0D);
assertEquals(1, interpolator.getInvocations());
}
}
@Test
public void horizontalGridAxisMatchesLegacyBilerpBitForBit() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
double x = 67D;
double z = -32D;
@RunWith(Parameterized.class)
public static class LegacyBilerpParity {
@Parameters(name = "x={0} z={1}")
public static Collection<Object[]> samples() {
return List.of(
new Object[]{67D, -32D, 2},
new Object[]{-32D, -29D, 2},
new Object[]{-29D, 67D, 4}
);
}
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
private final double x;
private final double z;
private final int expectedInvocations;
assertBoundsBitsEqual(expected, actual);
assertEquals(2, interpolator.getInvocations());
public LegacyBilerpParity(double x, double z, int expectedInvocations) {
this.x = x;
this.z = z;
this.expectedInvocations = expectedInvocations;
}
@Test
public void gridSampleMatchesLegacyBilerpBitForBit() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
assertBoundsBitsEqual(expected, actual);
assertEquals(expectedInvocations, interpolator.getInvocations());
}
}
@Test
public void verticalGridAxisMatchesLegacyBilerpBitForBitAtNegativeCoordinates() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
double x = -32D;
double z = -29D;
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
assertBoundsBitsEqual(expected, actual);
assertEquals(2, interpolator.getInvocations());
}
@Test
public void interiorGridSampleMatchesLegacyBilerpBitForBit() throws Exception {
IrisComplex complex = createComplex();
Method gridSampleBounds = gridSampleBoundsMethod();
CoordinateInterpolator interpolator = new CoordinateInterpolator();
double x = -29D;
double z = 67D;
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
NoiseBounds expected = legacyGridSampleBounds(x, z);
assertBoundsBitsEqual(expected, actual);
assertEquals(4, interpolator.getInvocations());
}
private IrisComplex createComplex() throws Exception {
private static IrisComplex createComplex() throws Exception {
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
Field generatorBounds = IrisComplex.class.getDeclaredField("generatorBounds");
@@ -138,7 +137,7 @@ public class IrisComplexGridBoundsCacheTest {
return complex;
}
private Object newCache(Constructor<?> constructor) {
private static Object newCache(Constructor<?> constructor) {
try {
return constructor.newInstance();
} catch (ReflectiveOperationException e) {
@@ -146,7 +145,7 @@ public class IrisComplexGridBoundsCacheTest {
}
}
private Method cornerBoundsMethod() throws Exception {
private static Method cornerBoundsMethod() throws Exception {
Class<?> cacheClass = Class.forName("art.arcane.iris.engine.IrisComplex$GridBoundsCache");
Method method = IrisComplex.class.getDeclaredMethod(
"cornerBounds",
@@ -162,7 +161,7 @@ public class IrisComplexGridBoundsCacheTest {
return method;
}
private Method gridSampleBoundsMethod() throws Exception {
private static Method gridSampleBoundsMethod() throws Exception {
Method method = IrisComplex.class.getDeclaredMethod(
"gridSampleBounds",
Engine.class,
@@ -176,14 +175,14 @@ public class IrisComplexGridBoundsCacheTest {
return method;
}
private long invokeCornerBounds(IrisComplex complex, Method method, IrisInterpolator interpolator, int x, int z) throws Exception {
private static long invokeCornerBounds(IrisComplex complex, Method method, IrisInterpolator interpolator, int x, int z) throws Exception {
Field gridBoundsCache = IrisComplex.class.getDeclaredField("gridBoundsCache");
gridBoundsCache.setAccessible(true);
ThreadLocal<?> cache = (ThreadLocal<?>) gridBoundsCache.get(complex);
return (long) method.invoke(complex, cache.get(), null, interpolator, 0, new IrisGenerator[0], x, z);
}
private NoiseBounds invokeGridSampleBounds(
private static NoiseBounds invokeGridSampleBounds(
IrisComplex complex,
Method method,
IrisInterpolator interpolator,
@@ -193,7 +192,7 @@ public class IrisComplexGridBoundsCacheTest {
return (NoiseBounds) method.invoke(complex, null, interpolator, 0, new IrisGenerator[0], x, z);
}
private NoiseBounds legacyGridSampleBounds(double x, double z) {
private static NoiseBounds legacyGridSampleBounds(double x, double z) {
int grid = 4;
int xi = (int) Math.floor(x);
int zi = (int) Math.floor(z);
@@ -212,14 +211,14 @@ public class IrisComplexGridBoundsCacheTest {
);
}
private NoiseBounds packedCoordinateBounds(int x, int z) {
private static NoiseBounds packedCoordinateBounds(int x, int z) {
return new NoiseBounds(
(float) CoordinateInterpolator.low(x, z),
(float) CoordinateInterpolator.high(x, z)
);
}
private double legacyBiLerp(
private static double legacyBiLerp(
double v00,
double v10,
double v01,
@@ -232,16 +231,16 @@ public class IrisComplexGridBoundsCacheTest {
return a + ((b - a) * fz);
}
private void assertBoundsBitsEqual(NoiseBounds expected, NoiseBounds actual) {
private static void assertBoundsBitsEqual(NoiseBounds expected, NoiseBounds actual) {
assertEquals(Double.doubleToRawLongBits(expected.min()), Double.doubleToRawLongBits(actual.min()));
assertEquals(Double.doubleToRawLongBits(expected.max()), Double.doubleToRawLongBits(actual.max()));
}
private float unpackLow(long packed) {
private static float unpackLow(long packed) {
return Float.intBitsToFloat((int) (packed >>> 32));
}
private float unpackHigh(long packed) {
private static float unpackHigh(long packed) {
return Float.intBitsToFloat((int) packed);
}
@@ -1,29 +1,38 @@
package art.arcane.iris.engine.object;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(Parameterized.class)
public class IrisBiomeCustomSpawnTest {
@Test
public void normalizesNamespacedEntityKey() {
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType(" Minecraft:Slime ");
@Parameters(name = "{0} -> {1}")
public static Collection<Object[]> keys() {
return List.of(
new Object[]{" Minecraft:Slime ", "minecraft:slime"},
new Object[]{"Slime", "minecraft:slime"},
new Object[]{" ", null}
);
}
assertEquals("minecraft:slime", spawn.getTypeKey());
private final String authored;
private final String expectedKey;
public IrisBiomeCustomSpawnTest(String authored, String expectedKey) {
this.authored = authored;
this.expectedKey = expectedKey;
}
@Test
public void prefixesBareEntityKey() {
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType("Slime");
public void normalizesTheEntityKey() {
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType(authored);
assertEquals("minecraft:slime", spawn.getTypeKey());
}
@Test
public void returnsNullForBlankEntityKey() {
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType(" ");
assertNull(spawn.getTypeKey());
assertEquals(expectedKey, spawn.getTypeKey());
}
}
@@ -8,6 +8,12 @@ import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
@@ -17,19 +23,25 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(Parameterized.class)
public final class IrisBiomeLayerSlopeSourceTest {
@Test
public void explicitSlopeStreamControlsSurfacePaletteSelection() {
assertExplicitSlopeStreamControlsSurfacePaletteSelection(false);
@Parameters(name = "lockLayers={0}")
public static Collection<Object[]> layerLocks() {
return List.of(
new Object[]{false},
new Object[]{true}
);
}
private final boolean lockLayers;
public IrisBiomeLayerSlopeSourceTest(boolean lockLayers) {
this.lockLayers = lockLayers;
}
@Test
public void explicitSlopeStreamControlsLockedSurfacePaletteSelection() {
assertExplicitSlopeStreamControlsSurfacePaletteSelection(true);
}
@SuppressWarnings("unchecked")
private static void assertExplicitSlopeStreamControlsSurfacePaletteSelection(boolean lockLayers) {
public void explicitSlopeStreamControlsSurfacePaletteSelection() {
IrisData data = mock(IrisData.class);
IrisComplex hostComplex = mock(IrisComplex.class);
ProceduralStream<Double> hostSlope = mock(ProceduralStream.class);
@@ -3,51 +3,68 @@ package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(Enclosed.class)
public class IrisStructureYBandTest {
@Test
public void authoredBoundsResolveInOrder() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-120).setMax(-20);
@RunWith(Parameterized.class)
public static class Resolution {
@Parameters(name = "min={0} max={1}")
public static Collection<Object[]> bands() {
return List.of(
new Object[]{-120, -20, -120, -20},
new Object[]{-20, -120, -120, -20},
new Object[]{-64, -64, -64, -64}
);
}
assertEquals(-120, band.resolvedMin());
assertEquals(-20, band.resolvedMax());
private final int min;
private final int max;
private final int expectedMin;
private final int expectedMax;
public Resolution(int min, int max, int expectedMin, int expectedMax) {
this.min = min;
this.max = max;
this.expectedMin = expectedMin;
this.expectedMax = expectedMax;
}
@Test
public void authoredBoundsResolveInOrder() {
IrisStructureYBand band = new IrisStructureYBand().setMin(min).setMax(max);
assertEquals(expectedMin, band.resolvedMin());
assertEquals(expectedMax, band.resolvedMax());
}
}
@Test
public void reversedBoundsNormalizeInsteadOfInverting() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-20).setMax(-120);
public static class SchemaBounds {
@Test
public void numericSchemaBoundsCoverTheWholeBuildRange() throws NoSuchFieldException {
assertBounds("min");
assertBounds("max");
}
assertEquals(-120, band.resolvedMin());
assertEquals(-20, band.resolvedMax());
}
private void assertBounds(String fieldName) throws NoSuchFieldException {
Field field = IrisStructureYBand.class.getDeclaredField(fieldName);
MinNumber minimum = field.getAnnotation(MinNumber.class);
MaxNumber maximum = field.getAnnotation(MaxNumber.class);
@Test
public void collapsedBoundsResolveToASingleY() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-64).setMax(-64);
assertEquals(-64, band.resolvedMin());
assertEquals(-64, band.resolvedMax());
}
@Test
public void numericSchemaBoundsCoverTheWholeBuildRange() throws NoSuchFieldException {
assertBounds("min");
assertBounds("max");
}
private void assertBounds(String fieldName) throws NoSuchFieldException {
Field field = IrisStructureYBand.class.getDeclaredField(fieldName);
MinNumber minimum = field.getAnnotation(MinNumber.class);
MaxNumber maximum = field.getAnnotation(MaxNumber.class);
assertNotNull(minimum);
assertNotNull(maximum);
assertEquals(-4064D, minimum.value(), 0D);
assertEquals(4064D, maximum.value(), 0D);
assertNotNull(minimum);
assertNotNull(maximum);
assertEquals(-4064D, minimum.value(), 0D);
assertEquals(4064D, maximum.value(), 0D);
}
}
}
@@ -1,25 +1,37 @@
package art.arcane.iris.engine.object;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class IrisWorldIdentityTest {
@Test
public void usesPlatformIdentity() {
IrisWorld world = IrisWorld.builder()
.platformIdentity("iris:bukkit")
.build();
@Parameters(name = "{0}")
public static Collection<Object[]> identities() {
return List.of(
new Object[]{"iris:bukkit"},
new Object[]{"minecraft:the_nether"}
);
}
assertEquals("iris:bukkit", world.identity());
private final String platformIdentity;
public IrisWorldIdentityTest(String platformIdentity) {
this.platformIdentity = platformIdentity;
}
@Test
public void preservesModdedIdentity() {
public void preservesThePlatformIdentity() {
IrisWorld world = IrisWorld.builder()
.platformIdentity("minecraft:the_nether")
.platformIdentity(platformIdentity)
.build();
assertEquals("minecraft:the_nether", world.identity());
assertEquals(platformIdentity, world.identity());
}
}