MPP tests: provide utils to setup test mpp from project structure

Allows to avoid configuration in test code
This commit is contained in:
Pavel V. Talanov
2018-03-21 18:07:27 +01:00
parent 01bff0e156
commit 67a436bf7e
2 changed files with 164 additions and 19 deletions
@@ -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<RootInfo>
): 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<Unit>(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<String>) =
parts.filter { it.startsWith("dep(") && it.endsWith(")") }.map {
parseModuleId(it.removePrefix("dep(").removeSuffix(")").split("-"))
}
private fun parseModuleId(parts: List<String>): ModuleId {
val platform = parsePlatform(parts).key
val name = parseModuleName(parts)
val moduleId = ModuleId(name, platform)
return moduleId
}
private fun parsePlatform(parts: List<String>) =
platformNames.entries.single { (_, names) ->
names.any { name -> parts.any { part -> part.equals(name, ignoreCase = true) } }
}
private fun parseModuleName(parts: List<String>) = when {
parts.size > 1 -> parts.first()
else -> "testModule"
}
private fun parseIsTestRoot(parts: List<String>) =
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<ModuleId>
)
@@ -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<Unit>(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<Unit>(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