test(mod): JUnit 5 tests for Stage 1 optimization helpers
Some checks failed
CI / backend (cargo test + clippy) (push) Successful in 3m43s
CI / frontend (lint + test + build) (push) Failing after 23s
CI / mod (gradle test + checkFolderLimit) (push) Successful in 2m13s

53 tests covering the pure-logic pieces: OptimizeToggles, LruMap, EntityLod,
ParticleLod, Workers, HudWorth, SpawnerCache, StableText, CachedStack.
This commit is contained in:
loki5512344 2026-10-10 14:50:50 +02:00
parent 1a50a757b7
commit 494990ca4c
Signed by: boba
GPG key ID: 253067914055423B
9 changed files with 953 additions and 0 deletions

View file

@ -0,0 +1,143 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.block;
import dev.loki.lovisual.render.engine.optimize.OptimizeToggles;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Verifies the {@link SpawnerCache} lookup logic. We can't construct a real
* {@code BaseSpawner} in the test JVM, so the test uses a hand-rolled mock
* that implements {@link TickCached} directly — the cache only relies on the
* interface, never on the {@code BaseSpawner} class itself.
*/
public final class SpawnerCacheTest {
/** Minimal TickCached implementation for testing. */
private static final class FakeSpawner implements TickCached {
Object cached;
long cachedTick = Long.MIN_VALUE;
@Override
public Object lovisual$cached() {
return cached;
}
@Override
public long lovisual$cachedTick() {
return cachedTick;
}
@Override
public void lovisual$setCached(Object value, long tick) {
this.cached = value;
this.cachedTick = tick;
}
}
@AfterEach
void resetToggles() {
OptimizeToggles.reset();
}
@Test
void enabledReflectsOptimizeToggles() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
assertFalse(SpawnerCache.enabled());
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
true, false, false, false);
assertTrue(SpawnerCache.enabled());
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
assertFalse(SpawnerCache.enabled());
}
@Test
void getReturnsNullForEmptyCache() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
true, false, false, false);
FakeSpawner spawner = new FakeSpawner();
assertNull(SpawnerCache.get(spawner, 100L));
}
@Test
void putThenGetReturnsCachedValueForSameTick() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
true, false, false, false);
FakeSpawner spawner = new FakeSpawner();
EntityRenderState state = new EntityRenderState();
SpawnerCache.put(spawner, 42L, state);
assertSame(state, SpawnerCache.get(spawner, 42L));
}
@Test
void getReturnsNullForDifferentTick() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
true, false, false, false);
FakeSpawner spawner = new FakeSpawner();
EntityRenderState state = new EntityRenderState();
SpawnerCache.put(spawner, 42L, state);
assertNull(SpawnerCache.get(spawner, 43L));
assertNull(SpawnerCache.get(spawner, 41L));
}
@Test
void putOverwritesPreviousCache() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
true, false, false, false);
FakeSpawner spawner = new FakeSpawner();
EntityRenderState first = new EntityRenderState();
EntityRenderState second = new EntityRenderState();
SpawnerCache.put(spawner, 1L, first);
SpawnerCache.put(spawner, 2L, second);
assertSame(second, SpawnerCache.get(spawner, 2L));
assertNull(SpawnerCache.get(spawner, 1L));
}
@Test
void cachedValuePersistsAcrossMultipleGets() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
true, false, false, false);
FakeSpawner spawner = new FakeSpawner();
EntityRenderState state = new EntityRenderState();
SpawnerCache.put(spawner, 100L, state);
// Multiple reads should all return the same value (no eviction).
for (int i = 0; i < 5; i++) {
assertSame(state, SpawnerCache.get(spawner, 100L));
}
assertEquals(100L, spawner.lovisual$cachedTick());
}
}

View file

