mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-11 19:14:11 +00:00
Preserved terrain across updates
- Saved chunks retain their natural geometry and generation ownership - Future chunks blend against frozen terrain boundaries - Build revisions capture generation source and dependencies automatically - Native terrain queries use contained transition geometry and final height caches - Historical searches retain recorded pack identities and placement facts - World creation and Studio updates preserve native lifecycle ordering - Hydrology containment and scoped content workers retain generation context - Pregeneration validates saved completion before trusting cached progress
This commit is contained in:
+27
-28
@@ -24,18 +24,18 @@ import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class GenerationKernelSeal {
|
||||
public static final String FORMAT = "iris-generation-kernel-seal-v1";
|
||||
public final class GenerationBuildRevision {
|
||||
public static final String FORMAT = "iris-generation-build-revision-v1";
|
||||
private static final Pattern HASH = Pattern.compile("[0-9a-f]{64}");
|
||||
private static final long MAXIMUM_SEAL_BYTES = 8L * 1024L * 1024L;
|
||||
private static final long MAXIMUM_REVISION_BYTES = 8L * 1024L * 1024L;
|
||||
private static final int MAXIMUM_ENTRIES = 30_000;
|
||||
|
||||
private GenerationKernelSeal() {
|
||||
private GenerationBuildRevision() {
|
||||
}
|
||||
|
||||
public static void main(String[] arguments) throws IOException {
|
||||
if (arguments.length < 4 || arguments.length % 2 != 0 || !arguments[0].equals("verify-dependencies")) {
|
||||
throw new IllegalArgumentException("Expected verify-dependencies <seal> <identity> <artifact> pairs.");
|
||||
throw new IllegalArgumentException("Expected verify-dependencies <revision> <identity> <artifact> pairs.");
|
||||
}
|
||||
Map<String, Path> dependencies = new TreeMap<>();
|
||||
for (int index = 2; index < arguments.length; index += 2) {
|
||||
@@ -46,8 +46,8 @@ public final class GenerationKernelSeal {
|
||||
verifyDependencySubset(Path.of(arguments[1]), dependencies);
|
||||
}
|
||||
|
||||
public static void verifyDependencySubset(Path seal, Map<String, Path> dependencies) throws IOException {
|
||||
SourceManifest expected = read(seal);
|
||||
public static void verifyDependencySubset(Path revision, Map<String, Path> dependencies) throws IOException {
|
||||
SourceManifest expected = read(revision);
|
||||
for (Map.Entry<String, String> dependency : dependencyHashes(dependencies).entrySet()) {
|
||||
if (!Objects.equals(expected.dependencies().get(dependency.getKey()), dependency.getValue())) {
|
||||
throw new IOException("Resolved platform dependency does not match generation ABI " + expected.abi()
|
||||
@@ -75,7 +75,7 @@ public final class GenerationKernelSeal {
|
||||
AlgorithmVersion algorithms = new AlgorithmVersion(positive(current[2]), positive(current[3]));
|
||||
SourceManifest selected = manifests.get(currentAbi);
|
||||
if (selected == null || !selected.algorithms().contains(algorithms)) {
|
||||
throw new IllegalArgumentException("Current generation version has no matching sealed factory.");
|
||||
throw new IllegalArgumentException("Current generation version has no matching build factory.");
|
||||
}
|
||||
TreeSet<Integer> registered = new TreeSet<>();
|
||||
int previous = 0;
|
||||
@@ -92,7 +92,7 @@ public final class GenerationKernelSeal {
|
||||
registered.add(abi);
|
||||
}
|
||||
if (!registered.equals(manifests.keySet())) {
|
||||
throw new IllegalArgumentException("Kernel catalog must register exactly the retained sealed kernels.");
|
||||
throw new IllegalArgumentException("Kernel catalog must register exactly the packaged kernels.");
|
||||
}
|
||||
} catch (IllegalArgumentException failure) {
|
||||
throw new IOException("Invalid generation kernel catalog.", failure);
|
||||
@@ -116,33 +116,32 @@ public final class GenerationKernelSeal {
|
||||
sourceHashes, dependencyHashes(dependencies));
|
||||
}
|
||||
|
||||
public static void verify(Path repository, Path seal, Map<String, Path> dependencies) throws IOException {
|
||||
SourceManifest expected = read(seal);
|
||||
public static void verifySnapshot(Path repository, Path revision, Map<String, Path> dependencies) throws IOException {
|
||||
SourceManifest expected = read(revision);
|
||||
SourceManifest actual = capture(new CaptureOptions(repository, expected.abi(), expected.factoryClass(), expected.algorithms(),
|
||||
expected.roots(), expected.exclusions()), dependencies);
|
||||
List<String> failures = new ArrayList<>();
|
||||
differences("source", expected.sources(), actual.sources(), failures);
|
||||
differences("dependency", expected.dependencies(), actual.dependencies(), failures);
|
||||
if (!failures.isEmpty()) {
|
||||
throw new IOException("Generation ABI " + expected.abi() + " no longer matches its sealed implementation:\n"
|
||||
+ String.join("\n", failures)
|
||||
+ "\nRetain this kernel's implementation and add a separately versioned kernel for changed generation.");
|
||||
throw new IOException("Generation build inputs changed after revision capture:\n"
|
||||
+ String.join("\n", failures));
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeNew(Path destination, SourceManifest manifest) throws IOException {
|
||||
public static void write(Path destination, SourceManifest manifest) throws IOException {
|
||||
Files.createDirectories(destination.toAbsolutePath().getParent());
|
||||
Files.writeString(destination, encode(manifest), StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||
}
|
||||
|
||||
public static SourceManifest read(Path path) throws IOException {
|
||||
if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS) || Files.size(path) > MAXIMUM_SEAL_BYTES) {
|
||||
throw new IOException("Missing or oversized generation kernel seal: " + path);
|
||||
if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS) || Files.size(path) > MAXIMUM_REVISION_BYTES) {
|
||||
throw new IOException("Missing or oversized generation build revision: " + path);
|
||||
}
|
||||
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
|
||||
if (lines.size() < 4 || !FORMAT.equals(lines.getFirst()) || lines.size() > MAXIMUM_ENTRIES) {
|
||||
throw new IOException("Invalid generation kernel seal: " + path);
|
||||
throw new IOException("Invalid generation build revision: " + path);
|
||||
}
|
||||
int abi = 0;
|
||||
String factory = null;
|
||||
@@ -155,7 +154,7 @@ public final class GenerationKernelSeal {
|
||||
for (int index = 1; index < lines.size(); index++) {
|
||||
String[] entry = lines.get(index).split("\t", -1);
|
||||
if (entry.length < 2 || entry.length > 3) {
|
||||
throw new IllegalArgumentException("Invalid seal entry.");
|
||||
throw new IllegalArgumentException("Invalid revision entry.");
|
||||
}
|
||||
switch (entry[0]) {
|
||||
case "abi" -> {
|
||||
@@ -180,16 +179,16 @@ public final class GenerationKernelSeal {
|
||||
case "exclude" -> addPath(exclusions, entry);
|
||||
case "source" -> addHash(sources, entry, true);
|
||||
case "dependency" -> addHash(dependencies, entry, false);
|
||||
default -> throw new IllegalArgumentException("Unknown generation kernel seal entry.");
|
||||
default -> throw new IllegalArgumentException("Unknown generation build revision entry.");
|
||||
}
|
||||
}
|
||||
SourceManifest manifest = new SourceManifest(abi, factory, algorithms, roots, exclusions, sources, dependencies);
|
||||
if (!Files.readString(path, StandardCharsets.UTF_8).equals(encode(manifest))) {
|
||||
throw new IllegalArgumentException("Generation kernel seal is not canonical.");
|
||||
throw new IllegalArgumentException("Generation build revision is not canonical.");
|
||||
}
|
||||
return manifest;
|
||||
} catch (RuntimeException failure) {
|
||||
throw new IOException("Invalid generation kernel seal: " + path, failure);
|
||||
throw new IOException("Invalid generation build revision: " + path, failure);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,11 +351,11 @@ public final class GenerationKernelSeal {
|
||||
|
||||
private static void addHash(Map<String, String> hashes, String[] entry, boolean path) {
|
||||
if (entry.length != 3 || !HASH.matcher(entry[2]).matches()) {
|
||||
throw new IllegalArgumentException("Invalid sealed hash.");
|
||||
throw new IllegalArgumentException("Invalid revision hash.");
|
||||
}
|
||||
String key = path ? relative(entry[1]) : token(entry[1]);
|
||||
if (hashes.putIfAbsent(key, entry[2]) != null) {
|
||||
throw new IllegalArgumentException("Duplicate sealed entry.");
|
||||
throw new IllegalArgumentException("Duplicate revision entry.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,9 +376,9 @@ public final class GenerationKernelSeal {
|
||||
}
|
||||
|
||||
private static String token(String value) {
|
||||
String required = Objects.requireNonNull(value, "seal token");
|
||||
String required = Objects.requireNonNull(value, "revision token");
|
||||
if (required.isBlank() || required.chars().anyMatch(Character::isWhitespace)) {
|
||||
throw new IllegalArgumentException("Invalid generation seal token.");
|
||||
throw new IllegalArgumentException("Invalid generation revision token.");
|
||||
}
|
||||
return required;
|
||||
}
|
||||
@@ -442,7 +441,7 @@ public final class GenerationKernelSeal {
|
||||
sources = Map.copyOf(sources);
|
||||
dependencies = Map.copyOf(dependencies);
|
||||
if (sources.isEmpty() || dependencies.isEmpty() || sources.size() + dependencies.size() > MAXIMUM_ENTRIES) {
|
||||
throw new IllegalArgumentException("Invalid generation kernel seal entry count.");
|
||||
throw new IllegalArgumentException("Invalid generation build revision entry count.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package art.arcane.iris.buildtools;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public final class GenerationRevisionScope {
|
||||
private static final List<String> SOURCE_ROOTS = List.of(
|
||||
"lombok.config",
|
||||
"core/agent/build.gradle",
|
||||
"core/agent/src/main/java",
|
||||
"core/src/main/java",
|
||||
"spi/src/main/java",
|
||||
"adapters/bukkit/plugin/src/main/java",
|
||||
"adapters/bukkit/nms/v26_2_R1/src/main/java",
|
||||
"adapters/minecraft-common/src/main/java",
|
||||
"adapters/modded-common/src/main/java",
|
||||
"adapters/client-common/src/main/java",
|
||||
"adapters/fabric/src/main/java",
|
||||
"adapters/forge/src/main/java",
|
||||
"adapters/neoforge/src/main/java"
|
||||
);
|
||||
private static final List<String> OPERATIONAL_EXCLUSIONS = List.of(
|
||||
"core/src/main/java/art/arcane/iris/engine/EngineDiagnostics.java",
|
||||
"core/src/main/java/art/arcane/iris/engine/EngineMetricsReport.java",
|
||||
"core/src/main/java/art/arcane/iris/core/IrisStartupValidation.java",
|
||||
"core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java",
|
||||
"core/src/main/java/art/arcane/iris/core/safeguard/Mode.java",
|
||||
"core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java",
|
||||
"core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java",
|
||||
"core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java",
|
||||
"core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java",
|
||||
"core/src/main/java/art/arcane/iris/core/splash/IrisSplashRenderer.java",
|
||||
"adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/NmsWorldLifecycle.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/PaperCommandRegistrar.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPack.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStructure.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandWhat.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandFind.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandEdit.java",
|
||||
"adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDatapack.java"
|
||||
);
|
||||
|
||||
private GenerationRevisionScope() {
|
||||
}
|
||||
|
||||
public static GenerationBuildRevision.CaptureOptions current(Path repository) {
|
||||
return new GenerationBuildRevision.CaptureOptions(repository, 1,
|
||||
"art.arcane.iris.engine.history.GenerationKernelV1",
|
||||
List.of(new GenerationBuildRevision.AlgorithmVersion(1, 1)), SOURCE_ROOTS, OPERATIONAL_EXCLUSIONS);
|
||||
}
|
||||
}
|
||||
+55
-57
@@ -1,4 +1,4 @@
|
||||
import art.arcane.iris.buildtools.GenerationKernelSeal;
|
||||
import art.arcane.iris.buildtools.GenerationBuildRevision;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
@@ -17,20 +17,20 @@ import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GenerationKernelSealTest {
|
||||
public class GenerationBuildRevisionTest {
|
||||
@Rule
|
||||
public final TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void verifiesUnchangedSourceAndCanonicalDependencyContents() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies());
|
||||
String original = GenerationKernelSeal.artifactHash(fixture.dependency());
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies());
|
||||
String original = GenerationBuildRevision.artifactHash(fixture.dependency());
|
||||
artifact(fixture.dependency(), "same code", "another version", 1_700_000_000_000L);
|
||||
assertEquals(original, GenerationKernelSeal.artifactHash(fixture.dependency()));
|
||||
GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies());
|
||||
assertEquals(GenerationKernelSeal.fingerprint(GenerationKernelSeal.read(fixture.seal())),
|
||||
GenerationKernelSeal.fingerprint(GenerationKernelSeal.capture(fixture.options(), fixture.dependencies())));
|
||||
assertEquals(original, GenerationBuildRevision.artifactHash(fixture.dependency()));
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies());
|
||||
assertEquals(GenerationBuildRevision.fingerprint(GenerationBuildRevision.read(fixture.revision())),
|
||||
GenerationBuildRevision.fingerprint(GenerationBuildRevision.capture(fixture.options(), fixture.dependencies())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -39,120 +39,118 @@ public class GenerationKernelSealTest {
|
||||
Path source = fixture.root().resolve("generation/Noise.java");
|
||||
Files.writeString(source, "class Noise { int value = 2; }");
|
||||
assertTrue(assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies()))
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies()))
|
||||
.getMessage().contains("source changed"));
|
||||
Files.writeString(source, "class Noise { int value = 1; }");
|
||||
Path added = fixture.root().resolve("generation/Added.java");
|
||||
Files.writeString(added, "class Added {}");
|
||||
assertTrue(assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies()))
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies()))
|
||||
.getMessage().contains("source added"));
|
||||
Files.delete(added);
|
||||
Files.delete(source);
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies()));
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsChangedAddedAndDeletedDependencies() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
String original = GenerationKernelSeal.artifactHash(fixture.dependency());
|
||||
String original = GenerationBuildRevision.artifactHash(fixture.dependency());
|
||||
artifact(fixture.dependency(), "changed code", "same version", 0L);
|
||||
assertNotEquals(original, GenerationKernelSeal.artifactHash(fixture.dependency()));
|
||||
assertNotEquals(original, GenerationBuildRevision.artifactHash(fixture.dependency()));
|
||||
assertTrue(assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies()))
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies()))
|
||||
.getMessage().contains("dependency changed"));
|
||||
artifact(fixture.dependency(), "same code", "same version", 0L);
|
||||
assertThrows(IOException.class, () -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(),
|
||||
assertThrows(IOException.class, () -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(),
|
||||
Map.of("math", fixture.dependency(), "new-math", fixture.dependency())));
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), Map.of()));
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), Map.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMultiReleaseManifestChanges() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
String original = GenerationKernelSeal.artifactHash(fixture.dependency());
|
||||
String original = GenerationBuildRevision.artifactHash(fixture.dependency());
|
||||
artifact(fixture.dependency(), "same code", "same version\r\nMulti-Release: true", 0L);
|
||||
assertNotEquals(original, GenerationKernelSeal.artifactHash(fixture.dependency()));
|
||||
assertNotEquals(original, GenerationBuildRevision.artifactHash(fixture.dependency()));
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies()));
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsAgentEntrypointAndCapabilityChanges() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
String original = GenerationKernelSeal.artifactHash(fixture.dependency());
|
||||
String original = GenerationBuildRevision.artifactHash(fixture.dependency());
|
||||
for (String attribute : List.of("Agent-Class", "Premain-Class", "Launcher-Agent-Class", "Boot-Class-Path",
|
||||
"Can-Redefine-Classes", "Can-Retransform-Classes", "Can-Set-Native-Method-Prefix")) {
|
||||
artifact(fixture.dependency(), "same code", "same version\r\n" + attribute + ": changed", 0L);
|
||||
assertNotEquals(attribute, original, GenerationKernelSeal.artifactHash(fixture.dependency()));
|
||||
assertNotEquals(attribute, original, GenerationBuildRevision.artifactHash(fixture.dependency()));
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies()));
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sealsExplicitAgentBuildInputsAndExecutableSources() throws Exception {
|
||||
public void capturesExplicitAgentBuildInputsAndExecutableSources() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
Path agentSource = fixture.root().resolve("agent/src/main/java/Installer.java");
|
||||
Path agentBuild = fixture.root().resolve("agent/build.gradle");
|
||||
Files.createDirectories(agentSource.getParent());
|
||||
Files.writeString(agentSource, "class Installer {}");
|
||||
Files.writeString(agentBuild, "plugins { id 'java' }");
|
||||
GenerationKernelSeal.CaptureOptions options = new GenerationKernelSeal.CaptureOptions(
|
||||
fixture.root(), 1, "example.KernelV1", List.of(new GenerationKernelSeal.AlgorithmVersion(1, 1)),
|
||||
GenerationBuildRevision.CaptureOptions options = new GenerationBuildRevision.CaptureOptions(
|
||||
fixture.root(), 1, "example.KernelV1", List.of(new GenerationBuildRevision.AlgorithmVersion(1, 1)),
|
||||
List.of("generation", "agent/build.gradle", "agent/src/main/java"), List.of("generation/kernels"));
|
||||
Path seal = fixture.root().resolve("agent.seal");
|
||||
GenerationKernelSeal.writeNew(seal, GenerationKernelSeal.capture(options, fixture.dependencies()));
|
||||
GenerationKernelSeal.verify(fixture.root(), seal, fixture.dependencies());
|
||||
Path revision = fixture.root().resolve("agent.revision");
|
||||
GenerationBuildRevision.write(revision, GenerationBuildRevision.capture(options, fixture.dependencies()));
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), revision, fixture.dependencies());
|
||||
Files.writeString(agentBuild, "plugins { id 'application' }");
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), seal, fixture.dependencies()));
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), revision, fixture.dependencies()));
|
||||
Files.writeString(agentBuild, "plugins { id 'java' }");
|
||||
Files.writeString(agentSource, "class Installer { boolean defers; }");
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verify(fixture.root(), seal, fixture.dependencies()));
|
||||
() -> GenerationBuildRevision.verifySnapshot(fixture.root(), revision, fixture.dependencies()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checksTheActualPlatformDependencySubset() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
GenerationKernelSeal.verifyDependencySubset(fixture.seal(), fixture.dependencies());
|
||||
assertThrows(IOException.class, () -> GenerationKernelSeal.verifyDependencySubset(fixture.seal(),
|
||||
GenerationBuildRevision.verifyDependencySubset(fixture.revision(), fixture.dependencies());
|
||||
assertThrows(IOException.class, () -> GenerationBuildRevision.verifyDependencySubset(fixture.revision(),
|
||||
Map.of("unknown-library", fixture.dependency())));
|
||||
artifact(fixture.dependency(), "platform override", "same version", 0L);
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.verifyDependencySubset(fixture.seal(), fixture.dependencies()));
|
||||
() -> GenerationBuildRevision.verifyDependencySubset(fixture.revision(), fixture.dependencies()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void catalogCannotSelectAnUnsealedAlgorithmTuple() throws Exception {
|
||||
public void catalogRequiresAPackagedAlgorithmTuple() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
Path catalog = fixture.root().resolve("catalog.tsv");
|
||||
Files.writeString(catalog, "iris-generation-kernel-catalog-v1\ncurrent\t1\t1\t1\nkernel\t1\n");
|
||||
Map<Integer, GenerationKernelSeal.SourceManifest> manifests = Map.of(1, GenerationKernelSeal.read(fixture.seal()));
|
||||
GenerationKernelSeal.verifyCatalog(catalog, manifests);
|
||||
Map<Integer, GenerationBuildRevision.SourceManifest> manifests = Map.of(1, GenerationBuildRevision.read(fixture.revision()));
|
||||
GenerationBuildRevision.verifyCatalog(catalog, manifests);
|
||||
Files.writeString(catalog, "iris-generation-kernel-catalog-v1\ncurrent\t1\t2\t1\nkernel\t1\n");
|
||||
assertThrows(IOException.class, () -> GenerationKernelSeal.verifyCatalog(catalog, manifests));
|
||||
assertThrows(IOException.class, () -> GenerationBuildRevision.verifyCatalog(catalog, manifests));
|
||||
Files.writeString(catalog, "iris-generation-kernel-catalog-v1\ncurrent\t1\t1\t1\nkernel\t1\nkernel\t2\n");
|
||||
assertThrows(IOException.class, () -> GenerationKernelSeal.verifyCatalog(catalog, manifests));
|
||||
assertThrows(IOException.class, () -> GenerationBuildRevision.verifyCatalog(catalog, manifests));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void permitsSeparatelyScopedFutureKernelsWithoutChangingRetainedSeal() throws Exception {
|
||||
public void recapturesChangedGenerationWithoutChangingAbi() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
Path future = fixture.root().resolve("generation/kernels/v2/NewMath.java");
|
||||
Files.createDirectories(future.getParent());
|
||||
Files.writeString(future, "class NewMath {}");
|
||||
GenerationKernelSeal.verify(fixture.root(), fixture.seal(), fixture.dependencies());
|
||||
GenerationKernelSeal.SourceManifest next = GenerationKernelSeal.capture(new GenerationKernelSeal.CaptureOptions(
|
||||
fixture.root(), 2, "example.KernelV2", List.of(new GenerationKernelSeal.AlgorithmVersion(2, 3)),
|
||||
List.of("generation/kernels/v2"), List.of()), fixture.dependencies());
|
||||
assertNotEquals(GenerationKernelSeal.fingerprint(next),
|
||||
GenerationKernelSeal.fingerprint(GenerationKernelSeal.read(fixture.seal())));
|
||||
assertThrows(IOException.class,
|
||||
() -> GenerationKernelSeal.writeNew(fixture.seal(), next));
|
||||
String original = GenerationBuildRevision.fingerprint(GenerationBuildRevision.read(fixture.revision()));
|
||||
Files.writeString(fixture.root().resolve("generation/Noise.java"), "class Noise { int value = 9; }");
|
||||
artifact(fixture.dependency(), "new generation code", "new version", 0L);
|
||||
GenerationBuildRevision.SourceManifest next = GenerationBuildRevision.capture(fixture.options(), fixture.dependencies());
|
||||
GenerationBuildRevision.write(fixture.revision(), next);
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies());
|
||||
assertEquals(1, next.abi());
|
||||
assertNotEquals(original, GenerationBuildRevision.fingerprint(next));
|
||||
assertEquals(next, GenerationBuildRevision.read(fixture.revision()));
|
||||
}
|
||||
|
||||
private Fixture fixture() throws Exception {
|
||||
@@ -161,13 +159,13 @@ public class GenerationKernelSealTest {
|
||||
Files.writeString(root.resolve("generation/Noise.java"), "class Noise { int value = 1; }");
|
||||
Path dependency = root.resolve("math.jar");
|
||||
artifact(dependency, "same code", "same version", 0L);
|
||||
GenerationKernelSeal.CaptureOptions options = new GenerationKernelSeal.CaptureOptions(
|
||||
root, 1, "example.KernelV1", List.of(new GenerationKernelSeal.AlgorithmVersion(1, 1)),
|
||||
GenerationBuildRevision.CaptureOptions options = new GenerationBuildRevision.CaptureOptions(
|
||||
root, 1, "example.KernelV1", List.of(new GenerationBuildRevision.AlgorithmVersion(1, 1)),
|
||||
List.of("generation"), List.of("generation/kernels"));
|
||||
Path seal = root.resolve("abi-1.seal");
|
||||
Path revision = root.resolve("abi-1.revision");
|
||||
Map<String, Path> dependencies = Map.of("math", dependency);
|
||||
GenerationKernelSeal.writeNew(seal, GenerationKernelSeal.capture(options, dependencies));
|
||||
return new Fixture(root, seal, dependency, dependencies, options);
|
||||
GenerationBuildRevision.write(revision, GenerationBuildRevision.capture(options, dependencies));
|
||||
return new Fixture(root, revision, dependency, dependencies, options);
|
||||
}
|
||||
|
||||
private static void artifact(Path path, String content, String version, long timestamp) throws IOException {
|
||||
@@ -186,7 +184,7 @@ public class GenerationKernelSealTest {
|
||||
}
|
||||
}
|
||||
|
||||
private record Fixture(Path root, Path seal, Path dependency, Map<String, Path> dependencies,
|
||||
GenerationKernelSeal.CaptureOptions options) {
|
||||
private record Fixture(Path root, Path revision, Path dependency, Map<String, Path> dependencies,
|
||||
GenerationBuildRevision.CaptureOptions options) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import art.arcane.iris.buildtools.GenerationRevisionScope;
|
||||
import art.arcane.iris.buildtools.GenerationBuildRevision;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GenerationRevisionScopeTest {
|
||||
private static final String ENGINE_ROOT = "core/src/main/java/art/arcane/iris/engine/";
|
||||
private static final String NMS_ROOT = "adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/";
|
||||
private static final String COMMAND_ROOT = "adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/";
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder temporary = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void operationalChangesPreserveTheGenerationFingerprint() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
String original = GenerationBuildRevision.fingerprint(GenerationBuildRevision.read(fixture.revision()));
|
||||
for (String exclusion : fixture.options().exclusions()) {
|
||||
if (!exclusion.endsWith(".java")) {
|
||||
continue;
|
||||
}
|
||||
Path operationalSource = fixture.root().resolve(exclusion);
|
||||
write(operationalSource, "class Operational { String message = \"updated\"; }");
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies());
|
||||
assertEquals(exclusion, original, GenerationBuildRevision.fingerprint(
|
||||
GenerationBuildRevision.capture(fixture.options(), fixture.dependencies())));
|
||||
Files.delete(operationalSource);
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generationChangesProduceANewBuildRevision() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
for (String relative : generationSources()) {
|
||||
Path source = fixture.root().resolve(relative);
|
||||
Files.writeString(source, "class Generation { int sample = 2; }");
|
||||
GenerationBuildRevision.SourceManifest changed = GenerationBuildRevision.capture(fixture.options(), fixture.dependencies());
|
||||
assertNotEquals(relative, GenerationBuildRevision.fingerprint(GenerationBuildRevision.read(fixture.revision())),
|
||||
GenerationBuildRevision.fingerprint(changed));
|
||||
GenerationBuildRevision.write(fixture.revision(), changed);
|
||||
GenerationBuildRevision.verifySnapshot(fixture.root(), fixture.revision(), fixture.dependencies());
|
||||
Files.writeString(source, "class Generation { int sample = 1; }");
|
||||
GenerationBuildRevision.write(fixture.revision(), GenerationBuildRevision.capture(fixture.options(), fixture.dependencies()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newSourcesChangeRevisionAcrossPlatforms() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
for (String root : fixture.options().roots()) {
|
||||
if (!root.endsWith("/java")) {
|
||||
continue;
|
||||
}
|
||||
assertNewSourceChangesRevision(fixture, root + "/NewSource.java");
|
||||
}
|
||||
assertNewSourceChangesRevision(fixture, ENGINE_ROOT + "NewDiagnostics.java");
|
||||
assertNewSourceChangesRevision(fixture, NMS_ROOT + "NmsWorldLifecycleHelper.java");
|
||||
assertNewSourceChangesRevision(fixture, COMMAND_ROOT + "NewCommand.java");
|
||||
assertNewSourceChangesRevision(fixture, "core/src/main/java/art/arcane/iris/core/safeguard/task/NewTask.java");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyExplicitOperationalFilesAreExcluded() throws Exception {
|
||||
Fixture fixture = fixture();
|
||||
List<String> directoryExclusions = fixture.options().exclusions().stream()
|
||||
.filter(path -> !path.endsWith(".java"))
|
||||
.toList();
|
||||
assertTrue(directoryExclusions.isEmpty());
|
||||
GenerationBuildRevision.SourceManifest manifest = GenerationBuildRevision.read(fixture.revision());
|
||||
for (String relative : generationSources()) {
|
||||
assertTrue(relative, manifest.sources().containsKey(relative));
|
||||
}
|
||||
assertFalse(manifest.sources().containsKey(ENGINE_ROOT + "EngineDiagnostics.java"));
|
||||
assertFalse(manifest.sources().containsKey(NMS_ROOT + "NmsWorldLifecycle.java"));
|
||||
assertFalse(manifest.sources().containsKey(COMMAND_ROOT + "CommandIris.java"));
|
||||
}
|
||||
|
||||
private static void assertNewSourceChangesRevision(Fixture fixture, String relative) throws IOException {
|
||||
Path source = fixture.root().resolve(relative);
|
||||
write(source, "class NewSource {}");
|
||||
GenerationBuildRevision.SourceManifest changed = GenerationBuildRevision.capture(fixture.options(), fixture.dependencies());
|
||||
assertTrue(changed.sources().containsKey(relative));
|
||||
assertNotEquals(GenerationBuildRevision.fingerprint(GenerationBuildRevision.read(fixture.revision())),
|
||||
GenerationBuildRevision.fingerprint(changed));
|
||||
Files.delete(source);
|
||||
}
|
||||
|
||||
private Fixture fixture() throws Exception {
|
||||
Path root = temporary.newFolder().toPath();
|
||||
GenerationBuildRevision.CaptureOptions options = GenerationRevisionScope.current(root);
|
||||
for (String scope : options.roots()) {
|
||||
Path path = root.resolve(scope);
|
||||
if (scope.endsWith("/java")) {
|
||||
Files.createDirectories(path);
|
||||
} else {
|
||||
write(path, "build input");
|
||||
}
|
||||
}
|
||||
for (String relative : generationSources()) {
|
||||
write(root.resolve(relative), "class Generation { int sample = 1; }");
|
||||
}
|
||||
for (String exclusion : options.exclusions()) {
|
||||
if (exclusion.endsWith(".java")) {
|
||||
write(root.resolve(exclusion), "class Operational {}");
|
||||
}
|
||||
}
|
||||
Path dependency = root.resolve("dependency.jar");
|
||||
try (ZipOutputStream output = new ZipOutputStream(Files.newOutputStream(dependency))) {
|
||||
output.putNextEntry(new ZipEntry("generation/Math.class"));
|
||||
output.write("generation dependency".getBytes(StandardCharsets.UTF_8));
|
||||
output.closeEntry();
|
||||
}
|
||||
Map<String, Path> dependencies = Map.of("volmlib", dependency);
|
||||
Path revision = root.resolve("abi-1.revision");
|
||||
GenerationBuildRevision.write(revision, GenerationBuildRevision.capture(options, dependencies));
|
||||
return new Fixture(root, revision, options, dependencies);
|
||||
}
|
||||
|
||||
private static List<String> generationSources() {
|
||||
return List.of(
|
||||
ENGINE_ROOT + "IrisEngine.java",
|
||||
ENGINE_ROOT + "IrisComplex.java",
|
||||
ENGINE_ROOT + "IrisEngineMantle.java",
|
||||
ENGINE_ROOT + "EngineRuntimeBuilder.java",
|
||||
ENGINE_ROOT + "history/GenerationKernelV1.java",
|
||||
ENGINE_ROOT + "object/IrisDimension.java",
|
||||
ENGINE_ROOT + "mode/ModeOverworld.java",
|
||||
"core/src/main/java/art/arcane/iris/core/loader/IrisData.java",
|
||||
"core/src/main/java/art/arcane/iris/core/IrisSettings.java",
|
||||
"core/agent/src/main/java/GenerationTransformer.java",
|
||||
NMS_ROOT + "NmsGenerationHooks.java",
|
||||
NMS_ROOT + "NmsGenerationRegistry.java",
|
||||
NMS_ROOT + "NMSBinding.java",
|
||||
"spi/src/main/java/art/arcane/iris/spi/PlatformGenerationRegistry.java",
|
||||
"adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java"
|
||||
);
|
||||
}
|
||||
|
||||
private static void write(Path path, String content) throws IOException {
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, content);
|
||||
}
|
||||
|
||||
private record Fixture(Path root, Path revision, GenerationBuildRevision.CaptureOptions options,
|
||||
Map<String, Path> dependencies) {
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class JarCompactorTest {
|
||||
writeEntry(output, "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\r\nMulti-Release: true\r\n\r\n"
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
writeEntry(output, "JarCompactorTest.class", classResource.readAllBytes());
|
||||
writeEntry(output, "META-INF/iris/generation-kernels/abi-1.seal", "registry fixture".getBytes(StandardCharsets.UTF_8));
|
||||
writeEntry(output, "META-INF/iris/generation-kernels/abi-1.revision", "registry fixture".getBytes(StandardCharsets.UTF_8));
|
||||
writeEntry(output, "example/caf\u00e9.txt", "unicode name".getBytes(StandardCharsets.UTF_8));
|
||||
for (int index = 0; index < 100; index++) {
|
||||
writeEntry(output, "example/item-" + index + ".txt", ("value-" + index).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Reference in New Issue
Block a user