diff --git a/mirai-console/backend/integration-test/README.md b/mirai-console/backend/integration-test/README.md new file mode 100644 index 000000000..6fd65b1b6 --- /dev/null +++ b/mirai-console/backend/integration-test/README.md @@ -0,0 +1,35 @@ +# Console - Integration Test + +Mirai Console 一体化测试单元 (目前仅内部测试) + +--- + +## 使用 Integration Test Framework + +TODO + +### 添加一个新测试 + +#### 创建 Integration Test 测试点 + +创建一个新的子测试单元并继承 `AbstractTestPoint` + +- 在其 `beforeConsoleStartup()` 准备测试环境 (如写入配置文件, etc) +- 在其 `onConsoleStartSuccessfully()` 检查插件相关行为是否正确 + +然后在 `MiraiConsoleIntegrationTestLauncher.points` 添加新单元的完整类路径 + +---- + +## Mirai Console Internal Testing + +### 添加一个新测试 (CONSOLE 内部测试) + +在 `test/testpoints` 添加新测试点, +然后在 [`MiraiConsoleIntegrationTestBootstrap.kt`](test/MiraiConsoleIntegrationTestBootstrap.kt) +添加相关单元 + +### 创建配套子插件 + +在 `testers` 创建新的文件夹即可创建新的配套插件, 可用于测试插件依赖, etc + diff --git a/mirai-console/backend/integration-test/build.gradle.kts b/mirai-console/backend/integration-test/build.gradle.kts new file mode 100644 index 000000000..b7acd9b62 --- /dev/null +++ b/mirai-console/backend/integration-test/build.gradle.kts @@ -0,0 +1,94 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("UnusedImport") + +import java.util.Base64 + +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("java") +} + +version = Versions.console +description = "Mirai Console Backend Real-Time Testing Unit" + +kotlin { + explicitApiWarning() +} + +dependencies { + api(project(":mirai-core-api")) + api(project(":mirai-core-utils")) + api(project(":mirai-console-compiler-annotations")) + api(project(":mirai-console")) + api(project(":mirai-console-terminal")) + + api(`kotlin-stdlib-jdk8`) + api(`kotlinx-atomicfu-jvm`) + api(`kotlinx-coroutines-core-jvm`) + api(`kotlinx-serialization-core-jvm`) + api(`kotlinx-serialization-json-jvm`) + api(`kotlin-reflect`) + api(`kotlin-test-junit5`) + + + api(`yamlkt-jvm`) + api(`jetbrains-annotations`) + api(`caller-finder`) + api(`kotlinx-coroutines-jdk8`) + + + val asmVersion = Versions.asm + fun asm(module: String) = "org.ow2.asm:asm-$module:$asmVersion" + + api(asm("tree")) + api(asm("util")) + api(asm("commons")) + +} + +val subplugins = mutableListOf>() + +val mcit_test = tasks.named("test") +mcit_test.configure { + val test0 = this + doFirst { + // For IDEA Debugging + @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") + val extArgs = test0.jvmArgs!!.asSequence().map { extArg -> + Base64.getEncoder().encodeToString(extArg.toByteArray()) + }.joinToString(",") + test0.jvmArgs = mutableListOf() + test0.environment("IT_ARGS", extArgs) + + // For plugins coping + val jars = subplugins.asSequence() + .map { it.get() } + .flatMap { it.outputs.files.files.asSequence() } + .toList() + + test0.environment("IT_PLUGINS", jars.size) + jars.forEachIndexed { index, jar -> + test0.environment("IT_PLUGINS_$index", jar.absolutePath) + } + + } +} + +rootProject.allprojects { + if (project.path.removePrefix(":").startsWith("mirai-console.integration-test.tp.")) { + project.afterEvaluate { + val tk = tasks.named("jar") + subplugins.add(tk) + mcit_test.configure { dependsOn(tk) } + } + } +} diff --git a/mirai-console/backend/integration-test/src/AbstractTestPoint.kt b/mirai-console/backend/integration-test/src/AbstractTestPoint.kt new file mode 100644 index 000000000..b073c4ad1 --- /dev/null +++ b/mirai-console/backend/integration-test/src/AbstractTestPoint.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest + +/** + * IntegrationTest 测试单元 + * + * 每个被注册的单元都会在 console 启动的各个阶段调用相关的函数, 可以在相关函数执行测试代码 + * + * ## 注册单元 + * + * 每个单元都需要被注册, 即被添加进 [MiraiConsoleIntegrationTestLauncher.points] + * + * @see MiraiConsoleIntegrationTestLauncher + * @see AbstractTestPointAsPlugin + */ +public abstract class AbstractTestPoint { + /** + * 本函数会在 console 启动前调用, 可以在此处进行环境配置 + */ + protected open fun beforeConsoleStartup() {} + + /** + * 本函数会在 console 启动成功后立即调用, 可进行环境检查, 命令执行测试, 或更多 + */ + protected open fun onConsoleStartSuccessfully() {} + + // access + internal companion object { + internal fun AbstractTestPoint.internalOSS() { + onConsoleStartSuccessfully() + } + + internal fun AbstractTestPoint.internalBCS() { + beforeConsoleStartup() + } + } +} diff --git a/mirai-console/backend/integration-test/src/AbstractTestPointAsPlugin.kt b/mirai-console/backend/integration-test/src/AbstractTestPointAsPlugin.kt new file mode 100644 index 000000000..40152ba8f --- /dev/null +++ b/mirai-console/backend/integration-test/src/AbstractTestPointAsPlugin.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest + +import net.mamoe.mirai.console.extension.PluginComponentStorage +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription +import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin + +/** + * IntegrationTest 测试单元 (Plugin mode) + * + * 该单元除了拥有 [AbstractTestPoint] 具有的功能之外, 还可以直接模拟一个插件的行为. + * + * 在此单元里, 可以像写正常的 console 插件一样在此写测试时插件 + */ +public abstract class AbstractTestPointAsPlugin : AbstractTestPoint() { + protected abstract fun newPluginDescription(): JvmPluginDescription + + protected open fun KotlinPlugin.onInit() {} + protected open fun KotlinPlugin.onLoad0(storage: PluginComponentStorage) {} + protected open fun KotlinPlugin.onEnable0() {} + protected open fun KotlinPlugin.onDisable0() {} + + + + @Suppress("unused") + @PublishedApi + internal abstract class TestPointPluginImpl( + private val impl: AbstractTestPointAsPlugin + ) : KotlinPlugin(impl.newPluginDescription()) { + + init { + impl.apply { onInit() } + } + + @PublishedApi + internal constructor( + impl: Class + ) : this(impl.kotlin.objectInstance ?: impl.newInstance()) + + override fun onDisable() { + impl.apply { onDisable0() } + } + + override fun onEnable() { + impl.apply { onEnable0() } + } + + override fun PluginComponentStorage.onLoad() { + impl.apply { onLoad0(this@onLoad) } + } + } + +} + diff --git a/mirai-console/backend/integration-test/src/IntegrationTestBootstrap.kt b/mirai-console/backend/integration-test/src/IntegrationTestBootstrap.kt new file mode 100644 index 000000000..40a6c2147 --- /dev/null +++ b/mirai-console/backend/integration-test/src/IntegrationTestBootstrap.kt @@ -0,0 +1,148 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +@file:JvmName("IntegrationTestBootstrap") + +package net.mamoe.console.integrationtest + +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.runBlocking +import net.mamoe.console.integrationtest.AbstractTestPoint.Companion.internalBCS +import net.mamoe.console.integrationtest.AbstractTestPoint.Companion.internalOSS +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.terminal.ConsoleTerminalExperimentalApi +import net.mamoe.mirai.console.terminal.ConsoleTerminalSettings +import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader +import net.mamoe.mirai.utils.cast +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import java.io.File +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import kotlin.system.exitProcess + +/** + * 入口点为 /test/MiraiConsoleIntegrationTestBootstrap.kt 并非此函数(文件), + * 不要直接执行此函数 + */ +@OptIn(ConsoleTerminalExperimentalApi::class) +@PublishedApi +internal fun main() { + // PRE CHECK + kotlin.run { + if (!System.getenv("MIRAI_CONSOLE_INTEGRATION_TEST").orEmpty().toBoolean()) { + error("Don't launch IntegrationTestBootstrap directly. See /test/MiraiConsoleIntegrationTestBootstrap.kt") + } + } + // @context: env.testunit = true + // @context: env.inJUnitProcess = false + // @context: env.exitProcessSafety = true + // @context: process.type = sandbox + // @context: process.cwd = /mirai-console/backend/build/rttu + // @context: process.timeout = 5min + + ConsoleTerminalSettings.setupAnsi = false + ConsoleTerminalSettings.noConsole = true + + val testUnits: List = readStringListFromEnv("IT_POINTS").asSequence() + .onEach { println("[MCIT] Loading test point: $it") } + .map { Class.forName(it) } + .map { it.kotlin.objectInstance ?: it.newInstance() } + .map { it.cast() } + .toList() + + File("plugins").mkdirs() + prepareConsole() + + testUnits.forEach { (it as? AbstractTestPointAsPlugin)?.generatePluginJar() } + testUnits.forEach { it.internalBCS() } + + MiraiConsoleTerminalLoader.startAsDaemon() + + if (!MiraiConsole.isActive) { + error("Failed to start console") + } + + // I/main: mirai-console started successfully. + + testUnits.forEach { it.internalOSS() } + + runBlocking { + MiraiConsole.job.cancelAndJoin() + } + exitProcess(0) +} + +private fun File.mkparents(): File = apply { parentFile?.mkdirs() } +private fun prepareConsole() { + File("config/Console/Logger.yml").mkparents().writeText( + """ +defaultPriority: ALL +loggers: + Bot: ALL +""" + ) + + readStringListFromEnv("IT_PLUGINS").forEach { path -> + val jarFile = File(path) + val target = File("plugins/${jarFile.name}").mkparents() + jarFile.copyTo(target, overwrite = true) + println("[MCIT] Copied external plugin: $jarFile") + } +} + +private fun AbstractTestPointAsPlugin.generatePluginJar() { + val simpleName = this.javaClass.simpleName + val point = this + val jarFile = File("plugins").resolve("$simpleName.jar") + // PluginMainPoint: net.mamoe.console.integrationtestAbstractTestPointAsPlugin$TestPointPluginImpl + jarFile.mkparents() + ZipOutputStream( + FileOutputStream(jarFile).buffered() + ).use { zipOutputStream -> + + // META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin + zipOutputStream.putNextEntry( + ZipEntry( + "META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin" + ) + ) + val delegateClassName = "net.mamoe.console.integrationtest.tpd.$simpleName" + zipOutputStream.write(delegateClassName.toByteArray()) + + // MainClass + val internalClassName = delegateClassName.replace('.', '/') + zipOutputStream.putNextEntry(ZipEntry("$internalClassName.class")) + val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS) + val superName = "net/mamoe/console/integrationtest/AbstractTestPointAsPlugin\$TestPointPluginImpl" + classWriter.visit( + Opcodes.V1_8, + Opcodes.ACC_PUBLIC, + internalClassName, + null, + superName, + null + ) + classWriter.visitMethod( + Opcodes.ACC_PUBLIC, + "", "()V", null, null + )!!.let { initMethod -> + initMethod.visitVarInsn(Opcodes.ALOAD, 0) + initMethod.visitLdcInsn(Type.getType(point.javaClass)) + initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "", "(Ljava/lang/Class;)V", false) + initMethod.visitInsn(Opcodes.RETURN) + initMethod.visitMaxs(0, 0) + initMethod.visitEnd() + } + + zipOutputStream.write(classWriter.toByteArray()) + } +} diff --git a/mirai-console/backend/integration-test/src/MiraiConsoleIntegrationTestLauncher.kt b/mirai-console/backend/integration-test/src/MiraiConsoleIntegrationTestLauncher.kt new file mode 100644 index 000000000..637928569 --- /dev/null +++ b/mirai-console/backend/integration-test/src/MiraiConsoleIntegrationTestLauncher.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest + +import net.mamoe.mirai.utils.lateinitMutableProperty +import java.io.File +import java.io.OutputStream +import java.io.PrintStream +import java.lang.management.ManagementFactory +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.concurrent.thread + +// TODO: 不完整, 还无法完全公开, 目前仅允许 console 内部使用 +/** + * MiraiConsoleIntegrationTest 启动器 + */ +public class MiraiConsoleIntegrationTestLauncher { + /** java.exe 路径 */ + public var javaexec: String by lateinitMutableProperty { findJavaExec() } + + /** + * 测试环境运行目录, **每次启动前会直接删除该文件夹的内容**(IMPORTANT) + */ + public var workingDir: File = File("mirai-console-integration-test") + + /** 额外 JVM 参数 */ + public var vmoptions: MutableList = mutableListOf() + + /** 额外环境变量 */ + public var extraEnvironment: MutableMap = mutableMapOf() + + /** 类路径, 需要包含 MiraiConsoleIntegrationTest Framework */ + public var classpath: String by lateinitMutableProperty { ManagementFactory.getRuntimeMXBean().classPath } + + /** 标准输出重定向位置 */ + public var output: OutputStream = System.out + /** 标准错误重定向位置 */ + public var error: OutputStream = System.err + /** [MiraiConsoleIntegrationTestLauncher] 启动日志的输出 */ + public var log: PrintStream = System.out + + /** 测试单元完整类名, 需要可以在 [classpath] 中找到 */ + public var points: MutableCollection = mutableListOf() + /** 测试环境的额外插件, 为文件路径, 相对于 [workingDir] */ + public var plugins: MutableCollection = mutableListOf() + + public fun launch() { + workingDir.deleteRecursively() + workingDir.mkdirs() + val isDebugging = vmoptions.any { it.startsWith("-agentlib:") } + + val builder = ProcessBuilder( + javaexec, + *vmoptions.toTypedArray(), + "-cp", classpath, + "net.mamoe.console.integrationtest.IntegrationTestBootstrap", + ) + .directory(workingDir) + // .inheritIO() // No output in idea + val env = builder.environment() + env.putAll(extraEnvironment) + env["MIRAI_CONSOLE_INTEGRATION_TEST"] = "true" + saveStringListToEnv("IT_PLUGINS", plugins, env) + saveStringListToEnv("IT_POINTS", points, env) + + log.println("[MCIT] Launching IntegrationTest") + log.println("[MCIT] `- Arguments: ${builder.command().joinToString(" ")}") + log.println("[MCIT] `- Directory: ${builder.directory().absoluteFile}") + log.println("[MCIT] `- Debugging: $isDebugging") + if (isDebugging) { + log.println("[MCIT] Running in debug mode. Watchdog thread will not start") + } + + val process = builder.start() + + val timedOut = AtomicBoolean(false) + val watchdog = thread { + if (isDebugging) return@thread + try { + Thread.sleep(TimeUnit.MINUTES.toMillis(5)) + timedOut.set(true) + process.destroyForcibly() + } catch (ignored: InterruptedException) { + } + } + + thread { process.inputStream.copyTo(output) } + thread { process.errorStream.copyTo(error) } + + val rsp = process.waitFor() + if (timedOut.get()) { + error("Mirai console daemon timed out") + } + watchdog.interrupt() + if (rsp != 0) error("Rsp $rsp") + } +} + +private fun findJavaExec(): String { + findJavaExec0()?.let { return it.absolutePath } + System.err.println("[MCIT] WARNING: Unable to determine the current runtime executable path.") + System.err.println("[MCIT] WARNING: Using default executable to launch test unit") + return "java" +} + +private fun findJavaExec0(): File? { + val ext = if ("windows" in System.getProperty("os.name").lowercase()) { + ".exe" + } else "" + + val javaHome = File(System.getProperty("java.home")) + javaHome.resolve("bin/java$ext").takeIf { it.exists() }?.let { return it } + javaHome.resolve("java$ext").takeIf { it.exists() }?.let { return it } + + + javaHome.resolve("jre/bin/java$ext").takeIf { it.exists() }?.let { return it } + javaHome.resolve("jre/java$ext").takeIf { it.exists() }?.let { return it } + + return null +} diff --git a/mirai-console/backend/integration-test/src/utils.kt b/mirai-console/backend/integration-test/src/utils.kt new file mode 100644 index 000000000..a2118caf9 --- /dev/null +++ b/mirai-console/backend/integration-test/src/utils.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest + +internal fun readStringListFromEnv(key: String): MutableList { + val size = System.getenv(key)!!.toInt() + val rsp = mutableListOf() + for (i in 0 until size) { + rsp.add(System.getenv("${key}_$i")!!) + } + return rsp +} + +internal fun saveStringListToEnv(key: String, value: Collection, env: MutableMap) { + env[key] = value.size.toString() + value.forEachIndexed { index, v -> + env["${key}_$index"] = v + } +} diff --git a/mirai-console/backend/integration-test/test/MiraiConsoleIntegrationTestBootstrap.kt b/mirai-console/backend/integration-test/test/MiraiConsoleIntegrationTestBootstrap.kt new file mode 100644 index 000000000..228908b12 --- /dev/null +++ b/mirai-console/backend/integration-test/test/MiraiConsoleIntegrationTestBootstrap.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest + +import net.mamoe.console.integrationtest.testpoints.DoNothingPoint +import net.mamoe.console.integrationtest.testpoints.MCITBSelfAssertions +import org.junit.jupiter.api.Test +import java.io.File +import java.lang.management.ManagementFactory +import java.util.* +import kotlin.reflect.KClass + + +class MiraiConsoleIntegrationTestBootstrap { + @Test + fun bootstrap() { + /* + implementation note: + 不使用 @TempDir 是为了保存最后一次失败快照, 便于 debug + */ + val workingDir = File("build/IntegrationTest") // mirai-console/backend/integration-test/build/IntegrationTest + val launcher = MiraiConsoleIntegrationTestLauncher() + launcher.workingDir = workingDir + launcher.plugins = readStringListFromEnv("IT_PLUGINS") + launcher.points = listOf( + DoNothingPoint, + MCITBSelfAssertions, + ).asSequence().map { v -> + when (v) { + is Class<*> -> v + is KClass<*> -> v.java + else -> v.javaClass + } + }.map { it.name }.toMutableList() + launcher.vmoptions = mutableListOf( + *ManagementFactory.getRuntimeMXBean().inputArguments.filterNot { + it.startsWith("-Djava.security.manager=") + }.toTypedArray(), + *System.getenv("IT_ARGS")!!.splitToSequence(",").map { + Base64.getDecoder().decode(it).decodeToString() + }.filter { it.isNotEmpty() }.toList().toTypedArray() + ) + launcher.launch() + } +} diff --git a/mirai-console/backend/integration-test/test/testpoints/DoNothingPoint.kt b/mirai-console/backend/integration-test/test/testpoints/DoNothingPoint.kt new file mode 100644 index 000000000..bc15bb126 --- /dev/null +++ b/mirai-console/backend/integration-test/test/testpoints/DoNothingPoint.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest.testpoints + +import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription +import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import net.mamoe.mirai.utils.info + +/* +DoNothingPoint: Example + */ +internal object DoNothingPoint : AbstractTestPointAsPlugin() { + var enableCalled = false + override fun newPluginDescription(): JvmPluginDescription { + return JvmPluginDescription( + id = "net.mamoe.testpoint.do-nothing", + version = "1.1.0", + name = "DoNothing", + ) + } + + override fun KotlinPlugin.onEnable0() { + logger.info { "DoNothing.onEnable() called" } + enableCalled = true + } + + override fun KotlinPlugin.onDisable0() { + logger.info { "DoNothing.onDisable() called" } + } + + override fun onConsoleStartSuccessfully() { + assert(enableCalled) { + "DoNothing.onEnable() not called." + } + } +} \ No newline at end of file diff --git a/mirai-console/backend/integration-test/test/testpoints/MCITBSelfAssertions.kt b/mirai-console/backend/integration-test/test/testpoints/MCITBSelfAssertions.kt new file mode 100644 index 000000000..c7a7409e4 --- /dev/null +++ b/mirai-console/backend/integration-test/test/testpoints/MCITBSelfAssertions.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest.testpoints + +import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin +import net.mamoe.mirai.console.plugin.PluginManager +import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription +import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import kotlin.test.* + +/* +MCITBSelfAssertions: 用于检查 Integration Test 可以正常加载 AbstractTestPointAsPlugin 与 外部测试插件 + */ +internal object MCITBSelfAssertions : AbstractTestPointAsPlugin() { + override fun newPluginDescription(): JvmPluginDescription { + return JvmPluginDescription( + id = "net.mamoe.testpoint.mirai-console-self-assertions", + version = "1.0.0", + name = "MCITBSelfAssertions", + ) + } + + var called = false + + override fun KotlinPlugin.onEnable0() { + called = true + assertFails { error("") } + assertTrue { true } + assertFalse { false } + assertFailsWith { throw InternalError("") } + assertEquals("", "") + assertSame(this, this) + } + + override fun onConsoleStartSuccessfully() { + assertTrue(called, "Mirai Console IntegrationTestBootstrap Internal Error") + + assertTrue("MCITSelfTestPlugin not found") { + PluginManager.plugins.any { it.description.id == "net.mamoe.tester.mirai-console-self-test" } + } + } +} \ No newline at end of file diff --git a/mirai-console/backend/integration-test/testers/.gitignore b/mirai-console/backend/integration-test/testers/.gitignore new file mode 100644 index 000000000..8a25f4e8a --- /dev/null +++ b/mirai-console/backend/integration-test/testers/.gitignore @@ -0,0 +1 @@ +build.gradle.kts diff --git a/mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin b/mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin new file mode 100644 index 000000000..516e819d7 --- /dev/null +++ b/mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin @@ -0,0 +1 @@ +net.mamoe.console.integrationtest.ep.mcitselftest.MCITSelfTestPlugin diff --git a/mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/src/MCITSelfTestPlugin.kt b/mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/src/MCITSelfTestPlugin.kt new file mode 100644 index 000000000..69361c413 --- /dev/null +++ b/mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/src/MCITSelfTestPlugin.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.console.integrationtest.ep.mcitselftest + +import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription +import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin +import net.mamoe.mirai.utils.info +import kotlin.test.assertTrue + +/* +MCITSelfTestPlugin: 用于测试 Integration-test 可正常加载 +@see /test/testpoints/MCITBSelfAssertions + */ +public object MCITSelfTestPlugin : KotlinPlugin( + JvmPluginDescription( + id = "net.mamoe.tester.mirai-console-self-test", + version = "1.0.0", + name = "MCITSelfTestPlugin", + ) +) { + override fun onEnable() { + logger.info { "MCITSelfTestPlugin.onEnable() called" } + + assertTrue { true } + } +} diff --git a/mirai-console/backend/integration-test/testers/README.md b/mirai-console/backend/integration-test/testers/README.md new file mode 100644 index 000000000..369896ffb --- /dev/null +++ b/mirai-console/backend/integration-test/testers/README.md @@ -0,0 +1,13 @@ +# Integration Test - Sub Testers + +Integration Test 的测试插件, 放置在本文件夹内的全部插件均为 console 内部测试用插件 + +如果您不是正在修改 mirai-console, 则不需要阅读此文件及此模块 + +--- + +创建新测试插件只需要在本文件夹创建新的目录, 然后重载 (Reimport gradle projects) + +如果需要添加新的依赖, 请在 [`IntegrationTest/build.gradle.kts`](../build.gradle.kts) 添加相关依赖 (使用 `testApi`) 并标注哪个测试框架使用此依赖, 为何使用此依赖 + +如果需要自定义 `build.gradle.kts`, 请在 IDEA 右键 `build.gradle.kts` 并选择 `Git > Add File` diff --git a/mirai-console/backend/integration-test/testers/tester.template.gradle.kts b/mirai-console/backend/integration-test/testers/tester.template.gradle.kts new file mode 100644 index 000000000..3ba96bc8b --- /dev/null +++ b/mirai-console/backend/integration-test/testers/tester.template.gradle.kts @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("UnusedImport") + +plugins { + kotlin("jvm") + kotlin("plugin.serialization") + id("java") +} + +version = "0.0.0" + +kotlin { + explicitApiWarning() +} + +dependencies { + api(project(":mirai-console.integration-test")) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index d16a65b18..ccc1caf58 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -52,6 +52,34 @@ includeConsoleProject(":mirai-console-compiler-annotations", "tools/compiler-ann includeConsoleProject(":mirai-console", "backend/mirai-console") includeConsoleProject(":mirai-console.codegen", "backend/codegen") includeConsoleProject(":mirai-console-terminal", "frontend/mirai-console-terminal") + +// region mirai-console.integration-test +includeConsoleProject(":mirai-console.integration-test", "backend/integration-test") + +val consoleIntegrationTestSubPluginBuildGradleKtsTemplate by lazy { + rootProject.projectDir + .resolve("mirai-console/backend/integration-test/testers") + .resolve("tester.template.gradle.kts") + .readText() +} + +@Suppress("SimpleRedundantLet") +fun includeConsoleITPlugin(path: File) { + path.resolve("build.gradle.kts").takeIf { !it.isFile }?.let { initScript -> + initScript.writeText(consoleIntegrationTestSubPluginBuildGradleKtsTemplate) + } + + val projectPath = ":mirai-console.integration-test.tp.${path.name}" + include(projectPath) + project(projectPath).projectDir = path +} +rootProject.projectDir + .resolve("mirai-console/backend/integration-test/testers") + .listFiles()?.asSequence().orEmpty() + .filter { it.isDirectory } + .forEach { includeConsoleITPlugin(it) } +// endregion + includeConsoleProject(":mirai-console-compiler-common", "tools/compiler-common") includeConsoleProject(":mirai-console-intellij", "tools/intellij-plugin") includeConsoleProject(":mirai-console-gradle", "tools/gradle-plugin")