From 2c6a03cea24354cb9ccb3bfd8f569ef318712df2 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Fri, 28 Apr 2023 08:53:56 +0100 Subject: [PATCH 01/12] Implement the beginnings of the integration test framework --- build.gradle.kts | 16 +++ .../cubicchunks/test/IntegrationTests.java | 81 ++++++++++++ .../cubicchunks/test/LevelTestRunner.java | 10 ++ .../mixin/MixinMinecraftServerTestRunner.java | 122 ++++++++++++++++++ .../mixin/MixinServerLevelTestRunner.java | 57 ++++++++ .../test/tests/PlaceholderTests.java | 38 ++++++ .../cubicchunks.mixins.integration_test.json | 17 +++ src/integrationTest/resources/fabric.mod.json | 12 ++ 8 files changed, 353 insertions(+) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java create mode 100644 src/integrationTest/resources/cubicchunks.mixins.integration_test.json create mode 100644 src/integrationTest/resources/fabric.mod.json diff --git a/build.gradle.kts b/build.gradle.kts index f60df7b61..b97582c4c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -143,6 +143,14 @@ sourceSets { runtimeClasspath += configurations.runtimeClasspath.get() runtimeClasspath += sourceSets.main.get().output } + create("integrationTest") { + compileClasspath += configurations.compileClasspath.get() + compileClasspath += configurations.testCompileClasspath.get() + compileClasspath += sourceSets.main.get().output + runtimeClasspath += configurations.runtimeClasspath.get() + runtimeClasspath += configurations.testRuntimeClasspath.get() + runtimeClasspath += sourceSets.main.get().output + } } repositories { @@ -169,6 +177,7 @@ repositories { loom { createRemapConfigurations(sourceSets.test.get()) + createRemapConfigurations(sourceSets["integrationTest"]) accessWidenerPath.set(file("src/main/resources/cubicchunks.accesswidener")) // intermediaryUrl = { "http://localhost:9000/intermediary-20w49a-v2.jar" } @@ -223,6 +232,11 @@ loom { server() vmArgs("-Xmx4G") } + create(" ").apply { + server() + source(project.sourceSets["integrationTest"]) + vmArgs("-Xmx4G") + } } runConfigs.configureEach { isIdeConfigGenerated = true @@ -309,6 +323,8 @@ dependencies { testImplementation("org.hamcrest:hamcrest-junit:2.0.0.0") testImplementation("org.hamcrest:hamcrest:2.2") + + "modIntegrationTestImplementation"(fabricApi.module("fabric-gametest-api-v1", fabricVersion)) } val jar: Jar by tasks diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java new file mode 100644 index 000000000..815ad23bf --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java @@ -0,0 +1,81 @@ +package io.github.opencubicchunks.cubicchunks.test; + +import java.util.Collection; +import java.util.HashSet; +import java.util.function.Consumer; + +import io.github.opencubicchunks.cubicchunks.test.tests.PlaceholderTests; + +public class IntegrationTests { + private static final Collection LIGHTING_TESTS = new HashSet<>(); + static { + PlaceholderTests.register(LIGHTING_TESTS); + } + + public static Collection getLightingTests() { + return LIGHTING_TESTS; + } + + public enum TestState { + PASS, + FAIL, + NONE, + } + + public static final class LightingIntegrationTest { + public final long seed; + + public final Consumer setup; + public final Consumer tick; + public final Consumer teardown; + + private TestState state = TestState.NONE; + private boolean finished = false; + + public LightingIntegrationTest( + long seed, + Consumer setup, + Consumer tick, + Consumer teardown + ) { + this.seed = seed; + this.setup = setup; + this.tick = tick; + this.teardown = teardown; + } + + public boolean isFinished() { + return this.finished; + } + + public TestState getState() { + return this.state; + } + + @Override + public String toString() { + return "LightingIntegrationTest[" + + "setup=" + setup + ", " + + "test=" + tick + ", " + + "teardown=" + teardown + ']'; + } + + public final class TestContext { + public TestContext() { + } + + public void fail() { + if (!finished) { + finished = true; + state = TestState.FAIL; + } + } + public void pass() { + if (!finished) { + finished = true; + state = TestState.PASS; + } + } + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java new file mode 100644 index 000000000..ef7c88f74 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java @@ -0,0 +1,10 @@ +package io.github.opencubicchunks.cubicchunks.test; + +public interface LevelTestRunner { + void startTestInLevel(IntegrationTests.LightingIntegrationTest test); + + boolean testFinished(); + IntegrationTests.TestState testState(); + + IntegrationTests.LightingIntegrationTest getTest(); +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java new file mode 100644 index 000000000..d90a4415b --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java @@ -0,0 +1,122 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.BooleanSupplier; + +import com.google.common.collect.ImmutableList; +import io.github.opencubicchunks.cubicchunks.CubicChunks; +import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; +import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.storage.DerivedLevelData; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.storage.WorldData; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = MinecraftServer.class, priority = 999) +public abstract class MixinMinecraftServerTestRunner { + @Shadow @Final protected WorldData worldData; + + @Shadow @Final protected LevelStorageSource.LevelStorageAccess storageSource; + + private final Collection incompleteTests = IntegrationTests.getLightingTests(); + + private int testIdx = 0; + + @Shadow @Final private Map, ServerLevel> levels; + + @Shadow @Final private Executor executor; + + @Shadow public abstract RegistryAccess.Frozen registryAccess(); + + @Shadow public abstract void halt(boolean waitForServer); + + @Inject(method = "createLevels", at = @At(value = "HEAD"), cancellable = true) + private void createTestLevels(ChunkProgressListener chunkProgressListener, CallbackInfo ci) { + ci.cancel(); + + // each registered test gets its own level + IntegrationTests.getLightingTests().forEach(test -> { + ResourceKey levelResourceKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("test" + testIdx++)); + Holder dimensionTypeHolder = this.registryAccess().registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getOrCreateHolder(DimensionType.OVERWORLD_LOCATION); + ChunkGenerator chunkGenerator2 = WorldGenSettings.makeDefaultOverworld(this.registryAccess(), test.seed); + DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, this.worldData.overworldData()); + ServerLevel level = new ServerLevel( + (MinecraftServer) (Object) this, this.executor, this.storageSource, + derivedLevelData, levelResourceKey, dimensionTypeHolder, chunkProgressListener, + chunkGenerator2, false, test.seed, + ImmutableList.of(), false); + + ((LevelTestRunner) level).startTestInLevel(test); + + this.levels.put(levelResourceKey, level); + }); + + } + + @Inject(method = "prepareLevels", at = @At("HEAD"), cancellable = true) + private void prepareLevels(ChunkProgressListener progressListener, CallbackInfo ci) { + ci.cancel(); + } + + @Inject(method = "tickServer", at = @At("RETURN")) + private void unloadLevelWhenTestFinish(BooleanSupplier hasTimeLeft, CallbackInfo ci) { + for (ResourceKey levelResourceKey : new ArrayList<>(this.levels.keySet())) { + LevelTestRunner levelTestRunner = (LevelTestRunner) this.levels.get(levelResourceKey); + if (levelTestRunner.testFinished()) { + ServerLevel removed = this.levels.remove(levelResourceKey); + try { + incompleteTests.remove(levelTestRunner.getTest()); + removed.close(); + } catch (IOException e) { + System.err.println("Exception when closing test level"); + e.printStackTrace(System.err); + } + } + } + + if (this.incompleteTests.isEmpty()) { + CubicChunks.LOGGER.info("Tests complete! Exiting..."); + this.halt(false); + } + } + + @Redirect(method = "saveAllChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/ServerLevelData;" + + "setWorldBorder(Lnet/minecraft/world/level/border/WorldBorder$Settings;)V")) + private void preventNoOverworldNPE(ServerLevelData instance, WorldBorder.Settings settings) { + // do nothing instead + } + @Redirect(method = "saveAllChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;" + + "getWorldBorder()Lnet/minecraft/world/level/border/WorldBorder;")) + private WorldBorder preventNoOverworldNPE(ServerLevel instance) { + return null; + } + @Redirect(method = "saveAllChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/border/WorldBorder;" + + "createSettings()Lnet/minecraft/world/level/border/WorldBorder$Settings;")) + private WorldBorder.Settings preventNoOverworldNPE(WorldBorder instance) { + return null; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java new file mode 100644 index 000000000..c3a67b945 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java @@ -0,0 +1,57 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin; + +import java.util.function.BooleanSupplier; + +import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; +import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import net.minecraft.server.level.ServerLevel; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerLevel.class) +public class MixinServerLevelTestRunner implements LevelTestRunner { + private boolean testStarted = false; + private IntegrationTests.LightingIntegrationTest test; + private IntegrationTests.LightingIntegrationTest.TestContext context; + + private boolean teardownComplete = false; + + @Override public void startTestInLevel(IntegrationTests.LightingIntegrationTest integrationTest) { + this.test = integrationTest; + this.context = integrationTest.new TestContext(); + this.testStarted = true; + + this.test.setup.accept(this.context); + } + + @Override public boolean testFinished() { + return this.testStarted && test.isFinished(); + } + + @Override public IntegrationTests.TestState testState() { + return test.getState(); + } + + @Override public IntegrationTests.LightingIntegrationTest getTest() { + return this.test; + } + + @Inject(method = "tick", at = @At("HEAD")) + private void tickTest(BooleanSupplier b, CallbackInfo ci) { + if (this.test != null && !this.test.isFinished()) { + this.test.tick.accept(this.context); + } + } + + @Inject(method = "tick", at = @At("RETURN")) + private void handleTestFinished(BooleanSupplier b, CallbackInfo ci) { + if (this.test != null) { + if (this.test.isFinished() && !this.teardownComplete) { + this.teardownComplete = true; + this.test.teardown.accept(this.context); + } + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java new file mode 100644 index 000000000..f656f8e48 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java @@ -0,0 +1,38 @@ +package io.github.opencubicchunks.cubicchunks.test.tests; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +import io.github.opencubicchunks.cubicchunks.test.IntegrationTests.LightingIntegrationTest; + +public class PlaceholderTests { + public static void register(Collection lightingTests) { + lightingTests.add(new LightingIntegrationTest(0, + context -> { + System.out.println("Test 0 start"); + }, + context -> { + System.out.println("Test 0 tick"); + context.fail(); + }, + context -> { + System.out.println("Test 0 end"); + } + )); + AtomicInteger ticksPassed = new AtomicInteger(); + lightingTests.add(new LightingIntegrationTest(0, + context -> { + System.out.println("Test 1 start"); + }, + context -> { + System.out.println("Test 1 tick"); + if (ticksPassed.getAndIncrement() == 300) { + context.pass(); + } + }, + context -> { + System.out.println("Test 1 end"); + } + )); + } +} diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json new file mode 100644 index 000000000..264d0b31c --- /dev/null +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -0,0 +1,17 @@ +{ + "required": true, + "package": "io.github.opencubicchunks.cubicchunks.test.mixin", + "refmap": "CubicChunks-refmap.json", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "conformVisibility": true + }, + "mixins": [ + "MixinMinecraftServerTestRunner", + "MixinServerLevelTestRunner" + ] +} \ No newline at end of file diff --git a/src/integrationTest/resources/fabric.mod.json b/src/integrationTest/resources/fabric.mod.json new file mode 100644 index 000000000..7057e0b3c --- /dev/null +++ b/src/integrationTest/resources/fabric.mod.json @@ -0,0 +1,12 @@ +{ + "schemaVersion": 1, + "id": "cubicchunks-integration-tests", + "name": "CubicChunks2 Integration Tests", + "version": "1.0.0", + "environment": "*", + "license": "MIT", + "entrypoints": { }, + "mixins": [ + "cubicchunks.mixins.integration_test.json" + ] +} \ No newline at end of file From 85d3217444c90f925ff4973c0098816b30ea1c25 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Fri, 28 Apr 2023 09:00:05 +0100 Subject: [PATCH 02/12] Add debug and integrationTest to package-info generation --- build.gradle.kts | 2 ++ .../opencubicchunks/cubicchunks/debug/package-info.java | 7 +++++++ .../cubicchunks/test/mixin/package-info.java | 7 +++++++ .../opencubicchunks/cubicchunks/test/package-info.java | 7 +++++++ .../cubicchunks/test/tests/package-info.java | 7 +++++++ 5 files changed, 30 insertions(+) create mode 100644 src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java diff --git a/build.gradle.kts b/build.gradle.kts index b97582c4c..375facd45 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,6 +46,8 @@ generatePackageInfo.apply { doFirst { GeneratePackageInfo.generateFiles(project.sourceSets["main"]) GeneratePackageInfo.generateFiles(project.sourceSets["test"]) + GeneratePackageInfo.generateFiles(project.sourceSets["debug"]) + GeneratePackageInfo.generateFiles(project.sourceSets["integrationTest"]) } } diff --git a/src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java b/src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java new file mode 100644 index 000000000..a979c5a7c --- /dev/null +++ b/src/debug/java/io/github/opencubicchunks/cubicchunks/debug/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.debug; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java new file mode 100644 index 000000000..23c8f497a --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test.mixin; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java new file mode 100644 index 000000000..692ea2191 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java new file mode 100644 index 000000000..994acdc96 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test.tests; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; From 0854f61d559d0ef4397c87ebf8dbffeb764f5fa8 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Fri, 28 Apr 2023 12:33:03 +0100 Subject: [PATCH 03/12] Create gradle tasks for integration tests & run in CI --- .github/workflows/gradleBuild.yml | 8 ++- .github/workflows/gradleBuildPR.yml | 7 ++- Jenkinsfile | 2 + build.gradle.kts | 61 ++++++++++++++++++- gradle.properties | 1 + .../cubicchunks.mixins.integration_test.json | 2 +- 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradleBuild.yml b/.github/workflows/gradleBuild.yml index 510e207b5..7012afae1 100644 --- a/.github/workflows/gradleBuild.yml +++ b/.github/workflows/gradleBuild.yml @@ -26,7 +26,13 @@ jobs: with: java-version: 17 - name: Build with Gradle - run: ./gradlew build + run: ./gradlew build -x test + - name: Run tests + run: | + ./gradlew test + ./gradlew agreeToMinecraftEula + ./gradlew runIntegrationTests + - uses: actions/upload-artifact@v2 with: name: Compiled jars diff --git a/.github/workflows/gradleBuildPR.yml b/.github/workflows/gradleBuildPR.yml index 7890fb3c7..6166b3f47 100644 --- a/.github/workflows/gradleBuildPR.yml +++ b/.github/workflows/gradleBuildPR.yml @@ -22,4 +22,9 @@ jobs: with: java-version: 17 - name: Build with Gradle - run: ./gradlew build \ No newline at end of file + run: ./gradlew build -x test + - name: Run tests + run: | + ./gradlew test + ./gradlew agreeToMinecraftEula + ./gradlew runIntegrationTests \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 1beddc306..ff471e79e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -50,6 +50,8 @@ pipeline { stage("Test") { steps { sh "./gradlew test" + sh "./gradlew agreeToMinecraftEula" + sh "./gradlew runIntegrationTests" } post { success { diff --git a/build.gradle.kts b/build.gradle.kts index 375facd45..c6dcceeb5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ @file:Suppress("INACCESSIBLE_TYPE", "UnstableApiUsage") import io.github.opencubicchunks.gradle.GeneratePackageInfo +import net.fabricmc.loom.task.RemapJarTask import org.gradle.internal.os.OperatingSystem import java.util.* @@ -23,6 +24,7 @@ plugins { val minecraftVersion: String by project val loaderVersion: String by project val fabricVersion: String by project +val installerVersion: String by project val lwjglVersion: String by project val lwjglNatives: String by project val modId: String by project @@ -136,6 +138,9 @@ val debugRuntime: Configuration by configurations.creating { val extraTests: Configuration by configurations.creating val shade: Configuration by configurations.creating +val productionRuntimeServer: Configuration by configurations.creating +val productionRuntimeMods: Configuration by configurations.creating + sourceSets { create("debug") { compileClasspath += debugCompile @@ -327,7 +332,11 @@ dependencies { testImplementation("org.hamcrest:hamcrest:2.2") "modIntegrationTestImplementation"(fabricApi.module("fabric-gametest-api-v1", fabricVersion)) -} + + productionRuntimeServer("net.fabricmc:fabric-installer:${installerVersion}:server") + listOf("fabric-api-base", "fabric-command-api-v1", "fabric-networking-v0", "fabric-lifecycle-events-v1", "fabric-resource-loader-v0").forEach { + productionRuntimeMods(fabricApi.module(it, fabricVersion)) + }} val jar: Jar by tasks jar.apply { @@ -343,6 +352,56 @@ if (project.tasks.findByName("ideaSyncTask") != null) { project.tasks.findByName("ideaSyncTask")!!.dependsOn("CubicChunksCore:assemble") } +val integrationTestJar by tasks.creating(Jar::class) { + from(sourceSets["integrationTest"].output) + destinationDirectory.set(File(project.buildDir, "devlibs")) + archiveClassifier.set("testmod") +} + +val remapIntegrationTestJar by tasks.creating(RemapJarTask::class) { + dependsOn(integrationTestJar) + input.set(integrationTestJar.archiveFile) + archiveClassifier.set("integrationTest") + addNestedDependencies.set(false) +} + +val serverPropertiesJar by tasks.creating(Jar::class) { + val propsFile = file("build/tmp/install.properties") + + doFirst { + propsFile.writeText("fabric-loader-version=${loaderVersion}\ngame-version=${minecraftVersion}") + } + + archiveFileName.set("test-server-properties.jar") + destinationDirectory.set(file("build/tmp")) + from(propsFile) +} + +val agreeToMinecraftEula: Task by tasks.creating { + mkdir("run") + file("run/eula.txt").writeText("eula=true") +} + +val runIntegrationTests by tasks.creating(JavaExec::class) { + dependsOn(tasks["remapJar"], remapIntegrationTestJar, serverPropertiesJar) + classpath(productionRuntimeServer, serverPropertiesJar) + mainClass.set("net.fabricmc.installer.ServerLauncher") + workingDir(file("run")) + + doFirst { + workingDir.mkdirs() + + val mods = productionRuntimeMods.files.joinToString(separator = File.pathSeparator) { it.absolutePath } + + jvmArgs( + "-Dfabric.addMods=${(tasks["remapJar"] as AbstractArchiveTask).archiveFile.get().asFile.absolutePath}${File.pathSeparator}${remapIntegrationTestJar.archiveFile.get() + .asFile.absolutePath}${File.pathSeparator}${mods}", + ) + + args("nogui") + } +} + // unzipping subproject (CubicChunksCore) tests val unzipTests by tasks.creating(Copy::class) { outputs.upToDateWhen { diff --git a/gradle.properties b/gradle.properties index 5471588e0..6b88b10a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ modId=cubicchunks minecraftVersion=1.18.2 yarnVersion=1.18.2+build.1 loaderVersion=0.14.19 +installerVersion=0.11.1 # Dependencies fabricVersion=0.67.1+1.18.2 diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json index 264d0b31c..31066a952 100644 --- a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -1,7 +1,7 @@ { "required": true, "package": "io.github.opencubicchunks.cubicchunks.test.mixin", - "refmap": "CubicChunks-refmap.json", + "refmap": "integrationTest-CubicChunks-refmap.json", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", "injectors": { From e3546c8bbc1251273724c367714848ed5e315ac2 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Fri, 28 Apr 2023 13:38:27 +0100 Subject: [PATCH 04/12] Add foojay resolver to fix toolchains issue --- CubicChunksCore | 2 +- settings.gradle | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CubicChunksCore b/CubicChunksCore index 1a02b0818..50d56eecd 160000 --- a/CubicChunksCore +++ b/CubicChunksCore @@ -1 +1 @@ -Subproject commit 1a02b08183a470b4e42e947b96be3c6a186a4796 +Subproject commit 50d56eecdfd9c805bf61fbe7bb3f9cff2a171051 diff --git a/settings.gradle b/settings.gradle index 51877f5c1..4cc9ebdee 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,4 +13,8 @@ pluginManagement { } } +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' +} + include ':CubicChunksCore' \ No newline at end of file From 419c4ce8370b74ddf94f89a468f29aa06c7cd25d Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Fri, 28 Apr 2023 14:01:48 +0100 Subject: [PATCH 05/12] Prevent tcp server from starting in CI --- .github/workflows/gradleBuild.yml | 2 +- .github/workflows/gradleBuildPR.yml | 2 +- Jenkinsfile | 2 +- build.gradle.kts | 3 +++ .../test/mixin/MixinDedicatedServer.java | 27 +++++++++++++++++++ .../cubicchunks.mixins.integration_test.json | 1 + 6 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java diff --git a/.github/workflows/gradleBuild.yml b/.github/workflows/gradleBuild.yml index 7012afae1..61838f8a0 100644 --- a/.github/workflows/gradleBuild.yml +++ b/.github/workflows/gradleBuild.yml @@ -31,7 +31,7 @@ jobs: run: | ./gradlew test ./gradlew agreeToMinecraftEula - ./gradlew runIntegrationTests + ./gradlew runIntegrationTests -PdisableNetworkingInIntegrationTest=true - uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/gradleBuildPR.yml b/.github/workflows/gradleBuildPR.yml index 6166b3f47..06a962ddc 100644 --- a/.github/workflows/gradleBuildPR.yml +++ b/.github/workflows/gradleBuildPR.yml @@ -27,4 +27,4 @@ jobs: run: | ./gradlew test ./gradlew agreeToMinecraftEula - ./gradlew runIntegrationTests \ No newline at end of file + ./gradlew runIntegrationTests -PdisableNetworkingInIntegrationTest=true \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index ff471e79e..0f05628d6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,7 +51,7 @@ pipeline { steps { sh "./gradlew test" sh "./gradlew agreeToMinecraftEula" - sh "./gradlew runIntegrationTests" + sh "./gradlew runIntegrationTests -PdisableNetworkingInIntegrationTest=true" } post { success { diff --git a/build.gradle.kts b/build.gradle.kts index c6dcceeb5..5756e15f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,8 @@ val lwjglNatives: String by project val modId: String by project val debugArtifactTransforms: String by project +val disableNetworkingInIntegrationTest: String? by project + javaHeaders { setAcceptedJars(".*CubicChunksCore.*") setConfig(file("javaHeaders.json")) @@ -396,6 +398,7 @@ val runIntegrationTests by tasks.creating(JavaExec::class) { jvmArgs( "-Dfabric.addMods=${(tasks["remapJar"] as AbstractArchiveTask).archiveFile.get().asFile.absolutePath}${File.pathSeparator}${remapIntegrationTestJar.archiveFile.get() .asFile.absolutePath}${File.pathSeparator}${mods}", + "-Dcubicchunks.test.disableNetwork=${disableNetworkingInIntegrationTest}" ) args("nogui") diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java new file mode 100644 index 000000000..d44ed8c30 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java @@ -0,0 +1,27 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin; + +import java.io.IOException; +import java.net.InetAddress; + +import io.github.opencubicchunks.cubicchunks.CubicChunks; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.network.ServerConnectionListener; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(DedicatedServer.class) +public class MixinDedicatedServer { + @Redirect(method = "initServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConnectionListener;startTcpServerListener(Ljava/net/InetAddress;I)V")) + private void noTcpServer(ServerConnectionListener instance, InetAddress address, int port) throws IOException { + + if (!System.getProperty("cubicchunks.test.disableNetwork", "false").equals("true")) { + instance.startTcpServerListener(address, port); + CubicChunks.LOGGER.info("Networking Enabled"); + } else { + CubicChunks.LOGGER.warn("*".repeat(30)); + CubicChunks.LOGGER.warn("NETWORKING DISABLED!!"); + CubicChunks.LOGGER.warn("*".repeat(30)); + } + } +} diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json index 31066a952..816d3e11c 100644 --- a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -11,6 +11,7 @@ "conformVisibility": true }, "mixins": [ + "MixinDedicatedServer", "MixinMinecraftServerTestRunner", "MixinServerLevelTestRunner" ] From ed23e6088308bde2ca7c21662d6ed32c0443b014 Mon Sep 17 00:00:00 2001 From: Bartosz Skrzypczak Date: Sun, 30 Apr 2023 01:29:04 +0100 Subject: [PATCH 06/12] Allow source set to be specified in MixinGen --- .../gradle/MixinGenExtension.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java b/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java index b512e89f7..bb343f724 100644 --- a/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java +++ b/buildSrc/src/main/java/io/github/opencubicchunks/gradle/MixinGenExtension.java @@ -96,6 +96,15 @@ public static class MixinConfig { private Integer injectorsDefaultRequire; private Boolean conformVisibility; + private String sourceSet; + + public String getSourceSet() { + return sourceSet; + } + + public void setSourceSet(String sourceSet) { + this.sourceSet = sourceSet; + } public Boolean getRequired() { return required; @@ -171,9 +180,6 @@ public void setMixinPriority(Integer mixinPriority) { } void generateFiles(JavaPluginConvention convention) throws IOException { - SourceSet main = convention.getSourceSets().getByName("main"); - Set resourcesSet = main.getResources().getSrcDirs(); - Path resources = resourcesSet.iterator().next().getCanonicalFile().toPath(); for (String name : configs.keySet()) { MixinConfig config = new MixinConfig(); Action configure = configs.get(name); @@ -191,6 +197,10 @@ void generateFiles(JavaPluginConvention convention) throws IOException { } configure.execute(config); + SourceSet main = convention.getSourceSets().getByName(config.sourceSet == null ? "main" : config.sourceSet); + Set resourcesSet = main.getResources().getSrcDirs(); + Path resources = resourcesSet.iterator().next().getCanonicalFile().toPath(); + String fileName = String.format(filePattern, name); try (JsonWriter writer = new JsonWriter(Files.newBufferedWriter(resources.resolve(fileName)))) { @@ -236,7 +246,8 @@ void generateFiles(JavaPluginConvention convention) throws IOException { } private void writeMixins(JavaPluginConvention convention, String name, MixinConfig config, JsonWriter writer) throws IOException { - Set classes = getMixinClasses(config, convention.getSourceSets().getByName("main").getAllJava()); + SourceSet main = convention.getSourceSets().getByName(config.sourceSet == null ? "main": config.sourceSet); + Set classes = getMixinClasses(config, main.getAllJava()); Set commonSet = new HashSet<>(); Set clientSet = new HashSet<>(); @@ -281,7 +292,7 @@ private void writeMixins(JavaPluginConvention convention, String name, MixinConf } private Set getMixinClasses(MixinConfig config, SourceDirectorySet allJava) throws IOException { - System.out.println("GetMixin Classes"); + System.out.println("GetMixin Classes for " + config.packageName + " in " + allJava.getSrcDirs()); Set srcPaths = new HashSet<>(); for (File file : allJava.getSrcDirs()) { Path toPath = file.getCanonicalFile().toPath(); From 22c0a8bb67bce76eb61a5c252d532f5c1eb6302f Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Sun, 30 Apr 2023 03:36:06 +0100 Subject: [PATCH 07/12] Properly prevent frozen worlds from ticking. Connected players can now join and move properly --- .gitignore | 1 + build.gradle.kts | 12 ++- .../cubicchunks/test/IntegrationTests.java | 31 ++++++- .../cubicchunks/test/ServerTestRunner.java | 13 +++ .../MixinChunkMap_SendPlayerAllChunks.java | 63 +++++++++++++ .../MixinDedicatedServer_DisableNetwork.java} | 15 +-- ...nDistanceManager_PreventPlayerTickets.java | 21 +++++ .../MixinLevelRenderer_NoLightUpdates.java | 15 +++ .../MixinMinecraftServer_TestRunner.java} | 91 ++++++++++++++++--- .../MixinPlayerLists_SetSpectatorOnJoin.java | 18 ++++ .../MixinPlayerLists_TestRenderDistance.java | 21 +++++ .../server/MixinServerChunkCache_NoSave.java | 15 +++ ...verGamePacketListenerImpl_NoChunkLoad.java | 18 ++++ .../MixinServerLevel_TestRunner.java} | 18 +++- .../server/MixinServerPlayer_NoChunkLoad.java | 16 ++++ .../server/MixinServerPlayer_NoTick.java | 26 ++++++ .../MixinServerPlayer_SpawnLocationTest.java | 38 ++++++++ .../test/mixin/{ => server}/package-info.java | 2 +- .../test/tests/PlaceholderTests.java | 34 ++++++- .../cubicchunks.mixins.integration_test.json | 20 +++- .../core/common/chunk/MixinChunkMap.java | 7 +- .../server/level/CubeMapInternal.java | 5 + 22 files changed, 459 insertions(+), 41 deletions(-) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java rename src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/{MixinDedicatedServer.java => server/MixinDedicatedServer_DisableNetwork.java} (81%) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java rename src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/{MixinMinecraftServerTestRunner.java => server/MixinMinecraftServer_TestRunner.java} (59%) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java rename src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/{MixinServerLevelTestRunner.java => server/MixinServerLevel_TestRunner.java} (69%) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java rename src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/{ => server}/package-info.java (75%) diff --git a/.gitignore b/.gitignore index 53b547ca5..7ce2dd69c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ build # other eclipse run +runIntegrationTests classes # Files from Forge MDK diff --git a/build.gradle.kts b/build.gradle.kts index 5756e15f6..eb4f8297d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -120,6 +120,15 @@ mixinGen { injectorsDefaultRequire = 1 configurationPlugin = "io.github.opencubicchunks.cubicchunks.mixin.TestMixinConfig" } + + config("integration_test") { + required = true + conformVisibility = true + injectorsDefaultRequire = 1 + sourceSet = "integrationTest" + refmap = "integrationTest-CubicChunks-refmap.json" + packageName = "io.github.opencubicchunks.cubicchunks.test.mixin" + } } group = "io.github.opencubicchunks" // http://maven.apache.org/guides/mini/guide-naming-conventions.html @@ -244,7 +253,8 @@ loom { create(" ").apply { server() source(project.sourceSets["integrationTest"]) - vmArgs("-Xmx4G") + vmArgs("-Xmx4G", "-Dcubicchunks.test.freezeFailingWorlds=true") + runDir("runIntegrationTests") } } runConfigs.configureEach { diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java index 815ad23bf..55934be8a 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java @@ -2,11 +2,19 @@ import java.util.Collection; import java.util.HashSet; +import java.util.Optional; import java.util.function.Consumer; +import javax.annotation.Nullable; + import io.github.opencubicchunks.cubicchunks.test.tests.PlaceholderTests; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; public class IntegrationTests { + public static final boolean DISABLE_NETWORK = System.getProperty("cubicchunks.test.disableNetwork", "false").equals("true"); + public static final boolean FREEZE_FAILING_WORLDS = System.getProperty("cubicchunks.test.freezeFailingWorlds", "false").equals("true"); + private static final Collection LIGHTING_TESTS = new HashSet<>(); static { PlaceholderTests.register(LIGHTING_TESTS); @@ -23,6 +31,7 @@ public enum TestState { } public static final class LightingIntegrationTest { + public final String testName; public final long seed; public final Consumer setup; @@ -31,13 +40,15 @@ public static final class LightingIntegrationTest { private TestState state = TestState.NONE; private boolean finished = false; + private BlockPos failurePos = null; public LightingIntegrationTest( - long seed, + String testName, long seed, Consumer setup, Consumer tick, Consumer teardown ) { + this.testName = testName; this.seed = seed; this.setup = setup; this.tick = tick; @@ -52,6 +63,10 @@ public TestState getState() { return this.state; } + public Optional getFailurePos() { + return Optional.ofNullable(this.failurePos); + } + @Override public String toString() { return "LightingIntegrationTest[" + @@ -61,7 +76,10 @@ public String toString() { } public final class TestContext { - public TestContext() { + private final ServerLevel level; + + public TestContext(ServerLevel level) { + this.level = level; } public void fail() { @@ -70,12 +88,21 @@ public void fail() { state = TestState.FAIL; } } + public void pass() { if (!finished) { finished = true; state = TestState.PASS; } } + + public ServerLevel level() { + return level; + } + + public void setFailurePos(@Nullable BlockPos pos) { + failurePos = pos; + } } } } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java new file mode 100644 index 000000000..57ffe85ba --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/ServerTestRunner.java @@ -0,0 +1,13 @@ +package io.github.opencubicchunks.cubicchunks.test; + +import java.util.Optional; + +import javax.annotation.Nullable; + +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; + +public interface ServerTestRunner { + @Nullable Pair> firstErrorLocation(); +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java new file mode 100644 index 000000000..0b3cbe797 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinChunkMap_SendPlayerAllChunks.java @@ -0,0 +1,63 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.server.level.CubeMapInternal; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.PlayerMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import org.apache.commons.lang3.mutable.MutableObject; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(value = ChunkMap.class, priority = 2000) +public abstract class MixinChunkMap_SendPlayerAllChunks implements CubeMapInternal { + @Shadow @Final private PlayerMap playerMap; + + @Shadow private volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; + + /** MUST match {@link io.github.opencubicchunks.cubicchunks.mixin.core.common.chunk.MixinChunkMap#visibleCubeMap} **/ + @SuppressWarnings({ "JavadocReference", "unused", "MismatchedQueryAndUpdateOfCollection" }) + private volatile Long2ObjectLinkedOpenHashMap visibleCubeMap; + + @Shadow protected abstract void updateChunkTracking(ServerPlayer player, ChunkPos chunkPos, MutableObject packetCache, boolean wasLoaded, + boolean load); + + /** + * @author NotStirred + * @reason Overwriting CC's overwrite of vanilla logic. Here we just send all chunks + */ + @Overwrite + void updatePlayerStatus(ServerPlayer player, boolean track) { + if (track) { + int xFloor = Coords.getCubeXForEntity(player); + int yFloor = Coords.getCubeYForEntity(player); + int zFloor = Coords.getCubeZForEntity(player); + this.playerMap.addPlayer(CubePos.of(xFloor, yFloor, zFloor).asChunkPos().toLong(), player, true); + this.updatePlayerCubePos(player); //This also sends the vanilla packet, as player#ManagedSectionPos is changed in this method. + } else { + SectionPos managedSectionPos = player.getLastSectionPos(); //Vanilla + CubePos cubePos = CubePos.from(managedSectionPos); + this.playerMap.removePlayer(cubePos.asChunkPos().toLong(), player); + } + + //Vanilla + this.visibleChunkMap.forEach((pos, holder) -> { + ChunkPos chunkPos = new ChunkPos(pos); + this.updateChunkTracking(player, chunkPos, new MutableObject<>(), !track, track); + }); + + //CC + this.visibleCubeMap.forEach((pos, holder) -> { + CubePos cubePos = CubePos.from(pos); + this.updateCubeTracking(player, cubePos, new Object[2], !track, track); + }); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDedicatedServer_DisableNetwork.java similarity index 81% rename from src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java rename to src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDedicatedServer_DisableNetwork.java index d44ed8c30..bb5c3e73a 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinDedicatedServer.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDedicatedServer_DisableNetwork.java @@ -1,4 +1,6 @@ -package io.github.opencubicchunks.cubicchunks.test.mixin; +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import static io.github.opencubicchunks.cubicchunks.test.IntegrationTests.DISABLE_NETWORK; import java.io.IOException; import java.net.InetAddress; @@ -11,17 +13,16 @@ import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(DedicatedServer.class) -public class MixinDedicatedServer { +public class MixinDedicatedServer_DisableNetwork { @Redirect(method = "initServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConnectionListener;startTcpServerListener(Ljava/net/InetAddress;I)V")) private void noTcpServer(ServerConnectionListener instance, InetAddress address, int port) throws IOException { - - if (!System.getProperty("cubicchunks.test.disableNetwork", "false").equals("true")) { - instance.startTcpServerListener(address, port); - CubicChunks.LOGGER.info("Networking Enabled"); - } else { + if (DISABLE_NETWORK) { CubicChunks.LOGGER.warn("*".repeat(30)); CubicChunks.LOGGER.warn("NETWORKING DISABLED!!"); CubicChunks.LOGGER.warn("*".repeat(30)); + } else { + instance.startTcpServerListener(address, port); + CubicChunks.LOGGER.info("Networking Enabled"); } } } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java new file mode 100644 index 000000000..4fed18969 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinDistanceManager_PreventPlayerTickets.java @@ -0,0 +1,21 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; +import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.Ticket; +import net.minecraft.server.level.TicketType; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(DistanceManager.class) +public abstract class MixinDistanceManager_PreventPlayerTickets { + @Dynamic @Inject(method = { "addTicket(JLnet/minecraft/server/level/Ticket;)V", "addCubeTicket(JLnet/minecraft/server/level/Ticket;)V" }, at = @At("HEAD"), cancellable = true) + private void cancelPlayerTickets(long position, Ticket ticket, CallbackInfo ci) { + if (ticket.getType() == TicketType.PLAYER || ticket.getType() == CubicTicketType.PLAYER) { + ci.cancel(); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java new file mode 100644 index 000000000..ac4b75747 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinLevelRenderer_NoLightUpdates.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(LevelRenderer.class) +public class MixinLevelRenderer_NoLightUpdates { + @Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runUpdates(IZZ)I")) + private int noLightUpdate(LevelLightEngine instance, int i, boolean bl, boolean bl2) { + return i; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java similarity index 59% rename from src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java rename to src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java index d90a4415b..ec748976c 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinMinecraftServerTestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java @@ -1,16 +1,25 @@ -package io.github.opencubicchunks.cubicchunks.test.mixin; +package io.github.opencubicchunks.cubicchunks.test.mixin.server; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Executor; import java.util.function.BooleanSupplier; +import javax.annotation.Nullable; + import com.google.common.collect.ImmutableList; import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import io.github.opencubicchunks.cubicchunks.test.ServerTestRunner; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; +import net.minecraft.CrashReport; +import net.minecraft.ReportedException; +import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -30,6 +39,7 @@ import net.minecraft.world.level.storage.WorldData; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -37,14 +47,17 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(value = MinecraftServer.class, priority = 999) -public abstract class MixinMinecraftServerTestRunner { +public abstract class MixinMinecraftServer_TestRunner implements ServerTestRunner { @Shadow @Final protected WorldData worldData; @Shadow @Final protected LevelStorageSource.LevelStorageAccess storageSource; + private boolean isFrozen = false; + private final Collection incompleteTests = IntegrationTests.getLightingTests(); + private final Collection failedTests = new ArrayList<>(); - private int testIdx = 0; + @Nullable private Pair> firstError = null; @Shadow @Final private Map, ServerLevel> levels; @@ -54,13 +67,17 @@ public abstract class MixinMinecraftServerTestRunner { @Shadow public abstract void halt(boolean waitForServer); + @Override @Nullable public Pair> firstErrorLocation() { + return this.firstError; + } + @Inject(method = "createLevels", at = @At(value = "HEAD"), cancellable = true) private void createTestLevels(ChunkProgressListener chunkProgressListener, CallbackInfo ci) { ci.cancel(); // each registered test gets its own level IntegrationTests.getLightingTests().forEach(test -> { - ResourceKey levelResourceKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("test" + testIdx++)); + ResourceKey levelResourceKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(test.testName)); Holder dimensionTypeHolder = this.registryAccess().registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getOrCreateHolder(DimensionType.OVERWORLD_LOCATION); ChunkGenerator chunkGenerator2 = WorldGenSettings.makeDefaultOverworld(this.registryAccess(), test.seed); DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, this.worldData.overworldData()); @@ -85,22 +102,68 @@ private void prepareLevels(ChunkProgressListener progressListener, CallbackInfo @Inject(method = "tickServer", at = @At("RETURN")) private void unloadLevelWhenTestFinish(BooleanSupplier hasTimeLeft, CallbackInfo ci) { for (ResourceKey levelResourceKey : new ArrayList<>(this.levels.keySet())) { - LevelTestRunner levelTestRunner = (LevelTestRunner) this.levels.get(levelResourceKey); + ServerLevel level = this.levels.get(levelResourceKey); + LevelTestRunner levelTestRunner = (LevelTestRunner) level; if (levelTestRunner.testFinished()) { - ServerLevel removed = this.levels.remove(levelResourceKey); - try { - incompleteTests.remove(levelTestRunner.getTest()); - removed.close(); - } catch (IOException e) { - System.err.println("Exception when closing test level"); - e.printStackTrace(System.err); + IntegrationTests.LightingIntegrationTest test = levelTestRunner.getTest(); + incompleteTests.remove(test); + + if (test.getState() == IntegrationTests.TestState.FAIL) { + this.failedTests.add(test); + if (firstError == null) { + firstError = new ObjectObjectImmutablePair<>(level, test.getFailurePos()); + } + } + + // Failing test levels are not unloaded if FREEZE_FAILING_WORLDS is set + if (test.getState() != IntegrationTests.TestState.FAIL || !IntegrationTests.FREEZE_FAILING_WORLDS) { + // Unload the test's level + ServerLevel removed = this.levels.remove(levelResourceKey); + try { + System.out.println("Removed " + levelResourceKey); + removed.close(); + } catch (IOException e) { + System.err.println("Exception when closing test level"); + e.printStackTrace(System.err); + } } } } if (this.incompleteTests.isEmpty()) { - CubicChunks.LOGGER.info("Tests complete! Exiting..."); - this.halt(false); + if (IntegrationTests.FREEZE_FAILING_WORLDS && !failedTests.isEmpty()) { + if (!this.isFrozen) { + this.isFrozen = true; + CubicChunks.LOGGER.info("Tests complete! Holding failing worlds open"); + } + } else { + CubicChunks.LOGGER.info("Tests complete! Exiting..."); + if (!failedTests.isEmpty()) { + CrashReport crashReport = new CrashReport("Failed tests!", new Exception("Some exception")); + throw new ReportedException(crashReport); + } else { + CubicChunks.LOGGER.info("No failed tests!"); + } + + this.halt(false); + } + } + } + + /** + * @author NotStirred + * @reason By default, the player is spawned in the overworld. Instead spawn the player in the first error location, or some valid world + * if none exists. + */ + @Overwrite + public final ServerLevel overworld() { + Pair> errorLocation = firstErrorLocation(); + + if (errorLocation != null) { + return errorLocation.first(); + } else { + // if there is no error location, we just place the player in some valid level + return this.levels.values().iterator().next(); } } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java new file mode 100644 index 000000000..229a3e7cc --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_SetSpectatorOnJoin.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.network.Connection; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.level.GameType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerList.class) +public class MixinPlayerLists_SetSpectatorOnJoin { + @Inject(method = "placeNewPlayer", at = @At(value = ("RETURN"))) + private void setSpectator(Connection netManager, ServerPlayer player, CallbackInfo ci) { + player.setGameMode(GameType.SPECTATOR); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java new file mode 100644 index 000000000..cfa18263f --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinPlayerLists_TestRenderDistance.java @@ -0,0 +1,21 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(PlayerList.class) +public class MixinPlayerLists_TestRenderDistance { + private static final int CLIENT_RENDER_DISTANCE = 64; + + @Redirect(method = "placeNewPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/server/players/PlayerList;viewDistance:I")) + private int testClientRenderDistance(PlayerList instance) { + return CLIENT_RENDER_DISTANCE; + } + + @Redirect(method = "placeNewPlayer", at = @At(value = "FIELD", target = "Lnet/minecraft/server/players/PlayerList;simulationDistance:I")) + private int testClientSimulationDistance(PlayerList instance) { + return CLIENT_RENDER_DISTANCE; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java new file mode 100644 index 000000000..4f67b08ee --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_NoSave.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.level.ServerChunkCache; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerChunkCache.class) +public class MixinServerChunkCache_NoSave { + @Inject(method = "save", at = @At("HEAD"), cancellable = true) + private void noSave(boolean flush, CallbackInfo ci) { + ci.cancel(); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java new file mode 100644 index 000000000..dcbaf30c5 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerGamePacketListenerImpl_NoChunkLoad.java @@ -0,0 +1,18 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerGamePacketListenerImpl.class) +public class MixinServerGamePacketListenerImpl_NoChunkLoad { + /** + * @author NotStirred + * @reason Vanilla impl accesses blocks around the player (and adds tickets) + */ + @Overwrite + private boolean noBlocksAround(Entity entity) { + return true; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java similarity index 69% rename from src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java rename to src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java index c3a67b945..9db676b7d 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/MixinServerLevelTestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java @@ -1,4 +1,4 @@ -package io.github.opencubicchunks.cubicchunks.test.mixin; +package io.github.opencubicchunks.cubicchunks.test.mixin.server; import java.util.function.BooleanSupplier; @@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ServerLevel.class) -public class MixinServerLevelTestRunner implements LevelTestRunner { +public class MixinServerLevel_TestRunner implements LevelTestRunner { private boolean testStarted = false; private IntegrationTests.LightingIntegrationTest test; private IntegrationTests.LightingIntegrationTest.TestContext context; @@ -20,7 +20,7 @@ public class MixinServerLevelTestRunner implements LevelTestRunner { @Override public void startTestInLevel(IntegrationTests.LightingIntegrationTest integrationTest) { this.test = integrationTest; - this.context = integrationTest.new TestContext(); + this.context = integrationTest.new TestContext((ServerLevel) (Object) this); this.testStarted = true; this.test.setup.accept(this.context); @@ -38,8 +38,16 @@ public class MixinServerLevelTestRunner implements LevelTestRunner { return this.test; } - @Inject(method = "tick", at = @At("HEAD")) + @Inject(method = "tick", at = @At("HEAD"), cancellable = true) private void tickTest(BooleanSupplier b, CallbackInfo ci) { + if (this.test != null && this.test.getState() == IntegrationTests.TestState.FAIL) { + // The test has failed, we don't want to update the level in any way for debugging purposes + if (IntegrationTests.FREEZE_FAILING_WORLDS) { + ci.cancel(); + return; + } + } + if (this.test != null && !this.test.isFinished()) { this.test.tick.accept(this.context); } @@ -48,7 +56,7 @@ private void tickTest(BooleanSupplier b, CallbackInfo ci) { @Inject(method = "tick", at = @At("RETURN")) private void handleTestFinished(BooleanSupplier b, CallbackInfo ci) { if (this.test != null) { - if (this.test.isFinished() && !this.teardownComplete) { + if (!this.teardownComplete && this.test.isFinished() && this.test.getState() != IntegrationTests.TestState.FAIL) { this.teardownComplete = true; this.test.teardown.accept(this.context); } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java new file mode 100644 index 000000000..4a460645a --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoChunkLoad.java @@ -0,0 +1,16 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.level.ServerPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerPlayer.class) +public class MixinServerPlayer_NoChunkLoad { + /** + * @author NotStirred + * @reason In integration tests we never check this (internally it checks blocks, and loads chunks) + */ + @Overwrite + public void doCheckFallDamage(double y, boolean onGround) { + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java new file mode 100644 index 000000000..5f6f82ab9 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_NoTick.java @@ -0,0 +1,26 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerPlayer.class) +public abstract class MixinServerPlayer_NoTick extends Player { + public MixinServerPlayer_NoTick(Level level, BlockPos blockPos, float f, GameProfile gameProfile) { + super(level, blockPos, f, gameProfile); + } + + /** + * @author NotStirred + * @reason Vanilla ticks players on packet received from the client. We do the minimum possible ticking. + */ + @Overwrite + public void doTick() { + this.noPhysics = true; + this.onGround = false; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java new file mode 100644 index 000000000..873a6109d --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerPlayer_SpawnLocationTest.java @@ -0,0 +1,38 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import java.util.Optional; + +import io.github.opencubicchunks.cubicchunks.test.ServerTestRunner; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(ServerPlayer.class) +public abstract class MixinServerPlayer_SpawnLocationTest extends Entity { + public MixinServerPlayer_SpawnLocationTest(EntityType entityType, Level level) { + super(entityType, level); + } + + @Shadow public abstract void moveTo(double x, double y, double z); + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;fudgeSpawnLocation(Lnet/minecraft/server/level/ServerLevel;)V")) + private void spawnAtTestLocation(ServerPlayer instance, ServerLevel serverLevel) { + Pair> spawnLocation = ((ServerTestRunner) serverLevel.getServer()).firstErrorLocation(); + + if (spawnLocation.second().isPresent()) { + this.moveTo(spawnLocation.second().get(), 0.0f, 0.0f); + this.setPos(this.getX(), this.getY() + 1.0, this.getZ()); + } else { + this.moveTo(new BlockPos(0, 0, 0), 0.0f, 0.0f); + this.setPos(this.getX(), this.getY() + 1.0, this.getZ()); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/package-info.java similarity index 75% rename from src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java rename to src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/package-info.java index 23c8f497a..a0c82dba2 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/package-info.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package io.github.opencubicchunks.cubicchunks.test.mixin; +package io.github.opencubicchunks.cubicchunks.test.mixin.server; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java index f656f8e48..bf4ad430f 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java @@ -3,34 +3,58 @@ import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; import io.github.opencubicchunks.cubicchunks.test.IntegrationTests.LightingIntegrationTest; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeStatus; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import net.minecraft.world.level.chunk.ChunkStatus; public class PlaceholderTests { public static void register(Collection lightingTests) { - lightingTests.add(new LightingIntegrationTest(0, + lightingTests.add(new LightingIntegrationTest("test_0", 0, context -> { + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).addCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); System.out.println("Test 0 start"); }, context -> { System.out.println("Test 0 tick"); - context.fail(); + context.pass(); }, context -> { + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).removeCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); System.out.println("Test 0 end"); } )); AtomicInteger ticksPassed = new AtomicInteger(); - lightingTests.add(new LightingIntegrationTest(0, + lightingTests.add(new LightingIntegrationTest("test_1", 0, context -> { System.out.println("Test 1 start"); + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).addCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); }, context -> { System.out.println("Test 1 tick"); - if (ticksPassed.getAndIncrement() == 300) { - context.pass(); + if (ticksPassed.getAndIncrement() == 30) { + context.fail(); + context.setFailurePos(new BlockPos(50, 50, 50)); } }, context -> { + CubePos pos = CubePos.of(0, 0, 0); + ((CubicDistanceManager) context.level().getChunkSource().chunkMap.getDistanceManager()).removeCubeTicket(TicketType.START, pos, + 33 + CubeStatus.getDistance(ChunkStatus.FULL) - 5, + Unit.INSTANCE); System.out.println("Test 1 end"); } )); diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json index 816d3e11c..30925c2f9 100644 --- a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -10,9 +10,21 @@ "overwrites": { "conformVisibility": true }, - "mixins": [ - "MixinDedicatedServer", - "MixinMinecraftServerTestRunner", - "MixinServerLevelTestRunner" + "mixins": [], + "client": [], + "server": [ + "server.MixinChunkMap_SendPlayerAllChunks", + "server.MixinDedicatedServer_DisableNetwork", + "server.MixinDistanceManager_PreventPlayerTickets", + "server.MixinLevelRenderer_NoLightUpdates", + "server.MixinMinecraftServer_TestRunner", + "server.MixinPlayerLists_SetSpectatorOnJoin", + "server.MixinPlayerLists_TestRenderDistance", + "server.MixinServerChunkCache_NoSave", + "server.MixinServerGamePacketListenerImpl_NoChunkLoad", + "server.MixinServerLevel_TestRunner", + "server.MixinServerPlayer_NoChunkLoad", + "server.MixinServerPlayer_NoTick", + "server.MixinServerPlayer_SpawnLocationTest" ] } \ No newline at end of file diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java index 3b01d3f3f..a8b6d0109 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/chunk/MixinChunkMap.java @@ -176,6 +176,7 @@ public BlockGetter getChunkForLighting(int i, int j) { private CubeTaskPriorityQueueSorter cubeQueueSorter; private final Long2ObjectLinkedOpenHashMap updatingCubeMap = new Long2ObjectLinkedOpenHashMap<>(); + /** MUST match MixinChunkMap_SendPlayerAllChunks#visibleCubeMap in integration tests **/ private volatile Long2ObjectLinkedOpenHashMap visibleCubeMap = this.updatingCubeMap.clone(); // NOTE: used from ASM, don't rename @@ -1267,7 +1268,8 @@ public void setIncomingVerticalViewDistance(int verticalDistance) { } // updatePlayerPos - private SectionPos updatePlayerCubePos(ServerPlayer serverPlayerEntityIn) { + @Override + public SectionPos updatePlayerCubePos(ServerPlayer serverPlayerEntityIn) { SectionPos sectionpos = SectionPos.of(serverPlayerEntityIn); serverPlayerEntityIn.setLastSectionPos(sectionpos); PacketDispatcher.sendTo(new PacketUpdateCubePosition(sectionpos), serverPlayerEntityIn); @@ -1276,7 +1278,8 @@ private SectionPos updatePlayerCubePos(ServerPlayer serverPlayerEntityIn) { } // updateChunkTracking - protected void updateCubeTracking(ServerPlayer player, CubePos cubePosIn, Object[] packetCache, boolean wasLoaded, boolean load) { + @Override + public void updateCubeTracking(ServerPlayer player, CubePos cubePosIn, Object[] packetCache, boolean wasLoaded, boolean load) { if (player.level == this.level) { //TODO: reimplement forge event //net.minecraftforge.event.ForgeEventFactory.fireChunkWatch(wasLoaded, load, player, cubePosIn, this.world); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java index 34921121f..1af66b7a1 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubeMapInternal.java @@ -1,8 +1,13 @@ package io.github.opencubicchunks.cubicchunks.server.level; import io.github.opencubicchunks.cc_core.api.CubePos; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ServerPlayer; public interface CubeMapInternal { + SectionPos updatePlayerCubePos(ServerPlayer player); + + void updateCubeTracking(ServerPlayer player, CubePos cubePosIn, Object[] packetCache, boolean wasLoaded, boolean load); boolean isExistingCubeFull(CubePos pos); } From 6b29380e4854a9746c3851c7387d1ddb39719d3d Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Sun, 30 Apr 2023 03:56:40 +0100 Subject: [PATCH 08/12] Delete existing test dimension directories before level creation --- .../server/MixinMinecraftServer_TestRunner.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java index ec748976c..177af0f08 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java @@ -1,6 +1,9 @@ package io.github.opencubicchunks.cubicchunks.test.mixin.server; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Map; @@ -37,6 +40,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.ServerLevelData; import net.minecraft.world.level.storage.WorldData; +import org.apache.commons.io.file.PathUtils; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @@ -81,6 +85,16 @@ private void createTestLevels(ChunkProgressListener chunkProgressListener, Callb Holder dimensionTypeHolder = this.registryAccess().registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getOrCreateHolder(DimensionType.OVERWORLD_LOCATION); ChunkGenerator chunkGenerator2 = WorldGenSettings.makeDefaultOverworld(this.registryAccess(), test.seed); DerivedLevelData derivedLevelData = new DerivedLevelData(this.worldData, this.worldData.overworldData()); + + Path dimensionPath = this.storageSource.getDimensionPath(levelResourceKey); + try { + if (Files.exists(dimensionPath)) { + PathUtils.deleteDirectory(dimensionPath); + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to delete test dimension directory", e); + } + ServerLevel level = new ServerLevel( (MinecraftServer) (Object) this, this.executor, this.storageSource, derivedLevelData, levelResourceKey, dimensionTypeHolder, chunkProgressListener, From 75bc33a5b60d8efc7f6704344c0590f970c4aa51 Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 1 May 2023 02:00:04 +0100 Subject: [PATCH 09/12] Add IndentingStringBuilder for error reporting --- .../test/util/IndentingStringBuilder.java | 45 +++++++++++++++++++ .../cubicchunks/test/util/package-info.java | 7 +++ 2 files changed, 52 insertions(+) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java new file mode 100644 index 000000000..9261d8e65 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/IndentingStringBuilder.java @@ -0,0 +1,45 @@ +package io.github.opencubicchunks.cubicchunks.test.util; + +public class IndentingStringBuilder { + private final int indentation; + private int indentationLevel = 0; + private final StringBuilder wrapped = new StringBuilder(); + + public IndentingStringBuilder(int indentation) { + this.indentation = indentation; + } + + public IndentingStringBuilder append(String string) { + String indent = " ".repeat(this.indentation * this.indentationLevel); + for (String line : string.split("\n")) { + this.wrapped.append(indent); + this.wrapped.append(line).append('\n'); + } + // remove last \n + this.wrapped.deleteCharAt(this.wrapped.length() - 1); + return this; + } + + public IndentingStringBuilder append(Object obj) { + return this.append(String.valueOf(obj)); + } + + public IndentingStringBuilder appendNewLine() { + this.wrapped.append('\n'); + return this; + } + + public IndentingStringBuilder indent() { + this.indentationLevel++; + return this; + } + + public IndentingStringBuilder unIndent() { + this.indentationLevel--; + return this; + } + + @Override public String toString() { + return this.wrapped.toString(); + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java new file mode 100644 index 000000000..9d98735a2 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/util/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package io.github.opencubicchunks.cubicchunks.test.util; + +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.opencubicchunks.cc_core.annotation.MethodsReturnNonnullByDefault; From faa9512cfb4d77bfe54fa9f50d74a3db9561d86c Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 1 May 2023 02:03:27 +0100 Subject: [PATCH 10/12] Implement logging test failures, and failing the gradle task --- .../cubicchunks/test/IntegrationTests.java | 14 ++++--- .../MixinMinecraftServer_TestRunner.java | 38 +++++++++++++++---- .../mixin/server/MixinServerLevel_NoSave.java | 19 ++++++++++ .../server/MixinServerLevel_TestRunner.java | 18 +++++++-- .../test/tests/PlaceholderTests.java | 4 +- .../cubicchunks.mixins.integration_test.json | 2 + .../common/ticket/MixinDistanceManager.java | 29 +++++++------- 7 files changed, 91 insertions(+), 33 deletions(-) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java index 55934be8a..43b74ac00 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java @@ -41,6 +41,7 @@ public static final class LightingIntegrationTest { private TestState state = TestState.NONE; private boolean finished = false; private BlockPos failurePos = null; + private Throwable throwable = null; public LightingIntegrationTest( String testName, long seed, @@ -67,6 +68,10 @@ public Optional getFailurePos() { return Optional.ofNullable(this.failurePos); } + public Optional getThrown() { + return Optional.ofNullable(this.throwable); + } + @Override public String toString() { return "LightingIntegrationTest[" + @@ -82,11 +87,10 @@ public TestContext(ServerLevel level) { this.level = level; } - public void fail() { - if (!finished) { - finished = true; - state = TestState.FAIL; - } + public void fail(Throwable t) { + finished = true; + state = TestState.FAIL; + throwable = t; } public void pass() { diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java index 177af0f08..b54ac891d 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java @@ -1,6 +1,8 @@ package io.github.opencubicchunks.cubicchunks.test.mixin.server; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; @@ -18,10 +20,9 @@ import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; import io.github.opencubicchunks.cubicchunks.test.ServerTestRunner; +import io.github.opencubicchunks.cubicchunks.test.util.IndentingStringBuilder; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; -import net.minecraft.CrashReport; -import net.minecraft.ReportedException; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; @@ -145,25 +146,46 @@ private void unloadLevelWhenTestFinish(BooleanSupplier hasTimeLeft, CallbackInfo } if (this.incompleteTests.isEmpty()) { - if (IntegrationTests.FREEZE_FAILING_WORLDS && !failedTests.isEmpty()) { + if (IntegrationTests.FREEZE_FAILING_WORLDS && !this.failedTests.isEmpty()) { if (!this.isFrozen) { this.isFrozen = true; CubicChunks.LOGGER.info("Tests complete! Holding failing worlds open"); } } else { CubicChunks.LOGGER.info("Tests complete! Exiting..."); - if (!failedTests.isEmpty()) { - CrashReport crashReport = new CrashReport("Failed tests!", new Exception("Some exception")); - throw new ReportedException(crashReport); + this.halt(false); + if (!this.failedTests.isEmpty()) { + CubicChunks.LOGGER.warn(testFailureInformation(this.failedTests)); + System.exit(-1); } else { CubicChunks.LOGGER.info("No failed tests!"); + System.exit(0); } - - this.halt(false); } } } + private static String testFailureInformation(Collection failedTests) { + IndentingStringBuilder s = new IndentingStringBuilder(4) + .append("Failed Tests: ").append(failedTests.size()).appendNewLine().appendNewLine(); + + for (IntegrationTests.LightingIntegrationTest test : failedTests) { + s.append("Failure: ").append(test.testName).appendNewLine().indent(); + + Optional thrown = test.getThrown(); + thrown.ifPresent(throwable -> { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + + s.append(sw) + .unIndent().appendNewLine(); + }); + } + + return s.toString(); + } + /** * @author NotStirred * @reason By default, the player is spawned in the overworld. Instead spawn the player in the first error location, or some valid world diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java new file mode 100644 index 000000000..2b12d2e8b --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_NoSave.java @@ -0,0 +1,19 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProgressListener; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(ServerLevel.class) +public class MixinServerLevel_NoSave { + /** + * @author NotStirred + * @reason Integration tests shouldn't save worlds + */ + @Overwrite + public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) { + + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java index 9db676b7d..d59a6f9de 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java @@ -23,7 +23,11 @@ public class MixinServerLevel_TestRunner implements LevelTestRunner { this.context = integrationTest.new TestContext((ServerLevel) (Object) this); this.testStarted = true; - this.test.setup.accept(this.context); + try { + this.test.setup.accept(this.context); + } catch (Throwable t) { + this.context.fail(t); + } } @Override public boolean testFinished() { @@ -49,7 +53,11 @@ private void tickTest(BooleanSupplier b, CallbackInfo ci) { } if (this.test != null && !this.test.isFinished()) { - this.test.tick.accept(this.context); + try { + this.test.tick.accept(this.context); + } catch (Throwable t) { + this.context.fail(t); + } } } @@ -58,7 +66,11 @@ private void handleTestFinished(BooleanSupplier b, CallbackInfo ci) { if (this.test != null) { if (!this.teardownComplete && this.test.isFinished() && this.test.getState() != IntegrationTests.TestState.FAIL) { this.teardownComplete = true; - this.test.teardown.accept(this.context); + try { + this.test.teardown.accept(this.context); + } catch (Throwable t) { + this.context.fail(t); + } } } } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java index bf4ad430f..674f76e92 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java @@ -7,7 +7,6 @@ import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; import io.github.opencubicchunks.cubicchunks.test.IntegrationTests.LightingIntegrationTest; import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeStatus; -import net.minecraft.core.BlockPos; import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; import net.minecraft.world.level.chunk.ChunkStatus; @@ -46,8 +45,7 @@ public static void register(Collection lightingTests) { context -> { System.out.println("Test 1 tick"); if (ticksPassed.getAndIncrement() == 30) { - context.fail(); - context.setFailurePos(new BlockPos(50, 50, 50)); + context.pass(); } }, context -> { diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json index 30925c2f9..2fbc5f97f 100644 --- a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -17,11 +17,13 @@ "server.MixinDedicatedServer_DisableNetwork", "server.MixinDistanceManager_PreventPlayerTickets", "server.MixinLevelRenderer_NoLightUpdates", + "server.MixinMain", "server.MixinMinecraftServer_TestRunner", "server.MixinPlayerLists_SetSpectatorOnJoin", "server.MixinPlayerLists_TestRenderDistance", "server.MixinServerChunkCache_NoSave", "server.MixinServerGamePacketListenerImpl_NoChunkLoad", + "server.MixinServerLevel_NoSave", "server.MixinServerLevel_TestRunner", "server.MixinServerPlayer_NoChunkLoad", "server.MixinServerPlayer_NoTick", diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java index cfd4f8ed4..c34b2ee23 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/ticket/MixinDistanceManager.java @@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import io.github.opencubicchunks.cc_core.annotation.UsedFromASM; import io.github.opencubicchunks.cc_core.api.CubePos; import io.github.opencubicchunks.cc_core.api.CubicConstants; import io.github.opencubicchunks.cc_core.utils.Coords; @@ -51,20 +52,20 @@ public abstract class MixinDistanceManager implements CubicDistanceManager, Vert private boolean isCubic; // fields below used from ASM - final Long2ObjectMap> playersPerCube = new Long2ObjectOpenHashMap<>(); - final Long2ObjectOpenHashMap>> cubeTickets = new Long2ObjectOpenHashMap<>(); - private final CubeTicketTracker cubeTicketTracker = new CubeTicketTracker(this); - - private final FixedPlayerDistanceCubeTracker naturalSpawnCubeCounter = new FixedPlayerDistanceCubeTracker(this, 8 / CubicConstants.DIAMETER_IN_SECTIONS); - private final CubeTickingTracker tickingCubeTicketsTracker = new CubeTickingTracker(); - private final CubicPlayerTicketTracker cubicPlayerTicketManager = new CubicPlayerTicketTracker(this, MathUtil.ceilDiv(33, CubicConstants.DIAMETER_IN_SECTIONS)); - final Set cubesToUpdateFutures = Sets.newHashSet(); - CubeTaskPriorityQueueSorter cubeTicketThrottler; - ProcessorHandle> cubeTicketThrottlerInput; - ProcessorHandle cubeTicketThrottlerReleaser; - final LongSet cubeTicketsToRelease = new LongOpenHashSet(); - - private long cubeTicketTickCounter; + @UsedFromASM final Long2ObjectMap> playersPerCube = new Long2ObjectOpenHashMap<>(); + @UsedFromASM final Long2ObjectOpenHashMap>> cubeTickets = new Long2ObjectOpenHashMap<>(); + @UsedFromASM private final CubeTicketTracker cubeTicketTracker = new CubeTicketTracker(this); + + @UsedFromASM private final FixedPlayerDistanceCubeTracker naturalSpawnCubeCounter = new FixedPlayerDistanceCubeTracker(this, 8 / CubicConstants.DIAMETER_IN_SECTIONS); + @UsedFromASM private final CubeTickingTracker tickingCubeTicketsTracker = new CubeTickingTracker(); + @UsedFromASM private final CubicPlayerTicketTracker cubicPlayerTicketManager = new CubicPlayerTicketTracker(this, MathUtil.ceilDiv(33, CubicConstants.DIAMETER_IN_SECTIONS)); + @UsedFromASM final Set cubesToUpdateFutures = Sets.newHashSet(); + @UsedFromASM CubeTaskPriorityQueueSorter cubeTicketThrottler; + @UsedFromASM ProcessorHandle> cubeTicketThrottlerInput; + @UsedFromASM ProcessorHandle cubeTicketThrottlerReleaser; + @UsedFromASM final LongSet cubeTicketsToRelease = new LongOpenHashSet(); + + @UsedFromASM private long cubeTicketTickCounter; @Shadow abstract void addTicket(long position, Ticket ticket); From b63871b69464e3be4ef7e491691300517638527c Mon Sep 17 00:00:00 2001 From: Tom Martin Date: Mon, 1 May 2023 02:04:00 +0100 Subject: [PATCH 11/12] Fix hang on System.exit() due to shutdown hook waiting for server stop --- .../cubicchunks/test/mixin/server/MixinMain.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java new file mode 100644 index 000000000..3e09faa93 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMain.java @@ -0,0 +1,14 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import net.minecraft.server.Main; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(Main.class) +public class MixinMain { + @Redirect(method = "main", at = @At(value = "INVOKE", target = "Ljava/lang/Runtime;addShutdownHook(Ljava/lang/Thread;)V"), remap = false) + private static void noShutdownHook(Runtime instance, Thread hook) { + // Do nothing + } +} From b9d26f26b84c28169217735626742c500497f809 Mon Sep 17 00:00:00 2001 From: CursedFlames <18627001+CursedFlames@users.noreply.github.com> Date: Fri, 14 Jul 2023 00:54:01 +1200 Subject: [PATCH 12/12] cube load unload futures for test --- CubicChunksCore | 2 +- .../cubicchunks/test/IntegrationTests.java | 86 ------------ .../cubicchunks/test/LevelTestRunner.java | 4 +- .../test/LightingIntegrationTest.java | 131 ++++++++++++++++++ .../MixinMinecraftServer_TestRunner.java | 11 +- .../MixinServerChunkCache_CubeLoadUnload.java | 81 +++++++++++ .../server/MixinServerLevel_TestRunner.java | 9 +- .../test/tests/PlaceholderTests.java | 34 ++++- .../cubicchunks.mixins.integration_test.json | 1 + .../utils/CubeCollectorFuture.java | 2 +- 10 files changed, 261 insertions(+), 100 deletions(-) create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java create mode 100644 src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java diff --git a/CubicChunksCore b/CubicChunksCore index 50d56eecd..f087cf00f 160000 --- a/CubicChunksCore +++ b/CubicChunksCore @@ -1 +1 @@ -Subproject commit 50d56eecdfd9c805bf61fbe7bb3f9cff2a171051 +Subproject commit f087cf00f7d39e5fec77d0c0c3fa2bb4086de97b diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java index 43b74ac00..e7d3973a5 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/IntegrationTests.java @@ -2,14 +2,8 @@ import java.util.Collection; import java.util.HashSet; -import java.util.Optional; -import java.util.function.Consumer; - -import javax.annotation.Nullable; import io.github.opencubicchunks.cubicchunks.test.tests.PlaceholderTests; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; public class IntegrationTests { public static final boolean DISABLE_NETWORK = System.getProperty("cubicchunks.test.disableNetwork", "false").equals("true"); @@ -29,84 +23,4 @@ public enum TestState { FAIL, NONE, } - - public static final class LightingIntegrationTest { - public final String testName; - public final long seed; - - public final Consumer setup; - public final Consumer tick; - public final Consumer teardown; - - private TestState state = TestState.NONE; - private boolean finished = false; - private BlockPos failurePos = null; - private Throwable throwable = null; - - public LightingIntegrationTest( - String testName, long seed, - Consumer setup, - Consumer tick, - Consumer teardown - ) { - this.testName = testName; - this.seed = seed; - this.setup = setup; - this.tick = tick; - this.teardown = teardown; - } - - public boolean isFinished() { - return this.finished; - } - - public TestState getState() { - return this.state; - } - - public Optional getFailurePos() { - return Optional.ofNullable(this.failurePos); - } - - public Optional getThrown() { - return Optional.ofNullable(this.throwable); - } - - @Override - public String toString() { - return "LightingIntegrationTest[" + - "setup=" + setup + ", " + - "test=" + tick + ", " + - "teardown=" + teardown + ']'; - } - - public final class TestContext { - private final ServerLevel level; - - public TestContext(ServerLevel level) { - this.level = level; - } - - public void fail(Throwable t) { - finished = true; - state = TestState.FAIL; - throwable = t; - } - - public void pass() { - if (!finished) { - finished = true; - state = TestState.PASS; - } - } - - public ServerLevel level() { - return level; - } - - public void setFailurePos(@Nullable BlockPos pos) { - failurePos = pos; - } - } - } } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java index ef7c88f74..d03f6b1fa 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LevelTestRunner.java @@ -1,10 +1,10 @@ package io.github.opencubicchunks.cubicchunks.test; public interface LevelTestRunner { - void startTestInLevel(IntegrationTests.LightingIntegrationTest test); + void startTestInLevel(LightingIntegrationTest test); boolean testFinished(); IntegrationTests.TestState testState(); - IntegrationTests.LightingIntegrationTest getTest(); + LightingIntegrationTest getTest(); } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java new file mode 100644 index 000000000..a0dfdcff4 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/LightingIntegrationTest.java @@ -0,0 +1,131 @@ +package io.github.opencubicchunks.cubicchunks.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeAccess; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkStatus; + +public final class LightingIntegrationTest { + public final String testName; + public final long seed; + + public final Consumer setup; + public final Consumer tick; + public final Consumer teardown; + + private IntegrationTests.TestState state = IntegrationTests.TestState.NONE; + private boolean finished = false; + private BlockPos failurePos = null; + private Throwable throwable = null; + + public LightingIntegrationTest( + String testName, long seed, + Consumer setup, + Consumer tick, + Consumer teardown + ) { + this.testName = testName; + this.seed = seed; + this.setup = setup; + this.tick = tick; + this.teardown = teardown; + } + + public boolean isFinished() { + return this.finished; + } + + public IntegrationTests.TestState getState() { + return this.state; + } + + public Optional getFailurePos() { + return Optional.ofNullable(this.failurePos); + } + + public Optional getThrown() { + return Optional.ofNullable(this.throwable); + } + + @Override + public String toString() { + return "LightingIntegrationTest[" + + "setup=" + setup + ", " + + "test=" + tick + ", " + + "teardown=" + teardown + ']'; + } + + public final class TestContext { + private final ServerLevel level; + + public TestContext(ServerLevel level) { + this.level = level; + } + + public void fail(Throwable t) { + finished = true; + state = IntegrationTests.TestState.FAIL; + throwable = t; + } + + public void pass() { + if (!finished) { + finished = true; + state = IntegrationTests.TestState.PASS; + } + } + + public ServerLevel level() { + return level; + } + + public void setFailurePos(@Nullable BlockPos pos) { + failurePos = pos; + } + + public CompletableFuture getCubeLoadFuturesInVolume(CubePos minPos, CubePos maxPos, ChunkStatus requiredStatus) { + var serverChunkCache = ((CubeLoadUnload) level.getChunkSource()); + List>> futures = new ArrayList<>(); + // for each cube: get the cube future + for (int x = minPos.getX(); x <= maxPos.getX(); x++) { + for (int z = minPos.getZ(); z <= maxPos.getZ(); z++) { + for (int y = minPos.getY(); y <= minPos.getY(); y++) { + futures.add(serverChunkCache.getCubeFuture(CubePos.of(x, y, z), requiredStatus)); + } + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + public CompletableFuture getCubeUnloadFuturesInVolume(CubePos minPos, CubePos maxPos, ChunkStatus requiredStatus) { + var serverChunkCache = ((CubeLoadUnload) level.getChunkSource()); + List> futures = new ArrayList<>(); + // for each cube: get the cube future + for (int x = minPos.getX(); x <= maxPos.getX(); x++) { + for (int z = minPos.getZ(); z <= maxPos.getZ(); z++) { + for (int y = minPos.getY(); y <= minPos.getY(); y++) { + futures.add(serverChunkCache.getCubeUnloadFuture(CubePos.of(x, y, z), requiredStatus)); + } + } + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + public interface CubeLoadUnload { + CompletableFuture> getCubeFuture(CubePos pos, ChunkStatus requiredStatus); + + CompletableFuture getCubeUnloadFuture(CubePos pos, ChunkStatus requiredStatus); + } + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java index b54ac891d..09ce809c2 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinMinecraftServer_TestRunner.java @@ -19,6 +19,7 @@ import io.github.opencubicchunks.cubicchunks.CubicChunks; import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; import io.github.opencubicchunks.cubicchunks.test.ServerTestRunner; import io.github.opencubicchunks.cubicchunks.test.util.IndentingStringBuilder; import it.unimi.dsi.fastutil.Pair; @@ -59,8 +60,8 @@ public abstract class MixinMinecraftServer_TestRunner implements ServerTestRunne private boolean isFrozen = false; - private final Collection incompleteTests = IntegrationTests.getLightingTests(); - private final Collection failedTests = new ArrayList<>(); + private final Collection incompleteTests = IntegrationTests.getLightingTests(); + private final Collection failedTests = new ArrayList<>(); @Nullable private Pair> firstError = null; @@ -120,7 +121,7 @@ private void unloadLevelWhenTestFinish(BooleanSupplier hasTimeLeft, CallbackInfo ServerLevel level = this.levels.get(levelResourceKey); LevelTestRunner levelTestRunner = (LevelTestRunner) level; if (levelTestRunner.testFinished()) { - IntegrationTests.LightingIntegrationTest test = levelTestRunner.getTest(); + LightingIntegrationTest test = levelTestRunner.getTest(); incompleteTests.remove(test); if (test.getState() == IntegrationTests.TestState.FAIL) { @@ -165,11 +166,11 @@ private void unloadLevelWhenTestFinish(BooleanSupplier hasTimeLeft, CallbackInfo } } - private static String testFailureInformation(Collection failedTests) { + private static String testFailureInformation(Collection failedTests) { IndentingStringBuilder s = new IndentingStringBuilder(4) .append("Failed Tests: ").append(failedTests.size()).appendNewLine().appendNewLine(); - for (IntegrationTests.LightingIntegrationTest test : failedTests) { + for (LightingIntegrationTest test : failedTests) { s.append("Failure: ").append(test.testName).appendNewLine().indent(); Optional thrown = test.getThrown(); diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java new file mode 100644 index 000000000..7d7c94973 --- /dev/null +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerChunkCache_CubeLoadUnload.java @@ -0,0 +1,81 @@ +package io.github.opencubicchunks.cubicchunks.test.mixin.server; + +import java.util.concurrent.CompletableFuture; + +import javax.annotation.Nullable; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.chunk.VerticalViewDistanceListener; +import io.github.opencubicchunks.cubicchunks.mixin.access.common.ChunkMapAccess; +import io.github.opencubicchunks.cubicchunks.server.level.CubeHolder; +import io.github.opencubicchunks.cubicchunks.server.level.CubeMap; +import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; +import io.github.opencubicchunks.cubicchunks.server.level.ServerCubeCache; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeStatus; +import io.github.opencubicchunks.cubicchunks.world.level.chunk.LightCubeGetter; +import net.minecraft.Util; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ServerChunkCache.class) +public abstract class MixinServerChunkCache_CubeLoadUnload implements LightingIntegrationTest.TestContext.CubeLoadUnload, ServerCubeCache, LightCubeGetter, VerticalViewDistanceListener { + @Shadow @Final public ChunkMap chunkMap; + @Shadow @Final ServerLevel level; + @Shadow @Final private DistanceManager distanceManager; + + @Shadow protected abstract boolean chunkAbsent(@Nullable ChunkHolder chunkHolderIn, int i); + + @Override public CompletableFuture> getCubeFuture(CubePos pos, ChunkStatus requiredStatus) { + CubePos cubePos = CubePos.of(pos.getX(), pos.getY(), pos.getZ()); + long i = cubePos.asLong(); + int j = 33 + CubeStatus.getDistance(requiredStatus); + ChunkHolder chunkholder = this.getVisibleCubeIfPresent(i); + ((CubicDistanceManager) this.distanceManager).addCubeTicket(TicketType.START, cubePos, j, Unit.INSTANCE); + if (this.chunkAbsent(chunkholder, j)) { + this.runCubeDistanceManagerUpdates(); + chunkholder = this.getVisibleCubeIfPresent(i); + if (this.chunkAbsent(chunkholder, j)) { + throw Util.pauseInIde(new IllegalStateException("No cube holder after ticket has been added")); + } + } + + if (this.chunkAbsent(chunkholder, j)) { + throw Util.pauseInIde(new IllegalStateException("This shouldn't happen")); + } + + return ((CubeHolder) chunkholder).getOrScheduleCubeFuture(requiredStatus, this.chunkMap); + } + + @Override public CompletableFuture getCubeUnloadFuture(CubePos cubePos, ChunkStatus requiredStatus) { + long i = cubePos.asLong(); + int j = 33 + CubeStatus.getDistance(requiredStatus); + ((CubicDistanceManager) this.distanceManager).removeCubeTicket(TicketType.START, cubePos, j, Unit.INSTANCE); + var cubeHolder = ((CubeHolder) this.getVisibleCubeIfPresent(i)); + return cubeHolder.getCubeToSave(); + } + + // getVisibleChunkIfPresent + @Nullable + private ChunkHolder getVisibleCubeIfPresent(long cubePosIn) { + return ((CubeMap) this.chunkMap).getVisibleCubeIfPresent(cubePosIn); + } + + // runDistanceManagerUpdates + private boolean runCubeDistanceManagerUpdates() { + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = ((ChunkMapAccess) this.chunkMap).invokePromoteChunkMap(); + return flag || flag1; + } +} diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java index d59a6f9de..1b66a8cad 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/mixin/server/MixinServerLevel_TestRunner.java @@ -4,6 +4,7 @@ import io.github.opencubicchunks.cubicchunks.test.IntegrationTests; import io.github.opencubicchunks.cubicchunks.test.LevelTestRunner; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; import net.minecraft.server.level.ServerLevel; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -13,12 +14,12 @@ @Mixin(ServerLevel.class) public class MixinServerLevel_TestRunner implements LevelTestRunner { private boolean testStarted = false; - private IntegrationTests.LightingIntegrationTest test; - private IntegrationTests.LightingIntegrationTest.TestContext context; + private LightingIntegrationTest test; + private LightingIntegrationTest.TestContext context; private boolean teardownComplete = false; - @Override public void startTestInLevel(IntegrationTests.LightingIntegrationTest integrationTest) { + @Override public void startTestInLevel(LightingIntegrationTest integrationTest) { this.test = integrationTest; this.context = integrationTest.new TestContext((ServerLevel) (Object) this); this.testStarted = true; @@ -38,7 +39,7 @@ public class MixinServerLevel_TestRunner implements LevelTestRunner { return test.getState(); } - @Override public IntegrationTests.LightingIntegrationTest getTest() { + @Override public LightingIntegrationTest getTest() { return this.test; } diff --git a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java index 674f76e92..19504d12b 100644 --- a/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java +++ b/src/integrationTest/java/io/github/opencubicchunks/cubicchunks/test/tests/PlaceholderTests.java @@ -1,11 +1,13 @@ package io.github.opencubicchunks.cubicchunks.test.tests; import java.util.Collection; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import io.github.opencubicchunks.cc_core.api.CubePos; +import io.github.opencubicchunks.cubicchunks.server.level.CubeMap; import io.github.opencubicchunks.cubicchunks.server.level.CubicDistanceManager; -import io.github.opencubicchunks.cubicchunks.test.IntegrationTests.LightingIntegrationTest; +import io.github.opencubicchunks.cubicchunks.test.LightingIntegrationTest; import io.github.opencubicchunks.cubicchunks.world.level.chunk.CubeStatus; import net.minecraft.server.level.TicketType; import net.minecraft.util.Unit; @@ -56,5 +58,35 @@ public static void register(Collection lightingTests) { System.out.println("Test 1 end"); } )); + var minPos = CubePos.of(-1, -1, -1); + var maxPos = CubePos.of(1, 1, 1); + var requiredLevel = ChunkStatus.LIGHT; + var unloading = new boolean[] {false}; + CompletableFuture[] future = new CompletableFuture[1]; + lightingTests.add(new LightingIntegrationTest("test_2", 0, + context -> { + System.out.println("Test 2 start"); + future[0] = context.getCubeLoadFuturesInVolume(minPos, maxPos, requiredLevel); + }, + context -> { + System.out.println("Test 2 tick"); + if (!future[0].isDone()) return; + if (!unloading[0]) { + unloading[0] = true; + future[0] = context.getCubeUnloadFuturesInVolume(minPos, maxPos, requiredLevel); + return; + } + context.pass(); + }, + context -> { + ((CubeMap) context.level().getChunkSource().chunkMap).getUpdatingCubeMap().values().stream().filter(holder -> { + var status = holder.getLastAvailableStatus(); + return status != null && status != ChunkStatus.EMPTY; + }).forEach( + holder -> System.out.println(holder.getLastAvailableStatus()) + ); + System.out.println("Test 2 end"); + } + )); } } diff --git a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json index 2fbc5f97f..6bee09174 100644 --- a/src/integrationTest/resources/cubicchunks.mixins.integration_test.json +++ b/src/integrationTest/resources/cubicchunks.mixins.integration_test.json @@ -21,6 +21,7 @@ "server.MixinMinecraftServer_TestRunner", "server.MixinPlayerLists_SetSpectatorOnJoin", "server.MixinPlayerLists_TestRenderDistance", + "server.MixinServerChunkCache_CubeLoadUnload", "server.MixinServerChunkCache_NoSave", "server.MixinServerGamePacketListenerImpl_NoChunkLoad", "server.MixinServerLevel_NoSave", diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java b/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java index f1b8d43d0..65ed50461 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/utils/CubeCollectorFuture.java @@ -34,4 +34,4 @@ public void add(int idx, Either eit this.complete(Arrays.asList(results)); } } -} \ No newline at end of file +}