Skip to content

Commit

Permalink
Kotlin Multiplatform plugin adapter rewritten to use reflection
Browse files Browse the repository at this point in the history
Fixes #100
  • Loading branch information
shanshin committed Dec 21, 2021
1 parent dcd724d commit 4b1a92e
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 29 deletions.
@@ -0,0 +1,35 @@
package kotlinx.kover.test.functional.cases

import kotlinx.kover.test.functional.cases.utils.assertCounterFullyCovered
import kotlinx.kover.test.functional.cases.utils.defaultXmlModuleReport
import kotlinx.kover.test.functional.cases.utils.defaultXmlReport
import kotlinx.kover.test.functional.core.BaseGradleScriptTest
import kotlin.test.*

internal class AdaptersTests : BaseGradleScriptTest() {
@Test
fun testSubmoduleHasAnotherPlugin() {
/*
Tests for https://github.com/Kotlin/kotlinx-kover/issues/100
Classes from plugins applied in submodule not accessible for Kover in root module.
Therefore, Kover is forced to use reflection to work with extensions of the kotlin multiplatform plugin.
*/
internalProject("different-plugins")
.run("koverXmlReport") {
xml(defaultXmlReport()) {
assertCounterFullyCovered(classCounter("org.jetbrains.CommonClass"))
assertCounterFullyCovered(classCounter("org.jetbrains.JvmClass"))
}
}

internalProject("different-plugins")
.run("koverXmlModuleReport") {
submodule("submodule-multiplatform") {
xml(defaultXmlModuleReport()) {
assertCounterFullyCovered(classCounter("org.jetbrains.CommonClass"))
assertCounterFullyCovered(classCounter("org.jetbrains.JvmClass"))
}
}
}
}
}
Expand Up @@ -12,4 +12,8 @@ internal open class BaseGradleScriptTest {
fun builder(description: String): ProjectBuilder {
return createBuilder(rootFolder.root, description)
}

fun internalProject(name: String): ProjectRunner {
return loadInternalProject(name, rootFolder.root)
}
}
@@ -0,0 +1,19 @@
package kotlinx.kover.test.functional.core

import java.io.*

private const val INTERNAL_PROJECTS_PATH = "src/functionalTest/templates/projects"


internal fun loadInternalProject(name: String, rootDir: File): ProjectRunner {
val targetDir = File.createTempFile(name, null, rootDir)

val srcDir = File(INTERNAL_PROJECTS_PATH, name)
if (!srcDir.exists()) {
throw IllegalArgumentException("Internal test project '$name' not found")
}

srcDir.copyRecursively(targetDir, true)

return SingleProjectRunnerImpl(targetDir)
}
Expand Up @@ -12,27 +12,36 @@ internal class ProjectRunnerImpl(private val projects: Map<ProjectSlice, File>)
override fun run(vararg args: String, checker: RunResult.() -> Unit): ProjectRunnerImpl {
val argsList = listOf(*args)

projects.forEach { (slice, project) -> project.runGradle(argsList, slice, checker) }
projects.forEach { (slice, project) ->
try {
project.runGradle(argsList, checker)
} catch (e: Throwable) {
throw AssertionError("Assertion error occurred in test for project $slice", e)
}
}

return this
}
}

