mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-03 23:22:59 +00:00 
			
		
		
		
	Wait for computers to run each tick in gametests
This commit is contained in:
		@@ -0,0 +1,51 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.mixin.gametest;
 | 
			
		||||
 | 
			
		||||
import com.mojang.datafixers.DataFixer;
 | 
			
		||||
import dan200.computercraft.gametest.core.TestHooks;
 | 
			
		||||
import net.minecraft.gametest.framework.GameTestServer;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.server.Services;
 | 
			
		||||
import net.minecraft.server.WorldStem;
 | 
			
		||||
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
 | 
			
		||||
import net.minecraft.server.packs.repository.PackRepository;
 | 
			
		||||
import net.minecraft.world.level.storage.LevelStorageSource;
 | 
			
		||||
import org.spongepowered.asm.mixin.Mixin;
 | 
			
		||||
import org.spongepowered.asm.mixin.Overwrite;
 | 
			
		||||
import org.spongepowered.asm.mixin.Shadow;
 | 
			
		||||
 | 
			
		||||
import java.net.Proxy;
 | 
			
		||||
import java.util.concurrent.locks.LockSupport;
 | 
			
		||||
 | 
			
		||||
@Mixin(GameTestServer.class)
 | 
			
		||||
abstract class GameTestServerMixin extends MinecraftServer {
 | 
			
