mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Full audit fixpass: 93 defect fixes, 14 perf wins, vestigial cleanup
This commit is contained in:
1 parent
13e02ecd2f
commit
14b6280668
215 files changed
+4604
-2407
No files matched your search
@@ -1,11 +1,32 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisSettingsDefaultsTest {
|
||||
@Test
|
||||
public void concurrencyIsNotSerializedIntoSettingsJson() {
|
||||
String json = new Gson().toJson(new IrisSettings());
|
||||
|
||||
assertFalse("settings.json must not advertise an unconfigurable concurrency block",
|
||||
json.contains("\"concurrency\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyConcurrencyBlockStillLoadsAndKeepsDerivedValues() {
|
||||
IrisSettings loaded = new Gson().fromJson(
|
||||
"{\"concurrency\":{\"parallelism\":8,\"ioParallelism\":4}}", IrisSettings.class);
|
||||
|
||||
assertNotNull(loaded.getConcurrency());
|
||||
assertEquals(Math.max(2, Runtime.getRuntime().availableProcessors()),
|
||||
loaded.getConcurrency().getParallelism());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void datapackIngestStaysAutomaticWhileEditableConversionIsOptIn() {
|
||||
IrisSettings.IrisSettingsGeneral settings = new IrisSettings.IrisSettingsGeneral();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class WorldEditLinkDetectionTest {
|
||||
@After
|
||||
public void resetDetectionState() {
|
||||
WorldEditLink.invalidate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negativeDetectionIsNotMemoized() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
boolean[] answers = {false, true};
|
||||
|
||||
assertFalse(WorldEditLink.hasWorldEdit(() -> answers[calls.getAndIncrement()]));
|
||||
assertTrue("WorldEdit enabling after a negative probe must be picked up without a restart",
|
||||
WorldEditLink.hasWorldEdit(() -> answers[calls.getAndIncrement()]));
|
||||
assertEquals(2, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void positiveDetectionIsMemoized() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
assertTrue(WorldEditLink.hasWorldEdit(() -> {
|
||||
calls.incrementAndGet();
|
||||
return true;
|
||||
}));
|
||||
assertTrue(WorldEditLink.hasWorldEdit(() -> {
|
||||
throw new AssertionError("memoized positive must not re-probe");
|
||||
}));
|
||||
assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectionFailureDoesNotLatchFalse() {
|
||||
assertTrue(WorldEditLink.hasWorldEdit(() -> true));
|
||||
WorldEditLink.invalidate();
|
||||
assertTrue(WorldEditLink.hasWorldEdit(() -> true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectorFailureIsNotCachedAndDoesNotThrow() {
|
||||
assertFalse(WorldEditLink.hasWorldEdit(() -> {
|
||||
throw new IllegalStateException("server not ready");
|
||||
}));
|
||||
assertTrue(WorldEditLink.hasWorldEdit(() -> true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package art.arcane.iris.core.localization;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ResetMantleHelpTextTest {
|
||||
@Test
|
||||
public void resetMantleParamDescribesTheWholeWorldMantle() {
|
||||
String english = DirectorCommandMessages
|
||||
.COMMAND_DEVELOPER_PARAM_DELETE_MANTLE_DATA_SCAN_AREA_FIRST_FULL_REGENERATION_FROM_SCRATCH
|
||||
.english().toLowerCase(Locale.ROOT);
|
||||
|
||||
assertTrue("help text must say the entire world mantle is deleted", english.contains("entire"));
|
||||
assertFalse("help text must not claim the deletion is scoped to the scan area", english.contains("scan area"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void goldenhashDirectorDescribesTheWholeWorldMantle() {
|
||||
String english = DirectorCommandMessages
|
||||
.COMMAND_DEVELOPER_DIRECTOR_GENERATE_CHUNKS_INTO_BUFFERS_NO_WORLD_WRITES_HASH_BLOCKS_BIOMES_CAPTURES_GOLDEN
|
||||
.english().toLowerCase(Locale.ROOT);
|
||||
|
||||
assertFalse("director text must not claim the mantle reset is scoped to the scan area",
|
||||
english.contains("scanned area"));
|
||||
assertTrue("director text must say the entire world mantle is deleted", english.contains("entire"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyIdsStayStableAcrossTheWordingFix() {
|
||||
assertTrue(DirectorCommandMessages
|
||||
.COMMAND_DEVELOPER_PARAM_DELETE_MANTLE_DATA_SCAN_AREA_FIRST_FULL_REGENERATION_FROM_SCRATCH
|
||||
.id().endsWith("delete_mantle_data_scan_area_first_full_regeneration_from_scratch"));
|
||||
assertTrue(DirectorCommandMessages
|
||||
.COMMAND_DEVELOPER_DIRECTOR_GENERATE_CHUNKS_INTO_BUFFERS_NO_WORLD_WRITES_HASH_BLOCKS_BIOMES_CAPTURES_GOLDEN
|
||||
.id().endsWith("generate_chunks_into_buffers_no_world_writes_hash_blocks_biomes_captures_golden"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
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.assertTrue;
|
||||
|
||||
public class PackBiomeLayerValidatorTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void rejectsMoreCeilingLayersThanLayers() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/nested/cave.json",
|
||||
"{\"layers\":[{}],\"caveCeilingLayers\":[{},{},{}]}");
|
||||
|
||||
assertEquals(List.of(
|
||||
"Biome 'nested/cave' declares 3 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."
|
||||
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void respectsSingleEntryDefaultsWhenFieldsAreAbsent() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
// Both fields default to one entry, so omitting both is legal...
|
||||
write(pack, "biomes/defaulted.json", "{\"name\":\"Defaulted\"}");
|
||||
// ...and two ceiling entries against the implicit single default layer is not.
|
||||
write(pack, "biomes/implicit.json", "{\"caveCeilingLayers\":[{},{}]}");
|
||||
|
||||
assertEquals(List.of(
|
||||
"Biome 'implicit' declares 2 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."
|
||||
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsEqualOrFewerCeilingLayers() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/ok.json", "{\"layers\":[{},{},{}],\"caveCeilingLayers\":[{},{}]}");
|
||||
|
||||
assertTrue(PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsNonArrayLayerFields() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/bad.json", "{\"layers\":7,\"caveCeilingLayers\":\"nope\"}");
|
||||
|
||||
assertEquals(List.of(
|
||||
"Biome 'bad' layers must be an array.",
|
||||
"Biome 'bad' caveCeilingLayers must be an array."
|
||||
), PackBiomeLayerValidator.validateCeilingLayerCounts(new File(pack, "biomes")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ceilingLayerOverflowBlocksFullPackValidation() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"]}");
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
write(pack, "biomes/biome.json", "{\"name\":\"Biome\",\"layers\":[{}],\"caveCeilingLayers\":[{},{}]}");
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Biome 'biome' declares 2 caveCeilingLayers but only 1 layers. caveCeilingLayers reuses the layers height generators and must not have more entries."));
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path target = new File(root, relative).toPath();
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.writeString(target, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackDownloadBranchDefaultTest {
|
||||
@Test
|
||||
public void sharedDefaultBranchIsStable() {
|
||||
assertEquals("stable", PackDownloader.DEFAULT_BRANCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void packRepositoryDefaultsToTheSharedBranch() {
|
||||
assertEquals(PackDownloader.DEFAULT_BRANCH, IrisPackRepository.builder().build().getBranch());
|
||||
assertEquals(PackDownloader.DEFAULT_BRANCH, IrisPackRepository.from("overworld").getBranch());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managedPacksIgnoreTheBranchEntirely() {
|
||||
assertTrue(PackDownloader.isManagedPack("overworld"));
|
||||
assertTrue(PackDownloader.isManagedPack("underworld"));
|
||||
}
|
||||
}
|
||||
@@ -420,7 +420,9 @@ public class PackDownloaderTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceOverwriteRefusesLoadedPackData() throws Exception {
|
||||
public void forceOverwriteReplacesEnginelessLoadedPackData() throws Exception {
|
||||
// A registered loader with no engines is a stale catalog registration (startup
|
||||
// validation registers one per visible pack); it must not block a forced update.
|
||||
File packsFolder = temp.newFolder("loaded-packs");
|
||||
File target = writePack(packsFolder.toPath().resolve("active_pack"), "active_pack", "old");
|
||||
File extracted = writePack(temp.newFolder("loaded-update").toPath(), "active_pack", "new");
|
||||
@@ -435,10 +437,41 @@ public class PackDownloaderTest {
|
||||
}
|
||||
);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("new", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8));
|
||||
assertTransactionStateClean(packsFolder);
|
||||
} finally {
|
||||
loaded.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceOverwriteRefusesPackWithLiveEngines() throws Exception {
|
||||
File packsFolder = temp.newFolder("engine-packs");
|
||||
File target = writePack(packsFolder.toPath().resolve("active_pack"), "active_pack", "old");
|
||||
File extracted = writePack(temp.newFolder("engine-update").toPath(), "active_pack", "new");
|
||||
// Engines attach to detached openRuntime loaders in production, never to the cached
|
||||
// IrisData.get instance; the refusal gate must see this registration through the
|
||||
// engine index.
|
||||
IrisData loaded = IrisData.openRuntime(target);
|
||||
art.arcane.iris.engine.framework.Engine engine =
|
||||
org.mockito.Mockito.mock(art.arcane.iris.engine.framework.Engine.class);
|
||||
loaded.registerEngine(engine);
|
||||
try {
|
||||
PackDownloader.PackInstallResult result = PackDownloader.installExtractedPack(
|
||||
packsFolder,
|
||||
extracted,
|
||||
true,
|
||||
"active_pack",
|
||||
ignored -> {
|
||||
}
|
||||
);
|
||||
|
||||
assertNull(result);
|
||||
assertEquals("old", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8));
|
||||
assertTransactionStateClean(packsFolder);
|
||||
} finally {
|
||||
loaded.unregisterEngine(engine);
|
||||
loaded.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisObjectMarker;
|
||||
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackExportClosureTest {
|
||||
private static IrisObjectPlacement placement(String objectKey, String... markerKeys) {
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement();
|
||||
placement.setPlace(new KList<>(objectKey));
|
||||
KList<IrisObjectMarker> markers = new KList<>();
|
||||
for (String markerKey : markerKeys) {
|
||||
IrisObjectMarker marker = new IrisObjectMarker();
|
||||
marker.setMarker(markerKey);
|
||||
markers.add(marker);
|
||||
}
|
||||
placement.setMarkers(markers);
|
||||
return placement;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectsMarkerAndObjectKeysFromPlacements() {
|
||||
List<IrisObjectPlacement> placements = List.of(
|
||||
placement("houses/hut", "spawn-point", "loot-marker"),
|
||||
placement("trees/oak"),
|
||||
placement("houses/hut", "spawn-point"));
|
||||
|
||||
assertEquals(List.of("loot-marker", "spawn-point"),
|
||||
PackExportClosure.collectMarkerKeys(placements).stream().sorted().toList());
|
||||
assertEquals(List.of("houses/hut", "trees/oak"),
|
||||
PackExportClosure.collectObjectKeys(placements).stream().sorted().toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toleratesNullPlacementsAndBlankKeys() {
|
||||
KList<IrisObjectPlacement> placements = new KList<>();
|
||||
placements.add(placement("", ""));
|
||||
placements.add((IrisObjectPlacement) null);
|
||||
|
||||
assertTrue(PackExportClosure.collectMarkerKeys(placements).isEmpty());
|
||||
assertTrue(PackExportClosure.collectObjectKeys(placements).isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Source guard: both packagers must export the ambient-spawning graph. Spawner and marker
|
||||
* folders were silently omitted from exports, leaving dangling entitySpawners references.
|
||||
*/
|
||||
@Test
|
||||
public void bothPackagersExportSpawnersMarkersAndTheFullEntityGraph() throws Exception {
|
||||
String bukkit = Files.readString(Path.of(
|
||||
"src/main/java/art/arcane/iris/core/project/IrisPackageCompiler.java"));
|
||||
assertTrue("Bukkit packager must write spawners/", bukkit.contains("\"spawners/\""));
|
||||
assertTrue("Bukkit packager must write markers/", bukkit.contains("\"markers/\""));
|
||||
assertTrue("Bukkit packager must export region objects alongside biome objects",
|
||||
bukkit.contains("regions.forEach((r) -> allPlacements.addAll(r.getObjects()))"));
|
||||
assertTrue("Bukkit packager must export entity loot tables", bukkit.contains("getLoot().getTables()"));
|
||||
|
||||
String modded = Files.readString(Path.of(
|
||||
"../adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java"));
|
||||
assertTrue("modded packager must write spawners/", modded.contains("\"spawners\""));
|
||||
assertTrue("modded packager must write markers/", modded.contains("\"markers\""));
|
||||
assertTrue("modded packager must include initial spawns", modded.contains("getInitialSpawns"));
|
||||
assertTrue("modded packager must export region objects", modded.contains("region.getObjects()"));
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackGeneratorDuplicateValidatorTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void warnsWhenTwoReferencedGeneratorsAreContentIdentical() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "generators/a.json", "{\"seed\": 1,\n \"zoom\": 2}");
|
||||
write(pack, "generators/b.json", "{\"zoom\":2,\"seed\":1}");
|
||||
write(pack, "biomes/one.json", "{\"generators\":[{\"generator\":\"a\"}]}");
|
||||
write(pack, "biomes/two.json", "{\"generators\":[{\"generator\":\"b\"}]}");
|
||||
|
||||
List<String> warnings = PackGeneratorDuplicateValidator.validateDuplicateGenerators(pack);
|
||||
|
||||
assertEquals(1, warnings.size());
|
||||
assertTrue(warnings.get(0).startsWith("Generators a, b have identical content"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotWarnWhenOnlyOneDuplicateIsReferenced() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "generators/a.json", "{\"seed\":1}");
|
||||
write(pack, "generators/b.json", "{\"seed\":1}");
|
||||
write(pack, "biomes/one.json", "{\"generators\":[{\"generator\":\"a\"}]}");
|
||||
|
||||
assertTrue(PackGeneratorDuplicateValidator.validateDuplicateGenerators(pack).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotWarnWhenGeneratorsDifferBySeed() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "generators/a.json", "{\"seed\":1}");
|
||||
write(pack, "generators/b.json", "{\"seed\":2}");
|
||||
write(pack, "biomes/one.json", "{\"generators\":[{\"generator\":\"a\"},{\"generator\":\"b\"}]}");
|
||||
|
||||
assertTrue(PackGeneratorDuplicateValidator.validateDuplicateGenerators(pack).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void treatsNumericFormattingAsIdentical() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "generators/a.json", "{\"zoom\":1}");
|
||||
write(pack, "generators/b.json", "{\"zoom\":1.0}");
|
||||
write(pack, "biomes/one.json", "{\"generators\":[{\"generator\":\"a\"},{\"generator\":\"b\"}]}");
|
||||
|
||||
assertEquals(1, PackGeneratorDuplicateValidator.validateDuplicateGenerators(pack).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesSnippetGeneratorLinks() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "generators/a.json", "{\"seed\":1}");
|
||||
write(pack, "generators/b.json", "{\"seed\":1}");
|
||||
write(pack, "biomes/one.json", "{\"generators\":[\"snippet/generator-layer/x\",{\"generator\":\"b\"}]}");
|
||||
write(pack, "snippet/generator-layer/x.json", "{\"generator\":\"a\"}");
|
||||
|
||||
assertEquals(1, PackGeneratorDuplicateValidator.validateDuplicateGenerators(pack).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warningOrderIsStableAndSorted() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "generators/c.json", "{\"seed\":1}");
|
||||
write(pack, "generators/a.json", "{\"seed\":1}");
|
||||
write(pack, "generators/b.json", "{\"seed\":1}");
|
||||
write(pack, "biomes/one.json",
|
||||
"{\"generators\":[{\"generator\":\"c\"},{\"generator\":\"a\"},{\"generator\":\"b\"}]}");
|
||||
|
||||
List<String> warnings = PackGeneratorDuplicateValidator.validateDuplicateGenerators(pack);
|
||||
|
||||
assertEquals(1, warnings.size());
|
||||
assertTrue(warnings.get(0).startsWith("Generators a, b, c have identical content"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duplicatesSurfaceAsPackWarningsNotErrors() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"]}");
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
write(pack, "biomes/biome.json", "{\"name\":\"Biome\",\"generators\":[{\"generator\":\"a\"},{\"generator\":\"b\"}]}");
|
||||
write(pack, "generators/a.json", "{\"seed\":1}");
|
||||
write(pack, "generators/b.json", "{\"seed\":1}");
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertTrue(result.isLoadable());
|
||||
assertTrue(result.getWarnings().stream().anyMatch(w -> w.startsWith("Generators a, b have identical content")));
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path target = new File(root, relative).toPath();
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.writeString(target, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackStyledRangeDefaultValidatorTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void flagsEmptyDensityStyleAsSharedDefault() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/x.json", "{\"objects\":[{\"place\":[\"o\"],\"densityStyle\":{}}]}");
|
||||
|
||||
PackStyledRangeDefaultValidator.Validation validation = PackStyledRangeDefaultValidator.validate(pack);
|
||||
|
||||
assertEquals(1, validation.errors().size());
|
||||
assertTrue(validation.errors().get(0).startsWith(
|
||||
"Biome 'x' objects[0].densityStyle omits both min and max"));
|
||||
assertTrue(validation.warnings().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsExplicitDensityStyle() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/x.json",
|
||||
"{\"objects\":[{\"place\":[\"o\"],\"densityStyle\":{\"min\":1,\"max\":3,\"style\":{\"style\":\"SIMPLEX\"}}}]}");
|
||||
|
||||
PackStyledRangeDefaultValidator.Validation validation = PackStyledRangeDefaultValidator.validate(pack);
|
||||
|
||||
assertTrue(validation.errors().isEmpty());
|
||||
assertTrue(validation.warnings().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warnsOnPartialDensityStyle() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/x.json", "{\"objects\":[{\"place\":[\"o\"],\"densityStyle\":{\"min\":1}}]}");
|
||||
|
||||
PackStyledRangeDefaultValidator.Validation validation = PackStyledRangeDefaultValidator.validate(pack);
|
||||
|
||||
assertTrue(validation.errors().isEmpty());
|
||||
assertEquals(1, validation.warnings().size());
|
||||
assertTrue(validation.warnings().get(0).contains("omits max"));
|
||||
assertTrue(validation.warnings().get(0).contains("32"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesSnippetReferences() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/x.json", "{\"objects\":[{\"place\":[\"o\"],\"densityStyle\":\"snippet/style-range/empty\"}]}");
|
||||
write(pack, "snippet/style-range/empty.json", "{}");
|
||||
|
||||
PackStyledRangeDefaultValidator.Validation validation = PackStyledRangeDefaultValidator.validate(pack);
|
||||
|
||||
assertEquals(1, validation.errors().size());
|
||||
assertTrue(validation.errors().get(0).contains("via snippet 'snippet/style-range/empty'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flagsEmptyCaveDensityThreshold() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"caveProfile\":{\"densityThreshold\":{}}}");
|
||||
|
||||
PackStyledRangeDefaultValidator.Validation validation = PackStyledRangeDefaultValidator.validate(pack);
|
||||
|
||||
assertEquals(1, validation.errors().size());
|
||||
assertTrue(validation.errors().get(0).startsWith(
|
||||
"Dimension 'main' caveProfile.densityThreshold omits both min and max"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyDensityStyleBlocksFullPackValidation() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"]}");
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
write(pack, "biomes/biome.json", "{\"name\":\"Biome\",\"objects\":[{\"place\":[\"o\"],\"densityStyle\":{}}]}");
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path target = new File(root, relative).toPath();
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.writeString(target, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisDimensionType;
|
||||
import art.arcane.iris.engine.object.IrisDimensionTypeOptions;
|
||||
import art.arcane.iris.core.nms.datapack.IDataFixer;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackValidatorDimensionHeightTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void rejectsNonMultipleOfSixteenSpan() throws Exception {
|
||||
PackValidationResult result = validate("{\"regions\":[\"region\"],\"dimensionHeight\":{\"min\":-64,\"max\":300}}");
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Dimension 'main' dimensionHeight span (max - min) is 364; it must be a multiple of 16."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMinYNotMultipleOfSixteen() throws Exception {
|
||||
PackValidationResult result = validate("{\"regions\":[\"region\"],\"dimensionHeight\":{\"min\":-60,\"max\":324}}");
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Dimension 'main' dimensionHeight.min is -60; it must be a multiple of 16."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsSpanOutsideBounds() throws Exception {
|
||||
assertTrue(validate("{\"regions\":[\"region\"],\"dimensionHeight\":{\"min\":0,\"max\":0}}")
|
||||
.getBlockingErrors().contains(
|
||||
"Dimension 'main' dimensionHeight span (max - min) is 0; it must be between 16 and 4064."));
|
||||
assertTrue(validate("{\"regions\":[\"region\"],\"dimensionHeight\":{\"min\":-2032,\"max\":2048}}")
|
||||
.getBlockingErrors().contains(
|
||||
"Dimension 'main' dimensionHeight span (max - min) is 4080; it must be between 16 and 4064."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMinYOutsideBounds() throws Exception {
|
||||
assertTrue(validate("{\"regions\":[\"region\"],\"dimensionHeight\":{\"min\":-2048,\"max\":-1664}}")
|
||||
.getBlockingErrors().contains(
|
||||
"Dimension 'main' dimensionHeight.min is -2048; it must be between -2032 and 2031."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsLogicalHeightAboveTotalHeight() throws Exception {
|
||||
PackValidationResult result = validate(
|
||||
"{\"regions\":[\"region\"],\"logicalHeight\":512,\"dimensionHeight\":{\"min\":-64,\"max\":320}}");
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Dimension 'main' logicalHeight is 512; it cannot be greater than the dimension height of 384."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsShippingOverworldShape() throws Exception {
|
||||
PackValidationResult result = validate(
|
||||
"{\"regions\":[\"region\"],\"logicalHeight\":512,\"dimensionHeight\":{\"min\":-256,\"max\":512}}");
|
||||
|
||||
assertTrue(result.getBlockingErrors().toString(), result.isLoadable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsDimensionWithoutExplicitHeight() throws Exception {
|
||||
PackValidationResult result = validate("{\"regions\":[\"region\"]}");
|
||||
|
||||
assertTrue(result.getBlockingErrors().toString(), result.isLoadable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesDimensionHeightSnippet() throws Exception {
|
||||
File pack = pack("{\"regions\":[\"region\"],\"dimensionHeight\":\"snippet/range/tall\"}");
|
||||
write(pack, "snippet/range/tall.json", "{\"min\":-60,\"max\":324}");
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Dimension 'main' dimensionHeight.min is -60; it must be a multiple of 16."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatorAgreesWithIrisDimensionTypeExactly() {
|
||||
int[][] triples = {
|
||||
// {minY, maxY, logicalHeight}
|
||||
{-64, 320, 256}, {-256, 512, 512}, {-64, 300, 256}, {-60, 324, 256},
|
||||
{0, 0, 0}, {-2032, 2048, 256}, {-2048, -1664, 0}, {0, 16, 16},
|
||||
{0, 16, 17}, {0, 16, -1}, {16, 32, 16}, {-64, 320, 384},
|
||||
{-64, 320, 385}, {2016, 2032, 16}, {2032, 2048, 16}, {-2032, -2016, 16},
|
||||
{8, 24, 16}, {0, 4064, 4064}, {0, 4080, 4064}, {-64, 4000, 4064},
|
||||
};
|
||||
|
||||
for (int[] triple : triples) {
|
||||
int minY = triple[0];
|
||||
int maxY = triple[1];
|
||||
int logicalHeight = triple[2];
|
||||
|
||||
boolean constructorRejects;
|
||||
try {
|
||||
new IrisDimensionType(IDataFixer.Dimension.OVERWORLD, new IrisDimensionTypeOptions(),
|
||||
logicalHeight, maxY - minY, minY);
|
||||
constructorRejects = false;
|
||||
} catch (IllegalArgumentException e) {
|
||||
constructorRejects = true;
|
||||
}
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
PackDimensionValidator.validateDimensionHeights(null, "main", new art.arcane.volmlib.util.json.JSONObject(
|
||||
"{\"logicalHeight\":" + logicalHeight
|
||||
+ ",\"dimensionHeight\":{\"min\":" + minY + ",\"max\":" + maxY + "}}"), errors);
|
||||
|
||||
assertEquals("triple (" + minY + "," + maxY + "," + logicalHeight + ") errors=" + errors,
|
||||
constructorRejects, !errors.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
private PackValidationResult validate(String dimensionJson) throws Exception {
|
||||
return PackValidator.validate(pack(dimensionJson));
|
||||
}
|
||||
|
||||
private File pack(String dimensionJson) throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack-" + System.nanoTime());
|
||||
write(pack, "dimensions/main.json", dimensionJson);
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
write(pack, "biomes/biome.json", "{\"name\":\"Biome\"}");
|
||||
return pack;
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path target = new File(root, relative).toPath();
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.writeString(target, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class PackValidatorLootTest {
|
||||
write(pack, "dimensions/main.json", "{\"loot\":{\"mode\":\"FALLBACK\",\"multiplier\":0.5,"
|
||||
+ "\"tables\":[\"global/clutter\"]}}");
|
||||
|
||||
assertTrue(PackLootValidator.validateLootGraph(pack).isEmpty());
|
||||
assertTrue(PackLootValidator.validateLootGraph(pack).errors().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,7 +37,7 @@ public class PackValidatorLootTest {
|
||||
write(pack, "dimensions/main.json", "{\"loot\":{\"tables\":[\"missing\"]}}");
|
||||
|
||||
assertEquals(List.of("Dimension 'main'.loot.tables[0] references missing loot table 'missing'."),
|
||||
PackLootValidator.validateLootGraph(pack));
|
||||
PackLootValidator.validateLootGraph(pack).errors());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -47,7 +47,7 @@ public class PackValidatorLootTest {
|
||||
+ "\"loot\":[{\"type\":\"\",\"rarity\":0,\"minAmount\":3,\"maxAmount\":1,"
|
||||
+ "\"enchantments\":[{\"enchantment\":\"\",\"minLevel\":4,\"maxLevel\":2,\"chance\":2}]}]}");
|
||||
|
||||
List<String> errors = PackLootValidator.validateLootGraph(pack);
|
||||
List<String> errors = PackLootValidator.validateLootGraph(pack).errors();
|
||||
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains(".rarity must be at least 1")));
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains(".minPicked must not exceed")));
|
||||
@@ -63,7 +63,7 @@ public class PackValidatorLootTest {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"loot\":{\"mode\":\"MERGE\",\"multiplier\":-1,\"tables\":[]}}");
|
||||
|
||||
List<String> errors = PackLootValidator.validateLootGraph(pack);
|
||||
List<String> errors = PackLootValidator.validateLootGraph(pack).errors();
|
||||
|
||||
assertEquals(2, errors.size());
|
||||
assertTrue(errors.get(0).contains(".mode must be"));
|
||||
@@ -74,12 +74,12 @@ public class PackValidatorLootTest {
|
||||
public void enforcesPublishedLootMultiplierCap() throws Exception {
|
||||
File acceptedPack = temporaryFolder.newFolder("accepted-pack");
|
||||
write(acceptedPack, "dimensions/main.json", "{\"loot\":{\"multiplier\":16,\"tables\":[]}}");
|
||||
assertTrue(PackLootValidator.validateLootGraph(acceptedPack).isEmpty());
|
||||
assertTrue(PackLootValidator.validateLootGraph(acceptedPack).errors().isEmpty());
|
||||
|
||||
File rejectedPack = temporaryFolder.newFolder("rejected-pack");
|
||||
write(rejectedPack, "dimensions/main.json", "{\"loot\":{\"multiplier\":16.01,\"tables\":[]}}");
|
||||
assertEquals(List.of("Dimension 'main'.loot.multiplier must be a finite number from 0 to 16."),
|
||||
PackLootValidator.validateLootGraph(rejectedPack));
|
||||
PackLootValidator.validateLootGraph(rejectedPack).errors());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,7 +88,7 @@ public class PackValidatorLootTest {
|
||||
write(pack, "loot/boundary.json", "{\"minPicked\":64,\"maxPicked\":64,\"maxTries\":256,"
|
||||
+ "\"loot\":[{\"type\":\"stone\",\"minAmount\":64,\"maxAmount\":64}]}");
|
||||
|
||||
assertTrue(PackLootValidator.validateLootGraph(pack).isEmpty());
|
||||
assertTrue(PackLootValidator.validateLootGraph(pack).errors().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,7 +97,7 @@ public class PackValidatorLootTest {
|
||||
write(pack, "loot/excessive.json", "{\"minPicked\":65,\"maxPicked\":65,\"maxTries\":257,"
|
||||
+ "\"loot\":[{\"type\":\"stone\",\"minAmount\":65,\"maxAmount\":65}]}");
|
||||
|
||||
List<String> errors = PackLootValidator.validateLootGraph(pack);
|
||||
List<String> errors = PackLootValidator.validateLootGraph(pack).errors();
|
||||
|
||||
assertTrue(errors.contains("Loot table 'excessive'.minPicked must be at most 64."));
|
||||
assertTrue(errors.contains("Loot table 'excessive'.maxPicked must be at most 64."));
|
||||
@@ -121,6 +121,31 @@ public class PackValidatorLootTest {
|
||||
assertEquals(expected, maximum.value(), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearModeWithTablesWarnsWithoutBlocking() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("clear-warn");
|
||||
write(pack, "loot/t.json", "{\"loot\":[{\"type\":\"minecraft:stone\"}]}");
|
||||
write(pack, "biomes/x.json", "{\"loot\":{\"mode\":\"CLEAR\",\"tables\":[\"t\"]}}");
|
||||
|
||||
PackLootValidator.LootGraphIssues issues = PackLootValidator.validateLootGraph(pack);
|
||||
|
||||
assertTrue(issues.errors().isEmpty());
|
||||
assertEquals(List.of("Biome 'x'.loot uses mode CLEAR and lists 1 table(s); CLEAR clears parent tables"
|
||||
+ " and contributes no tables of its own, so these entries are dead. Use REPLACE to substitute them."),
|
||||
issues.warnings());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearModeWithMissingTableStillBlocksOnTheDanglingKey() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("clear-dangling");
|
||||
write(pack, "biomes/x.json", "{\"loot\":{\"mode\":\"CLEAR\",\"tables\":[\"missing\"]}}");
|
||||
|
||||
PackLootValidator.LootGraphIssues issues = PackLootValidator.validateLootGraph(pack);
|
||||
|
||||
assertTrue(issues.errors().contains(
|
||||
"Biome 'x'.loot.tables[0] references missing loot table 'missing'."));
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
File file = new File(root, relative);
|
||||
Files.createDirectories(file.toPath().getParent());
|
||||
|
||||
@@ -315,26 +315,6 @@ public class DecoratorCoreTest {
|
||||
assertSame(occupied, output.get(0, 2, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tallSinglePlantDoesNotPlaceWhenLowerTargetIsOccupied() {
|
||||
IrisDecorator decorator = mock(IrisDecorator.class);
|
||||
IrisData data = mock(IrisData.class);
|
||||
PlatformBlockState occupied = mock(PlatformBlockState.class);
|
||||
PlatformBlockState air = airState();
|
||||
PlatformBlockState plant = tallPlantState();
|
||||
when(decorator.pickBlockData(any(RNG.class), eq(data), anyDouble(), anyDouble())).thenReturn(plant);
|
||||
|
||||
Hunk<PlatformBlockState> output = Hunk.newArrayHunk(1, 4, 1);
|
||||
output.set(0, 1, 0, occupied);
|
||||
output.set(0, 2, 0, air);
|
||||
|
||||
DecoratorCore.placeSingleUp(
|
||||
decorator, 0, 0, 0, 0, 0, output, new RNG(1L), data, false, null);
|
||||
|
||||
assertSame(occupied, output.get(0, 1, 0));
|
||||
assertSame(air, output.get(0, 2, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tallFloatingPlantDoesNotPlaceWhenUpperTargetIsOccupied() {
|
||||
IrisDecorator decorator = mock(IrisDecorator.class);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package art.arcane.iris.engine.framework;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisLootMode;
|
||||
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 LootResolverClearModeTest {
|
||||
@Test
|
||||
public void clearSuppressesItsOwnTablesAndReplaceDoesNot() {
|
||||
List<String> cleared = new ArrayList<>(List.of("placement", "dimension"));
|
||||
LootResolver.injectSources(cleared, List.of("biome"), IrisLootMode.CLEAR, false);
|
||||
assertTrue("CLEAR must clear parents and contribute nothing", cleared.isEmpty());
|
||||
|
||||
List<String> replaced = new ArrayList<>(List.of("placement", "dimension"));
|
||||
LootResolver.injectSources(replaced, List.of("biome"), IrisLootMode.REPLACE, false);
|
||||
assertEquals(List.of("biome"), replaced);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearIgnoresTheFallbackFlag() {
|
||||
List<String> sources = new ArrayList<>(List.of("placement"));
|
||||
LootResolver.injectSources(sources, List.of("biome"), IrisLootMode.CLEAR, true);
|
||||
assertTrue(sources.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package art.arcane.iris.engine.mantle;
|
||||
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.engine.framework.TreeBlockMaterial;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
@@ -43,6 +44,57 @@ public class EngineMantleCleanupTest {
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
MantleSliceRetention.clearForTesting();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retainedSlicesSurviveCoveredCleanup() {
|
||||
MantleSliceRetention.retain(MatterCavern.class.getCanonicalName());
|
||||
CleanupFixture fixture = cleanupFixture();
|
||||
doReturn(true).when(fixture.engineMantle()).isCovered(7, -4);
|
||||
|
||||
fixture.engineMantle().cleanupChunk(7, -4);
|
||||
|
||||
verify(fixture.chunk(), never()).deleteSlices(MatterCavern.class);
|
||||
verify(fixture.chunk()).deleteSlices(PlatformBlockState.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retainedSlicesSurvivePregenForceCleanup() {
|
||||
MantleSliceRetention.retain(TreeBlockMaterial.class.getCanonicalName());
|
||||
MantleSliceRetention.retain(String.class.getCanonicalName());
|
||||
CleanupFixture fixture = cleanupFixture();
|
||||
|
||||
fixture.engineMantle().forceCleanupChunk(7, -4);
|
||||
|
||||
verify(fixture.chunk(), never()).deleteSlices(TreeBlockMaterial.class);
|
||||
verify(fixture.chunk(), never()).deleteSlices(String.class);
|
||||
verify(fixture.chunk()).deleteSlices(MatterCavern.class);
|
||||
verify(fixture.chunk()).deleteSlices(PlatformBlockState.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bukkitBootRetentionsSurvivePregen() {
|
||||
// Exactly the slice types the Bukkit plugin registers at boot: markers and tree materials
|
||||
// must keep working in pregenerated chunks.
|
||||
MantleSliceRetention.retain(String.class.getCanonicalName());
|
||||
MantleSliceRetention.retain(TreeBlockMaterial.class.getCanonicalName());
|
||||
CleanupFixture fixture = cleanupFixture();
|
||||
|
||||
fixture.engineMantle().forceCleanupChunk(7, -4);
|
||||
|
||||
verify(fixture.chunk(), never()).deleteSlices(String.class);
|
||||
verify(fixture.chunk(), never()).deleteSlices(TreeBlockMaterial.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockStateSliceIsNeverRetainable() {
|
||||
MantleSliceRetention.retain(PlatformBlockState.class.getCanonicalName());
|
||||
CleanupFixture fixture = cleanupFixture();
|
||||
|
||||
fixture.engineMantle().forceCleanupChunk(7, -4);
|
||||
|
||||
verify(fixture.chunk()).deleteSlices(PlatformBlockState.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
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 IrisAxisRotationClampPurityTest {
|
||||
private static IrisAxisRotationClamp unlimited(double interval) {
|
||||
IrisAxisRotationClamp clamp = new IrisAxisRotationClamp();
|
||||
clamp.setEnabled(true);
|
||||
clamp.setInterval(interval);
|
||||
return clamp;
|
||||
}
|
||||
|
||||
private static double reference(double interval, int rng) {
|
||||
double resolved = interval < 1 ? 1 : interval;
|
||||
return Math.toRadians((resolved * (Math.ceil(Math.abs((rng % 360D) / resolved)))) % 360D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRadiansDoesNotMutateIntervalWhenUnlimited() {
|
||||
IrisAxisRotationClamp clamp = unlimited(0);
|
||||
clamp.getRadians(37);
|
||||
assertEquals(0D, clamp.getInterval(), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRadiansValuesAreUnchangedByTheFix() {
|
||||
double[] intervals = {0, 0.5, 1, 90};
|
||||
int[] rngs = {0, 1, 37, 91, 359, 720, -13};
|
||||
for (double interval : intervals) {
|
||||
for (int rng : rngs) {
|
||||
assertEquals("interval=" + interval + " rng=" + rng,
|
||||
reference(interval, rng), unlimited(interval).getRadians(rng), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsAndHashCodeSurviveRepeatedGetRadians() {
|
||||
IrisAxisRotationClamp a = unlimited(0);
|
||||
IrisAxisRotationClamp b = unlimited(0);
|
||||
a.getRadians(123);
|
||||
assertEquals(a, b);
|
||||
assertEquals(a.hashCode(), b.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentGetRadiansLeavesIntervalUntouched() throws InterruptedException {
|
||||
IrisAxisRotationClamp shared = unlimited(0);
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
for (int t = 0; t < 8; t++) {
|
||||
Thread thread = new Thread(() -> {
|
||||
for (int i = 0; i < 10_000; i++) {
|
||||
shared.getRadians(i);
|
||||
}
|
||||
});
|
||||
threads.add(thread);
|
||||
thread.start();
|
||||
}
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
assertTrue("interval must stay at the authored value", shared.getInterval() == 0D);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisBiomeCeilingLayerGuardTest {
|
||||
private static IrisBiome biome(int layers, int ceilingLayers) {
|
||||
IrisBiome biome = new IrisBiome();
|
||||
KList<IrisBiomePaletteLayer> surface = new KList<>();
|
||||
for (int i = 0; i < layers; i++) {
|
||||
surface.add(new IrisBiomePaletteLayer().zero());
|
||||
}
|
||||
KList<IrisBiomePaletteLayer> ceiling = new KList<>();
|
||||
for (int i = 0; i < ceilingLayers; i++) {
|
||||
IrisBiomePaletteLayer layer = new IrisBiomePaletteLayer().zero();
|
||||
layer.setMinHeight(0);
|
||||
layer.setMaxHeight(0);
|
||||
ceiling.add(layer);
|
||||
}
|
||||
biome.setLayers(surface);
|
||||
biome.setCaveCeilingLayers(ceiling);
|
||||
return biome;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void moreCeilingLayersThanLayersMustNotThrow() {
|
||||
IrisBiome biome = biome(1, 3);
|
||||
|
||||
KList<PlatformBlockState> result = biome.generateCeilingLayers(
|
||||
new IrisDimension(), 0, 0, new RNG(1), 8, 64, mock(IrisData.class), null);
|
||||
|
||||
assertTrue("extra ceiling layers are skipped, not crashed on", result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inRangeCeilingLayersKeepWorking() {
|
||||
IrisBiome biome = biome(3, 2);
|
||||
|
||||
KList<PlatformBlockState> result = biome.generateCeilingLayers(
|
||||
new IrisDimension(), 0, 0, new RNG(1), 8, 64, mock(IrisData.class), null);
|
||||
|
||||
assertTrue("zeroed palettes with zero heights produce no blocks", result.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IrisObjectIoWriteLimitsTest {
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
private static PlatformBlockState state(String key) {
|
||||
return (PlatformBlockState) Proxy.newProxyInstance(
|
||||
IrisObjectIoWriteLimitsTest.class.getClassLoader(),
|
||||
new Class<?>[]{PlatformBlockState.class},
|
||||
(proxy, method, args) -> switch (method.getName()) {
|
||||
case "key" -> key;
|
||||
case "toString" -> key;
|
||||
case "hashCode" -> key.hashCode();
|
||||
case "equals" -> proxy == args[0];
|
||||
default -> switch (method.getReturnType().getName()) {
|
||||
case "boolean" -> false;
|
||||
case "int" -> 0;
|
||||
case "double" -> 0D;
|
||||
default -> null;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static IrisObject objectWithDistinctStates(int paletteSize) {
|
||||
IrisObject object = new IrisObject(64, 64, 64);
|
||||
object.setLoadKey("limits-test");
|
||||
int placed = 0;
|
||||
outer:
|
||||
for (int x = 0; x < 64; x++) {
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int z = 0; z < 64; z++) {
|
||||
if (placed >= paletteSize) {
|
||||
break outer;
|
||||
}
|
||||
object.blocks.put(new IrisBlockVector(x, y, z), state("iris:test_" + placed));
|
||||
placed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsPaletteOverflowInsteadOfWrappingTheShort() {
|
||||
IrisObject object = objectWithDistinctStates(32_768);
|
||||
|
||||
try {
|
||||
IrisObjectIO.write(object, new ByteArrayOutputStream());
|
||||
fail("expected the oversized palette to be rejected");
|
||||
} catch (IOException e) {
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("limits-test"));
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("32768"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsPaletteAtExactlyTheCap() throws IOException {
|
||||
IrisObject object = objectWithDistinctStates(32_767);
|
||||
File file = folder.newFile("cap.iob");
|
||||
|
||||
IrisObjectIO.write(object, file);
|
||||
|
||||
assertEquals(32_767, IrisObjectIO.readPaletteKeys(file).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsCoordinateBeyondShortRange() {
|
||||
IrisObject object = new IrisObject(64, 64, 64);
|
||||
object.setLoadKey("limits-test");
|
||||
object.blocks.put(new IrisBlockVector(40_000, 0, 0), state("iris:test"));
|
||||
|
||||
try {
|
||||
IrisObjectIO.write(object, new ByteArrayOutputStream());
|
||||
fail("expected the out-of-range coordinate to be rejected");
|
||||
} catch (IOException e) {
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("40000"));
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("x"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedWriteLeavesExistingFileIntact() throws IOException {
|
||||
File file = folder.newFile("existing.iob");
|
||||
IrisObject valid = objectWithDistinctStates(3);
|
||||
IrisObjectIO.write(valid, file);
|
||||
byte[] before = Files.readAllBytes(file.toPath());
|
||||
|
||||
IrisObject oversized = objectWithDistinctStates(32_768);
|
||||
try {
|
||||
IrisObjectIO.write(oversized, file);
|
||||
fail("expected the oversized write to be rejected");
|
||||
} catch (IOException expected) {
|
||||
}
|
||||
|
||||
assertArrayEquals("a rejected write must not truncate the previous object", before,
|
||||
Files.readAllBytes(file.toPath()));
|
||||
}
|
||||
}
|
||||
-2
@@ -67,7 +67,6 @@ public class IrisObjectPlacementToPlacementTest {
|
||||
source.setBottom(true);
|
||||
source.setBore(true);
|
||||
source.setWarp(warp);
|
||||
source.setTranslateCenter(true);
|
||||
source.setMode(ObjectPlaceMode.FAST_MIN_STILT);
|
||||
source.setEdit(edit);
|
||||
source.setTranslate(translate);
|
||||
@@ -112,7 +111,6 @@ public class IrisObjectPlacementToPlacementTest {
|
||||
assertTrue(copy.isBottom());
|
||||
assertTrue(copy.isBore());
|
||||
assertSame(warp, copy.getWarp());
|
||||
assertTrue(copy.isTranslateCenter());
|
||||
assertEquals(ObjectPlaceMode.FAST_MIN_STILT, copy.getMode());
|
||||
assertSame(edit, copy.getEdit());
|
||||
assertSame(translate, copy.getTranslate());
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class RemovedDeadFieldsTest {
|
||||
@Test
|
||||
public void removedFieldsStayRemoved() {
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisDimension.class.getDeclaredField("forceConvertTo320Height"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisDimension.class.getDeclaredField("rockZoom"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisDimension.class.getDeclaredField("disableExplorerMaps"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("riverStyle"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("lakeStyle"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("riverGen"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("lakeGen"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("riverChanceGen"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("realRiverBiomes"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisRegion.class.getDeclaredField("realLakeBiomes"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisObjectPlacement.class.getDeclaredField("translateCenter"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisTree.class.getDeclaredField("anyTree"));
|
||||
assertThrows(NoSuchFieldException.class, () -> IrisTree.class.getDeclaredField("anySize"));
|
||||
assertThrows(NoSuchMethodException.class, () -> IrisRegion.class.getDeclaredMethod("getBiomeZoom", InferredType.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyKeysAreIgnoredNotFatal() {
|
||||
IrisDimension dimension = new Gson().fromJson(
|
||||
"{\"name\":\"x\",\"rockZoom\":9,\"disableExplorerMaps\":true,\"forceConvertTo320Height\":true}",
|
||||
IrisDimension.class);
|
||||
|
||||
assertNotNull(dimension);
|
||||
assertEquals("x", dimension.getName());
|
||||
|
||||
IrisTree tree = new Gson().fromJson("{\"anyTree\":true,\"anySize\":true}", IrisTree.class);
|
||||
assertNotNull(tree);
|
||||
|
||||
IrisRegion region = new Gson().fromJson(
|
||||
"{\"name\":\"r\",\"riverStyle\":{\"zoom\":7.77},\"lakeStyle\":{}}", IrisRegion.class);
|
||||
assertNotNull(region);
|
||||
assertEquals("r", region.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioSettingsKeepTheirLiveToggles() throws Exception {
|
||||
// disableTimeAndWeather is read by WorldRuntimeControlService, and autoStartDefaultStudio
|
||||
// by Iris.autoStartStudio() at boot - both are live and must NOT be removed.
|
||||
assertNotNull(IrisSettings.IrisSettingsStudio.class.getDeclaredField("disableTimeAndWeather"));
|
||||
assertNotNull(IrisSettings.IrisSettingsStudio.class.getDeclaredField("autoStartDefaultStudio"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package art.arcane.iris.spi;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PacksFolderContractTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void defaultPacksFolderIsDataFolderPacks() {
|
||||
File data = temporaryFolder.getRoot();
|
||||
IrisPlatform platform = mock(IrisPlatform.class, CALLS_REAL_METHODS);
|
||||
when(platform.dataFolder()).thenReturn(data);
|
||||
|
||||
assertEquals(new File(data, "packs"), platform.packsFolder());
|
||||
assertEquals(new File(data, "packs" + File.separator + "overworld"), platform.packsFolderNoCreate("overworld"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Source guard: nothing outside the SPI defaults and platform overrides may spell the packs
|
||||
* root by hand. New code goes through packsFolder()/packsFolderNoCreate().
|
||||
*/
|
||||
@Test
|
||||
public void noCallSiteResolvesThePacksRootByHand() throws Exception {
|
||||
List<Path> roots = List.of(
|
||||
Path.of("src/main/java"),
|
||||
Path.of("../adapters/bukkit/plugin/src/main/java"));
|
||||
for (Path root : roots) {
|
||||
try (var stream = Files.walk(root)) {
|
||||
for (Path file : stream.filter(p -> p.toString().endsWith(".java")).toList()) {
|
||||
String text = Files.readString(file);
|
||||
assertFalse(file + " must use packsFolder() instead of dataFolder(\"packs\")",
|
||||
text.contains("dataFolder(\"packs\")"));
|
||||
assertFalse(file + " must use packsFolderNoCreate() instead of the WORKSPACE_NAME indirection",
|
||||
text.contains("dataFolderNoCreate(StudioSVC.WORKSPACE_NAME"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,14 +81,16 @@ public class SimdKernelsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vectorSumMatchesScalarWithinTolerance() {
|
||||
public void vectorSumMatchesScalarExactly() {
|
||||
// Zero tolerance on purpose: sum feeds generation-path ranking, so any kernel that
|
||||
// reassociates FP addition must fail here instead of shipping a parity break.
|
||||
SimdKernels vector = requireVectorKernels();
|
||||
ScalarSimdKernels scalar = new ScalarSimdKernels();
|
||||
for (int length : new int[]{256, 251, 1, 7}) {
|
||||
double[] values = terrainLikeValues(length);
|
||||
double expected = scalar.sum(values, length);
|
||||
double actual = vector.sum(values, length);
|
||||
assertEquals("length " + length, expected, actual, Math.max(1e-9D, Math.abs(expected) * 1e-12D));
|
||||
assertEquals("length " + length, expected, actual, 0D);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user