From fa8eb466e7dd768a77e435063f29250a0c4ed885 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 6 May 2020 14:40:53 +0800 Subject: [PATCH] Rework core/console updating, implement command args --- buildSrc/src/main/kotlin/versions.kt | 4 +- mirai-console-wrapper/build.gradle.kts | 26 ++- .../mirai/console/wrapper/ConsoleUpdater.kt | 141 +++++++++++----- .../mirai/console/wrapper/CoreUpdater.kt | 44 ++--- .../console/wrapper/JCenterDownloader.kt | 9 +- .../mirai/console/wrapper/MiraiDownloader.kt | 9 ++ .../mirai/console/wrapper/WrapperMain.kt | 152 +++++++++++------- .../console/wrapper/ConsoleUpdaterKtTest.kt | 28 ++++ 8 files changed, 272 insertions(+), 141 deletions(-) create mode 100644 mirai-console-wrapper/src/test/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdaterKtTest.kt diff --git a/buildSrc/src/main/kotlin/versions.kt b/buildSrc/src/main/kotlin/versions.kt index f2ecc7d46..e457291f9 100644 --- a/buildSrc/src/main/kotlin/versions.kt +++ b/buildSrc/src/main/kotlin/versions.kt @@ -11,10 +11,10 @@ import org.gradle.kotlin.dsl.DependencyHandlerScope object Versions { object Mirai { - const val core = "0.39.1" + const val core = "1.0-RC" const val console = "0.4.11" const val consoleGraphical = "0.0.7" - const val consoleWrapper = "0.2.0" + const val consoleWrapper = "1.0.0" } object Kotlin { diff --git a/mirai-console-wrapper/build.gradle.kts b/mirai-console-wrapper/build.gradle.kts index cb324ebf8..1ceaaacfd 100644 --- a/mirai-console-wrapper/build.gradle.kts +++ b/mirai-console-wrapper/build.gradle.kts @@ -28,13 +28,35 @@ dependencies { api(kotlin("reflect", Versions.Kotlin.stdlib)) api(kotlinx("coroutines-core", Versions.Kotlin.coroutines)) - api(kotlinx("coroutines-swing",Versions.Kotlin.coroutines)) + api(kotlinx("coroutines-swing", Versions.Kotlin.coroutines)) api(ktor("client-cio", Versions.Kotlin.ktor)) api(ktor("client-core", Versions.Kotlin.ktor)) api(ktor("network", Versions.Kotlin.ktor)) + + api("com.github.ajalt:clikt:2.6.0") + + testApi(kotlin("stdlib", Versions.Kotlin.stdlib)) + testApi(kotlin("test-junit5")) } version = Versions.Mirai.consoleWrapper -description = "Console with plugin support for mirai" \ No newline at end of file +description = "Console with plugin support for mirai" + + +val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} +tasks.withType(JavaCompile::class.java) { + options.encoding = "UTF8" +} diff --git a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdater.kt b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdater.kt index 70ea86de7..8b8ee8ee1 100644 --- a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdater.kt +++ b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdater.kt @@ -1,3 +1,11 @@ +/* + * Copyright 2020 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 + */ package net.mamoe.mirai.console.wrapper import io.ktor.client.request.get @@ -14,15 +22,15 @@ const val CONSOLE_GRAPHICAL = "Graphical" internal object ConsoleUpdater { @Suppress("SpellCheckingInspection") - private object Links : HashMap>() { + private object Links : HashMap>() { init { put( - CONSOLE_PURE, mapOf( + ConsoleType.Pure, mapOf( "version" to "/net/mamoe/mirai-console/" ) ) put( - CONSOLE_GRAPHICAL, mapOf( + ConsoleType.Graphical, mapOf( "version" to "/net/mamoe/mirai-console-graphical/" ) ) @@ -30,23 +38,26 @@ internal object ConsoleUpdater { } - var consoleType = CONSOLE_PURE + var consoleType = ConsoleType.Pure fun getFile(): File? { contentPath.listFiles()?.forEach { file -> if (file != null && file.extension == "jar") { if (file.name.contains("mirai-console")) { when (consoleType) { - CONSOLE_PURE -> { - if(!file.name.contains("graphical")) { + ConsoleType.Pure -> { + if (!file.name.contains("graphical")) { return file } } - CONSOLE_GRAPHICAL -> { - if(file.name.contains("graphical")) { + ConsoleType.Graphical -> { + if (file.name.contains("graphical")) { return file } } + else -> { + + } } } } @@ -54,52 +65,35 @@ internal object ConsoleUpdater { return null } - suspend fun versionCheck(type: String) { + suspend fun versionCheck(type: ConsoleType, strategy: VersionUpdateStrategy) { this.consoleType = type println("Fetching Newest Console Version of $type") - val newest = getNewestVersion() - val current = getCurrentVersion() - println("Local Console-$type Version: $current | Newest Console-$type Version: $newest") + val current = CoreUpdater.getCurrentVersion() + if (current != "0.0.0" && strategy == VersionUpdateStrategy.KEEP) { + println("Stay on current version.") + return + } + + val newest = getNewestVersion( + strategy, + Links[consoleType]!!["version"] ?: error("Unknown Console Type") + ) + println("Local Console-$type Version: $current | Newest $strategy Console-$type Version: $newest") if (current != newest) { - println("Updating Console-$type from V$current -> V$newest, this is a force update") + println("Updating Console-$type from V$current -> V$newest") this.getFile()?.delete() /** MiraiDownloader.addTask( - "https://raw.githubusercontent.com/mamoe/mirai-repo/master/shadow/${getProjectName()}/${getProjectName()}-$newest.jar",getContent("${getProjectName()}-$newest.jar") + "https://raw.githubusercontent.com/mamoe/mirai-repo/master/shadow/${getProjectName()}/${getProjectName()}-$newest.jar",getContent("${getProjectName()}-$newest.jar") ) - */ + */ MiraiDownloader.addTask( - "https://pan.jasonczc.cn/?/mirai/${getProjectName()}/${getProjectName()}-$newest.mp4", getContent("${getProjectName()}-$newest.jar") + "https://pan.jasonczc.cn/?/mirai/${getProjectName()}/${getProjectName()}-$newest.mp4", + getContent("${getProjectName()}-$newest.jar") ) } } - - private suspend fun getNewestVersion(): String { - try { - return """>([0-9])*\.([0-9])*\.([0-9])*/""".toRegex().findAll( - Http.get { - url { - protocol = URLProtocol.HTTPS - host = "jcenter.bintray.com" - path(Links[consoleType]!!["version"] ?: error("Unknown Console Type")) - } - } - ).asSequence() - .map { it.value.drop(1).dropLast(1) } - .maxBy { - it.split('.').foldRightIndexed(0) { index: Int, s: String, acc: Int -> - acc + 100.0.pow(2 - index).toInt() * (s.toIntOrNull() ?: 0) - } - }!! - } catch (e: Exception) { - println("Failed to fetch newest Console version, please seek for help") - e.printStackTrace() - println("Failed to fetch newest Console version, please seek for help") - exitProcess(1) - } - } - fun getCurrentVersion(): String { val file = getFile() if (file != null) { @@ -112,11 +106,70 @@ internal object ConsoleUpdater { } private fun getProjectName(): String { - return if (consoleType == CONSOLE_PURE) { + return if (consoleType == ConsoleType.Pure) { "mirai-console" } else { - "mirai-console-${consoleType.toLowerCase()}" + "mirai-console-${consoleType.toString().toLowerCase()}" } } +} + + +suspend fun getNewestVersion(strategy: VersionUpdateStrategy, path: String): String { + try { + return Regex("""rel="nofollow">[0-9][0-9]*(\.[0-9]*)*.*/<""", RegexOption.IGNORE_CASE).findAll( + Http.get { + url { + protocol = URLProtocol.HTTPS + host = "jcenter.bintray.com" + path(path) + } + }) + .asSequence() + .map { it.value.substringAfter('>').substringBefore('/') } + .toList() + .let { list -> + if (list.filter { it.startsWith("1.") }.takeIf { it.isNotEmpty() }?.all { it.contains("-") } == true) { + // 只有 1.xxx-EA 版本, 那么也将他看作是正式版 + list.filter { it.startsWith("1.") } + } else when (strategy) { + VersionUpdateStrategy.KEEP, + VersionUpdateStrategy.STABLE + -> { + list.filterNot { it.contains("-") } // e.g. "-EA" + } + VersionUpdateStrategy.EA -> { + list + } + } + } + .latestVersion() + } catch (e: Exception) { + println("Failed to fetch newest Console version, please seek for help") + e.printStackTrace() + println("Failed to fetch newest Console version, please seek for help") + exitProcess(1) + } +} + +internal fun List.latestVersion(): String { + return sortByVersion().first() +} + +internal fun List.sortByVersion(): List { + return sortedByDescending { version -> + version.split('.').let { + if (it.size == 2) it + "0" + else it + }.reversed().foldIndexed(0.0) { index: Int, acc: Double, s: String -> + acc + 1000.0.pow(index) * s.convertPatchVersionToWeight() + } + } +} + +internal fun String.convertPatchVersionToWeight(): Double { + return this.split('-').reversed().foldIndexed(0.0) { index: Int, acc: Double, s: String -> + acc + 10.0.pow(index) * (s.toIntOrNull()?.toDouble() ?: -0.5) + } } \ No newline at end of file diff --git a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/CoreUpdater.kt b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/CoreUpdater.kt index a6ec7b4bd..e9a1ac92c 100644 --- a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/CoreUpdater.kt +++ b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/CoreUpdater.kt @@ -11,11 +11,7 @@ package net.mamoe.mirai.console.wrapper -import io.ktor.client.request.get -import io.ktor.http.URLProtocol import java.io.File -import kotlin.math.pow -import kotlin.system.exitProcess internal object CoreUpdater { @@ -29,13 +25,18 @@ internal object CoreUpdater { } - suspend fun versionCheck() { + suspend fun versionCheck(strategy: VersionUpdateStrategy) { println("Fetching Newest Core Version .. ") - val newest = getNewestVersion() val current = getCurrentVersion() - println("Local Core Version: $current | Newest Core Version: $newest") + if (current != "0.0.0" && strategy == VersionUpdateStrategy.KEEP) { + println("Stay on current version.") + return + } + + val newest = getNewestVersion(strategy, "net/mamoe/mirai-core-qqandroid/") + println("Local Core Version: $current | Newest $strategy Core Version: $newest") if (current != newest) { - println("Updating shadowed-core from V$current -> V$newest, this is a force update") + println("Updating shadowed-core from V$current -> V$newest") this.getProtocolLib()?.delete() MiraiDownloader .addTask( @@ -47,33 +48,6 @@ internal object CoreUpdater { } } - /** - * 判断最新版本 - * */ - private suspend fun getNewestVersion(): String { - try { - return """>([0-9])*\.([0-9])*\.([0-9])*/""".toRegex().findAll( - Http.get { - url { - protocol = URLProtocol.HTTPS - host = "jcenter.bintray.com" - path("net/mamoe/mirai-core-qqandroid/") - } - }).asSequence() - .map { it.value.drop(1).dropLast(1) } - .maxBy { - it.split('.').foldRightIndexed(0) { index: Int, s: String, acc: Int -> - acc + 100.0.pow(2 - index).toInt() * (s.toIntOrNull() ?: 0) - } - }!! - } catch (e: Exception) { - println("Failed to fetch newest Core version, please seek for help") - e.printStackTrace() - println("Failed to fetch newest Core version, please seek for help") - exitProcess(1) - } - } - /** * 判断当前版本 * 默认返回 "0.0.0" diff --git a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/JCenterDownloader.kt b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/JCenterDownloader.kt index 639527e5d..8e5a78214 100644 --- a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/JCenterDownloader.kt +++ b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/JCenterDownloader.kt @@ -1,3 +1,11 @@ +/* + * Copyright 2020 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("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.console.wrapper @@ -137,7 +145,6 @@ internal suspend fun ByteReadChannel.saveToContent(filepath: String) { } - internal fun getContent(filepath: String):File{ return File(contentPath, filepath) } diff --git a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/MiraiDownloader.kt b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/MiraiDownloader.kt index 51a622e1b..a3f8f6ea7 100644 --- a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/MiraiDownloader.kt +++ b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/MiraiDownloader.kt @@ -1,3 +1,11 @@ +/* + * Copyright 2020 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 + */ package net.mamoe.mirai.console.wrapper import kotlinx.coroutines.* @@ -146,6 +154,7 @@ class MiraiDownloaderProgressBarInUI(): MiraiDownloadProgressBar{ override fun ad(){ WrapperMain.uiLog("[Mirai国内镜像] 感谢崔Cloud慷慨提供更新服务器") } + private val barLen = 20 override fun update(rate: Float, message: String) { diff --git a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/WrapperMain.kt b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/WrapperMain.kt index 270a0b793..1ae00ffdb 100644 --- a/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/WrapperMain.kt +++ b/mirai-console-wrapper/src/main/kotlin/net/mamoe/mirai/console/wrapper/WrapperMain.kt @@ -10,11 +10,15 @@ package net.mamoe.mirai.console.wrapper +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.enum import kotlinx.coroutines.* import java.awt.TextArea import java.io.File import java.net.URLClassLoader -import java.util.* import java.util.jar.JarFile import javax.swing.JFrame import javax.swing.JPanel @@ -34,19 +38,43 @@ val extendedLibraries by lazy { file.also { if (!it.exists()) it.mkdirs() } } -object WrapperMain { - internal var uiBarOutput = StringBuilder() - private val uilog = StringBuilder() - internal fun uiLog(any: Any?) { - if (any != null) { - uilog.append(any) - } - } +object WrapperCli : CliktCommand(name = "mirai-warpper") { + private val native by option( + help = """ + Start in GRAPHICAL mode without command line outputs + ------------------------------------------ + 以图形界面模式启动 + """.trimIndent(), + envvar = "mirai.wrapper.native" + ).flag("-n", default = false) - @JvmStatic - fun main(args: Array) { - gc() - if (args.contains("native") || args.contains("-native")) { + private val update: VersionUpdateStrategy by option( + help = """ + Strategy to automatic updates. + "KEEP" to stay on the current version; + "STABLE" to update to the latest stable versions; + "EA" to update to use the newest features but might not be stable. + ------------------------------------------ + 版本升级策略. "KEEP" 为停留在当前版本; "STABLE" 为更新到最新稳定版; "EA" 为更新到最新预览版. + """.trimIndent(), + envvar = "mirai.wrapper.update" + ).enum().default(VersionUpdateStrategy.STABLE) + + private val console: ConsoleType by option( + help = """ + The type of the console to be started. + "GRAPHICAL" to use JavaFX graphical UI; + "TERMINAL" to use terminal UI for Unix; + "PURE" to use pure CLI. + ------------------------------------------ + UI 类型. "GRAPHICAL" 为 JavaFX 图形界面; "TERMINAL" 为 Unix 终端界面; "PURE" 为纯命令行. + """.trimIndent(), + envvar = "mirai.wrapper.console" + ).enum().default(ConsoleType.Pure) + + override fun run() { + + if (native) { val f = JFrame("Mirai-Console Version Check") f.setSize(500, 200) f.setLocationRelativeTo(null) @@ -60,29 +88,29 @@ object WrapperMain { f.isVisible = true - uiLog("正在进行版本检查\n") + WrapperMain.uiLog("正在进行版本检查\n") val dic = System.getProperty("user.dir") - uiLog("工作目录: ${dic}\n") - uiLog("扩展库目录: ${extendedLibraries}\n") - uiLog("若无法启动, 请尝试清除工作目录下/content/文件夹\n") + WrapperMain.uiLog("工作目录: ${dic}\n") + WrapperMain.uiLog("扩展库目录: ${extendedLibraries}\n") + WrapperMain.uiLog("若无法启动, 请尝试清除工作目录下/content/文件夹\n") var uiOpen = true GlobalScope.launch { while (isActive && uiOpen) { delay(16)//60 fps withContext(Dispatchers.Main) { - textArea.text = uilog.toString() + "\n" + uiBarOutput.toString() + textArea.text = WrapperMain.uiLog.toString() + "\n" + WrapperMain.uiBarOutput.toString() } } } runBlocking { launch { - CoreUpdater.versionCheck() + CoreUpdater.versionCheck(update) } launch { - ConsoleUpdater.versionCheck(CONSOLE_GRAPHICAL) + ConsoleUpdater.versionCheck(ConsoleType.Graphical, update) } } - uiLog("版本检查完成, 启动中\n") + WrapperMain.uiLog("版本检查完成, 启动中\n") runBlocking { MiraiDownloader.downloadIfNeed(true) @@ -93,41 +121,61 @@ object WrapperMain { f.isVisible = false } - start(CONSOLE_GRAPHICAL) + WrapperMain.start(ConsoleType.Graphical) } else { - preStartInNonNative() + WrapperMain.preStartInNonNative(console, update) + } + } +} + +enum class ConsoleType { + Graphical, + Terminal, + Pure +} + +enum class VersionUpdateStrategy { + KEEP, + STABLE, + EA +} + +object WrapperMain { + internal var uiBarOutput = StringBuilder() + internal val uiLog = StringBuilder() + + internal fun uiLog(any: Any?) { + if (any != null) { + uiLog.append(any) } } + @JvmStatic + fun main(args: Array) { + gc() + WrapperCli.main(args) + } - private fun preStartInNonNative() { + + internal fun preStartInNonNative(defaultType: ConsoleType, strategy: VersionUpdateStrategy) { println("You are running Mirai-Console-Wrapper under " + System.getProperty("user.dir")) println("All additional libraries are located at $extendedLibraries") - var type = WrapperProperties.determineConsoleType(WrapperProperties.content) + + var type = ConsoleType.values().firstOrNull { it.name.equals(WrapperProperties.content, ignoreCase = true) } if (type != null) { println("Starting Mirai Console $type, reset by clear /content/") } else { - println("Please select Console Type") - println("请选择 Console 版本") - println("=> Pure : pure console") - println("=> Graphical : graphical UI except unix") - println("=> Terminal : [Not Supported Yet] console in unix") - val scanner = Scanner(System.`in`) - while (type == null) { - var input = scanner.next() - input = input.toUpperCase()[0] + input.toLowerCase().substring(1) - println("Selecting $input") - type = WrapperProperties.determineConsoleType(input) - } - WrapperProperties.content = type + WrapperProperties.content = defaultType.toString() + type = defaultType } + println("Starting version check...") runBlocking { launch { - CoreUpdater.versionCheck() + CoreUpdater.versionCheck(strategy) } launch { - ConsoleUpdater.versionCheck(type) + ConsoleUpdater.versionCheck(type, strategy) } } @@ -143,7 +191,7 @@ object WrapperMain { start(type) } - private fun start(type: String) { + internal fun start(type: ConsoleType) { val loader = MiraiClassLoader( CoreUpdater.getProtocolLib()!!, ConsoleUpdater.getFile()!!, @@ -152,12 +200,12 @@ object WrapperMain { loader.loadClass("net.mamoe.mirai.BotFactoryJvm") loader.loadClass( - when (type) { - CONSOLE_PURE -> "net.mamoe.mirai.console.pure.MiraiConsolePureLoader" - CONSOLE_GRAPHICAL -> "net.mamoe.mirai.console.graphical.MiraiConsoleGraphicalLoader" - else -> return - } - ).getMethod("load", String::class.java, String::class.java) + when (type) { + ConsoleType.Pure -> "net.mamoe.mirai.console.pure.MiraiConsolePureLoader" + ConsoleType.Graphical -> "net.mamoe.mirai.console.graphical.MiraiConsoleGraphicalLoader" + else -> return + } + ).getMethod("load", String::class.java, String::class.java) .invoke(null, CoreUpdater.getCurrentVersion(), ConsoleUpdater.getCurrentVersion()) } @@ -211,16 +259,6 @@ private object WrapperProperties { var content get() = contentFile.readText() set(value) = contentFile.writeText(value) - - - fun determineConsoleType( - type: String - ): String? { - if (type == CONSOLE_PURE || type == CONSOLE_GRAPHICAL || type == CONSOLE_TERMINAL) { - return type - } - return null - } } private fun gc() { diff --git a/mirai-console-wrapper/src/test/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdaterKtTest.kt b/mirai-console-wrapper/src/test/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdaterKtTest.kt new file mode 100644 index 000000000..ad3c64fb6 --- /dev/null +++ b/mirai-console-wrapper/src/test/kotlin/net/mamoe/mirai/console/wrapper/ConsoleUpdaterKtTest.kt @@ -0,0 +1,28 @@ +package net.mamoe.mirai.console.wrapper + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class ConsoleUpdaterKtTest { + @Test + fun testVersionCompare() { + assertEquals( + listOf( + "1.0.0", + "1.0-EA-2", + "1.0-EA", + "0.40.0" + ), + listOf( + "1.0.0", + "0.40.0", + "1.0-EA", + "1.0-EA-2" + ).sortByVersion() + ) + } +} + +fun main() { + ConsoleUpdaterKtTest().testVersionCompare() +} \ No newline at end of file