From 67a436bf7e0a98122953a40c0acdfbebc7819ad2 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Wed, 21 Mar 2018 18:07:27 +0100 Subject: [PATCH] MPP tests: provide utils to setup test mpp from project structure Allows to avoid configuration in test code --- .../idea/multiplatform/multiPlatformSetup.kt | 138 ++++++++++++++++++ .../idea/stubs/AbstractMultiModuleTest.kt | 45 +++--- 2 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 idea/tests/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt diff --git a/idea/tests/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt b/idea/tests/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt new file mode 100644 index 00000000000..032f2a91916 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/multiplatform/multiPlatformSetup.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.multiplatform + +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.StdModuleTypes +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.testFramework.PlatformTestCase +import junit.framework.TestCase +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest +import org.jetbrains.kotlin.idea.stubs.createFacet +import java.io.File + +// allows to configure a test mpp project +// testRoot is supposed to contain several directories which contain module sources roots +// configuration is based on those directories names +fun AbstractMultiModuleTest.setupMppProjectFromDirStructure(testRoot: File) { + assert(testRoot.isDirectory) { testRoot.absolutePath + " must be a directory" } + val dirs = testRoot.listFiles().filter { it.isDirectory } + val rootInfos = dirs.map { parseDirName(it) } + val infosByModuleId = rootInfos.groupBy { it.moduleId } + val modulesById = infosByModuleId.mapValues { (moduleId, infos) -> + createModuleWithRoots(moduleId, infos) + } + + infosByModuleId.entries.forEach { (id, rootInfos) -> + val module = modulesById[id]!! + rootInfos.flatMap { it.dependencies }.forEach { + module.addDependency(modulesById[it]!!) + } + } + + modulesById.forEach { (nameAndPlatform, module) -> + val (name, platform) = nameAndPlatform + when (platform) { + TargetPlatformKind.Common -> module.createFacet(TargetPlatformKind.Common, useProjectSettings = false) + else -> { + val commonModuleId = ModuleId(name, TargetPlatformKind.Common) + + module.createFacet(platform, implementedModuleName = commonModuleId.ideaModuleName()) + module.enableMultiPlatform() + + modulesById[commonModuleId]?.let { commonModule -> + module.addDependency(commonModule) + } + } + } + } +} + +private fun AbstractMultiModuleTest.createModuleWithRoots( + moduleId: ModuleId, + infos: List +): Module { + val module = createModule(moduleId.ideaModuleName()) + for ((_, isTestRoot, moduleRoot) in infos) { + addRoot(module, moduleRoot, isTestRoot) + } + return module +} + +private fun AbstractMultiModuleTest.createModule(name: String): Module { + val moduleDir = PlatformTestCase.createTempDir("") + val module = createModule(moduleDir.toString() + "/" + name, StdModuleTypes.JAVA) + val root = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(moduleDir) + TestCase.assertNotNull(root) + object : WriteCommandAction.Simple(module.project) { + @Throws(Throwable::class) + protected override fun run() { + root!!.refresh(false, true) + } + }.execute().throwException() + return module +} + +private val testSuffixes = setOf("test", "tests") +private val platformNames = mapOf( + TargetPlatformKind.Common to listOf("header", "common", "expect"), + TargetPlatformKind.Jvm[JvmTarget.DEFAULT] to listOf("java", "jvm"), + TargetPlatformKind.JavaScript to listOf("js", "javascript") +) + +private fun parseDirName(dir: File): RootInfo { + val parts = dir.name.split("_") + return RootInfo(parseModuleId(parts), parseIsTestRoot(parts), dir, parseDependencies(parts)) +} + +private fun parseDependencies(parts: List) = + parts.filter { it.startsWith("dep(") && it.endsWith(")") }.map { + parseModuleId(it.removePrefix("dep(").removeSuffix(")").split("-")) + } + +private fun parseModuleId(parts: List): ModuleId { + val platform = parsePlatform(parts).key + val name = parseModuleName(parts) + val moduleId = ModuleId(name, platform) + return moduleId +} + +private fun parsePlatform(parts: List) = + platformNames.entries.single { (_, names) -> + names.any { name -> parts.any { part -> part.equals(name, ignoreCase = true) } } + } + +private fun parseModuleName(parts: List) = when { + parts.size > 1 -> parts.first() + else -> "testModule" +} + +private fun parseIsTestRoot(parts: List) = + testSuffixes.any { suffix -> parts.any { it.equals(suffix, ignoreCase = true) } } + +private data class ModuleId( + val groupName: String, + val platform: TargetPlatformKind<*> +) { + fun ideaModuleName() = "${groupName}_${platform.presentableName}" +} + +private val TargetPlatformKind<*>.presentableName: String + get() = when (this) { + is TargetPlatformKind.Common -> "Common" + is TargetPlatformKind.Jvm -> "JVM" + is TargetPlatformKind.JavaScript -> "JS" + } + +private data class RootInfo( + val moduleId: ModuleId, + val isTestRoot: Boolean, + val moduleRoot: File, + val dependencies: List +) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt index a2cb71c2eb0..e6f8d7ed96e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.application.WriteAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleType import com.intellij.openapi.module.StdModuleTypes import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.roots.ModuleRootModificationUtil @@ -44,11 +45,15 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() { VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory()) } - protected fun module(name: String, jdk: TestJdkKind = TestJdkKind.MOCK_JDK, hasTestRoot: Boolean = false): Module { + fun module(name: String, jdk: TestJdkKind = TestJdkKind.MOCK_JDK, hasTestRoot: Boolean = false): Module { val srcDir = testDataPath + "${getTestName(true)}/$name" val moduleWithSrcRootSet = createModuleFromTestData(srcDir, name, StdModuleTypes.JAVA, true)!! if (hasTestRoot) { - setTestRoot(moduleWithSrcRootSet, name) + addRoot( + moduleWithSrcRootSet, + File(testDataPath + "${getTestName(true)}/${name}Test"), + true + ) } ConfigLibraryUtil.configureSdk(moduleWithSrcRootSet, PluginTestCaseBase.addJdk(testRootDisposable) { PluginTestCaseBase.jdk(jdk) }) @@ -56,24 +61,26 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() { return moduleWithSrcRootSet } - private fun setTestRoot(module: Module, name: String) { - val testDir = testDataPath + "${getTestName(true)}/${name}Test" - val testRootDirInTestData = File(testDir) - val testRootDir = createTempDirectory()!! - FileUtil.copyDir(testRootDirInTestData, testRootDir) - val testRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testRootDir)!! - object : WriteCommandAction.Simple(project) { - override fun run() { - testRoot.refresh(false, true) - } - }.execute().throwException() - PsiTestUtil.addSourceRoot(module, testRoot, true) + public override fun createModule(path: String, moduleType: ModuleType<*>): Module { + return super.createModule(path, moduleType) } - protected fun Module.addDependency( - other: Module, - dependencyScope: DependencyScope = DependencyScope.COMPILE, - exported: Boolean = false + fun addRoot(module: Module, sourceDirInTestData: File, isTestRoot: Boolean) { + val tmpRootDir = createTempDirectory() + FileUtil.copyDir(sourceDirInTestData, tmpRootDir) + val virtualTempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tmpRootDir)!! + object : WriteCommandAction.Simple(project) { + override fun run() { + virtualTempDir.refresh(false, isTestRoot) + } + }.execute().throwException() + PsiTestUtil.addSourceRoot(module, virtualTempDir, isTestRoot) + } + + fun Module.addDependency( + other: Module, + dependencyScope: DependencyScope = DependencyScope.COMPILE, + exported: Boolean = false ): Module = this.apply { ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported) } protected fun Module.addLibrary(jar: File, @@ -85,7 +92,7 @@ abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() { }, this, kind) } - protected fun Module.enableMultiPlatform() { + fun Module.enableMultiPlatform() { createFacet() val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getInitializedSettings(this) facetSettings.useProjectSettings = false