@ -0,0 +1,74 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.core;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class LruMapTest {
@Test
void accessOrderIsRespected() {
LruMap<String, Integer> map = new LruMap<>(3);
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
// Touch "a" — it should now be the most-recently-used.
Integer touched = map.get("a");
assertEquals(1, touched);
// Adding a fourth entry evicts the LRU, which is "b" (since "a" was just touched and "c" is newest).
map.put("d", 4);
assertNull(map.get("b"));
assertTrue(map.containsKey("a"));
assertTrue(map.containsKey("c"));
assertTrue(map.containsKey("d"));
}
@Test
void capIsMaxSizePlusOne() {
// removeEldestEntry fires when size > maxSize, so we hold up to maxSize entries after the next put.
LruMap<Integer, String> map = new LruMap<>(2);
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
assertEquals(2, map.size());
assertNull(map.get(1));
assertEquals("two", map.get(2));
assertEquals("three", map.get(3));
}
@Test
void maxSizeAccessorMatchesConstructor() {
LruMap<String, String> map = new LruMap<>(1024);
assertEquals(1024, map.maxSize());
}
@Test
void iterationOrderFollowsAccessOrder() {
LruMap<String, Integer> map = new LruMap<>(5);
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.get("a");
List<String> keys = new ArrayList<>(map.keySet());
// After touching "a", iteration order should be b, c, a.
assertEquals(List.of("b", "c", "a"), keys);
}
@Test
void putReplacingExistingKeyDoesNotEvict() {
LruMap<String, Integer> map = new LruMap<>(2);
map.put("a", 1);
map.put("b", 2);
map.put("a", 100);
assertEquals(2, map.size());
assertEquals(100, map.get("a"));
// "b" should still be present — replacing a key's value doesn't grow size.
assertSame(2, map.get("b"));
}
}

View file

@ -0,0 +1,92 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.entity;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.system.MemoryStack;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
/**
* Verifies {@link CachedStack} — the ThreadLocal-free MemoryStack push helper.
*
* <p>Tests run on the test JVM's main thread; the first call populates the
* cache, subsequent calls from the same thread take the fast path.
*/
public final class CachedStackTest {
@AfterEach
void reset() {
// The cache is a single volatile — for the next test we just push something
// from this thread again so cachedThread() returns to the current thread.
// (Cross-test isolation isn't perfect because the cache is global, but
// the tests below assert observable invariants rather than internal state.)
CachedStack.push().close();
}
@Test
void pushReturnsValidStack() {
try (MemoryStack stack = CachedStack.push()) {
assertNotNull(stack);
// A sanity check that the returned stack actually works.
long ptr = stack.nmalloc(8);
assertNotSame(0L, ptr);
}
}
@Test
void pushTwiceFromSameThreadReturnsSameStack() {
MemoryStack first;
try (MemoryStack s = CachedStack.push()) {
first = s;
}
MemoryStack second;
try (MemoryStack s = CachedStack.push()) {
second = s;
}
assertSame(first, second,
"Same-thread push should return the same underlying MemoryStack instance");
assertEquals(Thread.currentThread(), CachedStack.cachedThread());
}
@Test
void pushFromDifferentThreadUpdatesCachedThread() throws InterruptedException {
// First call from this thread to populate the cache.
try (MemoryStack s = CachedStack.push()) {
assertNotNull(s);
}
assertEquals(Thread.currentThread(), CachedStack.cachedThread());
// Now push from a different thread — the cache should switch.
Thread other = new Thread(() -> {
try (MemoryStack s = CachedStack.push()) {
assertNotNull(s);
}
});
other.start();
other.join();
// After the other thread pushed, the cached thread is the other thread.
assertEquals(other, CachedStack.cachedThread());
assertNotNull(CachedStack.cachedStack());
// Restore the cache to this thread for the next test.
try (MemoryStack s = CachedStack.push()) {
assertNotNull(s);
}
assertEquals(Thread.currentThread(), CachedStack.cachedThread());
}
@Test
void nestedPushesWorkLikeVanillaStack() {
// Vanilla MemoryStack supports nested stackPush/stackClose — CachedStack should too.
try (MemoryStack outer = CachedStack.push()) {
long outerPtr = outer.nmalloc(8);
try (MemoryStack inner = CachedStack.push()) {
long innerPtr = inner.nmalloc(8);
assertNotSame(outerPtr, innerPtr);
}
}
}
}

View file

