172 lines
6.4 KiB
Kotlin
172 lines
6.4 KiB
Kotlin
package dan200.computercraft.gametest.core
|
|
|
|
import dan200.computercraft.api.ComputerCraftAPI
|
|
import dan200.computercraft.gametest.*
|
|
import dan200.computercraft.gametest.api.ClientGameTest
|
|
import dan200.computercraft.gametest.api.TestTags
|
|
import dan200.computercraft.gametest.api.Times
|
|
import dan200.computercraft.shared.computer.core.ServerContext
|
|
import net.minecraft.core.BlockPos
|
|
import net.minecraft.gametest.framework.*
|
|
import net.minecraft.server.MinecraftServer
|
|
import net.minecraft.world.level.GameRules
|
|
import org.slf4j.Logger
|
|
import org.slf4j.LoggerFactory
|
|
import java.io.File
|
|
import java.lang.reflect.InvocationTargetException
|
|
import java.lang.reflect.Method
|
|
import java.lang.reflect.Modifier
|
|
import java.nio.file.Path
|
|
import java.nio.file.Paths
|
|
import java.util.function.Consumer
|
|
import javax.xml.parsers.ParserConfigurationException
|
|
|
|
object TestHooks {
|
|
@JvmField
|
|
val LOG: Logger = LoggerFactory.getLogger(TestHooks::class.java)
|
|
|
|
@JvmStatic
|
|
val sourceDir: Path = Paths.get(System.getProperty("cctest.sources")).normalize().toAbsolutePath()
|
|
|
|
@JvmStatic
|
|
fun init() {
|
|
ServerContext.luaMachine = ManagedComputers
|
|
ComputerCraftAPI.registerAPIFactory(::TestAPI)
|
|
StructureUtils.testStructuresDir = sourceDir.resolve("structures").toString()
|
|
|
|
// Set up our test reporter if configured.
|
|
val outputPath = System.getProperty("cctest.gametest-report")
|
|
if (outputPath != null) {
|
|
try {
|
|
GlobalTestReporter.replaceWith(
|
|
MultiTestReporter(
|
|
JunitTestReporter(File(outputPath)),
|
|
LogTestReporter(),
|
|
),
|
|
)
|
|
} catch (e: ParserConfigurationException) {
|
|
throw RuntimeException(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
@JvmStatic
|
|
fun onServerStarted(server: MinecraftServer) {
|
|
val rules = server.gameRules
|
|
rules.getRule(GameRules.RULE_DAYLIGHT).set(false, server)
|
|
server.overworld().dayTime = Times.NOON
|
|
|
|
LOG.info("Cleaning up after last run")
|
|
GameTestRunner.clearAllTests(server.overworld(), BlockPos(0, -60, 0), GameTestTicker.SINGLETON, 200)
|
|
|
|
// Delete server context and add one with a mutable machine factory. This allows us to set the factory for
|
|
// specific test batches without having to reset all computers.
|
|
for (computer in ServerContext.get(server).registry().computers) {
|
|
val label = if (computer.label == null) "#" + computer.id else computer.label!!
|
|
LOG.warn("Unexpected computer {}", label)
|
|
}
|
|
|
|
LOG.info("Importing files")
|
|
CCTestCommand.importFiles(server)
|
|
}
|
|
|
|
private val testClasses = listOf(
|
|
Computer_Test::class.java,
|
|
CraftOs_Test::class.java,
|
|
Details_Test::class.java,
|
|
Disk_Drive_Test::class.java,
|
|
Inventory_Test::class.java,
|
|
Loot_Test::class.java,
|
|
Modem_Test::class.java,
|
|
Monitor_Test::class.java,
|
|
Pocket_Computer_Test::class.java,
|
|
Printer_Test::class.java,
|
|
Recipe_Test::class.java,
|
|
Turtle_Test::class.java,
|
|
)
|
|
|
|
/**
|
|
* Register all of our gametests.
|
|
*
|
|
* This is super nasty, as it bypasses any loader-specific hooks for registering tests. However, it makes it much
|
|
* easier to ensure consistent behaviour between loaders (namely making [GameTest.template] point to a
|
|
* structure rather than a per-test-class one), as well as supporting our custom client tests.
|
|
*
|
|
* @param fallbackRegister A fallback function which registers non-test methods (such as [BeforeBatch]). This
|
|
* should be [GameTestRegistry.register] or equivalent.
|
|
*/
|
|
@JvmStatic
|
|
fun loadTests(fallbackRegister: Consumer<Method>) {
|
|
for (testClass in testClasses) {
|
|
for (method in testClass.declaredMethods) {
|
|
registerTest(testClass, method, fallbackRegister)
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
method.getAnnotation(GameTest::class.java)?.let { testInfo ->
|
|
if (!TestTags.isEnabled(TestTags.COMMON)) return
|
|
|
|
GameTestRegistry.getAllTestFunctions().add(
|
|
TestFunction(
|
|
testInfo.batch, testName, testInfo.template.ifEmpty { testName },
|
|
StructureUtils.getRotationForRotationSteps(testInfo.rotationSteps),
|
|
adjustTimeout(testInfo.timeoutTicks),
|
|
testInfo.setupTicks,
|
|
testInfo.required, testInfo.requiredSuccesses, testInfo.attempts,
|
|
) { value -> safeInvoke(method, value) },
|
|
)
|
|
GameTestRegistry.getAllTestClassNames().add(testClass.simpleName)
|
|
return
|
|
}
|
|
|
|
method.getAnnotation(ClientGameTest::class.java)?.let { testInfo ->
|
|
if (!TestTags.isEnabled(testInfo.tag)) return
|
|
|
|
GameTestRegistry.getAllTestFunctions().add(
|
|
TestFunction(
|
|
testName,
|
|
testName,
|
|
testInfo.template.ifEmpty { testName },
|
|
adjustTimeout(testInfo.timeoutTicks),
|
|
0,
|
|
true,
|
|
) { value -> safeInvoke(method, value) },
|
|
)
|
|
GameTestRegistry.getAllTestClassNames().add(testClass.simpleName)
|
|
return
|
|
}
|
|
|
|
fallbackRegister.accept(method)
|
|
}
|
|
|
|
private fun safeInvoke(method: Method, value: Any) {
|
|
try {
|
|
var instance: Any? = null
|
|
if (!Modifier.isStatic(method.modifiers)) {
|
|
instance = method.declaringClass.getConstructor().newInstance()
|
|
}
|
|
method.invoke(instance, value)
|
|
} catch (e: InvocationTargetException) {
|
|
when (val cause = e.cause) {
|
|
is RuntimeException -> throw cause
|
|
else -> throw RuntimeException(cause)
|
|
}
|
|
} catch (e: ReflectiveOperationException) {
|
|
throw RuntimeException(e)
|
|
}
|
|
}
|
|
}
|