mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-04-15 07:13:13 +00:00
Wait for computers to run each tick in gametests
This commit is contained in:
parent
d6749f8461
commit
6739c4c6c0
@ -10,9 +10,7 @@ import org.gradle.api.GradleException
|
||||
import org.gradle.api.NamedDomainObjectProvider
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.SourceSet
|
||||
@ -168,7 +166,7 @@ abstract class CCTweakedExtension(private val project: Project) {
|
||||
jacoco.applyTo(this)
|
||||
|
||||
extensions.configure(JacocoTaskExtension::class.java) {
|
||||
includes = listOf("dan200.computercraft.*")
|
||||
includes = listOf("dan200.computercraft.*")
|
||||
excludes = listOf(
|
||||
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
|
||||
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
|
||||
|
@ -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": [
|
||||
|
@ -433,6 +433,16 @@ public final class ComputerThread implements ComputerScheduler {
|
||||
return computerQueueSize() > idleWorkers.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if no work is queued, and all workers are idle.
|
||||
*
|
||||
* @return If the threads are fully idle.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean isFullyIdle() {
|
||||
return computerQueueSize() == 0 && idleWorkers.get() >= workerCount();
|
||||
}
|
||||
|
||||
private void workerFinished(WorkerThread worker) {
|
||||
// We should only shut down a worker once! This should only happen if we fail to abort a worker and then the
|
||||
// worker finishes normally.
|
||||
|
Loading…
x
Reference in New Issue
Block a user