@ -0,0 +1,87 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.entity;
import dev.loki.lovisual.render.engine.optimize.OptimizeToggles;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class EntityLodTest {
@AfterEach
void resetToggles() {
OptimizeToggles.reset();
}
@Test
void farReturnsFalseWhenLodDisabled() {
OptimizeToggles.apply(
false, 35,
false, false, false,
false, 0,
false, false, false, false);
assertFalse(EntityLod.far(10_000_000));
assertFalse(EntityLod.farPlayer(10_000_000));
}
@Test
void farComparesSquaredDistance() {
OptimizeToggles.apply(
true, 35,
false, false, false,
false, 0,
false, false, false, false);
// d=35 -> d²=1225: 1225 is NOT greater than 1225, so not far.
assertFalse(EntityLod.far(35 * 35));
// d² just above threshold: should be far.
assertTrue(EntityLod.far(35 * 35 + 1));
// very close: not far.
assertFalse(EntityLod.far(0));
assertFalse(EntityLod.far(1));
}
@Test
void farThresholdSqMatchesDistance() {
OptimizeToggles.apply(
true, 16,
false, false, false,
false, 0,
false, false, false, false);
assertEquals(16 * 16, EntityLod.farThresholdSq(), 0.0001);
assertFalse(EntityLod.far(16 * 16));
assertTrue(EntityLod.far(16 * 16 + 1));
}
@Test
void farPlayerMatchesFarInStage1() {
OptimizeToggles.apply(
true, 35,
false, false, false,
false, 0,
false, false, false, false);
// Stage 1 collapses player LOD into entity LOD — same toggle, same threshold.
for (double dSq : new double[]{0, 100, 1224, 1226, 10_000}) {
assertEquals(EntityLod.far(dSq), EntityLod.farPlayer(dSq),
"far and farPlayer disagree at dSq=" + dSq);
}
}
@Test
void farRespectsRuntimeToggleChanges() {
OptimizeToggles.apply(
true, 35,
false, false, false,
false, 0,
false, false, false, false);
assertTrue(EntityLod.far(10_000));
// Toggle off mid-test — same distance should now be "not far".
OptimizeToggles.apply(
false, 35,
false, false, false,
false, 0,
false, false, false, false);
assertFalse(EntityLod.far(10_000));
}
}

View file

@ -0,0 +1,100 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.hud;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class HudWorthTest {
@AfterEach
void reset() {
HudWorth.reset();
}
@Test
void worthStartsTrue() {
assertTrue(HudWorth.worth());
}
@Test
void worthStaysTrueWhenFramesPerRefreshIsHigh() {
// Frame time 5 ms, refresh 16.6 ms (60 Hz) -> ~3.3 frames per refresh -> well above ON_ABOVE (2.0).
// Should stay "worth".
long refreshNs = 16_600_000L;
long now = 0;
for (int i = 0; i < 50; i++) {
now += 5_000_000L;
assertTrue(HudWorth.update(now, refreshNs));
}
assertTrue(HudWorth.worth());
}
@Test
void worthFlipsToFalseWhenFramesPerRefreshDropsBelowHysteresis() {
// Frame time ~= refresh time -> ~1 frame per refresh -> below OFF_BELOW (1.5).
long refreshNs = 16_600_000L;
long now = 0;
// Warm up the EMA at the high frame rate (worth stays true).
for (int i = 0; i < 50; i++) {
now += 5_000_000L;
HudWorth.update(now, refreshNs);
}
assertTrue(HudWorth.worth());
// Now drop the frame rate to one frame per refresh.
for (int i = 0; i < 50; i++) {
now += 16_600_000L;
HudWorth.update(now, refreshNs);
}
assertFalse(HudWorth.worth());
}
@Test
void hysteresisAvoidsFlickerNearTheThreshold() {
// Sawtooth between 1.4 and 1.6 frames-per-refresh should NOT cause worth to flip back and forth.
// (Default initial state is worth=true, so 1.4 will drop it; the test asserts it stays dropped
// rather than flickering on every 1.6 frame.)
long refreshNs = 16_600_000L;
long now = 0;
// Drop to ~1.4 frames per refresh first (16.6 / ~12ms = 1.38).
for (int i = 0; i < 60; i++) {
now += 12_000_000L;
HudWorth.update(now, refreshNs);
}
assertFalse(HudWorth.worth());
// A few short frames (~10ms => 1.66 fps/refresh) should NOT flip worth back on (below ON_ABOVE=2.0).
for (int i = 0; i < 5; i++) {
now += 10_000_000L;
HudWorth.update(now, refreshNs);
}
assertFalse(HudWorth.worth());
// A longer sustained high frame rate (5ms => 3.32 fps/refresh) flips it back on.
for (int i = 0; i < 60; i++) {
now += 5_000_000L;
HudWorth.update(now, refreshNs);
}
assertTrue(HudWorth.worth());
}
@Test
void updateIgnoresExtremeGaps() {
// A 2-second gap (paused or main-thread stall) should NOT poison the EMA.
long refreshNs = 16_600_000L;
long now = 0;
for (int i = 0; i < 30; i++) {
now += 5_000_000L;
HudWorth.update(now, refreshNs);
}
double beforeGap = HudWorth.frameNs();
now += 2_000_000_000L; // 2 s gap
HudWorth.update(now, refreshNs);
// EMA should not have jumped to the gap value (the guard rejects dt >= 1e9 ns).
// It may have moved slightly due to the legitimate 5 ms frames, but not to ~2 s.
assertTrue(HudWorth.frameNs() < 100_000_000L,
"EMA should not absorb a 2-second pause, frameNs=" + HudWorth.frameNs());
assertTrue(HudWorth.frameNs() > 0, "EMA should be positive after warmup");
// Sanity: the gap reading didn't blow up frameNs to near 2 seconds.
assertTrue(HudWorth.frameNs() < beforeGap * 2 + 1_000_000L);
}
}

