diff --git a/falco-benchmarks/src/test/java/net/onelitefeather/falco/benchmark/instance/ChunkFootprintTest.java b/falco-benchmarks/src/test/java/net/onelitefeather/falco/benchmark/instance/ChunkFootprintTest.java index cc4c866..bd3ebd8 100644 --- a/falco-benchmarks/src/test/java/net/onelitefeather/falco/benchmark/instance/ChunkFootprintTest.java +++ b/falco-benchmarks/src/test/java/net/onelitefeather/falco/benchmark/instance/ChunkFootprintTest.java @@ -19,8 +19,10 @@ import org.openjdk.jol.info.GraphLayout; import org.openjdk.jol.vm.VM; +import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -334,6 +336,26 @@ class ChunkFootprintTest { */ private static final int TRACKER_CHUNKS = 16; + /** + * How many classes may carry a negative count before the comparison is treated as broken. + *

+ * The per class figures are differences between two walks — everything the chunk and its instance + * retain, minus what the instance retains alone. A count below zero cannot be a property of the + * chunk, because a set of objects that exist because the chunk exists has no negative + * cardinality. It means the instance grew between the two walks, and the classes seen doing that + * are the JVM's own bookkeeping: {@code java.io.FileCleanable} when a file handle is registered, + * {@code byte[]} when a string is built. Comparing such a row states nothing about either chunk, + * so it is skipped and its bytes are carried into the total instead. + *

+ *

+ * The limit is what keeps that from turning into a way of passing. Skipping one or two rows loses + * nothing; skipping many would mean the walk disagreed with itself wholesale, and the answer to + * that is a failure, not a comparison of whatever survived. Two is the observed ceiling: the + * failures on 2026-08-03 carried exactly one such class each. + *

+ */ + private static final int UNMEASURABLE_CLASS_LIMIT = 2; + /** * The width of the label column of the breakdown table. */ @@ -885,11 +907,20 @@ private static void assertOnlyTheDeclaredClassesDiffer(Footprint minestom, Chunk classNames.addAll(declared.keySet()); long declaredBytes = 0; + long unmeasuredBytes = 0; + final List unmeasured = new ArrayList<>(); for (String className : classNames) { if (className.startsWith(minestomType) || className.startsWith(falcoType)) { continue; } + if (minestom.objectsOf(className) < 0 || falcoSide.objectsOf(className) < 0) { + unmeasured.add(className + ": Minestom " + minestom.objectsOf(className) + " objects / " + + minestom.bytesOf(className) + " B, Falco " + falcoSide.objectsOf(className) + + " / " + falcoSide.bytesOf(className) + " B"); + unmeasuredBytes += falcoSide.bytesOf(className) - minestom.bytesOf(className); + continue; + } final Declared row = declared.get(className); if (row == null) { @@ -920,10 +951,21 @@ private static void assertOnlyTheDeclaredClassesDiffer(Footprint minestom, Chunk + " bytes, so it holds the declared objects at a size nobody declared"); declaredBytes += expectedBytes - minestom.bytesOf(className); } - assertEquals(declaredBytes, falcoSide.bytes() - minestom.bytes(), + if (!unmeasured.isEmpty()) { + System.out.println(context + ": " + unmeasured.size() + " class(es) carried a negative count " + + "and were left out of the comparison, together worth " + unmeasuredBytes + + " B of the total difference: " + String.join("; ", unmeasured)); + } + assertTrue(unmeasured.size() <= UNMEASURABLE_CLASS_LIMIT, + context + ": " + unmeasured.size() + " classes carried a negative count. Skipping that many " + + "stops this from being a comparison at all — the exclusion exists for the one or " + + "two bookkeeping classes the JVM adds between the two walks, not for a walk that " + + "disagreed with itself wholesale: " + String.join("; ", unmeasured)); + assertEquals(declaredBytes + unmeasuredBytes, falcoSide.bytes() - minestom.bytes(), context + ": the two chunks differ by " + (falcoSide.bytes() - minestom.bytes()) + " bytes while the classes the plan declared account for " + declaredBytes - + " and every other class was just asserted equal. The two do not add up, which " + + ", the classes no walk could measure for " + unmeasuredBytes + + ", and every other class was just asserted equal. The three do not add up, which " + "is a statement about the walk rather than about the chunk: the per class table " + "of a footprint has to sum to the total that footprint reports."); assertEquals(ClassLayout.parseInstance(minestomChunk).instanceSize(),