private fun File.runGradle(args: List<String>, slice: ProjectSlice, checker: RunResult.() -> Unit) {
try {
val buildResult = GradleRunner.create()
.withProjectDir(this)
.withPluginClasspath()
.addPluginTestRuntimeClasspath()
.withArguments(args)
.build()

RunResultImpl(buildResult, slice, this).apply(checker)
} catch (e: Throwable) {
throw AssertionError("Assertion error occurred in test for project $slice", e)
}
internal class SingleProjectRunnerImpl(private val projectDir: File) : ProjectRunner {
override fun run(vararg args: String, checker: RunResult.() -> Unit): SingleProjectRunnerImpl {
projectDir.runGradle(listOf(*args), checker)
return this
}
}

private fun File.runGradle(args: List<String>, checker: RunResult.() -> Unit) {
val buildResult = GradleRunner.create()
.withProjectDir(this)
.withPluginClasspath()
.addPluginTestRuntimeClasspath()
.withArguments(args)
.build()

RunResultImpl(buildResult, this).apply(checker)
}

private fun GradleRunner.addPluginTestRuntimeClasspath() = apply {
val classpathFile = File(System.getProperty("plugin-classpath"))
if (!classpathFile.exists()) {
Expand All @@ -44,15 +53,42 @@ private fun GradleRunner.addPluginTestRuntimeClasspath() = apply {
}


private class RunResultImpl(private val result: BuildResult, private val slice: ProjectSlice, private val dir: File) :
RunResult {
private class RunResultImpl(private val result: BuildResult, private val dir: File) : RunResult {
val buildDir: File = File(dir, "build")

override val engine: CoverageEngine = slice.engine ?: CoverageEngine.INTELLIJ
override val projectType: ProjectType = slice.type
private val buildScriptFile: File = buildFile()
private val buildScript: String by lazy { buildScriptFile.readText() }

override val engine: CoverageEngine by lazy {
if (buildScript.contains("set(kotlinx.kover.api.CoverageEngine.JACOCO)")) {
CoverageEngine.JACOCO
} else {
CoverageEngine.INTELLIJ
}
}

override val projectType: ProjectType by lazy {
if (buildScriptFile.name.substringAfterLast(".") == "kts") {
if (buildScript.contains("""kotlin("jvm")""")) {
ProjectType.KOTLIN_JVM
} else if (buildScript.contains("""kotlin("multiplatform")""")) {
ProjectType.KOTLIN_MULTIPLATFORM
} else {
throw IllegalArgumentException("Impossible to determine the type of project")
}
} else {
if (buildScript.contains("""id 'org.jetbrains.kotlin.jvm'""")) {
ProjectType.KOTLIN_JVM
} else if (buildScript.contains("""id 'org.jetbrains.kotlin.multiplatform'""")) {
ProjectType.KOTLIN_MULTIPLATFORM
} else {
throw IllegalArgumentException("Impossible to determine the type of project")
}
}
}

override fun submodule(name: String, checker: RunResult.() -> Unit) {
RunResultImpl(result, slice, File(dir, name)).also(checker)
RunResultImpl(result, File(dir, name)).also(checker)
}

override fun output(checker: String.() -> Unit) {
Expand All @@ -73,6 +109,13 @@ private class RunResultImpl(private val result: BuildResult, private val slice:
result.task(taskPath)?.outcome?.checker()
?: throw IllegalArgumentException("Task '$taskPath' not found in build result")
}

private fun buildFile(): File {
val file = File(dir, "build.gradle")
if (file.exists() && file.isFile) return file

return File(dir, "build.gradle.kts")
}
}


Expand Down
@@ -0,0 +1,7 @@
plugins {
id("org.jetbrains.kotlinx.kover") version "DEV"
}

repositories {
mavenCentral()
}
@@ -0,0 +1,3 @@
rootProject.name = "different-plugins"

include(":submodule-multiplatform")
@@ -0,0 +1,19 @@
plugins {
kotlin("multiplatform")
}

repositories {
mavenCentral()
}

kotlin {
jvm()

sourceSets {
commonTest {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test")
}
}
}
}
@@ -0,0 +1,7 @@
package org.jetbrains

class CommonClass {
fun function() {
println("Function")
}
}
@@ -0,0 +1,10 @@
package org.jetbrains

import kotlin.test.Test

class CommonTest {
@Test
fun testCommon() {
CommonClass().function()
}
}
@@ -0,0 +1,7 @@
package org.jetbrains

class JvmClass {
fun jvmFunction() {
println("JVM function")
}
}
@@ -0,0 +1,10 @@
package org.jetbrains

import kotlin.test.Test

class JvmTest {
@Test
fun jvmTest() {
JvmClass().jvmFunction()
}
}
Expand Up @@ -4,29 +4,43 @@

package kotlinx.kover.adapters

import groovy.lang.*
import kotlinx.kover.adapters.api.*
import org.gradle.api.*
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.plugin.*
import org.gradle.api.file.*
import org.gradle.internal.metaobject.*

class KotlinMultiplatformPluginAdapter : CompilationPluginAdapter {

@Suppress("UNCHECKED_CAST")
override fun findDirs(project: Project): PluginDirs {
return safe(project) {
this.plugins.findPlugin("kotlin-multiplatform") ?: return@safe PluginDirs(emptyList(), emptyList())

val extension = project.extensions.findByType(
KotlinMultiplatformExtension::class.java
) ?: return@safe PluginDirs(emptyList(), emptyList())
val extension =
project.extensions.findByName("kotlin")?.let { BeanDynamicObject(it) } ?: return@safe PluginDirs(
emptyList(),
emptyList()
)

val targets =
extension.targets.filter { it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm }
val targets = (extension.getProperty("targets") as NamedDomainObjectCollection<GroovyObject>).filter {
val platformTypeName = (it.getProperty("platformType") as Named).name
platformTypeName == "jvm" || platformTypeName == "androidJvm"
}

val compilations = targets.flatMap { it.compilations.filter { c -> c.name != "test" } }
val sourceDirs = compilations.asSequence().flatMap { it.allKotlinSourceSets }.map { it.kotlin }.flatMap { it.srcDirs }.toList()
val compilations = targets.flatMap {
(it.getProperty("compilations") as NamedDomainObjectCollection<Named>)
.filter { c -> c.name != "test" }
}.map { BeanDynamicObject(it) }

val sourceDirs = compilations.asSequence()
.flatMap { it.getProperty("allKotlinSourceSets") as Collection<*> }
.map { BeanDynamicObject(it).getProperty("kotlin") as SourceDirectorySet }.flatMap { it.srcDirs }
.toList()

val outputDirs =
compilations.asSequence().flatMap { it.output.classesDirs }.toList()
compilations.asSequence().map { BeanDynamicObject(it.getProperty("output")) }
.flatMap { it.getProperty("classesDirs") as FileCollection }.toList()

return@safe PluginDirs(sourceDirs, outputDirs)
}
Expand Down

0 comments on commit 4b1a92e

Please sign in to comment.