From bbc05608200ec57ee1cfb9d32f37152caabfb749 Mon Sep 17 00:00:00 2001 From: Sergey Shanshin Date: Wed, 31 May 2023 23:37:01 +0300 Subject: [PATCH] Implemented Worker API for use with Kover toolset Using the Worker API allows directly call the methods of the Kover tool's classes, bypassing the serialization and deserialization of arguments in JSON and passing them to a separately running JVM process. PR #357 --- build.gradle.kts | 2 + .../builds/nested-project/build.gradle.kts | 4 +- .../alpha-project/build.gradle.kts | 4 +- .../kover/gradle/plugin/appliers/Artifacts.kt | 22 +- .../gradle/plugin/appliers/ProjectApplier.kt | 2 +- .../gradle/plugin/appliers/ReportsApplier.kt | 5 +- .../kover/gradle/plugin/commons/Artifacts.kt | 20 +- .../kover/gradle/plugin/commons/Types.kt | 11 +- .../kover/gradle/plugin/dsl/KoverVersions.kt | 2 +- .../gradle/plugin/tasks/KoverVerifyTask.kt | 85 ------ .../{ => reports}/AbstractKoverReportTask.kt | 21 +- .../tasks/{ => reports}/KoverHtmlTask.kt | 13 +- .../plugin/tasks/reports/KoverVerifyTask.kt | 31 ++ .../tasks/{ => reports}/KoverXmlTask.kt | 8 +- .../KoverAgentJarTask.kt | 13 +- .../KoverArtifactGenerationTask.kt | 17 +- .../kover/gradle/plugin/tools/CoverageTool.kt | 6 +- .../kover/gradle/plugin/tools/Violations.kt | 46 +++ .../gradle/plugin/tools/jacoco/JacocoAnt.kt | 12 +- .../tools/jacoco/JacocoHtmlOrXmlReport.kt | 8 +- .../gradle/plugin/tools/jacoco/JacocoTool.kt | 21 +- .../plugin/tools/jacoco/Verification.kt | 36 ++- .../tools/kover/KoverHtmlOrXmlReport.kt | 156 +++++----- .../tools/kover/KoverReportAggregator.kt | 82 ++---- .../gradle/plugin/tools/kover/KoverTool.kt | 37 ++- .../gradle/plugin/tools/kover/Verification.kt | 213 +++++++------- .../kotlinx/kover/gradle/plugin/util/Util.kt | 3 + .../gradle/plugin/util/json/SimpleParser.kt | 269 ------------------ .../gradle/plugin/util/json/SimpleWriter.kt | 113 -------- src/test/kotlin/JsonTests.kt | 60 ---- toolset/kover-cli/build.gradle.kts | 2 +- .../cli/commands/OfflineInstrumentCommand.kt | 7 +- .../kover/cli/commands/ReportCommand.kt | 19 +- .../kover-offline-runtime/build.gradle.kts | 2 +- 34 files changed, 452 insertions(+), 900 deletions(-) delete mode 100644 src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverVerifyTask.kt rename src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/{ => reports}/AbstractKoverReportTask.kt (82%) rename src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/{ => reports}/KoverHtmlTask.kt (59%) create mode 100644 src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt rename src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/{ => reports}/KoverXmlTask.kt (58%) rename src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/{internal => services}/KoverAgentJarTask.kt (70%) rename src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/{internal => services}/KoverArtifactGenerationTask.kt (69%) delete mode 100644 src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleParser.kt delete mode 100644 src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleWriter.kt delete mode 100644 src/test/kotlin/JsonTests.kt diff --git a/build.gradle.kts b/build.gradle.kts index 59d7dc84..37074f72 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,6 +43,8 @@ dependencies { compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + compileOnly("org.jetbrains.intellij.deps:intellij-coverage-reporter:1.0.719") + testImplementation(kotlin("test")) "functionalTestImplementation"("org.junit.jupiter:junit-jupiter:5.9.0") diff --git a/src/functionalTest/templates/builds/nested-project/build.gradle.kts b/src/functionalTest/templates/builds/nested-project/build.gradle.kts index 86cf047e..c7010d45 100644 --- a/src/functionalTest/templates/builds/nested-project/build.gradle.kts +++ b/src/functionalTest/templates/builds/nested-project/build.gradle.kts @@ -3,7 +3,9 @@ plugins { id("org.jetbrains.kotlinx.kover") } -repositories { mavenCentral() } +repositories { + mavenCentral() +} dependencies { kover(project(":subprojects:alpha-project")) diff --git a/src/functionalTest/templates/builds/nested-project/subprojects/alpha-project/build.gradle.kts b/src/functionalTest/templates/builds/nested-project/subprojects/alpha-project/build.gradle.kts index 3b4e5804..8eae7acc 100644 --- a/src/functionalTest/templates/builds/nested-project/subprojects/alpha-project/build.gradle.kts +++ b/src/functionalTest/templates/builds/nested-project/subprojects/alpha-project/build.gradle.kts @@ -3,7 +3,9 @@ plugins { id("org.jetbrains.kotlinx.kover") } -repositories { mavenCentral() } +repositories { + mavenCentral() +} dependencies { testImplementation(kotlin("test")) diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/Artifacts.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/Artifacts.kt index a5bd8209..f1fccc28 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/Artifacts.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/Artifacts.kt @@ -5,7 +5,6 @@ package kotlinx.kover.gradle.plugin.appliers import kotlinx.kover.gradle.plugin.commons.AppliedKotlinPlugin -import kotlinx.kover.gradle.plugin.commons.Variant import kotlinx.kover.gradle.plugin.commons.ArtifactNameAttr import kotlinx.kover.gradle.plugin.commons.CompilationUnit import kotlinx.kover.gradle.plugin.commons.KotlinPluginAttr @@ -19,11 +18,15 @@ import kotlinx.kover.gradle.plugin.commons.localArtifactConfigurationName import kotlinx.kover.gradle.plugin.commons.rawReportName import kotlinx.kover.gradle.plugin.commons.rawReportsRootPath import kotlinx.kover.gradle.plugin.dsl.KoverNames.DEPENDENCY_CONFIGURATION_NAME -import kotlinx.kover.gradle.plugin.tasks.internal.KoverArtifactGenerationTask +import kotlinx.kover.gradle.plugin.tasks.services.KoverArtifactGenerationTask import kotlinx.kover.gradle.plugin.tools.CoverageToolVariant +import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskCollection +import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register @@ -60,7 +63,7 @@ internal fun Project.createArtifactGenerationTask( dependsOn(compileTasks) this.sources.from(sources) - this.outputs.from(outputs) + this.outputDirs.from(outputs) this.reports.from(rawReportFiles) this.artifactFile.set(localArtifactFile) } @@ -90,4 +93,15 @@ internal fun Project.createArtifactGenerationTask( } return Variant(variantName, localArtifactFile, artifactGenTask, local, dependencies) -} \ No newline at end of file +} + +/** + * Comprehensive information sufficient to generate a variant of the report. + */ +internal class Variant( + val name: String, + val localArtifact: Provider, + val localArtifactGenerationTask: TaskProvider, + val localArtifactConfiguration: NamedDomainObjectProvider, + val dependentArtifactsConfiguration: NamedDomainObjectProvider +) \ No newline at end of file diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ProjectApplier.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ProjectApplier.kt index 5cd91d64..44cacb71 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ProjectApplier.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ProjectApplier.kt @@ -11,7 +11,7 @@ import kotlinx.kover.gradle.plugin.dsl.KoverNames.REPORT_EXTENSION_NAME import kotlinx.kover.gradle.plugin.dsl.internal.KoverProjectExtensionImpl import kotlinx.kover.gradle.plugin.dsl.internal.KoverReportExtensionImpl import kotlinx.kover.gradle.plugin.locators.CompilationsLocatorFactory -import kotlinx.kover.gradle.plugin.tasks.internal.KoverAgentJarTask +import kotlinx.kover.gradle.plugin.tasks.services.KoverAgentJarTask import kotlinx.kover.gradle.plugin.tools.CoverageTool import kotlinx.kover.gradle.plugin.tools.CoverageToolFactory import kotlinx.kover.gradle.plugin.tools.CoverageToolVariant diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ReportsApplier.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ReportsApplier.kt index 288e2583..b56c266c 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ReportsApplier.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/ReportsApplier.kt @@ -6,7 +6,10 @@ package kotlinx.kover.gradle.plugin.appliers import kotlinx.kover.gradle.plugin.commons.* import kotlinx.kover.gradle.plugin.dsl.internal.* -import kotlinx.kover.gradle.plugin.tasks.* +import kotlinx.kover.gradle.plugin.tasks.reports.AbstractKoverReportTask +import kotlinx.kover.gradle.plugin.tasks.reports.KoverHtmlTask +import kotlinx.kover.gradle.plugin.tasks.reports.KoverVerifyTask +import kotlinx.kover.gradle.plugin.tasks.reports.KoverXmlTask import kotlinx.kover.gradle.plugin.tools.* import org.gradle.api.* import org.gradle.api.artifacts.* diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Artifacts.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Artifacts.kt index 1ead737c..1624e5e1 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Artifacts.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Artifacts.kt @@ -4,24 +4,8 @@ package kotlinx.kover.gradle.plugin.commons -import kotlinx.kover.gradle.plugin.tasks.internal.KoverArtifactGenerationTask -import org.gradle.api.NamedDomainObjectProvider -import org.gradle.api.artifacts.Configuration -import org.gradle.api.file.RegularFile -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.TaskProvider import java.io.File - -/** - * Comprehensive information sufficient to generate a variant of the report. - */ -internal class Variant( - val name: String, - val localArtifact: Provider, - val localArtifactGenerationTask: TaskProvider, - val localArtifactConfiguration: NamedDomainObjectProvider, - val dependentArtifactsConfiguration: NamedDomainObjectProvider -) +import java.io.Serializable /** * The contents of a single Kover artifact. @@ -30,7 +14,7 @@ internal class ArtifactContent( val sources: Set, val outputs: Set, val reports: Set -) { +): Serializable { fun joinWith(others: List): ArtifactContent { val sources = this.sources.toMutableSet() val outputs = this.outputs.toMutableSet() diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt index 6da11064..e658a558 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt @@ -11,7 +11,7 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.gradle.api.tasks.testing.* -import org.gradle.process.* +import org.gradle.workers.WorkerExecutor import java.io.* import java.math.BigDecimal import javax.annotation.* @@ -140,6 +140,7 @@ internal class CompilationUnit( internal class ReportContext( val files: ArtifactContent, + val filters: ReportFilters, val classpath: FileCollection, val tempDir: File, val projectPath: String, @@ -147,7 +148,7 @@ internal class ReportContext( ) internal class GradleReportServices( - val exec: ExecOperations, + val workerExecutor: WorkerExecutor, val antBuilder: AntBuilder, val objects: ObjectFactory ) @@ -161,7 +162,7 @@ internal data class ReportFilters( val excludesClasses: Set = emptySet(), @get:Input val excludesAnnotations: Set = emptySet() -) +): Serializable internal open class VerificationRule @Inject constructor( @get:Input @@ -182,7 +183,7 @@ internal open class VerificationRule @Inject constructor( @get:Nested internal val bounds: List -) +): Serializable internal open class VerificationBound( @get:Input @@ -200,4 +201,4 @@ internal open class VerificationBound( @get:Input val aggregation: AggregationType -) +): Serializable diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt index a6f428ea..b3140328 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt @@ -15,7 +15,7 @@ public object KoverVersions { /** * Kover coverage tool version. */ - public const val KOVER_TOOL_VERSION = "1.0.716" + public const val KOVER_TOOL_VERSION = "1.0.721" /** * JaCoCo coverage tool version used by default. diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverVerifyTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverVerifyTask.kt deleted file mode 100644 index 945ba5d8..00000000 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverVerifyTask.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.gradle.plugin.tasks - -import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.commons.VerificationRule -import kotlinx.kover.gradle.plugin.dsl.* -import kotlinx.kover.gradle.plugin.tools.* -import kotlinx.kover.gradle.plugin.tools.CoverageTool -import org.gradle.api.file.* -import org.gradle.api.provider.* -import org.gradle.api.tasks.* -import org.gradle.kotlin.dsl.* -import javax.inject.* - -@CacheableTask -internal open class KoverVerifyTask @Inject constructor(tool: CoverageTool) : AbstractKoverReportTask(tool) { - @get:Nested - val rules: ListProperty = project.objects.listProperty() - - @get:OutputFile - val resultFile: RegularFileProperty = project.objects.fileProperty() - - @TaskAction - fun verify() { - val enabledRules = rules.get().filter { it.isEnabled } - - val violations = tool.verify(enabledRules, filters.get(), context()) - - if (violations.isNotEmpty()) { - val message = generateErrorMessage(violations) - resultFile.get().asFile.writeText(message) - throw KoverVerificationException(message) - } - } - - private fun generateErrorMessage(violations: List): String { - val messageBuilder = StringBuilder() - - violations.forEach { rule -> - val namedRule = if (rule.name != null) "Rule '${rule.name}'" else "Rule" - - if (rule.bounds.size == 1) { - messageBuilder.appendLine("$namedRule violated: ${rule.bounds[0].format(rule)}") - } else { - messageBuilder.appendLine("$namedRule violated:") - - rule.bounds.forEach { bound -> - messageBuilder.append(" ") - messageBuilder.appendLine(bound.format(rule)) - } - } - } - - return messageBuilder.toString() - } - - private fun BoundViolations.format(rule: RuleViolations): String { - val directionText = if (isMax) "maximum" else "minimum" - - val metricText = when (metric) { - MetricType.LINE -> "lines" - MetricType.INSTRUCTION -> "instructions" - MetricType.BRANCH -> "branches" - } - - val valueTypeText = when (aggregation) { - AggregationType.COVERED_COUNT -> "covered count" - AggregationType.MISSED_COUNT -> "missed count" - AggregationType.COVERED_PERCENTAGE -> "covered percentage" - AggregationType.MISSED_PERCENTAGE -> "missed percentage" - } - - val entityText = when (rule.entityType) { - GroupingEntityType.APPLICATION -> "" - GroupingEntityType.CLASS -> " for class '$entityName'" - GroupingEntityType.PACKAGE -> " for package '$entityName'" - } - - return "$metricText $valueTypeText$entityText is $actualValue, but expected $directionText is $expectedValue" - } - -} diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/AbstractKoverReportTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt similarity index 82% rename from src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/AbstractKoverReportTask.kt rename to src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt index 56fe48d7..4005a9f9 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/AbstractKoverReportTask.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt @@ -2,7 +2,7 @@ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.gradle.plugin.tasks +package kotlinx.kover.gradle.plugin.tasks.reports import kotlinx.kover.api.* import kotlinx.kover.gradle.plugin.commons.* @@ -11,26 +11,29 @@ import kotlinx.kover.gradle.plugin.tools.* import kotlinx.kover.gradle.plugin.tools.kover.* import org.gradle.api.* import org.gradle.api.file.* +import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.* import org.gradle.api.tasks.* import org.gradle.configurationcache.extensions.* import org.gradle.kotlin.dsl.* import org.gradle.process.* +import org.gradle.workers.WorkerExecutor import java.io.File +import javax.inject.Inject internal abstract class AbstractKoverReportTask(@Internal protected val tool: CoverageTool) : DefaultTask() { @get:InputFile @get:PathSensitive(PathSensitivity.RELATIVE) - val localArtifact: RegularFileProperty = project.objects.fileProperty() + abstract val localArtifact: RegularFileProperty @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val externalArtifacts: ConfigurableFileCollection = project.objects.fileCollection() + abstract val externalArtifacts: ConfigurableFileCollection @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val reportClasspath: ConfigurableFileCollection = project.objects.fileCollection() + abstract val reportClasspath: ConfigurableFileCollection /** * This will cause the task to be considered out-of-date when source files of dependencies have changed. @@ -79,9 +82,11 @@ internal abstract class AbstractKoverReportTask(@Internal protected val tool: Co @get:Internal protected val projectPath: String = project.path - private val exec: ExecOperations = project.serviceOf() + @get:Inject + protected abstract val obj: ObjectFactory - private val obj = project.objects + @get:Inject + protected abstract val workerExecutor: WorkerExecutor fun hasRawReportsAndLog(): Boolean { val hasReports = collectAllFiles().reports.isNotEmpty() @@ -92,8 +97,8 @@ internal abstract class AbstractKoverReportTask(@Internal protected val tool: Co } protected fun context(): ReportContext { - val services = GradleReportServices(exec, ant, obj) - return ReportContext(collectAllFiles(), reportClasspath, temporaryDir, projectPath, services) + val services = GradleReportServices(workerExecutor, ant, obj) + return ReportContext(collectAllFiles(), filters.get(), reportClasspath, temporaryDir, projectPath, services) } private fun collectAllFiles(): ArtifactContent { diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverHtmlTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverHtmlTask.kt similarity index 59% rename from src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverHtmlTask.kt rename to src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverHtmlTask.kt index 057fa9b9..594b607c 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverHtmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverHtmlTask.kt @@ -2,32 +2,31 @@ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.gradle.plugin.tasks +package kotlinx.kover.gradle.plugin.tasks.reports import kotlinx.kover.gradle.plugin.tools.CoverageTool import org.gradle.api.file.* import org.gradle.api.provider.Property import org.gradle.api.tasks.* -import org.gradle.kotlin.dsl.* import javax.inject.* @CacheableTask -internal open class KoverHtmlTask @Inject constructor(tool: CoverageTool) : AbstractKoverReportTask(tool) { +internal abstract class KoverHtmlTask @Inject constructor(tool: CoverageTool) : AbstractKoverReportTask(tool) { @get:OutputDirectory - val reportDir: DirectoryProperty = project.objects.directoryProperty() + abstract val reportDir: DirectoryProperty @get:Input - val title: Property = project.objects.property() + abstract val title: Property @get:Input @get:Optional - val charset: Property = project.objects.property() + abstract val charset: Property @TaskAction fun generate() { val htmlDir = reportDir.get().asFile htmlDir.mkdirs() - tool.htmlReport(htmlDir, title.get(), charset.orNull, filters.get(), context()) + tool.htmlReport(htmlDir, title.get(), charset.orNull, context()) } fun printPath(): Boolean { diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt new file mode 100644 index 00000000..ce13ce59 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.gradle.plugin.tasks.reports + +import kotlinx.kover.gradle.plugin.commons.VerificationRule +import kotlinx.kover.gradle.plugin.tools.CoverageTool +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import javax.inject.Inject + +@CacheableTask +internal abstract class KoverVerifyTask @Inject constructor(tool: CoverageTool) : AbstractKoverReportTask(tool) { + @get:Nested + abstract val rules: ListProperty + + @get:OutputFile + abstract val resultFile: RegularFileProperty + + @TaskAction + fun verify() { + val enabledRules = rules.get().filter { it.isEnabled } + tool.verify(enabledRules, resultFile.get().asFile, context()) + } + +} diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverXmlTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverXmlTask.kt similarity index 58% rename from src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverXmlTask.kt rename to src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverXmlTask.kt index b8dccef1..c6e91a43 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/KoverXmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverXmlTask.kt @@ -2,7 +2,7 @@ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.gradle.plugin.tasks +package kotlinx.kover.gradle.plugin.tasks.reports import kotlinx.kover.gradle.plugin.tools.CoverageTool import org.gradle.api.file.* @@ -10,14 +10,14 @@ import org.gradle.api.tasks.* import javax.inject.* @CacheableTask -internal open class KoverXmlTask @Inject constructor(tool: CoverageTool) : AbstractKoverReportTask(tool) { +internal abstract class KoverXmlTask @Inject constructor(tool: CoverageTool) : AbstractKoverReportTask(tool) { @get:OutputFile - internal val reportFile: RegularFileProperty = project.objects.fileProperty() + internal abstract val reportFile: RegularFileProperty @TaskAction fun generate() { val xmlFile = reportFile.get().asFile xmlFile.parentFile.mkdirs() - tool.xmlReport(xmlFile, filters.get(), context()) + tool.xmlReport(xmlFile, context()) } } diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/internal/KoverAgentJarTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverAgentJarTask.kt similarity index 70% rename from src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/internal/KoverAgentJarTask.kt rename to src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverAgentJarTask.kt index 317e8fb4..61c719a0 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/internal/KoverAgentJarTask.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverAgentJarTask.kt @@ -2,7 +2,7 @@ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.gradle.plugin.tasks.internal +package kotlinx.kover.gradle.plugin.tasks.services import kotlinx.kover.gradle.plugin.dsl.* import kotlinx.kover.gradle.plugin.tools.* @@ -19,19 +19,20 @@ import javax.inject.* * The task is cached, so in general there should not be a performance issue on large projects. */ @CacheableTask -internal open class KoverAgentJarTask @Inject constructor(private val tool: CoverageTool) : DefaultTask() { - private val archiveOperations: ArchiveOperations = project.serviceOf() - +internal abstract class KoverAgentJarTask @Inject constructor(private val tool: CoverageTool) : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.ABSOLUTE) - val agentClasspath: ConfigurableFileCollection = project.objects.fileCollection() + abstract val agentClasspath: ConfigurableFileCollection @get:OutputFile - val agentJar: RegularFileProperty = project.objects.fileProperty() + abstract val agentJar: RegularFileProperty @get:Nested val toolVariant: CoverageToolVariant = tool.variant + @get:Inject + protected abstract val archiveOperations: ArchiveOperations + @TaskAction fun find() { val srcJar = tool.findJvmAgentJar(agentClasspath, archiveOperations) diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/internal/KoverArtifactGenerationTask.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverArtifactGenerationTask.kt similarity index 69% rename from src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/internal/KoverArtifactGenerationTask.kt rename to src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverArtifactGenerationTask.kt index d2b43c3b..b4620cd9 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/internal/KoverArtifactGenerationTask.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverArtifactGenerationTask.kt @@ -2,7 +2,7 @@ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.gradle.plugin.tasks.internal +package kotlinx.kover.gradle.plugin.tasks.services import kotlinx.kover.gradle.plugin.commons.* import org.gradle.api.* @@ -20,30 +20,29 @@ import javax.inject.* * This artifact that will be shared between projects through dependencies for creating merged reports. */ @CacheableTask -internal open class KoverArtifactGenerationTask : DefaultTask() { +internal abstract class KoverArtifactGenerationTask : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val sources: ConfigurableFileCollection = project.objects.fileCollection() + abstract val sources: ConfigurableFileCollection @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val outputs: ConfigurableFileCollection = project.objects.fileCollection() + abstract val outputDirs: ConfigurableFileCollection @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val reports: ConfigurableFileCollection = project.objects.fileCollection() + abstract val reports: ConfigurableFileCollection @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val additionalArtifacts: ConfigurableFileCollection = project.objects.fileCollection() + abstract val additionalArtifacts: ConfigurableFileCollection @get:OutputFile - val artifactFile: RegularFileProperty = project.objects.fileProperty() - + abstract val artifactFile: RegularFileProperty @TaskAction fun generate() { - val mainContent = ArtifactContent(sources.toSet(), outputs.toSet(), reports.toSet()) + val mainContent = ArtifactContent(sources.toSet(), outputDirs.toSet(), reports.toSet()) val additional = additionalArtifacts.files.map { it.parseArtifactFile() } mainContent.joinWith(additional).write(artifactFile.get().asFile) } diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt index 91e1d438..e6e71552 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt @@ -93,17 +93,17 @@ internal interface CoverageTool { /** * Generate XML report. */ - fun xmlReport(xmlFile: File, filters: ReportFilters, context: ReportContext) + fun xmlReport(xmlFile: File, context: ReportContext) /** * Generate HTML report. */ - fun htmlReport(htmlDir: File, title: String, charset: String?, filters: ReportFilters, context: ReportContext) + fun htmlReport(htmlDir: File, title: String, charset: String?, context: ReportContext) /** * Perform verification. */ - fun verify(rules: List, commonFilters: ReportFilters, context: ReportContext): List + fun verify(rules: List, outputFile: File, context: ReportContext) } /** diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Violations.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Violations.kt index ab849bf9..b6144572 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Violations.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Violations.kt @@ -21,3 +21,49 @@ internal data class BoundViolations( val aggregation: AggregationType, val entityName: String? = null ) + +internal fun generateErrorMessage(violations: List): String { + val messageBuilder = StringBuilder() + + violations.forEach { rule -> + val namedRule = if (rule.name != null) "Rule '${rule.name}'" else "Rule" + + if (rule.bounds.size == 1) { + messageBuilder.appendLine("$namedRule violated: ${rule.bounds[0].format(rule)}") + } else { + messageBuilder.appendLine("$namedRule violated:") + + rule.bounds.forEach { bound -> + messageBuilder.append(" ") + messageBuilder.appendLine(bound.format(rule)) + } + } + } + + return messageBuilder.toString() +} + +private fun BoundViolations.format(rule: RuleViolations): String { + val directionText = if (isMax) "maximum" else "minimum" + + val metricText = when (metric) { + MetricType.LINE -> "lines" + MetricType.INSTRUCTION -> "instructions" + MetricType.BRANCH -> "branches" + } + + val valueTypeText = when (aggregation) { + AggregationType.COVERED_COUNT -> "covered count" + AggregationType.MISSED_COUNT -> "missed count" + AggregationType.COVERED_PERCENTAGE -> "covered percentage" + AggregationType.MISSED_PERCENTAGE -> "missed percentage" + } + + val entityText = when (rule.entityType) { + GroupingEntityType.APPLICATION -> "" + GroupingEntityType.CLASS -> " for class '$entityName'" + GroupingEntityType.PACKAGE -> " for package '$entityName'" + } + + return "$metricText $valueTypeText$entityText is $actualValue, but expected $directionText is $expectedValue" +} diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt index 4f98e966..fbcaa531 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt @@ -4,17 +4,13 @@ package kotlinx.kover.gradle.plugin.tools.jacoco -import groovy.lang.* -import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.commons.ReportFilters -import kotlinx.kover.gradle.plugin.util.* -import org.gradle.api.* -import org.gradle.api.file.* -import java.io.* +import groovy.lang.Closure +import groovy.lang.GroovyObject +import kotlinx.kover.gradle.plugin.commons.ReportContext +import kotlinx.kover.gradle.plugin.util.wildcardsToClassFileRegex internal fun ReportContext.callAntReport( - filters: ReportFilters, reportName: String, block: GroovyObject.() -> Unit ) { diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoHtmlOrXmlReport.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoHtmlOrXmlReport.kt index 6cb87c83..6b63339e 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoHtmlOrXmlReport.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoHtmlOrXmlReport.kt @@ -8,8 +8,8 @@ import kotlinx.kover.gradle.plugin.commons.* import java.io.* -internal fun ReportContext.jacocoHtmlReport(htmlDir: File, title: String, charset: String?, filters: ReportFilters) { - callAntReport(filters, title) { +internal fun ReportContext.jacocoHtmlReport(htmlDir: File, title: String, charset: String?) { + callAntReport(title) { htmlDir.mkdirs() val element = if (charset != null) { @@ -21,8 +21,8 @@ internal fun ReportContext.jacocoHtmlReport(htmlDir: File, title: String, charse } } -internal fun ReportContext.jacocoXmlReport(xmlFile: File, filters: ReportFilters) { - callAntReport(filters, projectPath) { +internal fun ReportContext.jacocoXmlReport(xmlFile: File) { + callAntReport(projectPath) { xmlFile.parentFile.mkdirs() invokeMethod("xml", mapOf("destfile" to xmlFile)) } diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt index 733d3b28..afaad01d 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt @@ -5,13 +5,12 @@ package kotlinx.kover.gradle.plugin.tools.jacoco import kotlinx.kover.gradle.plugin.commons.ReportContext -import kotlinx.kover.gradle.plugin.commons.ReportFilters import kotlinx.kover.gradle.plugin.commons.VerificationRule -import kotlinx.kover.gradle.plugin.tools.* import kotlinx.kover.gradle.plugin.tools.CoverageTool -import kotlinx.kover.gradle.plugin.tools.RuleViolations -import org.gradle.api.file.* -import java.io.* +import kotlinx.kover.gradle.plugin.tools.CoverageToolVariant +import org.gradle.api.file.ArchiveOperations +import org.gradle.api.file.FileCollection +import java.io.File internal class JacocoTool(override val variant: CoverageToolVariant) : CoverageTool { @@ -36,16 +35,16 @@ internal class JacocoTool(override val variant: CoverageToolVariant) : CoverageT return buildJvmAgentArgs(jarFile, rawReportFile, excludedClasses) } - override fun xmlReport(xmlFile: File, filters: ReportFilters, context: ReportContext) { - context.jacocoXmlReport(xmlFile, filters) + override fun xmlReport(xmlFile: File, context: ReportContext) { + context.jacocoXmlReport(xmlFile) } - override fun htmlReport(htmlDir: File, title: String, charset: String?, filters: ReportFilters, context: ReportContext) { - context.jacocoHtmlReport(htmlDir, title, charset, filters) + override fun htmlReport(htmlDir: File, title: String, charset: String?, context: ReportContext) { + context.jacocoHtmlReport(htmlDir, title, charset) } - override fun verify(rules: List, commonFilters: ReportFilters, context: ReportContext): List { - return context.jacocoVerify(rules, commonFilters) + override fun verify(rules: List, outputFile: File, context: ReportContext) { + context.jacocoVerify(rules, outputFile) } } diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt index dd8725ee..b14d91f3 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt @@ -4,24 +4,39 @@ package kotlinx.kover.gradle.plugin.tools.jacoco -import groovy.lang.* +import groovy.lang.GroovyObject import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.commons.ReportFilters -import kotlinx.kover.gradle.plugin.commons.VerificationRule -import kotlinx.kover.gradle.plugin.dsl.* -import kotlinx.kover.gradle.plugin.tools.* +import kotlinx.kover.gradle.plugin.dsl.AggregationType +import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType +import kotlinx.kover.gradle.plugin.dsl.MetricType +import kotlinx.kover.gradle.plugin.tools.BoundViolations import kotlinx.kover.gradle.plugin.tools.RuleViolations +import kotlinx.kover.gradle.plugin.tools.generateErrorMessage import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED -import org.gradle.internal.reflect.* -import java.math.* +import org.gradle.internal.reflect.JavaMethod +import java.io.File +import java.math.BigDecimal import java.util.* internal fun ReportContext.jacocoVerify( rules: List, - filters: ReportFilters -): List { - callAntReport(filters, projectPath) { + outputFile: File +) { + val violations = doVerify(rules) + + val errorMessage = generateErrorMessage(violations) + outputFile.writeText(errorMessage) + + if (violations.isNotEmpty()) { + throw KoverVerificationException(errorMessage) + } +} + + +private fun ReportContext.doVerify(rules: List): List { + + callAntReport(projectPath) { invokeWithBody("check", mapOf("failonviolation" to "false", "violationsproperty" to "jacocoErrors")) { rules.forEach { val entityType = when (it.entityType) { @@ -80,7 +95,6 @@ internal fun ReportContext.jacocoVerify( } - private val errorMessageRegex = "Rule violated for (\\w+) (.+): (\\w+) (.+) is ([\\d\\.]+), but expected (\\w+) is ([\\d\\.]+)".toRegex() diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt index 815c11fc..72a3dac7 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt @@ -4,80 +4,100 @@ package kotlinx.kover.gradle.plugin.tools.kover -import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.commons.ReportFilters -import kotlinx.kover.gradle.plugin.util.json.* -import java.io.* - -internal fun ReportContext.koverHtmlReport(htmlDir: File, title: String, charset: String?, filters: ReportFilters) { - val aggGroups = aggregateRawReports(listOf(filters)) - generateHtmlOrXml(aggGroups.first(), htmlDir = htmlDir, title = title, charset = charset) +import com.intellij.rt.coverage.report.api.Filters +import com.intellij.rt.coverage.report.api.ReportApi +import kotlinx.kover.gradle.plugin.commons.ReportContext +import kotlinx.kover.gradle.plugin.util.asPatterns +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkQueue +import java.io.File + + +internal fun ReportContext.koverHtmlReport(htmlReportDir: File, htmlTitle: String, charsetName: String?) { + val workQueue: WorkQueue = services.workerExecutor.classLoaderIsolation { + this.classpath.from(this@koverHtmlReport.classpath) + } + + workQueue.submit(HtmlReportAction::class.java) { + htmlDir.set(htmlReportDir) + title.convention(htmlTitle) + charset.convention(charsetName) + filters.convention(this@koverHtmlReport.filters) + + files.convention(this@koverHtmlReport.files) + tempDir.set(this@koverHtmlReport.tempDir) + projectPath.convention(this@koverHtmlReport.projectPath) + }} + +internal fun ReportContext.koverXmlReport(xmlReportFile: File) { + val workQueue: WorkQueue = services.workerExecutor.classLoaderIsolation { + classpath.from(this@koverXmlReport.classpath) + } + + workQueue.submit(XmlReportAction::class.java) { + xmlFile.set(xmlReportFile) + filters.convention(this@koverXmlReport.filters) + + files.convention(this@koverXmlReport.files) + + tempDir.set(this@koverXmlReport.tempDir) + projectPath.convention(this@koverXmlReport.projectPath) + } } -internal fun ReportContext.koverXmlReport(xmlFile: File, filters: ReportFilters) { - val aggGroups = aggregateRawReports(listOf(filters)) - generateHtmlOrXml(aggGroups.first(), xmlFile = xmlFile) +internal interface XmlReportParameters: ReportParameters { + val xmlFile: RegularFileProperty } -private fun ReportContext.generateHtmlOrXml( - aggGroup: AggregationGroup, - htmlDir: File? = null, - xmlFile: File? = null, - title: String? = null, - charset: String? = null, -) { - val argsFile = tempDir.resolve("kover-report.json") - argsFile.writeHtmlOrXmlJson(files.sources, aggGroup, xmlFile, htmlDir, title, charset) - - services.exec.javaexec { - mainClass.set("com.intellij.rt.coverage.report.Main") - this@javaexec.classpath = this@generateHtmlOrXml.classpath - args = mutableListOf(argsFile.canonicalPath) - } +internal interface HtmlReportParameters: ReportParameters { + val htmlDir: DirectoryProperty + val title: Property } +internal abstract class XmlReportAction : WorkAction { + override fun execute() { + val files = parameters.files.get() + val filtersIntern = parameters.filters.get() + val filters = Filters( + filtersIntern.includesClasses.toList().asPatterns(), + filtersIntern.excludesClasses.toList().asPatterns(), + filtersIntern.excludesAnnotations.toList().asPatterns() + ) -/* -JSON format: -``` -{ - "format": "kover-agg", - "title": "report title" [OPTIONAL], - "reports": [{ic: String, "smap": String}], // single element - "modules": [{sources: [String...]}], // single element - "xml": String, // optional - "html": String // optional + ReportApi.xmlReport( + parameters.xmlFile.get().asFile, + files.reports.toList(), + files.outputs.toList(), + files.sources.toList(), + filters + ) + } } -``` - */ -private fun File.writeHtmlOrXmlJson( - sources: Set, - aggregationGroup: AggregationGroup, - xmlFile: File?, - htmlDir: File?, - title: String?, - charset: String?, -) { - writeJsonObject(mutableMapOf( - // required fields - - "format" to "kover-agg", - "reports" to listOf(mapOf("ic" to aggregationGroup.ic, "smap" to aggregationGroup.smap)), - "modules" to listOf(mapOf("sources" to sources)), - ).also { - // optional fields - - title?.also { t -> - it["title"] = t - } - xmlFile?.also { f -> - it["xml"] = f - } - htmlDir?.also { d -> - it["html"] = d - } - charset?.also { c -> - it["charset"] = c - } - }) + +internal abstract class HtmlReportAction : WorkAction { + override fun execute() { + val htmlDir = parameters.htmlDir.get().asFile + htmlDir.mkdirs() + + val files = parameters.files.get() + val filtersIntern = parameters.filters.get() + val filters = Filters( + filtersIntern.includesClasses.toList().asPatterns(), + filtersIntern.excludesClasses.toList().asPatterns(), + filtersIntern.excludesAnnotations.toList().asPatterns() + ) + + ReportApi.htmlReport( + parameters.htmlDir.get().asFile, + parameters.title.get(), + parameters.charset.orNull, + files.reports.toList(), + files.outputs.toList(), + files.sources.toList(), + filters + ) + } } diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverReportAggregator.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverReportAggregator.kt index 4c709844..88d939f8 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverReportAggregator.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverReportAggregator.kt @@ -4,15 +4,15 @@ package kotlinx.kover.gradle.plugin.tools.kover -import kotlinx.kover.gradle.plugin.commons.* +import com.intellij.rt.coverage.aggregate.api.AggregatorApi +import com.intellij.rt.coverage.aggregate.api.Request +import com.intellij.rt.coverage.report.api.Filters +import kotlinx.kover.gradle.plugin.commons.ArtifactContent import kotlinx.kover.gradle.plugin.commons.ReportFilters -import kotlinx.kover.gradle.plugin.util.* -import kotlinx.kover.gradle.plugin.util.json.* -import java.io.* - -internal fun ReportContext.aggregateRawReports(filters: List): List { - val aggRequestFile = tempDir.resolve("agg-request.json") +import kotlinx.kover.gradle.plugin.util.asPatterns +import java.io.File +internal fun aggregateRawReports(files: ArtifactContent, filters: List, tempDir: File): List { val aggGroups = filters.mapIndexed { index: Int, reportFilters: ReportFilters -> val filePrefix = if (filters.size > 1) "-$index" else "" AggregationGroup( @@ -22,68 +22,18 @@ internal fun ReportContext.aggregateRawReports(filters: List): Li ) } - aggRequestFile.writeAggJson(files, aggGroups) - services.exec.javaexec { - mainClass.set("com.intellij.rt.coverage.aggregate.Main") - this@javaexec.classpath = this@aggregateRawReports.classpath - args = mutableListOf(aggRequestFile.canonicalPath) + val requests = aggGroups.map { group -> + val filtersR = Filters( + group.filters.includesClasses.toList().asPatterns(), + group.filters.excludesClasses.toList().asPatterns(), + group.filters.excludesAnnotations.toList().asPatterns() + ) + Request(filtersR, group.ic, group.smap) } + AggregatorApi.aggregate(requests, files.reports.toList(), files.outputs.toList()) + return aggGroups } internal class AggregationGroup(val ic: File, val smap: File, val filters: ReportFilters) - -/* -{ - "reports": [{"ic": "path"}, ...], - "modules": [{"output": ["path1", "path2"], "sources": ["source1",...]},... ], - "result": [{ - "filters": { // optional - "include": { // optional - "classes": [ String,... ] - }, - "exclude": { // optional - "classes": [ String,... ] - "annotations": [String,...] - } - }, - "aggregatedReportFile": String, - "smapFile": String - },... - ] - - } -} - */ -private fun File.writeAggJson( - artifactContent: ArtifactContent, - groups: List -) { - writeJsonObject(mapOf( - "reports" to artifactContent.reports.map { mapOf("ic" to it) }, - "modules" to listOf(mapOf("sources" to artifactContent.sources, "output" to artifactContent.outputs)), - "result" to groups.map { group -> - mapOf( - "aggregatedReportFile" to group.ic, - "smapFile" to group.smap, - "filters" to mutableMapOf().also { - if (group.filters.includesClasses.isNotEmpty()) { - it["include"] = - mapOf("classes" to group.filters.includesClasses.map { c -> c.wildcardsToRegex() }) - } - val excludes = mutableMapOf() - if (group.filters.excludesClasses.isNotEmpty()) { - excludes += "classes" to group.filters.excludesClasses.map { c -> c.wildcardsToRegex() } - } - if (group.filters.excludesAnnotations.isNotEmpty()) { - excludes += "annotations" to group.filters.excludesAnnotations.map { c -> c.wildcardsToRegex() } - } - if (excludes.isNotEmpty()) { - it["exclude"] = excludes - } - } - ) - } - )) -} diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt index 50c77f22..efa50f52 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt @@ -4,11 +4,16 @@ package kotlinx.kover.gradle.plugin.tools.kover -import kotlinx.kover.gradle.plugin.commons.* +import kotlinx.kover.gradle.plugin.commons.ArtifactContent +import kotlinx.kover.gradle.plugin.commons.ReportContext +import kotlinx.kover.gradle.plugin.commons.ReportFilters import kotlinx.kover.gradle.plugin.commons.VerificationRule import kotlinx.kover.gradle.plugin.tools.* import org.gradle.api.* import org.gradle.api.file.* +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.workers.WorkParameters import java.io.* @@ -33,19 +38,35 @@ internal class KoverTool(override val variant: CoverageToolVariant) : CoverageTo return buildJvmAgentArgs(jarFile, tempDir, rawReportFile, excludedClasses) } - override fun xmlReport(xmlFile: File, filters: ReportFilters, context: ReportContext) { - context.koverXmlReport(xmlFile, filters) + override fun xmlReport(xmlFile: File, context: ReportContext) { + context.koverXmlReport(xmlFile) } - override fun htmlReport(htmlDir: File, title: String, charset: String?, filters: ReportFilters, context: ReportContext) { - context.koverHtmlReport(htmlDir, title, charset, filters) + override fun htmlReport(htmlDir: File, title: String, charset: String?, context: ReportContext) { + context.koverHtmlReport(htmlDir, title, charset) } override fun verify( rules: List, - commonFilters: ReportFilters, + outputFile: File, context: ReportContext - ): List { - return context.koverVerify(rules, commonFilters) + ) { + context.koverVerify(rules, outputFile) } } + + +internal interface ReportParameters: WorkParameters { + val filters: Property + + val files: Property + val tempDir: DirectoryProperty + val projectPath: Property + val charset: Property +} + + +internal interface VerifyReportParameters: ReportParameters { + val outputFile: RegularFileProperty + val rules: ListProperty +} \ No newline at end of file diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt index 1da92fbf..7790c013 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt @@ -4,35 +4,93 @@ package kotlinx.kover.gradle.plugin.tools.kover +import com.intellij.rt.coverage.verify.Verifier +import com.intellij.rt.coverage.verify.api.* +import com.intellij.rt.coverage.verify.api.Target import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.dsl.* -import kotlinx.kover.gradle.plugin.tools.* -import kotlinx.kover.gradle.plugin.util.* -import kotlinx.kover.gradle.plugin.util.json.* -import org.gradle.api.* -import org.gradle.api.file.* -import org.gradle.process.* -import org.jetbrains.kotlin.gradle.utils.* -import java.io.* -import java.math.* +import kotlinx.kover.gradle.plugin.dsl.AggregationType +import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType +import kotlinx.kover.gradle.plugin.dsl.MetricType +import kotlinx.kover.gradle.plugin.tools.BoundViolations +import kotlinx.kover.gradle.plugin.tools.RuleViolations +import kotlinx.kover.gradle.plugin.tools.generateErrorMessage +import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkQueue +import java.io.File +import java.math.BigDecimal +import java.math.RoundingMode import java.util.* -internal fun ReportContext.koverVerify(rules: List, commonFilters: ReportFilters): List { +internal fun ReportContext.koverVerify(specifiedRules: List, outputReportFile: File) { + val workQueue: WorkQueue = services.workerExecutor.classLoaderIsolation { + this.classpath.from(this@koverVerify.classpath) + } + + workQueue.submit(VerifyReportAction::class.java) { + outputFile.set(outputReportFile) + rules.convention(specifiedRules) + filters.convention(this@koverVerify.filters) + + files.convention(this@koverVerify.files) + tempDir.set(this@koverVerify.tempDir) + projectPath.convention(this@koverVerify.projectPath) + } +} + + + +internal abstract class VerifyReportAction : WorkAction { + override fun execute() { + val violations = koverVerify( + parameters.rules.get(), + parameters.filters.get(), + parameters.tempDir.get().asFile, + parameters.files.get() + ) + + val errorMessage = generateErrorMessage(violations) + parameters.outputFile.get().asFile.writeText(errorMessage) + + if (violations.isNotEmpty()) { + throw KoverVerificationException(errorMessage) + } + } +} + +internal fun koverVerify( + rules: List, + commonFilters: ReportFilters, + tempDir: File, + files: ArtifactContent +): List { val rulesByFilter = groupRules(rules, commonFilters) val usedFilters = rulesByFilter.map { it.first } val groupedRules = rulesByFilter.map { it.second } - val groups = aggregateRawReports(usedFilters) - - val verifyRequest = tempDir.resolve("verify-request.json") - val verifyResponseFile = tempDir.resolve("verify-result.json") - verifyRequest.writeVerifyJson(groups, groupedRules, verifyResponseFile) - services.exec.javaexec { - mainClass.set("com.intellij.rt.coverage.verify.Main") - this@javaexec.classpath = this@koverVerify.classpath - args = mutableListOf(verifyRequest.canonicalPath) + + val groups = aggregateRawReports(files, usedFilters, tempDir) + + val rulesArray = mutableListOf() + groups.forEachIndexed { index, group -> + val rulesForGroup = groupedRules[index] + rulesForGroup.forEachIndexed { ruleIndex, rule -> + val bounds = rule.bounds.mapIndexed { boundIndex, b -> + Bound( + boundIndex, b.counterToReporter(), b.valueTypeToReporter(), b.valueToReporter(b.minValue), + b.valueToReporter(b.maxValue) + ) + + } + rulesArray += Rule(ruleIndex, group.ic, rule.targetToReporter(), bounds) + } } - val violations = verifyResponseFile.readJsonObject() + + val verifier = Verifier(rulesArray) + verifier.processRules() + + val violations = VerificationApi.verify(rulesArray) + return processViolations(rules, violations) } @@ -56,90 +114,34 @@ private fun groupRules( return groupedMap.entries.map { it.key to it.value } } -/* -{ - "resultFile": String, - "rules": [{ - "id": Int, - "aggregatedReportFile": String, - "targetType": String, // enum values: "CLASS", "PACKAGE", "ALL", (later may be added "FILE" and "FUNCTION") - "bounds": [ - { - "id": Int, - "counter": String, // "LINE", "INSTRUCTION", "BRANCH" - "valueType": String, // "MISSED", "COVERED", "MISSED_RATE", "COVERED_RATE" - "min": BigDecimal, // optional - "max": BigDecimal, // optional - },... - ] - },... - ] -} - */ -private fun File.writeVerifyJson( - groups: List, - groupedRules: List>, - result: File, -) { - val rulesArray = mutableListOf>() - - groups.forEachIndexed { index, group -> - val rules = groupedRules[index] - rules.forEachIndexed { ruleIndex, rule -> - rulesArray += mapOf( - "id" to ruleIndex, - "aggregatedReportFile" to group.ic, - "smapFile" to group.smap, - "targetType" to rule.targetToReporter(), - "bounds" to rule.bounds.mapIndexed { boundIndex, b -> - mutableMapOf( - "id" to boundIndex, - "counter" to b.counterToReporter(), - "valueType" to b.valueTypeToReporter(), - ).also { - val minValue = b.minValue - val maxValue = b.maxValue - if (minValue != null) { - it["min"] = b.valueToReporter(minValue) - } - if (maxValue != null) { - it["max"] = b.valueToReporter(maxValue) - } - } - } - ) - } - } - - writeJsonObject(mapOf("resultFile" to result, "rules" to rulesArray)) -} -private fun VerificationRule.targetToReporter(): String { +private fun VerificationRule.targetToReporter(): Target { return when (entityType) { - GroupingEntityType.APPLICATION -> "ALL" - GroupingEntityType.CLASS -> "CLASS" - GroupingEntityType.PACKAGE -> "PACKAGE" + GroupingEntityType.APPLICATION -> Target.ALL + GroupingEntityType.CLASS -> Target.CLASS + GroupingEntityType.PACKAGE -> Target.PACKAGE } } -private fun VerificationBound.counterToReporter(): String { +private fun VerificationBound.counterToReporter(): Counter { return when (metric) { - MetricType.LINE -> "LINE" - MetricType.INSTRUCTION -> "INSTRUCTION" - MetricType.BRANCH -> "BRANCH" + MetricType.LINE -> Counter.LINE + MetricType.INSTRUCTION -> Counter.INSTRUCTION + MetricType.BRANCH -> Counter.BRANCH } } -private fun VerificationBound.valueTypeToReporter(): String { +private fun VerificationBound.valueTypeToReporter(): ValueType { return when (aggregation) { - AggregationType.COVERED_COUNT -> "COVERED" - AggregationType.MISSED_COUNT -> "MISSED" - AggregationType.COVERED_PERCENTAGE -> "COVERED_RATE" - AggregationType.MISSED_PERCENTAGE -> "MISSED_RATE" + AggregationType.COVERED_COUNT -> ValueType.COVERED + AggregationType.MISSED_COUNT -> ValueType.MISSED + AggregationType.COVERED_PERCENTAGE -> ValueType.COVERED_RATE + AggregationType.MISSED_PERCENTAGE -> ValueType.MISSED_RATE } } -private fun VerificationBound.valueToReporter(value: BigDecimal): BigDecimal { +private fun VerificationBound.valueToReporter(value: BigDecimal?): BigDecimal? { + value ?: return null return if (aggregation.isPercentage) { value.divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP) } else { @@ -147,10 +149,9 @@ private fun VerificationBound.valueToReporter(value: BigDecimal): BigDecimal { } } -@Suppress("UNCHECKED_CAST") private fun processViolations( rules: List, - violations: Map + violations: List ): List { val rulesMap = rules.mapIndexed { index, rule -> index to rule }.associate { it } @@ -158,8 +159,8 @@ private fun processViolations( val result = TreeMap() try { - violations.forEach { (ruleIdString, boundViolations) -> - val ruleIndex = ruleIdString.toInt() + violations.forEach { violation -> + val ruleIndex = violation.id val rule = rulesMap[ruleIndex] ?: throw KoverCriticalException("Error occurred while parsing verification result: unmapped rule with index $ruleIndex") @@ -168,19 +169,18 @@ private fun processViolations( // the order of the bound is guaranteed for Kover (as in config + suborder by entity name) val boundsResult = TreeMap() - (boundViolations as Map>>).forEach { (boundIdString, v) -> - val boundIndex = boundIdString.toInt() + violation.violations.forEach { boundViolation -> + val boundIndex = boundViolation.id val bound = boundsMap[boundIndex] ?: throw KoverCriticalException("Error occurred while parsing verification error: unmapped bound with index $boundIndex and rule index $ruleIndex") - v["min"]?.map { + boundViolation.minViolations.forEach { bound.minValue ?: throw KoverCriticalException("Error occurred while parsing verification error: no minimal bound with ID $boundIndex and rule index $ruleIndex") - val entityName = it.key.ifEmpty { null } - val rawValue = it.value - val value = if (rawValue is String) rawValue.toBigDecimal() else rawValue as BigDecimal + val entityName = it.targetName.ifEmpty { null } + val value = it.targetValue val actual = if (bound.aggregation.isPercentage) value * ONE_HUNDRED else value boundsResult += ViolationId(boundIndex, entityName) to BoundViolations( false, @@ -192,13 +192,12 @@ private fun processViolations( ) } - v["max"]?.map { + boundViolation.maxViolations.forEach { bound.maxValue ?: throw KoverCriticalException("Error occurred while parsing verification error: no maximal bound with index $boundIndex and rule index $ruleIndex") - val entityName = it.key.ifEmpty { null } - val rawValue = it.value - val value = if (rawValue is String) rawValue.toBigDecimal() else rawValue as BigDecimal + val entityName = it.targetName.ifEmpty { null } + val value = it.targetValue val actual = if (bound.aggregation.isPercentage) value * ONE_HUNDRED else value boundsResult += ViolationId(boundIndex, entityName) to BoundViolations( true, @@ -220,7 +219,7 @@ private fun processViolations( return result.values.toList() } -private data class ViolationId(val index: Int, val entityName: String?): Comparable { +private data class ViolationId(val index: Int, val entityName: String?) : Comparable { override fun compareTo(other: ViolationId): Int { // first compared by index index.compareTo(other.index).takeIf { it != 0 }?.let { return it } diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt index 8cd7e7f8..94be6ba3 100644 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt +++ b/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt @@ -5,6 +5,9 @@ package kotlinx.kover.gradle.plugin.util import java.io.File +import java.util.regex.Pattern + +internal fun List.asPatterns(): List = map { Pattern.compile(it.wildcardsToRegex()) } /** * Executes `block` of code only if boolean value is `true`. diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleParser.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleParser.kt deleted file mode 100644 index 9ceeac3c..00000000 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleParser.kt +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.gradle.plugin.util.json - -import java.io.* -import java.math.BigDecimal - -internal fun File.readJsonObject(): Map { - return FileJsonReader(this).use { - it.readObject()!! - } -} - -internal fun File.readJsonArray(): List { - return FileJsonReader(this).use { - it.readArray()!! - } -} - -internal class FileJsonReader(file: File) : JsonReader() { - private val buffered = file.bufferedReader() - private var char: Char? = buffered.read().let { if (it == -1) null else it.toChar() } - private var pos: Int = 0 - - override fun next() { - char = buffered.read().let { if (it == -1) null else it.toChar() } - pos++ - } - - override fun eof(): Boolean = char == null - - override fun pos(): Int = pos - - override fun char(): Char = char ?: throw Exception("Can't get current char: JSON reader at the end-of file") - - override fun close() = buffered.close() -} - -/** - * Simple lightweight JSON parser. - * It does not support all the features of the JSON format, because its purpose is very limited. - * - * Type parsing rules: - * - JSON object converts to `Map` - * - JSON array converts to `List` - * - JSON string literal converts to `String` (escapes are not supported) - * - JSON numeric literal converts to `BigDecimal` - * - JSON boolean literal not supported - * - JSON null literal not supported - */ -internal abstract class JsonReader : Closeable { - - fun readObject(): Map? { - skipSpaces() - if (!skipObjectOpen()) { - return null - } - - var isFirst = true - val result = mutableMapOf() - while (!skipObjectClose()) { - if (eof()) { - throw Exception("Unexpected end of file: object is not closed") - } - if (!isFirst) { - if (!skipComma()) { - throw Exception("Expected comma between object fields at pos ${pos()}") - } - } - isFirst = false - - val fieldName = readStringLiteral() ?: throw Exception("Can't read name of field at pos ${pos()}") - if (!skipValueDelimiter()) { - throw Exception("Expected ':' or '=' char between field name and value inside the object at pos ${pos()}") - } - val fieldValue = nextValue() ?: throw Exception("Can't read value of the field at pos ${pos()}") - result[fieldName] = fieldValue - } - - return result - } - - fun readArray(): List? { - skipSpaces() - if (!skipArrayOpen()) { - return null - } - - val result = mutableListOf() - var isFirst = true - while (!skipArrayClose()) { - if (eof()) { - throw Exception("Unexpected end of file: array is not closed") - } - - if (!isFirst) { - if (!skipComma()) { - throw Exception("Expected comma between array values at pos ${pos()}") - } - } - isFirst = false - - result += nextValue() ?: throw Exception("Can't read value of in the array at pos ${pos()}") - } - return result - } - - - private fun nextValue(): Any? { - readStringLiteral()?.let { return it } - readObject()?.let { return it } - readArray()?.let { return it } - readNumber()?.let { return it } - return null - } - - private fun readStringLiteral(): String? { - skipSpaces() - if (char() != '"') { - return null - } - - var buffer = CharArray(16) - var size = 0 - next() - - while (char() != '"') { - if (size == buffer.size) { - buffer = buffer.copyOf(size * 2) - } - buffer[size++] = char() - - next() - if (eof()) { - throw Exception("Unexpected end of file: string literal is not terminated") - } - } - next() - - return buffer.concatToString(0, size) - } - - private fun readNumber(): BigDecimal? { - skipSpaces() - - if (!char().isDigit() && char() != '-') { - return null - } - - var hasDot = false - var buffer = CharArray(8) - var size = 0 - buffer[size++] = char() - next() - - while (!eof()) { - val char = char() - if (char.isWhitespace() || char == ',' || char == '}' || char == ']') { - break - } - - if (size == buffer.size) { - buffer = buffer.copyOf(size * 2) - } - - if (char.isDigit()) { - buffer[size++] = char - next() - } else if (char == '.') { - if (hasDot) { - throw Exception("") - } - buffer[size++] = char - next() - hasDot = true - } else { - throw Exception("") - } - } - - return buffer.concatToString(0, size).toBigDecimal() - } - - - private fun skipComma(): Boolean { - skipSpaces() - return when (char()) { - ',' -> { - next() - true - } - else -> false - } - } - - private fun skipValueDelimiter(): Boolean { - skipSpaces() - return when (char()) { - ':' -> { - next() - true - } - '=' -> { - next() - true - } - else -> false - } - } - - private fun skipArrayOpen(): Boolean { - skipSpaces() - if (char() == '[') { - next() - return true - } - return false - } - - private fun skipArrayClose(): Boolean { - skipSpaces() - if (char() == ']') { - next() - return true - } - return false - } - - - private fun skipObjectOpen(): Boolean { - skipSpaces() - if (char() == '{') { - next() - return true - } - return false - } - - private fun skipObjectClose(): Boolean { - skipSpaces() - if (char() == '}') { - next() - return true - } - return false - } - - private fun skipSpaces(): Boolean { - var hasSpaces = false - while (!eof()) { - if (!char().isWhitespace()) { - break - } - next() - hasSpaces = true - } - return hasSpaces - } - - protected abstract fun pos(): Int - - protected abstract fun eof(): Boolean - - protected abstract fun next() - - protected abstract fun char(): Char -} diff --git a/src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleWriter.kt b/src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleWriter.kt deleted file mode 100644 index 6b80fd51..00000000 --- a/src/main/kotlin/kotlinx/kover/gradle/plugin/util/json/SimpleWriter.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.gradle.plugin.util.json - -import java.io.* -import java.math.BigDecimal - -/* - * Simple lightweight converter to JSON string. - * It does not support all the features of the JSON format, because its purpose is very limited. - * - * Type converting rules: - * - `Map` converts to JSON object - * - `Iterable` converts to JSON array - * - `String` converts to JSON string literal (escapes are supported) - * - `Int` converts to JSON number literal - * - `BigDecimal` converts to JSON string literal - * - `File` converts to JSON string literal with canonicalPath (escapes are supported) - * - `Boolean` converts to JSON boolean literal - * - `null` values not supported - */ - -internal fun File.writeJsonObject(obj: Map) { - printWriter().use { pw -> - pw.writeObject(obj, 0) - } -} - -internal fun File.writeJsonArray(array: Iterable) { - printWriter().use { pw -> - pw.writeArray(array, 0) - } -} - - - -internal fun PrintWriter.writeObject(obj: Map, spaces: Int) { - val indent = " ".repeat(spaces + 1) - - append('{') - if (obj.size > 1) { - appendLine() - append(indent) - } - - var first = true - obj.forEach { (name, value) -> - if (!first) { - appendLine(',') - append(indent) - } - append('"').append(name).append("\": ") - writeValue(value, spaces) - first = false - } - if (obj.size > 1) { - appendLine() - append(" ".repeat(spaces)) - } - append('}') -} - -internal fun PrintWriter.writeArray(obj: Iterable, spaces: Int) { - val indent = " ".repeat(spaces + 1) - - append('[') - if (obj.count() > 1) { - appendLine() - append(indent) - } - - var first = true - obj.forEach { - if (!first) { - appendLine(',') - append(indent) - } - writeValue(it, spaces) - first = false - } - if (obj.count() > 1) { - appendLine() - append(" ".repeat(spaces)) - } - append(']') -} - -@Suppress("UNCHECKED_CAST") -private fun PrintWriter.writeValue(value: Any, spaces: Int) { - when (value) { - is String -> append(value.jsonEscapes) - is Int -> append(value.toString()) - is Map<*, *> -> writeObject(value as Map, spaces + 1) - is Iterable<*> -> writeArray(value as Iterable, spaces + 1) - is File -> append(value.canonicalPath.jsonEscapes) - is BigDecimal -> { - append('"') - append(value.toPlainString()) - append('"') - } - is Boolean -> append(value.toString()) - else -> { - throw Exception("Unsupported type ${value.javaClass}") - } - } -} - -internal val String.jsonEscapes: String - get() { - return '"' + replace("\\", "\\\\").replace("\"", "\\\"") + '"' - } diff --git a/src/test/kotlin/JsonTests.kt b/src/test/kotlin/JsonTests.kt deleted file mode 100644 index 60ccd8f9..00000000 --- a/src/test/kotlin/JsonTests.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.json - -import kotlinx.kover.gradle.plugin.util.json.* -import java.io.File -import java.math.BigDecimal -import kotlin.test.* - -class JsonTests { - private val file = File("my-test-file") - private val escapedFilePath = file.canonicalPath.replace("\\", "\\\\") - - private val encodingText = """{ - "field": "text", - "numberField": 42, - "array": [ - "array \" value", - true, - "$escapedFilePath" - ], - "BD": "230" -}""" - - private val encodingObject = mapOf( - "field" to "text", - "numberField" to 42, - "array" to listOf("array \" value", true, file), - "BD" to 230.toBigDecimal() - ) - - private val decodingText = """[{ - "very long field name to check array expansion": {"0": {"min": {"all": 3.0000007}}}, - "id": 0 -}]""" - - private val decodedObject = listOf( - mapOf( - "very long field name to check array expansion" to mapOf("0" to mapOf("min" to mapOf("all" to "3.0000007".toBigDecimal()))), - "id" to BigDecimal.ZERO - ) - ) - - @Test - fun testEncoding() { - val file = File.createTempFile("encoding", "json") - file.writeJsonObject(encodingObject) - assertEquals(encodingText, file.readText()) - } - - @Test - fun testDecoding() { - val file = File.createTempFile("decoding", "json") - file.writeText(decodingText) - val decoded = file.readJsonArray() - assertEquals(decodedObject, decoded) - } -} diff --git a/toolset/kover-cli/build.gradle.kts b/toolset/kover-cli/build.gradle.kts index f23c1585..4880b7ef 100644 --- a/toolset/kover-cli/build.gradle.kts +++ b/toolset/kover-cli/build.gradle.kts @@ -30,7 +30,7 @@ java { } dependencies { - implementation("org.jetbrains.intellij.deps:intellij-coverage-reporter:1.0.715") + implementation("org.jetbrains.intellij.deps:intellij-coverage-reporter:1.0.719") implementation("args4j:args4j:2.33") testImplementation(kotlin("test")) diff --git a/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt b/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt index 322c952f..76e3665e 100644 --- a/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt +++ b/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt @@ -16,8 +16,8 @@ package kotlinx.kover.cli.commands -import com.intellij.rt.coverage.instrument.Instrumentator -import com.intellij.rt.coverage.report.data.Filters +import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi +import com.intellij.rt.coverage.report.api.Filters import kotlinx.kover.cli.util.asPatterns import org.kohsuke.args4j.Argument import org.kohsuke.args4j.Option @@ -77,8 +77,7 @@ internal class OfflineInstrumentCommand : Command { ) try { - val instrumentator = Instrumentator(roots, outputRoots, filters) - instrumentator.instrument(countHits) + OfflineInstrumentationApi.instrument(roots, outputRoots, filters, countHits) } catch (e: Exception) { errorWriter.println("Instrumentation failed: " + e.message) return -1 diff --git a/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt b/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt index 5e88fe7e..5c8cbdff 100644 --- a/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt +++ b/toolset/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt @@ -16,12 +16,8 @@ package kotlinx.kover.cli.commands -import com.intellij.rt.coverage.report.ReportLoadStrategy -import com.intellij.rt.coverage.report.ReportLoadStrategy.RawReportLoadStrategy -import com.intellij.rt.coverage.report.Reporter -import com.intellij.rt.coverage.report.data.BinaryReport -import com.intellij.rt.coverage.report.data.Filters -import com.intellij.rt.coverage.report.data.Module +import com.intellij.rt.coverage.report.api.Filters +import com.intellij.rt.coverage.report.api.ReportApi import kotlinx.kover.cli.util.asPatterns import org.kohsuke.args4j.Argument import org.kohsuke.args4j.Option @@ -81,22 +77,15 @@ internal class ReportCommand : Command { override fun call(output: PrintWriter, errorWriter: PrintWriter): Int { - val binaryReports: MutableList = ArrayList() - for (binaryReport in this.binaryReports) { - binaryReports.add(BinaryReport(binaryReport, null)) - } - val module = Module(outputRoots, sourceRoots) val filters = Filters( includeClasses.asPatterns(), excludeClasses.asPatterns(), excludeAnnotation.asPatterns() ) - val loadStrategy: ReportLoadStrategy = RawReportLoadStrategy(binaryReports, listOf(module), filters) - val reporter = Reporter(loadStrategy) var fail = false if (xmlFile != null) { try { - reporter.createXMLReport(xmlFile) + ReportApi.xmlReport(xmlFile, binaryReports, outputRoots, sourceRoots, filters) } catch (e: IOException) { fail = true errorWriter.println("XML generation failed: " + e.message) @@ -104,7 +93,7 @@ internal class ReportCommand : Command { } if (htmlDir != null) { try { - reporter.createHTMLReport(htmlDir, htmlTitle) + ReportApi.htmlReport(htmlDir, htmlTitle, null, binaryReports, outputRoots, sourceRoots, filters) } catch (e: IOException) { fail = true errorWriter.println("HTML generation failed: " + e.message) diff --git a/toolset/kover-offline-runtime/build.gradle.kts b/toolset/kover-offline-runtime/build.gradle.kts index 6db6cd81..52b5407c 100644 --- a/toolset/kover-offline-runtime/build.gradle.kts +++ b/toolset/kover-offline-runtime/build.gradle.kts @@ -29,7 +29,7 @@ repositories { } dependencies { - implementation("org.jetbrains.intellij.deps:intellij-coverage-offline:1.0.715") + implementation("org.jetbrains.intellij.deps:intellij-coverage-offline:1.0.719") } tasks.jar {