View file

@ -0,0 +1,166 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.particle;
import dev.loki.lovisual.render.engine.optimize.OptimizeToggles;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class ParticleLodTest {
@AfterEach
void resetToggles() {
OptimizeToggles.reset();
}
@Test
void keepAllWhenLodDisabled() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
Object p = new Object();
assertTrue(ParticleLod.keep(p, 0));
assertTrue(ParticleLod.keep(p, 100));
assertTrue(ParticleLod.keep(p, 10_000));
}
@Test
void keepAllWhenNear() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
Object p = new Object();
// Strictly under 16 blocks: always keep (NEAR_SQ = 16² = 256).
assertTrue(ParticleLod.keep(p, 0));
assertTrue(ParticleLod.keep(p, 16 * 16 - 1));
assertTrue(ParticleLod.keep(p, 255));
assertTrue(ParticleLod.keep(p, 255.999));
}
@Test
void exactlyAtSixteenBlocksFallsThroughToMidRange() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
// At exactly 16² (the boundary), the particle is no longer "near" — its
// keep decision is delegated to the mid-range thinning. Over many
// particles, about half should survive.
int kept = 0;
for (int i = 0; i < 1024; i++) {
Object p = new Object();
if (ParticleLod.keep(p, 16 * 16)) kept++;
}
assertTrue(kept >= 410 && kept <= 614,
"At 16² (boundary), about half should survive thinning, got " + kept + "/1024");
}
@Test
void thinningAtMidRangeKeepsAboutHalf() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
double midSq = 24 * 24; // Between 16² and 32².
Set<Boolean> seen = new HashSet<>();
for (int i = 0; i < 256; i++) {
Object p = new Object();
seen.add(ParticleLod.keep(p, midSq));
}
// We should see both keep and drop, with keep being roughly half (60-100).
assertTrue(seen.contains(true) && seen.contains(false),
"Mid-range thinning should keep some and drop some over 256 trials");
}
@Test
void thinningAtFarRangeKeepsAboutQuarter() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
double farSq = 48 * 48; // Beyond 32².
int kept = 0;
for (int i = 0; i < 1024; i++) {
Object p = new Object();
if (ParticleLod.keep(p, farSq)) kept++;
}
// Expect roughly 256 (1024/4). Allow a wide band (200-320) because identityHashCode is not perfectly uniform.
assertTrue(kept >= 200 && kept <= 320,
"Far-range thinning should keep roughly 1/4 of particles, got " + kept + "/1024");
}
@Test
void sameParticleKeepsStableChoiceAcrossCalls() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
Object p = new Object();
double farSq = 48 * 48;
boolean first = ParticleLod.keep(p, farSq);
for (int i = 0; i < 16; i++) {
assertTrue(first == ParticleLod.keep(p, farSq),
"Same particle should get the same keep decision every call");
}
}
@Test
void primitiveOverloadMatchesObjectOverload() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
Object p = new Object();
// particle at (50, 0, 0), camera at (0, 0, 0): distance² = 2500.
boolean objectApi = ParticleLod.keep(p, 50 * 50);
boolean primitiveApi = ParticleLod.keep(p, 50, 0, 0, 0, 0, 0);
assertTrue(objectApi == primitiveApi,
"Primitive overload should agree with object overload at same distance");
}
@Test
void nearAlwaysKeepsEvenWhenParticleLodOn() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
Object p = new Object();
assertTrue(ParticleLod.keep(p, 15 * 15));
assertTrue(ParticleLod.keep(p, 0));
}
@Test
void disablingParticleLodMidTestFlipsEverythingToKeep() {
OptimizeToggles.apply(
false, 0,
true, false, false,
false, 0,
false, false, false, false);
Object p = new Object();
boolean first = ParticleLod.keep(p, 48 * 48);
// Toggle off — every distance should now keep.
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
assertTrue(ParticleLod.keep(p, 48 * 48));
assertTrue(ParticleLod.keep(p, 0));
// first is just whatever it was — we only care that the new state is "keep everything".
assertFalse(first == false && first != false, "dummy assertion to mark first read");
}
}

View file

@ -0,0 +1,85 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.particle;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class WorkersTest {
@AfterAll
static void shutdown() {
Workers.shutdown();
}
@Test
void runDispatchesAllBlocks() {
int blocks = 32;
AtomicInteger counter = new AtomicInteger();
Throwable err = Workers.run(blocks, b -> counter.incrementAndGet());
assertNull(err);
assertEquals(blocks, counter.get());
}
@Test
void zeroBlocksIsNoop() {
AtomicInteger counter = new AtomicInteger();
Throwable err = Workers.run(0, b -> counter.incrementAndGet());
assertNull(err);
assertEquals(0, counter.get());
}
@Test
void negativeBlocksIsNoop() {
AtomicInteger counter = new AtomicInteger();
Throwable err = Workers.run(-1, b -> counter.incrementAndGet());
assertNull(err);
assertEquals(0, counter.get());
}
@Test
void firstFailureIsReturnedAndRemainingBlocksShortCircuit() {
int blocks = 16;
AtomicInteger completed = new AtomicInteger();
Throwable err = Workers.run(blocks, b -> {
if (b == 3) {
throw new IllegalStateException("block 3 failed");
}
completed.incrementAndGet();
});
assertNotNull(err);
assertTrue(err instanceof IllegalStateException, "error should be IllegalStateException, got " + err.getClass());
assertEquals("block 3 failed", err.getMessage());
// Some other blocks may have run before block 3 errored, but no more than the full count.
assertTrue(completed.get() <= blocks);
}
@Test
void poolSizeIsAtLeastOne() {
assertTrue(Workers.poolSize() >= 1, "pool size should be at least 1, got " + Workers.poolSize());
assertTrue(Workers.poolSize() <= 8, "pool size should be at most 8, got " + Workers.poolSize());
}
@Test
void parallelWorkCompletesAndCounterMatches() {
int blocks = 64;
AtomicInteger counter = new AtomicInteger();
Throwable err = Workers.run(blocks, b -> {
// Simulate a tiny unit of work. Swallow InterruptedException so the
// IntConsumer stays compatible with Workers.run's signature.
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
counter.incrementAndGet();
});
assertNull(err);
assertEquals(blocks, counter.get());
}
}

View file

