IntegrationTestBootstrap.kt 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /*
  2. * Copyright 2019-2022 Mamoe Technologies and contributors.
  3. *
  4. * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  5. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  6. *
  7. * https://github.com/mamoe/mirai/blob/dev/LICENSE
  8. */
  9. @file:JvmName("IntegrationTestBootstrap")
  10. package net.mamoe.console.integrationtest
  11. import kotlinx.coroutines.cancelAndJoin
  12. import kotlinx.coroutines.runBlocking
  13. import net.mamoe.console.integrationtest.AbstractTestPoint.Companion.internalBCS
  14. import net.mamoe.console.integrationtest.AbstractTestPoint.Companion.internalOSS
  15. import net.mamoe.mirai.console.MiraiConsole
  16. import net.mamoe.mirai.console.terminal.ConsoleTerminalExperimentalApi
  17. import net.mamoe.mirai.console.terminal.ConsoleTerminalSettings
  18. import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
  19. import net.mamoe.mirai.utils.cast
  20. import org.objectweb.asm.ClassWriter
  21. import org.objectweb.asm.Opcodes
  22. import org.objectweb.asm.Type
  23. import java.io.File
  24. import java.io.FileDescriptor
  25. import java.io.FileOutputStream
  26. import java.io.PrintStream
  27. import java.util.concurrent.ConcurrentLinkedDeque
  28. import java.util.zip.ZipEntry
  29. import java.util.zip.ZipOutputStream
  30. import kotlin.system.exitProcess
  31. internal object IntegrationTestBootstrapContext {
  32. val failures = ConcurrentLinkedDeque<Class<*>>()
  33. }
  34. /**
  35. * 入口点为 /test/MiraiConsoleIntegrationTestBootstrap.kt 并非此函数(文件),
  36. * 不要直接执行此函数
  37. */
  38. @OptIn(ConsoleTerminalExperimentalApi::class)
  39. @PublishedApi
  40. internal fun main() {
  41. // PRE CHECK
  42. run {
  43. if (!System.getenv("MIRAI_CONSOLE_INTEGRATION_TEST").orEmpty().toBoolean()) {
  44. error("Don't launch IntegrationTestBootstrap directly. See /test/MiraiConsoleIntegrationTestBootstrap.kt")
  45. }
  46. }
  47. // @context: env.testunit = true
  48. // @context: env.inJUnitProcess = false
  49. // @context: env.exitProcessSafety = true
  50. // @context: process.type = sandbox
  51. // @context: process.cwd = /mirai-console/backend/build/rttu
  52. // @context: process.timeout = 5min
  53. ConsoleTerminalSettings.setupAnsi = false
  54. ConsoleTerminalSettings.noConsole = true
  55. ConsoleTerminalSettings.launchOptions.crashWhenPluginLoadFailed = true
  56. val testUnits: List<AbstractTestPoint> = readStringListFromEnv("IT_POINTS").asSequence()
  57. .onEach { println("[MCIT] Loading test point: $it") }
  58. .map { Class.forName(it) }
  59. .map {
  60. @Suppress("DEPRECATION")
  61. it.kotlin.objectInstance ?: it.newInstance()
  62. }
  63. .map { it.cast<AbstractTestPoint>() }
  64. .toList()
  65. File("plugins").mkdirs()
  66. File("modules").mkdirs()
  67. prepareConsole()
  68. testUnits.forEach { (it as? AbstractTestPointAsPlugin)?.generatePluginJar() }
  69. testUnits.forEach { it.internalBCS() }
  70. Thread.sleep(2000L)
  71. try {
  72. MiraiConsoleTerminalLoader.startAsDaemon()
  73. } catch (e: Throwable) {
  74. val ps = PrintStream(FileOutputStream(FileDescriptor.out))
  75. e.printStackTrace(ps)
  76. ps.flush()
  77. exitProcess(1)
  78. }
  79. if (!MiraiConsole.isActive) {
  80. error("Failed to start console")
  81. }
  82. if (IntegrationTestBootstrapContext.failures.isNotEmpty()) {
  83. val logger = MiraiConsole.mainLogger
  84. logger.error("Failed tests: ")
  85. IntegrationTestBootstrapContext.failures.toSet().forEach {
  86. logger.error(" `- $it")
  87. }
  88. error("Failed tests: ${IntegrationTestBootstrapContext.failures.toSet()}")
  89. }
  90. // I/main: mirai-console started successfully.
  91. testUnits.forEach { it.internalOSS() }
  92. runBlocking {
  93. MiraiConsole.job.cancelAndJoin()
  94. }
  95. exitProcess(0)
  96. }
  97. private fun File.mkparents(): File = apply { parentFile?.mkdirs() }
  98. private fun prepareConsole() {
  99. File("config/Console/Logger.yml").mkparents().writeText(
  100. """
  101. defaultPriority: ALL
  102. loggers:
  103. Bot: ALL
  104. """
  105. )
  106. readStringListFromEnv("IT_PLUGINS").forEach { path ->
  107. val jarFile = File(path)
  108. if (jarFile.name.startsWith("module-")) {
  109. // DYN MODULE
  110. val target = File("modules/${jarFile.name}").mkparents()
  111. jarFile.copyTo(target, overwrite = true)
  112. println("[MCIT] Copied module: $jarFile")
  113. } else {
  114. val target = File("plugins/${jarFile.name}").mkparents()
  115. jarFile.copyTo(target, overwrite = true)
  116. println("[MCIT] Copied external plugin: $jarFile")
  117. }
  118. }
  119. }
  120. private fun AbstractTestPointAsPlugin.generatePluginJar() {
  121. val simpleName = this.javaClass.simpleName
  122. val point = this
  123. val jarFile = File("plugins").resolve("$simpleName.jar")
  124. // PluginMainPoint: net.mamoe.console.integrationtestAbstractTestPointAsPlugin$TestPointPluginImpl
  125. jarFile.mkparents()
  126. ZipOutputStream(
  127. FileOutputStream(jarFile).buffered()
  128. ).use { zipOutputStream ->
  129. // META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin
  130. zipOutputStream.putNextEntry(
  131. ZipEntry(
  132. "META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin"
  133. )
  134. )
  135. val delegateClassName = "net.mamoe.console.integrationtest.tpd.$simpleName"
  136. zipOutputStream.write(delegateClassName.toByteArray())
  137. // MainClass
  138. val internalClassName = delegateClassName.replace('.', '/')
  139. zipOutputStream.putNextEntry(ZipEntry("$internalClassName.class"))
  140. val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
  141. val superName = "net/mamoe/console/integrationtest/AbstractTestPointAsPlugin\$TestPointPluginImpl"
  142. classWriter.visit(
  143. Opcodes.V1_8,
  144. Opcodes.ACC_PUBLIC,
  145. internalClassName,
  146. null,
  147. superName,
  148. null
  149. )
  150. classWriter.visitMethod(
  151. Opcodes.ACC_PUBLIC,
  152. "<init>", "()V", null, null
  153. )!!.let { initMethod ->
  154. initMethod.visitVarInsn(Opcodes.ALOAD, 0)
  155. initMethod.visitLdcInsn(Type.getType(point.javaClass))
  156. initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "(Ljava/lang/Class;)V", false)
  157. initMethod.visitInsn(Opcodes.RETURN)
  158. initMethod.visitMaxs(0, 0)
  159. initMethod.visitEnd()
  160. }
  161. zipOutputStream.write(classWriter.toByteArray())
  162. }
  163. }