From 67896da1d981afc636558a351bb9cbc512972bb7 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Tue, 13 Aug 2024 18:19:24 +0200 Subject: [PATCH] Fabric import test --- src/main/kotlin/creator/maven-repo-utils.kt | 46 +++++ .../kotlin/platform/fabric/FabricModule.kt | 6 + src/test/kotlin/framework/GradleImportTest.kt | 125 ++++++++++++ .../platform/fabric/FabricImportTest.kt | 193 ++++++++++++++++++ .../platform/neoforge/NeoForgeImportTest.kt | 102 +++------ 5 files changed, 401 insertions(+), 71 deletions(-) create mode 100644 src/test/kotlin/framework/GradleImportTest.kt create mode 100644 src/test/kotlin/platform/fabric/FabricImportTest.kt diff --git a/src/main/kotlin/creator/maven-repo-utils.kt b/src/main/kotlin/creator/maven-repo-utils.kt index 901a1a0af..eb6a9baab 100644 --- a/src/main/kotlin/creator/maven-repo-utils.kt +++ b/src/main/kotlin/creator/maven-repo-utils.kt @@ -70,6 +70,52 @@ suspend fun collectMavenVersions(url: String, filter: Predicate = Predic return result } +data class MavenLatestAndReleaseVersion(val latest: String?, val release: String?) + +@Throws(IOException::class) +suspend fun collectMavenLatestAndReleaseVersion(url: String): MavenLatestAndReleaseVersion { + val manager = FuelManager() + manager.proxy = selectProxy(url) + + val response = manager.get(url) + .header("User-Agent", PluginUtil.useragent) + .allowRedirects(true) + .suspendable() + .await() + + var latest: String? = null + var release: String? = null + response.body().toStream().use { stream -> + val inputFactory = XMLInputFactory.newInstance() + + @Suppress("UNCHECKED_CAST") + val reader = inputFactory.createXMLEventReader(stream) as Iterator + for (event in reader) { + if (!event.isStartElement) { + continue + } + val start = event.asStartElement() + val name = start.name.localPart + if (name != "latest" && name != "release") { + continue + } + + val versionEvent = reader.next() + if (!versionEvent.isCharacters) { + continue + } + + val version = versionEvent.asCharacters().data + when (name) { + "latest" -> latest = version + "release" -> release = version + } + } + } + + return MavenLatestAndReleaseVersion(latest, release) +} + @Throws(IOException::class) suspend fun scrapeArtifactoryDirectoryListing(url: String): List { val manager = FuelManager() diff --git a/src/main/kotlin/platform/fabric/FabricModule.kt b/src/main/kotlin/platform/fabric/FabricModule.kt index df1c4ac12..035685654 100644 --- a/src/main/kotlin/platform/fabric/FabricModule.kt +++ b/src/main/kotlin/platform/fabric/FabricModule.kt @@ -52,6 +52,12 @@ class FabricModule internal constructor(facet: MinecraftFacet) : AbstractModule( var fabricJson by nullable { facet.findFile(FabricConstants.FABRIC_MOD_JSON, SourceType.RESOURCE) } private set + val loomData: FabricLoomData? + get() { + val gradleData = GradleUtil.findGradleModuleData(facet.module) ?: return null + return gradleData.children.find { it.key == FabricLoomData.KEY }?.data as? FabricLoomData + } + private var namedToMojangManagerField: MappingsManager? = null override val namedToMojangManager: MappingsManager? get() = namedToMojangManagerField diff --git a/src/test/kotlin/framework/GradleImportTest.kt b/src/test/kotlin/framework/GradleImportTest.kt new file mode 100644 index 000000000..f166f3033 --- /dev/null +++ b/src/test/kotlin/framework/GradleImportTest.kt @@ -0,0 +1,125 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.framework + +import com.demonwav.mcdev.facet.MinecraftFacet +import com.demonwav.mcdev.platform.AbstractModule +import com.demonwav.mcdev.platform.AbstractModuleType +import com.demonwav.mcdev.platform.PlatformType +import com.demonwav.mcdev.util.runWriteTask +import com.intellij.openapi.externalSystem.model.ProjectSystemId +import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.modules +import com.intellij.openapi.projectRoots.JavaSdk +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.ex.JavaSdkUtil +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.platform.externalSystem.testFramework.ExternalSystemImportingTestCase +import java.nio.file.FileSystemException +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings +import org.jetbrains.plugins.gradle.util.GradleConstants +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestInfo + +/** + * Based on https://github.com/JetBrains/intellij-community/blob/a1c5c737ba52cd5810c95d28e6e685d11958844b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/importing/GradleImportingTestCase.java + */ +abstract class BaseGradleImportTest : ExternalSystemImportingTestCase() { + + companion object { + const val GRADLE_WRAPPER_PROPERTIES = "gradle/wrapper/gradle-wrapper.properties" + const val BUILD_GRADLE = "build.gradle" + const val BUILD_GRADLE_KT = "build.gradle.kts" + const val GRADLE_PROPERTIES = "build.gradle" + const val SETTINGS_GRADLE = "settings.gradle" + } + + private val projectSettings = GradleProjectSettings().withQualifiedModuleNames() + + override fun getCurrentExternalProjectSettings(): ExternalProjectSettings? = projectSettings + + override fun getExternalSystemId(): ProjectSystemId? = GradleConstants.SYSTEM_ID + + override fun getTestsTempDir(): String? = "tmp" + + override fun getExternalSystemConfigFileName(): String? = "build.gradle" + + @BeforeEach + @Suppress("JUnitMalformedDeclaration") // Hey JetBrains, it's working fine... + fun setUp(testInfo: TestInfo) { + name = testInfo.displayName + super.setUp() + + runWriteTask { + val jdk21Home = System.getenv("JDK_21_0") ?: System.getenv("JDK_21") ?: System.getenv("JDK21") + assertNotNull("Could not find JDK 21 home", jdk21Home) + + val jdk = JavaSdk.getInstance().createJdk("JDK 21", jdk21Home, false) + ProjectJdkTable.getInstance().addJdk(jdk) + JavaSdkUtil.applyJdkToProject(myProject, jdk) + } + } + + @AfterEach + override fun tearDown() { + runWriteTask { + val sdk = ProjectRootManager.getInstance(myProject).projectSdk + ProjectRootManager.getInstance(myProject).projectSdk = null + if (sdk != null) { + ProjectJdkTable.getInstance().removeJdk(sdk) + } + } + + try { + super.tearDown() + } catch (e: FileSystemException) { + // This is a frequent issue on Windows, but is harmless, except the fact it leaves the culprit file existing + println("Ignoring FSException: ${e.message}") + } + } + + protected fun findModule(name: String): Module { + val module = myProject.modules.find { it.name.contains(name) } + assertNotNull("Could not find module containing '$name'", module) + return module!! + } + + protected fun findMainModule() = findModule("main") + + protected fun Module.assertDetectedPlatformTypes(vararg platformTypes: PlatformType) { + val facet = MinecraftFacet.getInstance(this) + assertNotNull("MinecraftFacet is null in module ${this.name}", facet) + + assertSameElements(facet!!.configuration.state.autoDetectTypes, platformTypes.toSet()) + } + + protected fun Module.assertMinecraftModule(moduleType: AbstractModuleType): T { + val facet = MinecraftFacet.getInstance(this) + assertNotNull("MinecraftFacet is null in module ${this.name}", facet) + + val module = facet!!.getModuleOfType(moduleType) + assertNotNull("Could not find Minecraft module of type ${moduleType.javaClass.name}", module) + + return module!! + } +} diff --git a/src/test/kotlin/platform/fabric/FabricImportTest.kt b/src/test/kotlin/platform/fabric/FabricImportTest.kt new file mode 100644 index 000000000..97f534c21 --- /dev/null +++ b/src/test/kotlin/platform/fabric/FabricImportTest.kt @@ -0,0 +1,193 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.fabric + +import com.demonwav.mcdev.creator.collectMavenLatestAndReleaseVersion +import com.demonwav.mcdev.framework.BaseGradleImportTest +import com.demonwav.mcdev.platform.PlatformType +import com.demonwav.mcdev.platform.mcp.McpModuleType +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.DisplayName +import org.junitpioneer.jupiter.cartesian.ArgumentSets +import org.junitpioneer.jupiter.cartesian.CartesianTest + +@DisplayName("Fabric Import Tests") +class FabricImportTest : BaseGradleImportTest() { + + private fun prepareTest( + loomVersion: String, + minecraftVersion: String, + yarnMappings: String, + loaderVersion: String, + splitSources: Boolean, + ) { + createProjectSubFile( + GRADLE_WRAPPER_PROPERTIES, + "distributionUrl=https://services.gradle.org/distributions/gradle-8.8-bin.zip" + ) + + createProjectSubFile( + BUILD_GRADLE, + """ + plugins { + id 'fabric-loom' version '$loomVersion' + } + + loom { + ${if (splitSources) "splitEnvironmentSourceSets()" else ""} + + mods { + "test-mod" { + sourceSet sourceSets.main + ${if (splitSources) "sourceSet sourceSets.client" else ""} + } + } + } + + dependencies { + minecraft "com.mojang:minecraft:$minecraftVersion" + mappings "net.fabricmc:yarn:$yarnMappings:v2" + modImplementation "net.fabricmc:fabric-loader:$loaderVersion" + } + """.trimIndent() + ) + + createProjectSubFile( + SETTINGS_GRADLE, + """ + pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } + } + """ + ) + + importProject(true) + importProject(true) // Import twice otherwise McpModule settings won't be populated properly + } + + @CartesianTest + @CartesianTest.MethodFactory("argumentsFactory") + fun fabricImportedData( + loomVersion: String, + fabricVersions: TestVersions, + splitSources: Boolean, + ) { + prepareTest(loomVersion, fabricVersions.mc, fabricVersions.yarn, fabricVersions.loader, splitSources) + + val module = findMainModule() + module.assertDetectedPlatformTypes(PlatformType.FABRIC, PlatformType.MCP, PlatformType.MIXIN) + + val mcpModule = module.assertMinecraftModule(McpModuleType) + val mcpSettings = mcpModule.getSettings() + assertEquals(mcpSettings.minecraftVersion, fabricVersions.mc) + + val fabricModule = module.assertMinecraftModule(FabricModuleType) + val loomData = fabricModule.loomData + assertNotNull(loomData) + + assertNotNull(loomData!!.tinyMappings) + assertTrue(loomData.tinyMappings!!.exists()) + assertTrue(loomData.tinyMappings!!.isFile()) + + val expectedDecompilers = setOf("cfr", "fernFlower", "vineflower") + + if (splitSources) { + assertTrue(loomData.splitMinecraftJar) + + // SourceSets + val modSourceSets = loomData.modSourceSets + assertNotNull(modSourceSets) + assertEquals(1, modSourceSets!!.size) + + val testModSourceSets = modSourceSets["test-mod"] + assertNotNull(testModSourceSets) + assertContainsElements(testModSourceSets!!, "main", "client") + + // Decompilers + val decompilers = loomData.decompileTasks + assertNotNull(decompilers) + assertEquals(2, decompilers.size) + + val commonDecompilers = decompilers["common"] + assertNotNull(commonDecompilers) + assertTrue(commonDecompilers!!.all { it.name in expectedDecompilers }) + + val clientDecompilers = decompilers["client"] + assertNotNull(clientDecompilers) + assertTrue(clientDecompilers!!.all { it.name in expectedDecompilers }) + } else { + assertFalse(loomData.splitMinecraftJar) + + // SourceSets + val modSourceSets = loomData.modSourceSets + assertNotNull(modSourceSets) + assertEquals(1, modSourceSets!!.size) + + val testModSourceSets = modSourceSets["test-mod"] + assertNotNull(testModSourceSets) + assertContainsElements(testModSourceSets!!, "main") + + // Decompilers + val decompilers = loomData.decompileTasks + assertNotNull(decompilers) + assertEquals(1, decompilers.size) + + val singleDecompilers = decompilers["single"] + assertNotNull(singleDecompilers) + assertTrue(singleDecompilers!!.all { it.name in expectedDecompilers }) + } + } + + data class TestVersions(val mc: String, val yarn: String, val loader: String) + + companion object { + @JvmStatic + @Suppress("unused") // Used in @CartesianTest.MethodFactory + fun argumentsFactory(): ArgumentSets { + val loomVersions = runBlocking { + collectMavenLatestAndReleaseVersion( + "https://maven.fabricmc.net/fabric-loom/fabric-loom.gradle.plugin/maven-metadata.xml" + ) + } + + return ArgumentSets.argumentsForFirstParameter( + loomVersions.latest, + "1.7-SNAPSHOT", + "1.6-SNAPSHOT", + "1.5-SNAPSHOT", + "1.4-SNAPSHOT", + "1.3-SNAPSHOT", + ).argumentsForNextParameter( + TestVersions("1.21.1", "1.21.1+build.3", "0.16.0"), + TestVersions("1.21", "1.21+build.9", "0.16.0"), + TestVersions("1.20.6", "1.20.6+build.3", "0.16.0"), + TestVersions("1.19.4", "1.19.4+build.2", "0.16.0"), + TestVersions("1.18.2", "1.18.2+build.4", "0.16.0"), + ).argumentsForNextParameter(true, false) + } + } +} diff --git a/src/test/kotlin/platform/neoforge/NeoForgeImportTest.kt b/src/test/kotlin/platform/neoforge/NeoForgeImportTest.kt index 1762d6dbb..5a1f561cd 100644 --- a/src/test/kotlin/platform/neoforge/NeoForgeImportTest.kt +++ b/src/test/kotlin/platform/neoforge/NeoForgeImportTest.kt @@ -1,80 +1,45 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package com.demonwav.mcdev.platform.neoforge -import com.demonwav.mcdev.facet.MinecraftFacet +import com.demonwav.mcdev.framework.BaseGradleImportTest +import com.demonwav.mcdev.platform.PlatformType import com.demonwav.mcdev.platform.mcp.McpModuleType -import com.demonwav.mcdev.platform.mixin.MixinModuleType import com.demonwav.mcdev.platform.neoforge.version.platform.neoforge.version.NeoModDevVersion -import com.demonwav.mcdev.util.runWriteTask -import com.intellij.JavaTestUtil -import com.intellij.openapi.externalSystem.model.ProjectSystemId -import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings -import com.intellij.openapi.project.modules -import com.intellij.openapi.projectRoots.ProjectJdkTable -import com.intellij.openapi.projectRoots.ex.JavaSdkUtil -import com.intellij.openapi.roots.ProjectRootManager -import com.intellij.platform.externalSystem.testFramework.ExternalSystemImportingTestCase -import java.nio.file.FileSystemException import kotlinx.coroutines.runBlocking -import org.jetbrains.plugins.gradle.settings.GradleProjectSettings -import org.jetbrains.plugins.gradle.util.GradleConstants -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.TestInfo import org.junitpioneer.jupiter.cartesian.ArgumentSets import org.junitpioneer.jupiter.cartesian.CartesianTest @DisplayName("NeoForge Import Tests") -class NeoForgeImportTest : ExternalSystemImportingTestCase() { - - private val projectSettings = GradleProjectSettings().withQualifiedModuleNames() - - override fun getCurrentExternalProjectSettings(): ExternalProjectSettings? = projectSettings - - override fun getExternalSystemId(): ProjectSystemId? = GradleConstants.SYSTEM_ID - - override fun getTestsTempDir(): String? = "tmp" - - override fun getExternalSystemConfigFileName(): String? = "build.gradle" - - @BeforeEach - @Suppress("JUnitMalformedDeclaration") // Hey JetBrains, it's working fine... - fun setUp(testInfo: TestInfo) { - name = testInfo.displayName - super.setUp() - - runWriteTask { - val jdk = JavaTestUtil.setupInternalJdkAsTestJDK(testRootDisposable, "JDK") - JavaSdkUtil.applyJdkToProject(myProject, jdk) - } - } - - @AfterEach - override fun tearDown() { - runWriteTask { - val sdk = ProjectRootManager.getInstance(myProject).projectSdk - ProjectRootManager.getInstance(myProject).projectSdk = null - if (sdk != null) { - ProjectJdkTable.getInstance().removeJdk(sdk) - } - } - - try { - super.tearDown() - } catch (e: FileSystemException) { - // This is a frequent issue on Windows, but is harmless, except the fact it leaves the culprit file existing - println("Ignoring FSException: ${e.message}") - } - } +class NeoForgeImportTest : BaseGradleImportTest() { private fun prepareTest(neoModDevVersion: String, neoForgeVersion: String) { createProjectSubFile( - "gradle/wrapper/gradle-wrapper.properties", + GRADLE_WRAPPER_PROPERTIES, "distributionUrl=https://services.gradle.org/distributions/gradle-8.8-bin.zip" ) createProjectSubFile( - "build.gradle", + BUILD_GRADLE, """ plugins { id 'net.neoforged.moddev' version '$neoModDevVersion' @@ -87,7 +52,7 @@ class NeoForgeImportTest : ExternalSystemImportingTestCase() { ) createProjectSubFile( - "settings.gradle", + SETTINGS_GRADLE, """ pluginManagement { repositories { @@ -112,16 +77,11 @@ class NeoForgeImportTest : ExternalSystemImportingTestCase() { val (neoForgeVersion, expectedMcVersion) = neoForgeAndExpectedMcVersion prepareTest(neoModDevVersion, neoForgeVersion) - val module = myProject.modules.find { it.name.contains("main") } - assertNotNull(module) - - val facet = MinecraftFacet.getInstance(module!!) - assertSameElements(facet!!.types, NeoForgeModuleType, McpModuleType, MixinModuleType) - - val mcpModule = MinecraftFacet.getInstance(module, McpModuleType) - assertNotNull(mcpModule) + val module = findMainModule() + module.assertDetectedPlatformTypes(PlatformType.NEOFORGE, PlatformType.MCP, PlatformType.MIXIN) - val mcpSettings = mcpModule!!.getSettings() + val mcpModule = module.assertMinecraftModule(McpModuleType) + val mcpSettings = mcpModule.getSettings() assertEquals(mcpSettings.platformVersion, neoForgeVersion) assertEquals(mcpSettings.minecraftVersion, expectedMcVersion) } @@ -129,7 +89,7 @@ class NeoForgeImportTest : ExternalSystemImportingTestCase() { companion object { @JvmStatic @Suppress("unused") // Used in @CartesianTest.MethodFactory - fun versionFactory(): ArgumentSets? { + fun versionFactory(): ArgumentSets { val neoModDevVersions = mutableListOf("2.0.7-beta", "1.0.17", "0.1.131") val latestNeoModDev = runBlocking { NeoModDevVersion.downloadData()?.versions?.firstOrNull() }