@ -0,0 +1,127 @@
package dev.loki.lovisual.features.module.modules.misc.optimize.text;
import dev.loki.lovisual.render.engine.optimize.OptimizeToggles;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.FormattedCharSink;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class StableTextTest {
/** A trivial FormattedCharSequence that just records whether accept() was called. */
private static final class Recorder implements FormattedCharSequence {
boolean accepted;
@Override
public boolean accept(FormattedCharSink sink) {
accepted = true;
return true;
}
}
@AfterEach
void resetTogglesAndTracked() {
OptimizeToggles.reset();
StableText.clearTracked();
}
@Test
void markReturnsStableTextInstance() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
Recorder inner = new Recorder();
FormattedCharSequence marked = StableText.mark(inner);
assertTrue(marked instanceof StableText,
"mark() should return a StableText wrapper");
assertEquals(1, StableText.trackedCount());
}
@Test
void isStableReturnsTrueForMarkedText() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
Recorder inner = new Recorder();
FormattedCharSequence marked = StableText.mark(inner);
assertTrue(StableText.isStable(marked));
}
@Test
void isStableReturnsFalseForUnmarkedText() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
Recorder plain = new Recorder();
assertFalse(StableText.isStable(plain));
}
@Test
void isStableFastPathUsedWhenMicroOptsOn() {
// When microOpts is on, isStable uses instanceof — should return true for StableText
// wrappers even after clearing the tracked set (the set isn't consulted on the fast path).
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, true);
Recorder inner = new Recorder();
FormattedCharSequence marked = StableText.mark(inner);
StableText.clearTracked();
// Fast path: instanceof check, doesn't consult the set.
assertTrue(StableText.isStable(marked));
}
@Test
void isStableSlowPathUsedWhenMicroOptsOff() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
Recorder inner = new Recorder();
FormattedCharSequence marked = StableText.mark(inner);
assertTrue(StableText.isStable(marked));
StableText.clearTracked();
// Slow path: after clearing the set, the wrapper is no longer "known stable".
assertFalse(StableText.isStable(marked));
}
@Test
void acceptDelegatesToInner() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
Recorder inner = new Recorder();
FormattedCharSequence marked = StableText.mark(inner);
// Pass a sink that just returns true; the marked wrapper should delegate to inner.
marked.accept((index, style, codePoint) -> true);
assertTrue(inner.accepted, "StableText.accept should delegate to the wrapped sequence");
}
@Test
void multipleMarksAreTrackedSeparately() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false, false, false, false);
assertEquals(0, StableText.trackedCount());
StableText.mark(new Recorder());
StableText.mark(new Recorder());
StableText.mark(new Recorder());
assertEquals(3, StableText.trackedCount());
}
}

View file

@ -0,0 +1,79 @@
package dev.loki.lovisual.render.engine.optimize;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Verifies the {@link OptimizeToggles} snapshot holder. Mirrors the existing
* {@code OptimizeState} tests in shape: reset → assert default false → apply →
* assert each knob tracks independently → reset → assert default again.
*/
public final class OptimizeTogglesTest {
@Test
void stateIsCleanUntilApplied() {
OptimizeToggles.reset();
assertFalse(OptimizeToggles.entityLod());
assertFalse(OptimizeToggles.particleLod());
assertFalse(OptimizeToggles.spawnerCull());
assertFalse(OptimizeToggles.spawnerTickCache());
assertFalse(OptimizeToggles.blockEntityCache());
assertFalse(OptimizeToggles.preparedTextCache());
assertFalse(OptimizeToggles.microOpts());
assertEquals(0, OptimizeToggles.entityLodDistance());
assertEquals(0, OptimizeToggles.spawnerDistance());
}
@Test
void applyTracksEachKnobIndependently() {
OptimizeToggles.apply(
true, 35,
false, true, false,
true, 16,
true,
false,
true,
true);
assertTrue(OptimizeToggles.entityLod());
assertEquals(35, OptimizeToggles.entityLodDistance());
assertFalse(OptimizeToggles.particleLod());
assertTrue(OptimizeToggles.particleLight());
assertFalse(OptimizeToggles.particlePhysics());
assertTrue(OptimizeToggles.spawnerCull());
assertEquals(16, OptimizeToggles.spawnerDistance());
assertTrue(OptimizeToggles.spawnerTickCache());
assertFalse(OptimizeToggles.blockEntityCache());
assertTrue(OptimizeToggles.preparedTextCache());
assertTrue(OptimizeToggles.microOpts());
OptimizeToggles.reset();
assertFalse(OptimizeToggles.entityLod());
assertEquals(0, OptimizeToggles.entityLodDistance());
assertFalse(OptimizeToggles.spawnerCull());
assertFalse(OptimizeToggles.microOpts());
}
@Test
void applyWithAllOffMatchesReset() {
OptimizeToggles.apply(
false, 0,
false, false, false,
false, 0,
false,
false,
false,
false);
assertFalse(OptimizeToggles.entityLod());
assertFalse(OptimizeToggles.particleLod());
assertFalse(OptimizeToggles.spawnerCull());
assertFalse(OptimizeToggles.spawnerTickCache());
assertFalse(OptimizeToggles.blockEntityCache());
assertFalse(OptimizeToggles.preparedTextCache());
assertFalse(OptimizeToggles.microOpts());
assertEquals(0, OptimizeToggles.entityLodDistance());
assertEquals(0, OptimizeToggles.spawnerDistance());
}
}