		||||
    GameTestServerMixin(Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer fixerUpper, Services services, ChunkProgressListenerFactory progressListenerFactory) {
 | 
			
		||||
        super(serverThread, storageSource, packRepository, worldStem, proxy, fixerUpper, services, progressListenerFactory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Overwrite {@link GameTestServer#waitUntilNextTick()} to wait for all computers to finish executing.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is a little dangerous (breaks async behaviour of computers), but it forces tests to be deterministic.
 | 
			
		||||
     *
 | 
			
		||||
     * @reason See above. This is only in the test mod, so no risk of collision.
 | 
			
		||||
     * @author SquidDev.
 | 
			
		||||
     */
 | 
			
		||||
    @Overwrite
 | 
			
		||||
    @Override
 | 
			
		||||
    public void waitUntilNextTick() {
 | 
			
		||||
        while (true) {
 | 
			
		||||
            runAllTasks();
 | 
			
		||||
            if (!haveTestsStarted() || TestHooks.areComputersIdle(this)) break;
 | 
			
		||||
            LockSupport.parkNanos(100_000);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Shadow
 | 
			
		||||
    private boolean haveTestsStarted() {
 | 
			
		||||
        throw new AssertionError("Stub.");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
package dan200.computercraft.gametest.core
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI
 | 
			
		||||
import dan200.computercraft.core.ComputerContext
 | 
			
		||||
import dan200.computercraft.core.computer.computerthread.ComputerThread
 | 
			
		||||
import dan200.computercraft.gametest.*
 | 
			
		||||
import dan200.computercraft.gametest.api.ClientGameTest
 | 
			
		||||
import dan200.computercraft.gametest.api.TestTags
 | 
			
		||||
@@ -23,6 +25,8 @@ import net.minecraft.world.phys.Vec3
 | 
			
		||||
import org.slf4j.Logger
 | 
			
		||||
import org.slf4j.LoggerFactory
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.lang.invoke.MethodHandle
 | 
			
		||||
import java.lang.invoke.MethodHandles
 | 
			
		||||
import java.lang.reflect.InvocationTargetException
 | 
			
		||||
import java.lang.reflect.Method
 | 
			
		||||
import java.lang.reflect.Modifier
 | 
			
		||||
@@ -80,6 +84,9 @@ object TestHooks {
 | 
			
		||||
        CCTestCommand.importFiles(server)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun areComputersIdle(server: MinecraftServer) = ComputerThreadReflection.isFullyIdle(ServerContext.get(server))
 | 
			
		||||
 | 
			
		||||
    private val testClasses = listOf(
 | 
			
		||||
        Computer_Test::class.java,
 | 
			
		||||
        CraftOs_Test::class.java,
 | 
			
		||||
@@ -116,14 +123,6 @@ object TestHooks {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val isCi = System.getenv("CI") != null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust the timeout of a test. This makes it 1.5 times longer when run under CI, as CI servers are less powerful
 | 
			
		||||
     * than our own.
 | 
			
		||||
     */
 | 
			
		||||
    private fun adjustTimeout(timeout: Int): Int = if (isCi) timeout + (timeout / 2) else timeout
 | 
			
		||||
 | 
			
		||||
    private fun registerTest(testClass: Class<*>, method: Method, fallbackRegister: Consumer<Method>) {
 | 
			
		||||
        val className = testClass.simpleName.lowercase()
 | 
			
		||||
        val testName = className + "." + method.name.lowercase()
 | 
			
		||||
@@ -135,7 +134,7 @@ object TestHooks {
 | 
			
		||||
                TestFunction(
 | 
			
		||||
                    testInfo.batch, testName, testInfo.template.ifEmpty { testName },
 | 
			
		||||
                    StructureUtils.getRotationForRotationSteps(testInfo.rotationSteps),
 | 
			
		||||
                    adjustTimeout(testInfo.timeoutTicks),
 | 
			
		||||
                    testInfo.timeoutTicks,
 | 
			
		||||
                    testInfo.setupTicks,
 | 
			
		||||
                    testInfo.required, testInfo.requiredSuccesses, testInfo.attempts,
 | 
			
		||||
                ) { value -> safeInvoke(method, value) },
 | 
			
		||||
@@ -149,10 +148,8 @@ object TestHooks {
 | 
			
		||||
 | 
			
		||||
            GameTestRegistry.getAllTestFunctions().add(
 | 
			
		||||
                TestFunction(
 | 
			
		||||
                    testName,
 | 
			
		||||
                    testName,
 | 
			
		||||
                    testInfo.template.ifEmpty { testName },
 | 
			
		||||
                    adjustTimeout(testInfo.timeoutTicks),
 | 
			
		||||
                    testName, testName, testInfo.template.ifEmpty { testName },
 | 
			
		||||
                    testInfo.timeoutTicks,
 | 
			
		||||
                    0,
 | 
			
		||||
                    true,
 | 
			
		||||
                ) { value -> safeInvoke(method, value) },
 | 
			
		||||
@@ -200,3 +197,31 @@ object TestHooks {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nasty reflection to determine if computers are fully idle.
 | 
			
		||||
 *
 | 
			
		||||
 * This is horribly nasty, and should not be used as a model for any production code!
 | 
			
		||||
 *
 | 
			
		||||
 * @see [ComputerThread.isFullyIdle]
 | 
			
		||||
 * @see [dan200.computercraft.mixin.gametest.GameTestServerMixin]
 | 
			
		||||
 */
 | 
			
		||||
private object ComputerThreadReflection {
 | 
			
		||||
    private val lookup = MethodHandles.lookup()
 | 
			
		||||
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val computerContext: MethodHandle = lookup.unreflectGetter(
 | 
			
		||||
        ServerContext::class.java.getDeclaredField("context").also { it.isAccessible = true },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val isFullyIdle: MethodHandle = lookup.unreflect(
 | 
			
		||||
        ComputerThread::class.java.getDeclaredMethod("isFullyIdle").also { it.isAccessible = true },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fun isFullyIdle(context: ServerContext): Boolean {
 | 
			
		||||
        val computerContext = computerContext.invokeExact(context) as ComputerContext
 | 
			
		||||
        val computerThread = computerContext.computerScheduler() as ComputerThread
 | 
			
		||||
        return isFullyIdle.invokeExact(computerThread) as Boolean
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ class MultiTestReporter(private val reporters: List<TestReporter>) : TestReporte
 | 
			
		||||
 * Reports tests to a JUnit XML file. This is equivalent to [JUnitLikeTestReporter], except it ensures the destination
 | 
			
		||||
 * directory exists.
 | 
			
		||||
 */
 | 
			
		||||
class JunitTestReporter constructor(destination: File) : JUnitLikeTestReporter(destination) {
 | 
			
		||||
class JunitTestReporter(destination: File) : JUnitLikeTestReporter(destination) {
 | 
			
		||||
    override fun save(file: File) {
 | 
			
		||||
        try {
 | 
			
		||||
            Files.createDirectories(file.toPath().parent)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@
 | 
			
		||||
        "GameTestInfoAccessor",
 | 
			
		||||
        "GameTestSequenceAccessor",
 | 
			
		||||
        "GameTestSequenceMixin",
 | 
			
		||||
        "GameTestServerMixin",
 | 
			
		||||
        "TestCommandAccessor"
 | 
			
		||||
    ],
 | 
			
		||||
    "client": [
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user