All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kotlinx.kover.gradle.aggregation.project.instrumentation.JvmTestTaskInstrumentation.kt Maven / Gradle / Ivy

There is a newer version: 0.9.0-RC
Show newest version
/*
 * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.kover.gradle.aggregation.project.instrumentation

import kotlinx.kover.gradle.aggregation.commons.names.KoverPaths
import kotlinx.kover.gradle.aggregation.settings.dsl.ProjectInstrumentationSettings
import org.gradle.api.Named
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.api.tasks.testing.Test
import org.gradle.process.CommandLineArgumentProvider
import java.io.File


internal object JvmOnFlyInstrumenter {

    /**
     * Add online instrumentation to all JVM test tasks.
     */
    fun instrument(
        tasks: TaskCollection,
        jarConfiguration: Configuration,
        instrumentation: ProjectInstrumentationSettings
    ) {
        tasks.configureEach {
            val taskName = name
            val enabledProvider = instrumentation.disabledForTestTasks.map { taskName !in it }
            val included = instrumentation.includedClasses
            val excluded = instrumentation.excludedClasses

            val binReportProvider =
                project.layout.buildDirectory.map { dir ->
                    dir.file(KoverPaths.binReportPath(name))
                }

            doFirst {
                // delete report so that when the data is re-measured, it is not appended to an already existing file
                // see https://github.com/Kotlin/kotlinx-kover/issues/489
                binReportProvider.get().asFile.delete()
            }

            // Always excludes android classes, see https://github.com/Kotlin/kotlinx-kover/issues/89
            val androidClasses = setOf(
                // Always excludes android classes, see https://github.com/Kotlin/kotlinx-kover/issues/89
                "android.*", "com.android.*",
                // excludes JVM internal classes, in some cases, errors occur when trying to instrument these classes, for example, when using JaCoCo + Robolectric. There is also no point in instrumenting them in Kover.
                "jdk.internal.*"
            )

            val excludedWithAndroid = excluded.map { it + androidClasses }

            dependsOn(jarConfiguration)
            jvmArgumentProviders += JvmTestTaskArgumentProvider(
                temporaryDir,
                project.objects.fileCollection().from(jarConfiguration),
                enabledProvider,
                included,
                excludedWithAndroid,
                binReportProvider
            )
        }
    }
}

/**
 * Provider of additional JVM string arguments for running a test task.
 */
private class JvmTestTaskArgumentProvider(
    private val tempDir: File,

    // relative sensitivity for file is a comparison by file name and its contents
    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    val jarFiles: ConfigurableFileCollection,

    @get:Input
    val enabled: Provider,
    @get:Input
    val includedClasses: Provider>,
    @get:Input
    val excludedClasses: Provider>,

    @get:OutputFile
    val reportProvider: Provider
) : CommandLineArgumentProvider, Named {

    @Internal
    override fun getName(): String {
        return "koverArgumentsProvider"
    }

    override fun asArguments(): MutableIterable {
        val files = jarFiles.files
        if (!enabled.get() || files.size != 1) {
            return mutableSetOf()
        }
        val jarFile = files.single()

        return buildKoverJvmAgentArgs(
            jarFile,
            tempDir,
            reportProvider.get().asFile,
            includedClasses.orNull ?: emptySet(),
            excludedClasses.orNull ?: emptySet()
        ).toMutableList()
    }
}


private fun buildKoverJvmAgentArgs(
    jarFile: File,
    tempDir: File,
    binReportFile: File,
    includedClasses: Set,
    excludedClasses: Set
): List {
    val argsFile = tempDir.resolve("kover-agent.args")
    argsFile.writeAgentArgs(binReportFile, includedClasses, excludedClasses)

    return mutableListOf("-javaagent:${jarFile.canonicalPath}=file:${argsFile.canonicalPath}")
}

private fun File.writeAgentArgs(binReportFile: File, includedClasses: Set, excludedClasses: Set) {
    binReportFile.parentFile.mkdirs()
    val binReportPath = binReportFile.canonicalPath

    printWriter().use { pw ->
        pw.append("report.file=").appendLine(binReportPath)
        excludedClasses.forEach { e ->
            pw.append("exclude=").appendLine(e)
        }
        includedClasses.forEach { i ->
            pw.append("include=").appendLine(i)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy