From 648e61473a11cd8ecd761fdca4b76b6bf9e9654a Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 21 Mar 2024 12:11:27 +0100 Subject: [PATCH 1/6] Used compile dependency to Kover Features in Kover Gradle Plugin Since the version of the aggregator and report generator has been enshrined in the Kover Gradle Plugin for a long time, it makes no sense to use dynamic classpath of and classloader isolations in Gradle Workers. We can use Kover Features as a compile dependency and use its API. This change can also eliminate the error with access to Freemarker template inside Gradle Worker, and simplify the implementation of warn on verification error. Resolves #567 --- .../cli/commands/OfflineInstrumentCommand.kt | 7 +- .../kover/cli/commands/ReportCommand.kt | 9 +- .../kotlinx/kover/cli/util/KoverUtils.kt | 41 --- .../kover/features/jvm/KoverFeatures.java | 14 + .../features/jvm/KoverLegacyFeatures.java | 259 +++++++++++++++--- .../features/jvm/LegacyVerification.java | 177 ++++++++++++ .../kotlinx/kover/features/jvm/Wildcards.java | 70 +++++ kover-gradle-plugin/build.gradle.kts | 1 - .../functional/framework/checker/Checker.kt | 6 +- .../kover/gradle/plugin/commons/Types.kt | 1 - .../tasks/reports/AbstractKoverReportTask.kt | 5 +- .../kover/gradle/plugin/tools/Verification.kt | 75 ++--- .../gradle/plugin/tools/jacoco/Evaluation.kt | 11 +- .../gradle/plugin/tools/jacoco/JacocoAnt.kt | 4 +- .../plugin/tools/jacoco/Verification.kt | 13 +- .../gradle/plugin/tools/kover/Actions.kt | 54 ---- .../gradle/plugin/tools/kover/Evaluation.kt | 80 ++---- .../gradle/plugin/tools/kover/IcReport.kt | 38 +-- .../gradle/plugin/tools/kover/IntellijApi.kt | 56 ---- .../tools/kover/KoverFeaturesIntegration.kt | 56 ++++ .../tools/kover/KoverHtmlOrXmlReport.kt | 94 ++----- .../tools/kover/KoverOnlineInstrumentation.kt | 1 - .../gradle/plugin/tools/kover/KoverTool.kt | 6 +- .../gradle/plugin/tools/kover/Verification.kt | 217 +-------------- .../kotlinx/kover/gradle/plugin/util/Util.kt | 46 ---- 25 files changed, 664 insertions(+), 677 deletions(-) delete mode 100644 kover-cli/src/main/kotlin/kotlinx/kover/cli/util/KoverUtils.kt create mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java create mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java delete mode 100644 kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Actions.kt delete mode 100644 kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IntellijApi.kt create mode 100644 kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt diff --git a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt index 247944b6..2f517647 100644 --- a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt +++ b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt @@ -16,7 +16,6 @@ package kotlinx.kover.cli.commands -import kotlinx.kover.cli.util.asRegex import kotlinx.kover.features.jvm.KoverLegacyFeatures import org.kohsuke.args4j.Argument import org.kohsuke.args4j.Option @@ -63,9 +62,9 @@ internal class OfflineInstrumentCommand : Command { override fun call(output: PrintWriter, errorWriter: PrintWriter): Int { val filters = KoverLegacyFeatures.ClassFilters( - includeClasses.asRegex().toSet(), - excludeClasses.asRegex().toSet(), - excludeAnnotation.asRegex().toSet() + includeClasses.toSet(), + excludeClasses.toSet(), + excludeAnnotation.toSet() ) try { diff --git a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt index fa0eba6c..ea5e2492 100644 --- a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt +++ b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt @@ -16,7 +16,6 @@ package kotlinx.kover.cli.commands -import kotlinx.kover.cli.util.asRegex import kotlinx.kover.features.jvm.KoverLegacyFeatures import kotlinx.kover.features.jvm.KoverLegacyFeatures.ClassFilters import org.kohsuke.args4j.Argument @@ -78,9 +77,9 @@ internal class ReportCommand : Command { override fun call(output: PrintWriter, errorWriter: PrintWriter): Int { val filters = ClassFilters( - includeClasses.asRegex().toSet(), - excludeClasses.asRegex().toSet(), - excludeAnnotation.asRegex().toSet() + includeClasses.toSet(), + excludeClasses.toSet(), + excludeAnnotation.toSet() ) var fail = false @@ -94,7 +93,7 @@ internal class ReportCommand : Command { } if (htmlDir != null) { try { - KoverLegacyFeatures.generateHtmlReport(htmlDir, binaryReports, outputRoots, sourceRoots, title ?: "Kover HTML Report", filters) + KoverLegacyFeatures.generateHtmlReport(htmlDir, null, binaryReports, outputRoots, sourceRoots, title ?: "Kover HTML Report", filters) } catch (e: IOException) { fail = true errorWriter.println("HTML generation failed: " + e.message) diff --git a/kover-cli/src/main/kotlin/kotlinx/kover/cli/util/KoverUtils.kt b/kover-cli/src/main/kotlin/kotlinx/kover/cli/util/KoverUtils.kt deleted file mode 100644 index e0e01070..00000000 --- a/kover-cli/src/main/kotlin/kotlinx/kover/cli/util/KoverUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package kotlinx.kover.cli.util - -internal fun List.asRegex(): List = map { it.wildcardsToRegex() } - -/** - * Replaces characters `*` or `.` to `.*` and `.` regexp characters and also add escape char '\' before regexp metacharacters (see [regexMetacharactersSet]). - */ -internal fun String.wildcardsToRegex(): String { - // in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance - val builder = StringBuilder(length * 2) - - forEach { char -> - when (char) { - in regexMetacharactersSet -> builder.append('\\').append(char) - '*' -> builder.append('.').append("*") - '?' -> builder.append('.') - else -> builder.append(char) - } - } - - return builder.toString() -} - -private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet() - diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.java index 04ba5dde..28688d23 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.java +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.java @@ -18,6 +18,20 @@ public static String getVersion() { return koverVersion; } + /** + * Converts a Kover template string to a regular expression string. + *

+ * Replaces characters *` or `.` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters. + * All special characters of regular expressions are also escaped. + *

+ * + * @param template Template string in Kover format + * @return Regular expression corresponding given Kover template + */ + public static String koverWildcardToRegex(String template) { + return Wildcards.wildcardsToRegex(template); + } + /** * Create instance to instrument already compiled class-files. * diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.java index 8c091c53..cce7615e 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.java +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.java @@ -1,33 +1,42 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + package kotlinx.kover.features.jvm; +import com.intellij.rt.coverage.aggregate.api.AggregatorApi; +import com.intellij.rt.coverage.aggregate.api.Request; import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi; -import com.intellij.rt.coverage.report.api.Filters; import com.intellij.rt.coverage.report.api.ReportApi; +import com.intellij.rt.coverage.util.ErrorReporter; import java.io.File; import java.io.IOException; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.regex.Pattern; /** * Kover Features for support Kover capabilities in Kover CLI via outdated API. */ public class KoverLegacyFeatures { + private static final String FREE_MARKER_LOGGER_PROPERTY_NAME = "org.freemarker.loggerLibrary"; + /** * Generate modified class-files to measure the coverage. * - * @param resultDir Directory where the instrumented class-files will be placed + * @param resultDir Directory where the instrumented class-files will be placed * @param originalDirs Root directories where the original files are located, the coverage of which needs to be measured - * @param filters Filters to limit the classes that will be displayed in the report - * @param countHits Flag indicating whether to count the number of executions to each block of code. {@code false} if it is enough to register only the fact of at least one execution + * @param filters Filters to limit the classes that will be displayed in the report + * @param countHits Flag indicating whether to count the number of executions to each block of code. {@code false} if it is enough to register only the fact of at least one execution */ public static void instrument(File resultDir, - List originalDirs, - ClassFilters filters, - boolean countHits + List originalDirs, + ClassFilters filters, + boolean countHits ) { ArrayList outputs = new ArrayList<>(originalDirs.size()); for (int i = 0; i < originalDirs.size(); i++) { @@ -36,7 +45,7 @@ public static void instrument(File resultDir, String previousConDySetting = ConDySettings.disableConDy(); try { - OfflineInstrumentationApi.instrument(originalDirs, outputs, convertFilters(filters), countHits); + OfflineInstrumentationApi.instrument(originalDirs, outputs, Wildcards.convertFilters(filters), countHits); } finally { ConDySettings.restoreConDy(previousConDySetting); } @@ -45,12 +54,12 @@ public static void instrument(File resultDir, /** * Generate Kover XML report, compatible with JaCoCo XML. * - * @param xmlFile path to the generated XML report - * @param binaryReports list of coverage binary reports in IC format - * @param classfileDirs list of root directories for compiled class-files - * @param sourceDirs list of root directories for Java and Kotlin source files - * @param title Title for header - * @param filters Filters to limit the classes that will be displayed in the report + * @param xmlFile Path to the generated XML report + * @param binaryReports List of coverage binary reports in IC format + * @param classfileDirs List of root directories for compiled class-files + * @param sourceDirs List of root directories for Java and Kotlin source files + * @param title Title for header + * @param filters Filters to limit the classes that will be displayed in the report * @throws IOException In case of a report generation error */ public static void generateXmlReport( @@ -61,29 +70,106 @@ public static void generateXmlReport( String title, ClassFilters filters ) throws IOException { - ReportApi.xmlReport(xmlFile, title, binaryReports, classfileDirs, sourceDirs, convertFilters(filters)); + ReportApi.xmlReport(xmlFile, title, binaryReports, classfileDirs, sourceDirs, Wildcards.convertFilters(filters)); } /** * Generate Kover HTML report. * - * @param htmlDir output directory with result HTML report - * @param binaryReports list of coverage binary reports in IC format - * @param classfileDirs list of root directories for compiled class-files - * @param sourceDirs list of root directories for Java and Kotlin source files - * @param title Title for header - * @param filters Filters to limit the classes that will be displayed in the report. + * @param htmlDir Output directory with result HTML report + * @param binaryReports List of coverage binary reports in IC format + * @param classfileDirs List of root directories for compiled class-files + * @param sourceDirs List of root directories for Java and Kotlin source files + * @param title Title for header + * @param filters Filters to limit the classes that will be displayed in the report. * @throws IOException In case of a report generation error */ public static void generateHtmlReport( File htmlDir, + String charsetName, List binaryReports, List classfileDirs, List sourceDirs, String title, ClassFilters filters ) throws IOException { - ReportApi.htmlReport(htmlDir, title, null, binaryReports, classfileDirs, sourceDirs, convertFilters(filters)); + // repeat reading freemarker temple from resources if error occurred, see https://github.com/Kotlin/kotlinx-kover/issues/510 + // the values are selected empirically so that the maximum report generation time is not much more than a second + ReportApi.setFreemarkerRetry(7, 150); + + // print to stdout only critical errors + ErrorReporter.setLogLevel(ErrorReporter.ERROR); + + // disable freemarker logging to stdout for the time of report generation + String oldFreemarkerLogger = System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, "none"); + try { + ReportApi.htmlReport(htmlDir, title, charsetName, binaryReports, classfileDirs, sourceDirs, Wildcards.convertFilters(filters)); + } finally { + if (oldFreemarkerLogger == null) { + System.clearProperty(FREE_MARKER_LOGGER_PROPERTY_NAME); + } else { + System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, oldFreemarkerLogger); + } + } + } + + + /** + * Verify coverage by specified verification rules. + * + * @param tempDir Directory to create temporary files + * @param filters Filters to limit the classes that will be verified + * @param binaryReports List of coverage binary binaryReports in IC format + * @param classfileDirs List of root directories for compiled class-files + * @return List of rule violation errors, empty list if there is no verification errors. + */ + public static List verify(List rules, File tempDir, KoverLegacyFeatures.ClassFilters filters, List binaryReports, List classfileDirs) { + try { + return LegacyVerification.verify(rules, tempDir, filters, binaryReports, classfileDirs); + } catch (IOException e) { + throw new RuntimeException("Kover features exception occurred while verification", e); + } + } + + /** + * Merge several IC binaryReports into one file. + * + * @param icFile Target IC report file + * @param filters Filters to limit the classes that will be placed into result file + * @param tempDir Directory to create temporary files + * @param binaryReports List of coverage binary binaryReports in IC format + * @param classfileDirs List of root directories for compiled class-files + */ + public static void aggregateIc(File icFile, KoverLegacyFeatures.ClassFilters filters, File tempDir, List binaryReports, List classfileDirs) { + final File smapFile = new File(tempDir, "report.smap"); + + Request request = new Request(Wildcards.convertFilters(filters), icFile, smapFile); + AggregatorApi.aggregate(Collections.singletonList(request), binaryReports, classfileDirs); + } + + /** + * Get coverage values from binary reports. + * + * @param tempDir Directory to create temporary files + * @param filters Filters to limit the classes that will be placed into result coverage + * @param binaryReports List of coverage binary binaryReports in IC format + * @param classfileDirs List of root directories for compiled class-files + * @return List of coverage values. + */ + public static List evalCoverage(GroupingBy groupBy, CoverageUnit coverageUnit, AggregationType aggregationForGroup, File tempDir, KoverLegacyFeatures.ClassFilters filters, List binaryReports, List classfileDirs) { + Bound bound = new Bound(LegacyVerification.ONE_HUNDRED, BigDecimal.ZERO, coverageUnit, aggregationForGroup); + Rule rule = new Rule("", groupBy, Collections.singletonList(bound)); + + List violations = verify(Collections.singletonList(rule), tempDir, filters, binaryReports, classfileDirs); + ArrayList result = new ArrayList<>(); + + for (KoverLegacyFeatures.RuleViolations violation : violations) { + for (KoverLegacyFeatures.BoundViolation boundViolation : violation.violations) { + result.add(new KoverLegacyFeatures.CoverageValue(boundViolation.entityName, boundViolation.value)); + } + } + + return result; } /** @@ -114,21 +200,126 @@ public ClassFilters(Set includeClasses, } } - private static Filters convertFilters(ClassFilters filters) { - return new Filters( - convert(filters.includeClasses), - convert(filters.excludeClasses), - convert(filters.excludeAnnotation) - ); + /** + * Entity type for grouping code to coverage evaluation. + */ + public enum GroupingBy { + /** + * Counts the coverage values for all code. + */ + APPLICATION, + /** + * Counts the coverage values for each class separately. + */ + CLASS, + /** + * Counts the coverage values for each package that has classes separately. + */ + PACKAGE } - private static List convert(Set regexes) { - ArrayList patterns = new ArrayList<>(regexes.size()); - for (String regex : regexes) { - patterns.add(Pattern.compile(regex)); + /** + * Type of the metric to evaluate code coverage. + */ + public enum CoverageUnit { + /** + * Number of lines. + */ + LINE, + /** + * Number of JVM bytecode instructions. + */ + INSTRUCTION, + /** + * Number of branches covered. + */ + BRANCH + } + + /** + * Type of counter value to compare with minimal and maximal values if them defined. + */ + public enum AggregationType { + COVERED_COUNT, + MISSED_COUNT, + COVERED_PERCENTAGE, + MISSED_PERCENTAGE + } + + public static class CoverageValue { + public final String entityName; + public final BigDecimal value; + + public CoverageValue(String entityName, BigDecimal value) { + this.entityName = entityName; + this.value = value; + } + } + + /** + * Describes a single bound for the verification rule to enforce + */ + public static class Bound { + public final BigDecimal minValue; + public final BigDecimal maxValue; + + public final CoverageUnit coverageUnits; + public final AggregationType aggregationForGroup; + + public Bound(BigDecimal minValue, BigDecimal maxValue, CoverageUnit coverageUnits, AggregationType aggregationForGroup) { + this.minValue = minValue; + this.maxValue = maxValue; + this.coverageUnits = coverageUnits; + this.aggregationForGroup = aggregationForGroup; } - return patterns; } + /** + * Verification rule - a named set of bounds of coverage value to check. + */ + public static class Rule { + public final String name; + + public final GroupingBy groupBy; + + public final List bounds; + + public Rule(String name, GroupingBy groupBy, List bounds) { + this.name = name; + this.groupBy = groupBy; + this.bounds = bounds; + } + } + + /** + * Violation of verification rule. + */ + public static class RuleViolations { + public final Rule rule; + + public final List violations; + + public RuleViolations(Rule rule, List violations) { + this.rule = rule; + this.violations = violations; + } + } + + /** + * Violation of verification bound. + */ + public static class BoundViolation { + public final Bound bound; + public final boolean isMax; + public final BigDecimal value; + public final String entityName; + + public BoundViolation(Bound bound, boolean isMax, BigDecimal value, String entityName) { + this.bound = bound; + this.isMax = isMax; + this.value = value; + this.entityName = entityName; + } + } } diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java new file mode 100644 index 00000000..23075d92 --- /dev/null +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm; + +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 com.intellij.rt.coverage.verify.Verifier; +import com.intellij.rt.coverage.verify.api.*; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeMap; + +class LegacyVerification { + private LegacyVerification() { + // no-op + } + + static final BigDecimal ONE_HUNDRED = new BigDecimal(100); + + public static List verify(List rules, File tempDir, KoverLegacyFeatures.ClassFilters filters, List reports, List outputs) throws IOException { + Filters intellijFilters = Wildcards.convertFilters(filters); + + final ArrayList rulesArray = new ArrayList<>(); + + File ic = new File(tempDir, "agg-ic.ic"); + File smap = new File(tempDir, "agg-smap.smap"); + + Request requests = new Request(intellijFilters, ic, smap); + AggregatorApi.aggregate(Collections.singletonList(requests), reports, outputs); + + for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) { + KoverLegacyFeatures.Rule rule = rules.get(ruleIndex); + + final List bounds = new ArrayList<>(); + for (int boundIndex = 0; boundIndex < rule.bounds.size(); boundIndex++) { + KoverLegacyFeatures.Bound b = rule.bounds.get(boundIndex); + + bounds.add(new Bound(boundIndex, counterToIntellij(b), valueTypeToIntellij(b), valueToIntellij(b, b.minValue), valueToIntellij(b, b.maxValue))); + } + + rulesArray.add(new Rule(ruleIndex, ic, targetToIntellij(rule), bounds)); + } + + + final Verifier verifier = new Verifier(rulesArray); + verifier.processRules(); + List violations = VerificationApi.verify(rulesArray); + + ArrayList ruleViolations = new ArrayList<>(); + for (RuleViolation ruleViolation : violations) { + TreeMap resultBounds = new TreeMap<>(); + + KoverLegacyFeatures.Rule rule = rules.get(ruleViolation.id); + for (int boundIndex = 0; boundIndex < ruleViolation.violations.size(); boundIndex++) { + BoundViolation boundViolation = ruleViolation.violations.get(boundIndex); + KoverLegacyFeatures.Bound bound = rule.bounds.get(boundViolation.id); + + for (Violation maxViolation : boundViolation.maxViolations) { + String entityName = rule.groupBy != KoverLegacyFeatures.GroupingBy.APPLICATION ? maxViolation.targetName : null; + resultBounds.put( + new ViolationId(boundViolation.id, entityName), + new KoverLegacyFeatures.BoundViolation(bound, true, intellijToValue(maxViolation.targetValue, bound), entityName) + ); + } + for (Violation minViolation : boundViolation.minViolations) { + String entityName = rule.groupBy != KoverLegacyFeatures.GroupingBy.APPLICATION ? minViolation.targetName : null; + resultBounds.put( + new ViolationId(boundViolation.id, entityName), + new KoverLegacyFeatures.BoundViolation(bound, false, intellijToValue(minViolation.targetValue, bound), entityName) + ); + } + } + + ruleViolations.add(new KoverLegacyFeatures.RuleViolations(rule, new ArrayList<>(resultBounds.values()))); + } + + return ruleViolations; + } + + private static class ViolationId implements Comparable { + private final int index; + private final String entityName; + + private ViolationId(int index, String entityName) { + this.index = index; + this.entityName = entityName; + } + + @Override + public int compareTo(@NotNull LegacyVerification.ViolationId other) { + // first compared by index + if (index != other.index) { + return Integer.compare(index, other.index); + } + + // if indexes are equals then compare by entity name + if (entityName == null) { + // bounds with empty entity names goes first + return (other.entityName == null) ? 0 : -1; + } + + if (other.entityName == null) return 1; + + return entityName.compareTo(other.entityName); + } + } + + private static BigDecimal intellijToValue(BigDecimal intellijValue, KoverLegacyFeatures.Bound bound) { + if (isPercentage(bound.aggregationForGroup)) { + return intellijValue.multiply(ONE_HUNDRED); + } else { + return intellijValue; + } + } + + private static Target targetToIntellij(KoverLegacyFeatures.Rule rule) { + switch (rule.groupBy) { + case APPLICATION: + return Target.ALL; + case CLASS: + return Target.CLASS; + case PACKAGE: + return Target.PACKAGE; + } + return null; + } + + private static Counter counterToIntellij(KoverLegacyFeatures.Bound bound) { + switch (bound.coverageUnits) { + case LINE: + return Counter.LINE; + case INSTRUCTION: + return Counter.INSTRUCTION; + case BRANCH: + return Counter.BRANCH; + } + return null; + } + + private static ValueType valueTypeToIntellij(KoverLegacyFeatures.Bound bound) { + switch (bound.aggregationForGroup) { + case COVERED_COUNT: + return ValueType.COVERED; + case MISSED_COUNT: + return ValueType.MISSED; + case COVERED_PERCENTAGE: + return ValueType.COVERED_RATE; + case MISSED_PERCENTAGE: + return ValueType.MISSED_RATE; + } + return null; + } + + private static BigDecimal valueToIntellij(KoverLegacyFeatures.Bound bound, BigDecimal value) { + if (value == null) return null; + + if (isPercentage(bound.aggregationForGroup)) { + return value.divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP); + } else { + return value; + } + } + + private static boolean isPercentage(KoverLegacyFeatures.AggregationType aggregationType) { + return aggregationType == KoverLegacyFeatures.AggregationType.COVERED_PERCENTAGE || aggregationType == KoverLegacyFeatures.AggregationType.MISSED_PERCENTAGE; + } +} diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java new file mode 100644 index 00000000..a4f0fc19 --- /dev/null +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm; + +import com.intellij.rt.coverage.report.api.Filters; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +class Wildcards { + private static final HashSet regexMetacharactersSet = new HashSet<>(); + + private static final String regexMetacharacters = "<([{\\^-=$!|]})+.>"; + + static { + for (int i = 0; i < regexMetacharacters.length(); i++) { + char c = regexMetacharacters.charAt(i); + regexMetacharactersSet.add(c); + } + } + + private Wildcards() { + // no-op + } + + static Filters convertFilters(KoverLegacyFeatures.ClassFilters filters) { + return new Filters( + convert(filters.includeClasses), + convert(filters.excludeClasses), + convert(filters.excludeAnnotation) + ); + } + + private static List convert(Set templates) { + ArrayList patterns = new ArrayList<>(templates.size()); + for (String template : templates) { + patterns.add(Pattern.compile(Wildcards.wildcardsToRegex(template))); + } + return patterns; + } + + /** + * Replaces characters `*` or `.` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters. + */ + static String wildcardsToRegex(String value) { + // in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance + final StringBuilder builder = new StringBuilder(value.length() * 2); + + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (regexMetacharactersSet.contains(c)) { + builder.append('\\').append(c); + } else if (c == '*') { + builder.append(".*"); + } else if (c == '?') { + builder.append('.'); + } else if (c == '#') { + builder.append("[^.]*"); + } else { + builder.append(c); + } + } + return builder.toString(); + } +} diff --git a/kover-gradle-plugin/build.gradle.kts b/kover-gradle-plugin/build.gradle.kts index 109a93e5..cccf09ef 100644 --- a/kover-gradle-plugin/build.gradle.kts +++ b/kover-gradle-plugin/build.gradle.kts @@ -44,7 +44,6 @@ dependencies { // exclude transitive dependency on stdlib, the Gradle version should be used compileOnly(kotlin("stdlib")) compileOnly(libs.gradlePlugin.kotlin) - compileOnly(libs.intellij.reporter) functionalTestImplementation(kotlin("test")) functionalTestImplementation(libs.junit.jupiter) diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt index 1f368ace..54ea8a95 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt @@ -4,11 +4,11 @@ package kotlinx.kover.gradle.plugin.test.functional.framework.checker +import kotlinx.kover.features.jvm.KoverFeatures import kotlinx.kover.gradle.plugin.commons.* import kotlinx.kover.gradle.plugin.test.functional.framework.common.* import kotlinx.kover.gradle.plugin.test.functional.framework.runner.* import kotlinx.kover.gradle.plugin.tools.* -import kotlinx.kover.gradle.plugin.util.* import org.opentest4j.* import org.w3c.dom.* import java.io.* @@ -401,7 +401,7 @@ private class XmlReportCheckerImpl(val context: CheckerContextImpl, file: File) private class VerifyReportCheckerImpl(val context: CheckerContextImpl, val content: String) : VerifyReportChecker { override fun assertKoverResult(expected: String) { if (context.project.toolVariant.vendor != CoverageToolVendor.KOVER) return - val regex = expected.wildcardsToRegex().toRegex() + val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex() if (!content.matches(regex)) { throw AssertionError("Unexpected verification result for Kover Tool.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]") } @@ -409,7 +409,7 @@ private class VerifyReportCheckerImpl(val context: CheckerContextImpl, val conte override fun assertJaCoCoResult(expected: String) { if (context.project.toolVariant.vendor != CoverageToolVendor.JACOCO) return - val regex = expected.wildcardsToRegex().toRegex() + val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex() if (!content.matches(regex)) { throw AssertionError("Unexpected verification result for JaCoCo Tool.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]") } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt index d0ceb584..3ae5d13f 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt @@ -63,7 +63,6 @@ internal class ReportContext( ) internal class GradleReportServices( - val workerExecutor: WorkerExecutor, val antBuilder: AntBuilder, val objects: ObjectFactory ) diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt index 6617f158..1574b255 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt @@ -84,13 +84,10 @@ internal abstract class AbstractKoverReportTask : DefaultTask() { @get:Inject protected abstract val obj: ObjectFactory - @get:Inject - protected abstract val workerExecutor: WorkerExecutor - private val rootDir: File = project.rootDir protected fun context(): ReportContext { - val services = GradleReportServices(workerExecutor, ant, obj) + val services = GradleReportServices(ant, obj) return ReportContext(collectAllFiles(), filters.get(), reportClasspath, temporaryDir, projectPath, services) } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt index e570bd32..4a76ec11 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt @@ -4,17 +4,19 @@ package kotlinx.kover.gradle.plugin.tools +import kotlinx.kover.features.jvm.KoverLegacyFeatures +import kotlinx.kover.features.jvm.KoverLegacyFeatures.BoundViolation +import kotlinx.kover.features.jvm.KoverLegacyFeatures.CoverageValue import kotlinx.kover.gradle.plugin.dsl.* import java.io.File import java.io.Serializable -import java.math.* import java.nio.charset.Charset -internal fun CoverageMeasures.writeToFile(file: File, header: String?, lineFormat: String) { +internal fun Iterable.writeToFile(file: File, header: String?, lineFormat: String) { file.bufferedWriter(Charset.forName("UTF-8")).use { writer -> header?.let { h -> writer.appendLine(h) } - values.forEach { coverage -> + forEach { coverage -> val entityName = coverage.entityName ?: "application" writer.appendLine( lineFormat.replace("", coverage.value.stripTrailingZeros().toPlainString()) @@ -40,42 +42,18 @@ internal data class CoverageRequest( val lineFormat: String, ): Serializable -internal data class CoverageMeasures( - val values: List -) - -internal data class CoverageValue( - val value: BigDecimal, - val entityName: String? = null, -) - -internal data class RuleViolations( - val entityType: GroupingEntityType, - val bounds: List, - val name: String -) - -internal data class BoundViolations( - val isMax: Boolean, - val expectedValue: BigDecimal, - val actualValue: BigDecimal, - val metric: CoverageUnit, - val aggregation: AggregationType, - val entityName: String? = null -) - -internal fun generateErrorMessage(violations: List): String { +internal fun generateErrorMessage(violations: List): String { val messageBuilder = StringBuilder() violations.forEach { rule -> - val namedRule = if (rule.name.isNotEmpty()) "Rule '${rule.name}'" else "Rule" + val namedRule = if (rule.rule.name.isNotEmpty()) "Rule '${rule.rule.name}'" else "Rule" - if (rule.bounds.size == 1) { - messageBuilder.appendLine("$namedRule violated: ${rule.bounds[0].format(rule)}") + if (rule.violations.size == 1) { + messageBuilder.appendLine("$namedRule violated: ${rule.violations[0].format(rule)}") } else { messageBuilder.appendLine("$namedRule violated:") - rule.bounds.forEach { bound -> + rule.violations.forEach { bound -> messageBuilder.append(" ") messageBuilder.appendLine(bound.format(rule)) } @@ -85,27 +63,30 @@ internal fun generateErrorMessage(violations: List): String { return messageBuilder.toString() } -private fun BoundViolations.format(rule: RuleViolations): String { +@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") +private fun BoundViolation.format(rule: KoverLegacyFeatures.RuleViolations): String { val directionText = if (isMax) "maximum" else "minimum" - val metricText = when (metric) { - CoverageUnit.LINE -> "lines" - CoverageUnit.INSTRUCTION -> "instructions" - CoverageUnit.BRANCH -> "branches" + val metricText = when (bound.coverageUnits) { + KoverLegacyFeatures.CoverageUnit.LINE -> "lines" + KoverLegacyFeatures.CoverageUnit.INSTRUCTION -> "instructions" + KoverLegacyFeatures.CoverageUnit.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 valueTypeText = when (bound.aggregationForGroup) { + KoverLegacyFeatures.AggregationType.COVERED_COUNT -> "covered count" + KoverLegacyFeatures.AggregationType.MISSED_COUNT -> "missed count" + KoverLegacyFeatures.AggregationType.COVERED_PERCENTAGE -> "covered percentage" + KoverLegacyFeatures.AggregationType.MISSED_PERCENTAGE -> "missed percentage" } - val entityText = when (rule.entityType) { - GroupingEntityType.APPLICATION -> "" - GroupingEntityType.CLASS -> " for class '$entityName'" - GroupingEntityType.PACKAGE -> " for package '$entityName'" + val entityText = when (rule.rule.groupBy) { + KoverLegacyFeatures.GroupingBy.APPLICATION -> "" + KoverLegacyFeatures.GroupingBy.CLASS -> " for class '$entityName'" + KoverLegacyFeatures.GroupingBy.PACKAGE -> " for package '$entityName'" } - return "$metricText $valueTypeText$entityText is $actualValue, but expected $directionText is $expectedValue" + val expectedValue = if (isMax) bound.maxValue else bound.minValue + + return "$metricText $valueTypeText$entityText is $value, but expected $directionText is $expectedValue" } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt index 0ed32c6d..758254d3 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt @@ -4,14 +4,13 @@ package kotlinx.kover.gradle.plugin.tools.jacoco +import kotlinx.kover.features.jvm.KoverLegacyFeatures.CoverageValue import kotlinx.kover.gradle.plugin.commons.KoverCriticalException import kotlinx.kover.gradle.plugin.commons.ReportContext import kotlinx.kover.gradle.plugin.commons.VerificationBound import kotlinx.kover.gradle.plugin.commons.VerificationRule import kotlinx.kover.gradle.plugin.tools.* -import kotlinx.kover.gradle.plugin.tools.CoverageMeasures import kotlinx.kover.gradle.plugin.tools.CoverageRequest -import kotlinx.kover.gradle.plugin.tools.CoverageValue import kotlinx.kover.gradle.plugin.tools.writeToFile import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED import java.io.File @@ -28,15 +27,15 @@ internal fun ReportContext.printJacocoCoverage(request: CoverageRequest, outputF } val values = violations.flatMap { rule -> - if (rule.bounds.isEmpty()) { + if (rule.violations.isEmpty()) { throw KoverCriticalException("Expected at least one bound violation for JaCoCo") } - rule.bounds.map { - CoverageValue(it.actualValue, it.entityName) + rule.violations.map { + CoverageValue(it.entityName, it.value) } } - CoverageMeasures(values).writeToFile( + values.writeToFile( outputFile, request.header, request.lineFormat diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt index 126cf475..68a1ac88 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt @@ -6,8 +6,8 @@ package kotlinx.kover.gradle.plugin.tools.jacoco import groovy.lang.Closure import groovy.lang.GroovyObject +import kotlinx.kover.features.jvm.KoverFeatures import kotlinx.kover.gradle.plugin.commons.ReportContext -import kotlinx.kover.gradle.plugin.util.wildcardsToRegex import java.io.File @@ -88,6 +88,6 @@ internal inline fun GroovyObject.invokeWithBody( */ private fun String.wildcardsToClassFileRegex(): String { val filenameWithWildcards = this.replace('.', File.separatorChar) + ".class" - return filenameWithWildcards.wildcardsToRegex() + return KoverFeatures.koverWildcardToRegex(filenameWithWildcards) } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt index 79fdcd05..150560c8 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt @@ -5,13 +5,16 @@ package kotlinx.kover.gradle.plugin.tools.jacoco import groovy.lang.GroovyObject +import kotlinx.kover.features.jvm.KoverLegacyFeatures.Bound +import kotlinx.kover.features.jvm.KoverLegacyFeatures.BoundViolation +import kotlinx.kover.features.jvm.KoverLegacyFeatures.Rule +import kotlinx.kover.features.jvm.KoverLegacyFeatures.RuleViolations import kotlinx.kover.gradle.plugin.commons.* import kotlinx.kover.gradle.plugin.dsl.AggregationType import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType import kotlinx.kover.gradle.plugin.dsl.CoverageUnit -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.tools.kover.convert import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED import org.gradle.internal.reflect.JavaMethod import java.io.File @@ -119,7 +122,11 @@ private fun GroovyObject.violations(): List { val isMax = match.groupValues[6].asIsMax(it) val expected = match.groupValues[7].asValue(it, agg) - RuleViolations(entityType, listOf(BoundViolations(isMax, expected, value, coverageUnits, agg, entityName)), "") + val bound = + Bound(if (!isMax) expected else null, if (isMax) expected else null, coverageUnits.convert(), agg.convert()) + val rule = Rule("", entityType.convert(), listOf(bound)) + + RuleViolations(rule, listOf(BoundViolation(bound, isMax, value, entityName))) }.toList() } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Actions.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Actions.kt deleted file mode 100644 index 52e6f03b..00000000 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Actions.kt +++ /dev/null @@ -1,54 +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.tools.kover - -import com.intellij.rt.coverage.util.ErrorReporter -import kotlinx.kover.gradle.plugin.commons.ArtifactContent -import kotlinx.kover.gradle.plugin.commons.ReportContext -import kotlinx.kover.gradle.plugin.commons.ReportFilters -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.Property -import org.gradle.workers.WorkAction -import org.gradle.workers.WorkParameters -import org.gradle.workers.WorkQueue - -private const val FREE_MARKER_LOGGER_PROPERTY_NAME = "org.freemarker.loggerLibrary" - -internal interface ReportParameters: WorkParameters { - val filters: Property - - val files: Property - val tempDir: DirectoryProperty - val projectPath: Property - val charset: Property -} - -internal abstract class AbstractReportAction : WorkAction { - protected abstract fun generate() - - final override fun execute() { - // print to stdout only critical errors - ErrorReporter.setLogLevel(ErrorReporter.ERROR) - - // disable freemarker logging to stdout for the time of report generation - val oldFreemarkerLogger = System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, "none") - try { - generate() - } finally { - if (oldFreemarkerLogger == null) { - System.clearProperty(FREE_MARKER_LOGGER_PROPERTY_NAME) - } else { - System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, oldFreemarkerLogger) - } - } - } -} - -internal inline fun , P : ReportParameters> ReportContext.submitAction(noinline parametersConfig: P.() -> Unit) { - val workQueue: WorkQueue = services.workerExecutor.classLoaderIsolation { - classpath.from(this@submitAction.classpath) - } - workQueue.submit(A::class.java) { parametersConfig(this) } -} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Evaluation.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Evaluation.kt index 64daee36..cf7406ff 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Evaluation.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Evaluation.kt @@ -4,71 +4,33 @@ package kotlinx.kover.gradle.plugin.tools.kover -import kotlinx.kover.gradle.plugin.commons.KoverCriticalException +import kotlinx.kover.features.jvm.KoverLegacyFeatures import kotlinx.kover.gradle.plugin.commons.ReportContext -import kotlinx.kover.gradle.plugin.commons.VerificationBound -import kotlinx.kover.gradle.plugin.commons.VerificationRule -import kotlinx.kover.gradle.plugin.tools.* -import kotlinx.kover.gradle.plugin.tools.CoverageMeasures import kotlinx.kover.gradle.plugin.tools.CoverageRequest -import kotlinx.kover.gradle.plugin.tools.CoverageValue +import kotlinx.kover.gradle.plugin.tools.writeNoSources import kotlinx.kover.gradle.plugin.tools.writeToFile -import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property import java.io.File -import java.math.BigDecimal - internal fun ReportContext.printCoverage(request: CoverageRequest, outputFile: File) { - submitAction { - this.outputFile.set(outputFile) - this.request.convention(request) - - filters.convention(this@printCoverage.filters) - files.convention(this@printCoverage.files) - tempDir.set(this@printCoverage.tempDir) - projectPath.convention(this@printCoverage.projectPath) + // change API after https://youtrack.jetbrains.com/issue/IDEA-323463 will be implemented + val coverage = KoverLegacyFeatures.evalCoverage( + request.entity.convert(), + request.metric.convert(), + request.aggregation.convert(), + tempDir, + filters.toKoverFeatures(), + files.reports.toList(), + files.outputs.toList() + ) + + if (coverage.isEmpty()) { + outputFile.writeNoSources(request.header) + return } + coverage.writeToFile( + outputFile, + request.header, + request.lineFormat + ) } - -internal abstract class CollectCoverageAction : AbstractReportAction() { - override fun generate() { - val request = parameters.request.get() - val bound = VerificationBound(ONE_HUNDRED, BigDecimal.ZERO, request.metric, request.aggregation) - val failRule = VerificationRule(true, "", request.entity, listOf(bound)) - - // change API after https://youtrack.jetbrains.com/issue/IDEA-323463 will be implemented - val violations = koverVerify( - listOf(failRule), - parameters.filters.get(), - parameters.tempDir.get().asFile, - parameters.files.get() - ) - - if (violations.isEmpty()) { - parameters.outputFile.get().asFile.writeNoSources(parameters.request.get().header) - return - } - - val violation = violations.singleOrNull() ?: throw KoverCriticalException("Expected only one rule violation for Kover") - if (violation.bounds.isEmpty()) { - throw KoverCriticalException("Expected at least one bound violation for Kover") - } - - val values = violation.bounds.map { - CoverageValue(it.actualValue, it.entityName) - } - CoverageMeasures(values).writeToFile( - parameters.outputFile.get().asFile, - parameters.request.get().header, - parameters.request.get().lineFormat - ) - } -} - -internal interface CollectCoverageParameters : ReportParameters { - val outputFile: RegularFileProperty - val request: Property -} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IcReport.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IcReport.kt index 9ae1a3b2..8a7de8e9 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IcReport.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IcReport.kt @@ -1,37 +1,17 @@ package kotlinx.kover.gradle.plugin.tools.kover -import com.intellij.rt.coverage.aggregate.api.AggregatorApi -import com.intellij.rt.coverage.aggregate.api.Request +import kotlinx.kover.features.jvm.KoverLegacyFeatures import kotlinx.kover.gradle.plugin.commons.ReportContext -import org.gradle.api.provider.Property import java.io.File -internal fun ReportContext.koverBinaryReport(binary: File) { - submitAction { - binaryFile.set(binary) - filters.convention(this@koverBinaryReport.filters) - - files.convention(this@koverBinaryReport.files) - tempDir.set(this@koverBinaryReport.tempDir) - projectPath.convention(this@koverBinaryReport.projectPath) - } -} - -internal interface BinaryReportParameters : ReportParameters { - val binaryFile: Property -} - -internal abstract class BinaryReportAction : AbstractReportAction() { - override fun generate() { - val binary = parameters.binaryFile.get() - val smapFile = parameters.tempDir.file("report.smap").get().asFile - - val files = parameters.files.get() - val filters = parameters.filters.get() - val request = Request(filters.toIntellij(), binary, smapFile) - - AggregatorApi.aggregate(listOf(request), files.reports.toList(), files.outputs.toList()) - } +internal fun ReportContext.koverBinaryReport(binaryFile: File) { + KoverLegacyFeatures.aggregateIc( + binaryFile, + filters.toKoverFeatures(), + tempDir, + files.reports.toList(), + files.outputs.toList() + ) } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IntellijApi.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IntellijApi.kt deleted file mode 100644 index 12a3a0be..00000000 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/IntellijApi.kt +++ /dev/null @@ -1,56 +0,0 @@ -package kotlinx.kover.gradle.plugin.tools.kover - -import com.intellij.rt.coverage.report.api.Filters -import com.intellij.rt.coverage.verify.api.Counter -import com.intellij.rt.coverage.verify.api.Target -import com.intellij.rt.coverage.verify.api.ValueType -import kotlinx.kover.gradle.plugin.commons.ReportFilters -import kotlinx.kover.gradle.plugin.commons.VerificationBound -import kotlinx.kover.gradle.plugin.commons.VerificationRule -import kotlinx.kover.gradle.plugin.dsl.AggregationType -import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType -import kotlinx.kover.gradle.plugin.dsl.CoverageUnit -import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED -import kotlinx.kover.gradle.plugin.util.asPatterns -import java.math.BigDecimal -import java.math.RoundingMode - -internal fun ReportFilters.toIntellij() = Filters( - includesClasses.asPatterns(), - excludesClasses.asPatterns(), - excludesAnnotations.asPatterns() -) - -internal fun VerificationRule.targetToIntellij(): Target { - return when (entityType) { - GroupingEntityType.APPLICATION -> Target.ALL - GroupingEntityType.CLASS -> Target.CLASS - GroupingEntityType.PACKAGE -> Target.PACKAGE - } -} - -internal fun VerificationBound.counterToIntellij(): Counter { - return when (metric) { - CoverageUnit.LINE -> Counter.LINE - CoverageUnit.INSTRUCTION -> Counter.INSTRUCTION - CoverageUnit.BRANCH -> Counter.BRANCH - } -} - -internal fun VerificationBound.valueTypeToIntellij(): ValueType { - return when (aggregation) { - AggregationType.COVERED_COUNT -> ValueType.COVERED - AggregationType.MISSED_COUNT -> ValueType.MISSED - AggregationType.COVERED_PERCENTAGE -> ValueType.COVERED_RATE - AggregationType.MISSED_PERCENTAGE -> ValueType.MISSED_RATE - } -} - -internal fun VerificationBound.valueToIntellij(value: BigDecimal?): BigDecimal? { - value ?: return null - return if (aggregation.isPercentage) { - value.divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP) - } else { - value - } -} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt new file mode 100644 index 00000000..84a31e7b --- /dev/null +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt @@ -0,0 +1,56 @@ +package kotlinx.kover.gradle.plugin.tools.kover + +import kotlinx.kover.features.jvm.KoverLegacyFeatures +import kotlinx.kover.features.jvm.KoverLegacyFeatures.Bound +import kotlinx.kover.gradle.plugin.commons.ReportFilters +import kotlinx.kover.gradle.plugin.commons.VerificationBound +import kotlinx.kover.gradle.plugin.commons.VerificationRule +import kotlinx.kover.gradle.plugin.dsl.AggregationType +import kotlinx.kover.gradle.plugin.dsl.CoverageUnit +import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType + +internal fun ReportFilters.toKoverFeatures() = KoverLegacyFeatures.ClassFilters( + includesClasses, + excludesClasses, + excludesAnnotations +) + +internal fun VerificationRule.convert(): KoverLegacyFeatures.Rule { + return KoverLegacyFeatures.Rule( + name, + entityType.convert(), + bounds.map { it.convert() } + ) +} + + +internal fun VerificationBound.convert(): Bound { + return Bound(minValue, maxValue, metric.convert(), aggregation.convert()) +} + + +internal fun GroupingEntityType.convert(): KoverLegacyFeatures.GroupingBy { + return when (this) { + GroupingEntityType.APPLICATION -> KoverLegacyFeatures.GroupingBy.APPLICATION + GroupingEntityType.CLASS -> KoverLegacyFeatures.GroupingBy.CLASS + GroupingEntityType.PACKAGE -> KoverLegacyFeatures.GroupingBy.PACKAGE + } +} + + +internal fun CoverageUnit.convert(): KoverLegacyFeatures.CoverageUnit { + return when (this) { + CoverageUnit.LINE -> KoverLegacyFeatures.CoverageUnit.LINE + CoverageUnit.BRANCH -> KoverLegacyFeatures.CoverageUnit.BRANCH + CoverageUnit.INSTRUCTION -> KoverLegacyFeatures.CoverageUnit.INSTRUCTION + } +} + +internal fun AggregationType.convert(): KoverLegacyFeatures.AggregationType { + return when (this) { + AggregationType.COVERED_COUNT -> KoverLegacyFeatures.AggregationType.COVERED_COUNT + AggregationType.COVERED_PERCENTAGE -> KoverLegacyFeatures.AggregationType.COVERED_PERCENTAGE + AggregationType.MISSED_COUNT -> KoverLegacyFeatures.AggregationType.MISSED_COUNT + AggregationType.MISSED_PERCENTAGE -> KoverLegacyFeatures.AggregationType.MISSED_PERCENTAGE + } +} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt index 0c5c3ed7..d06d3952 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverHtmlOrXmlReport.kt @@ -4,86 +4,32 @@ package kotlinx.kover.gradle.plugin.tools.kover -import com.intellij.rt.coverage.report.api.ReportApi +import kotlinx.kover.features.jvm.KoverLegacyFeatures import kotlinx.kover.gradle.plugin.commons.ReportContext -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property import java.io.File internal fun ReportContext.koverHtmlReport(htmlReportDir: File, htmlTitle: String, charsetName: String?) { - submitAction { - 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) - } + htmlReportDir.mkdirs() + + KoverLegacyFeatures.generateHtmlReport( + htmlReportDir, + charsetName, + files.reports.toList(), + files.outputs.toList(), + files.sources.toList(), + htmlTitle, + filters.toKoverFeatures() + ) } internal fun ReportContext.koverXmlReport(xmlReportFile: File, xmlTitle: String) { - submitAction { - xmlFile.set(xmlReportFile) - title.convention(xmlTitle) - filters.convention(this@koverXmlReport.filters) - - files.convention(this@koverXmlReport.files) - - tempDir.set(this@koverXmlReport.tempDir) - projectPath.convention(this@koverXmlReport.projectPath) - } -} - -internal interface XmlReportParameters : ReportParameters { - val xmlFile: RegularFileProperty - val title: Property -} - -internal interface HtmlReportParameters : ReportParameters { - val htmlDir: DirectoryProperty - val title: Property -} - -internal abstract class XmlReportAction : AbstractReportAction() { - override fun generate() { - val files = parameters.files.get() - val filters = parameters.filters.get() - - ReportApi.xmlReport( - parameters.xmlFile.get().asFile, - parameters.title.get(), - files.reports.toList(), - files.outputs.toList(), - files.sources.toList(), - filters.toIntellij() - ) - } -} - -internal abstract class HtmlReportAction : AbstractReportAction() { - override fun generate() { - val htmlDir = parameters.htmlDir.get().asFile - htmlDir.mkdirs() - - val files = parameters.files.get() - val filters = parameters.filters.get() - - // repeat reading freemarker temple from resources if error occurred, see https://github.com/Kotlin/kotlinx-kover/issues/510 - // the values are selected empirically so that the maximum report generation time is not much more than a second - ReportApi.setFreemarkerRetry(7, 150) - - ReportApi.htmlReport( - parameters.htmlDir.get().asFile, - parameters.title.get(), - parameters.charset.orNull, - files.reports.toList(), - files.outputs.toList(), - files.sources.toList(), - filters.toIntellij() - ) - } + KoverLegacyFeatures.generateXmlReport( + xmlReportFile, + files.reports.toList(), + files.outputs.toList(), + files.sources.toList(), + xmlTitle, + filters.toKoverFeatures() + ) } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverOnlineInstrumentation.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverOnlineInstrumentation.kt index 9f3f8864..647e2699 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverOnlineInstrumentation.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverOnlineInstrumentation.kt @@ -4,7 +4,6 @@ package kotlinx.kover.gradle.plugin.tools.kover -import kotlinx.kover.gradle.plugin.util.wildcardsToRegex import java.io.File internal fun buildJvmAgentArgs( diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt index 529e1563..683952b8 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt @@ -19,8 +19,10 @@ import java.io.File internal class KoverTool(override val variant: CoverageToolVariant) : CoverageTool { override val jvmAgentDependency: String = "org.jetbrains.kotlinx:kover-jvm-agent:${KoverFeatures.getVersion()}" - override val jvmReporterDependency: String = "org.jetbrains.intellij.deps:intellij-coverage-reporter:${variant.version}" - override val jvmReporterExtraDependency: String = "org.jetbrains.intellij.deps:intellij-coverage-reporter:${variant.version}" + // since Kover Features is in compile dependency and there is no need in additional dependency to reporter + // we can't just specify null dependency, so use agent as a mock + override val jvmReporterDependency: String = jvmAgentDependency + override val jvmReporterExtraDependency: String = jvmAgentDependency override fun findJvmAgentJar(classpath: FileCollection, archiveOperations: ArchiveOperations): File { diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt index 4c7ca0eb..0b96524c 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt @@ -4,217 +4,24 @@ package kotlinx.kover.gradle.plugin.tools.kover -import com.intellij.rt.coverage.aggregate.api.AggregatorApi -import com.intellij.rt.coverage.aggregate.api.Request -import com.intellij.rt.coverage.verify.Verifier -import com.intellij.rt.coverage.verify.api.* +import kotlinx.kover.features.jvm.KoverLegacyFeatures import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType -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.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty import java.io.File -import java.util.* internal fun ReportContext.koverVerify(specifiedRules: List, outputReportFile: File) { - submitAction { - outputFile.set(outputReportFile) - rules.convention(specifiedRules) - filters.convention(this@koverVerify.filters) + val violations = KoverLegacyFeatures.verify( + specifiedRules.map { it.convert() }, + tempDir, + filters.toKoverFeatures(), + files.reports.toList(), + files.outputs.toList() + ) - files.convention(this@koverVerify.files) - tempDir.set(this@koverVerify.tempDir) - projectPath.convention(this@koverVerify.projectPath) - } -} - -internal interface VerifyReportParameters: ReportParameters { - val outputFile: RegularFileProperty - val rules: ListProperty -} - -internal abstract class VerifyReportAction : AbstractReportAction() { - override fun generate() { - 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 = aggregateBinReports(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.counterToIntellij(), b.valueTypeToIntellij(), b.valueToIntellij(b.minValue), - b.valueToIntellij(b.maxValue) - ) - - } - rulesArray += Rule(ruleIndex, group.ic, rule.targetToIntellij(), bounds) - } - } - - - val verifier = Verifier(rulesArray) - verifier.processRules() - - val violations = VerificationApi.verify(rulesArray) - - return processViolations(rules, violations) -} - -private fun groupRules( - allRules: List, - commonFilters: ReportFilters -): List>> { - val groupedMap = mutableMapOf>() - - allRules.forEach { - val excludesClasses = commonFilters.excludesClasses - val includesClasses = commonFilters.includesClasses - val excludesAnnotations = commonFilters.excludesAnnotations - - val reportFilters = ReportFilters(includesClasses, emptySet(), excludesClasses, excludesAnnotations) - groupedMap.computeIfAbsent(reportFilters) { mutableListOf() } += it - } - - return groupedMap.entries.map { it.key to it.value } -} - -private fun processViolations( - rules: List, - violations: List -): List { + val errorMessage = generateErrorMessage(violations) + outputReportFile.writeText(errorMessage) - val rulesMap = rules.mapIndexed { index, rule -> index to rule }.associate { it } - // the order of the rules is guaranteed for Kover (as in config) - val result = TreeMap() - - try { - violations.forEach { violation -> - val ruleIndex = violation.id - val rule = rulesMap[ruleIndex] - ?: throw KoverCriticalException("Error occurred while parsing verification result: unmapped rule with index $ruleIndex") - - val boundsMap = rule.bounds.mapIndexed { index, bound -> index to bound }.associate { it } - - // the order of the bound is guaranteed for Kover (as in config + suborder by entity name) - val boundsResult = TreeMap() - - 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") - - 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 = if (rule.entityType == GroupingEntityType.APPLICATION) null else it.targetName - val value = it.targetValue - val actual = if (bound.aggregation.isPercentage) value * ONE_HUNDRED else value - boundsResult += ViolationId(boundIndex, entityName) to BoundViolations( - false, - bound.minValue, - actual, - bound.metric, - bound.aggregation, - entityName - ) - } - - 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 = if (rule.entityType == GroupingEntityType.APPLICATION) null else it.targetName - val value = it.targetValue - val actual = if (bound.aggregation.isPercentage) value * ONE_HUNDRED else value - boundsResult += ViolationId(boundIndex, entityName) to BoundViolations( - true, - bound.maxValue, - actual, - bound.metric, - bound.aggregation, - entityName - ) - } - } - - result += ruleIndex to RuleViolations(rule.entityType, boundsResult.values.toList(), rule.name) - } - } catch (e: Throwable) { - throw KoverCriticalException("Error occurred while parsing verifier result", e) - } - - return result.values.toList() -} - -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 } - - // if indexes are equals then compare by entity name - - if (entityName == null) { - // bounds with empty entity names goes first - return if (other.entityName == null) 0 else -1 - } - if (other.entityName == null) return 1 - - entityName.compareTo(other.entityName).takeIf { it != 0 }?.let { return it } - - // indexes and names are equals - return 0 + if (violations.isNotEmpty()) { + throw KoverVerificationException(errorMessage) } } - -private fun aggregateBinReports(files: ArtifactContent, filters: List, tempDir: File): List { - val aggGroups = filters.mapIndexed { index: Int, reportFilters: ReportFilters -> - val filePrefix = if (filters.size > 1) "-$index" else "" - AggregationGroup( - tempDir.resolve("agg-ic$filePrefix.ic"), - tempDir.resolve("agg-smap$filePrefix.smap"), - reportFilters - ) - } - - val requests = aggGroups.map { group -> - Request(group.filters.toIntellij(), group.ic, group.smap) - } - - AggregatorApi.aggregate(requests, files.reports.toList(), files.outputs.toList()) - - return aggGroups -} - -private class AggregationGroup(val ic: File, val smap: File, val filters: ReportFilters) diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt index c75ae096..f9db4847 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/util/Util.kt @@ -5,55 +5,9 @@ package kotlinx.kover.gradle.plugin.util import java.io.File -import java.util.regex.Pattern - -internal fun Iterable.asPatterns(): List = map { Pattern.compile(it.wildcardsToRegex()) } - -/** - * Executes `block` of code only if boolean value is `true`. - */ -internal inline fun Boolean.ifTrue(block: () -> T): T? { - return if (this) { - block() - } else { - null - } -} - -/** - * Executes `block` of code only if boolean value is `false`. - */ -internal inline fun Boolean.ifFalse(block: () -> T): T? { - return if (!this) { - block() - } else { - null - } -} - -/** - * Replaces characters `*` or `.` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters. - */ -internal fun String.wildcardsToRegex(): String { - // in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance - val builder = StringBuilder(length * 2) - - forEach { char -> - when (char) { - in regexMetacharactersSet -> builder.append('\\').append(char) - '*' -> builder.append(".*") - '?' -> builder.append('.') - '#' -> builder.append("[^.]*") - else -> builder.append(char) - } - } - - return builder.toString() -} internal val ONE_HUNDRED = 100.toBigDecimal() -private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet() internal fun File.subdirs(): List { return listFiles { it -> From 37bbae3a8e4f23fea05c3b5bd455810704759d8c Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 27 Mar 2024 16:09:54 +0100 Subject: [PATCH 2/6] Rename .java to .kt --- .../kover/features/jvm/{KoverFeatures.java => KoverFeatures.kt} | 0 .../jvm/{KoverLegacyFeatures.java => KoverLegacyFeatures.kt} | 0 .../jvm/{OfflineInstrumenter.java => OfflineInstrumenter.kt} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/{KoverFeatures.java => KoverFeatures.kt} (100%) rename kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/{KoverLegacyFeatures.java => KoverLegacyFeatures.kt} (100%) rename kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/{OfflineInstrumenter.java => OfflineInstrumenter.kt} (100%) diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.kt similarity index 100% rename from kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.java rename to kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.kt diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt similarity index 100% rename from kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.java rename to kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt similarity index 100% rename from kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.java rename to kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt From 075ac6f3e5fda1f7eea9dd016de3347abbdcec98 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 27 Mar 2024 16:09:54 +0100 Subject: [PATCH 3/6] ~translate features to Kotlin --- .../cli/commands/OfflineInstrumentCommand.kt | 3 +- .../kover/cli/commands/ReportCommand.kt | 6 +- kover-features-jvm/build.gradle.kts | 29 +- .../kover/features/jvm/KoverFeatures.kt | 59 ++-- .../kover/features/jvm/KoverLegacyFeatures.kt | 331 ++++++------------ .../features/jvm/LegacyVerification.java | 177 ---------- .../kover/features/jvm/OfflineInstrumenter.kt | 12 +- .../features/jvm/OfflineInstrumenterImpl.java | 34 -- .../kover/features/jvm/Verification.kt | 85 +++++ .../kotlinx/kover/features/jvm/Wildcards.java | 70 ---- .../ConDySettings.kt} | 28 +- .../features/jvm/impl/LegacyVerification.kt | 163 +++++++++ .../jvm/impl/OfflineInstrumenterImpl.kt | 37 ++ .../kover/features/jvm/impl/Wildcards.kt | 47 +++ .../kover/gradle/plugin/commons/Paths.kt | 2 +- .../kover/gradle/plugin/dsl/KoverVersions.kt | 2 +- .../kover/gradle/plugin/tools/Verification.kt | 31 +- .../gradle/plugin/tools/jacoco/Evaluation.kt | 2 +- .../plugin/tools/jacoco/Verification.kt | 8 +- .../tools/kover/KoverFeaturesIntegration.kt | 35 +- .../gradle/plugin/tools/kover/KoverTool.kt | 2 +- 21 files changed, 563 insertions(+), 600 deletions(-) delete mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java delete mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenterImpl.java create mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt delete mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java rename kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/{ConDySettings.java => impl/ConDySettings.kt} (53%) create mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt create mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/OfflineInstrumenterImpl.kt create mode 100644 kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/Wildcards.kt diff --git a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt index 2f517647..86187a12 100644 --- a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt +++ b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt @@ -16,6 +16,7 @@ package kotlinx.kover.cli.commands +import kotlinx.kover.features.jvm.ClassFilters import kotlinx.kover.features.jvm.KoverLegacyFeatures import org.kohsuke.args4j.Argument import org.kohsuke.args4j.Option @@ -61,7 +62,7 @@ internal class OfflineInstrumentCommand : Command { override fun call(output: PrintWriter, errorWriter: PrintWriter): Int { - val filters = KoverLegacyFeatures.ClassFilters( + val filters = ClassFilters( includeClasses.toSet(), excludeClasses.toSet(), excludeAnnotation.toSet() diff --git a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt index ea5e2492..7f11be76 100644 --- a/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt +++ b/kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt @@ -16,8 +16,8 @@ package kotlinx.kover.cli.commands +import kotlinx.kover.features.jvm.ClassFilters import kotlinx.kover.features.jvm.KoverLegacyFeatures -import kotlinx.kover.features.jvm.KoverLegacyFeatures.ClassFilters import org.kohsuke.args4j.Argument import org.kohsuke.args4j.Option import java.io.File @@ -85,7 +85,7 @@ internal class ReportCommand : Command { var fail = false if (xmlFile != null) { try { - KoverLegacyFeatures.generateXmlReport(xmlFile, binaryReports, outputRoots, sourceRoots, title ?: "Kover XML Report", filters) + KoverLegacyFeatures.generateXmlReport(xmlFile!!, binaryReports, outputRoots, sourceRoots, title ?: "Kover XML Report", filters) } catch (e: IOException) { fail = true errorWriter.println("XML generation failed: " + e.message) @@ -93,7 +93,7 @@ internal class ReportCommand : Command { } if (htmlDir != null) { try { - KoverLegacyFeatures.generateHtmlReport(htmlDir, null, binaryReports, outputRoots, sourceRoots, title ?: "Kover HTML Report", filters) + KoverLegacyFeatures.generateHtmlReport(htmlDir!!, null, binaryReports, outputRoots, sourceRoots, title ?: "Kover HTML Report", filters) } catch (e: IOException) { fail = true errorWriter.println("HTML generation failed: " + e.message) diff --git a/kover-features-jvm/build.gradle.kts b/kover-features-jvm/build.gradle.kts index 0d53c9b8..31568e19 100644 --- a/kover-features-jvm/build.gradle.kts +++ b/kover-features-jvm/build.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + /* * Copyright 2000-2024 JetBrains s.r.o. * @@ -15,7 +19,7 @@ */ plugins { - java + kotlin("jvm") id("kover-publishing-conventions") } @@ -23,9 +27,26 @@ extensions.configure().configureEach { + compilerOptions { + allWarningsAsErrors.set(true) + jvmTarget.set(JvmTarget.JVM_1_8) + languageVersion.set(KotlinVersion.KOTLIN_1_5) + apiVersion.set(KotlinVersion.KOTLIN_1_5) + freeCompilerArgs.add("-Xsuppress-version-warnings") + } + } } repositories { diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.kt index 28688d23..4ec40bb4 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverFeatures.kt @@ -1,35 +1,30 @@ -package kotlinx.kover.features.jvm; +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm -import java.io.InputStream; -import java.util.Scanner; +import kotlinx.kover.features.jvm.impl.OfflineInstrumenterImpl +import kotlinx.kover.features.jvm.impl.wildcardsToRegex +import java.util.* /** * A class for using features via Java calls. */ -public class KoverFeatures { - private static final String koverVersion = readVersion(); - +public object KoverFeatures { /** - * Getting the Kover version. - * - * @return The version of Kover used in these utilities. + * Getting version of Kover used in these utilities. */ - public static String getVersion() { - return koverVersion; - } + public val version: String = readVersion() /** - * Converts a Kover template string to a regular expression string. - *

- * Replaces characters *` or `.` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters. + * Converts a Kover [template] string to a regular expression string. + *Ñ‘ + * Replaces characters `*` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters. * All special characters of regular expressions are also escaped. - *

- * - * @param template Template string in Kover format - * @return Regular expression corresponding given Kover template */ - public static String koverWildcardToRegex(String template) { - return Wildcards.wildcardsToRegex(template); + public fun koverWildcardToRegex(template: String): String { + return template.wildcardsToRegex() } /** @@ -37,20 +32,22 @@ public class KoverFeatures { * * @return instrumenter for offline instrumentation. */ - public static OfflineInstrumenter createOfflineInstrumenter() { - return new OfflineInstrumenterImpl(false); + public fun createOfflineInstrumenter(): OfflineInstrumenter { + return OfflineInstrumenterImpl(false) } - private static String readVersion() { - String version = "unrecognized"; + private fun readVersion(): String { + var version = "unrecognized" // read version from file in resources - try (InputStream stream = KoverFeatures.class.getClassLoader().getResourceAsStream("kover.version")) { - if (stream != null) { - version = new Scanner(stream).nextLine(); + try { + KoverFeatures::class.java.classLoader.getResourceAsStream("kover.version").use { stream -> + if (stream != null) { + version = Scanner(stream).nextLine() + } } - } catch (Throwable e) { + } catch (e: Throwable) { // can't read } - return version; + return version } -} +} \ No newline at end of file diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt index cce7615e..096f27cf 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt @@ -2,28 +2,25 @@ * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.features.jvm; - -import com.intellij.rt.coverage.aggregate.api.AggregatorApi; -import com.intellij.rt.coverage.aggregate.api.Request; -import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi; -import com.intellij.rt.coverage.report.api.ReportApi; -import com.intellij.rt.coverage.util.ErrorReporter; - -import java.io.File; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +package kotlinx.kover.features.jvm + +import com.intellij.rt.coverage.aggregate.api.AggregatorApi +import com.intellij.rt.coverage.aggregate.api.Request +import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi +import com.intellij.rt.coverage.report.api.ReportApi +import com.intellij.rt.coverage.util.ErrorReporter +import kotlinx.kover.features.jvm.impl.ConDySettings +import kotlinx.kover.features.jvm.impl.LegacyVerification +import kotlinx.kover.features.jvm.impl.convert +import java.io.File +import java.io.IOException +import java.math.BigDecimal /** * Kover Features for support Kover capabilities in Kover CLI via outdated API. */ -public class KoverLegacyFeatures { - - private static final String FREE_MARKER_LOGGER_PROPERTY_NAME = "org.freemarker.loggerLibrary"; +public object KoverLegacyFeatures { + private const val FREE_MARKER_LOGGER_PROPERTY_NAME = "org.freemarker.loggerLibrary" /** * Generate modified class-files to measure the coverage. @@ -31,23 +28,24 @@ public class KoverLegacyFeatures { * @param resultDir Directory where the instrumented class-files will be placed * @param originalDirs Root directories where the original files are located, the coverage of which needs to be measured * @param filters Filters to limit the classes that will be displayed in the report - * @param countHits Flag indicating whether to count the number of executions to each block of code. {@code false} if it is enough to register only the fact of at least one execution + * @param countHits Flag indicating whether to count the number of executions to each block of code. `false` if it is enough to register only the fact of at least one execution */ - public static void instrument(File resultDir, - List originalDirs, - ClassFilters filters, - boolean countHits + public fun instrument( + resultDir: File, + originalDirs: List, + filters: ClassFilters, + countHits: Boolean ) { - ArrayList outputs = new ArrayList<>(originalDirs.size()); - for (int i = 0; i < originalDirs.size(); i++) { - outputs.add(resultDir); + val outputs = ArrayList(originalDirs.size) + for (i in originalDirs.indices) { + outputs.add(resultDir) } - String previousConDySetting = ConDySettings.disableConDy(); + val previousConDySetting = ConDySettings.disableConDy() try { - OfflineInstrumentationApi.instrument(originalDirs, outputs, Wildcards.convertFilters(filters), countHits); + OfflineInstrumentationApi.instrument(originalDirs, outputs, filters.convert(), countHits) } finally { - ConDySettings.restoreConDy(previousConDySetting); + ConDySettings.restoreConDy(previousConDySetting) } } @@ -62,15 +60,16 @@ public class KoverLegacyFeatures { * @param filters Filters to limit the classes that will be displayed in the report * @throws IOException In case of a report generation error */ - public static void generateXmlReport( - File xmlFile, - List binaryReports, - List classfileDirs, - List sourceDirs, - String title, - ClassFilters filters - ) throws IOException { - ReportApi.xmlReport(xmlFile, title, binaryReports, classfileDirs, sourceDirs, Wildcards.convertFilters(filters)); + @Throws(IOException::class) + public fun generateXmlReport( + xmlFile: File, + binaryReports: List, + classfileDirs: List, + sourceDirs: List, + title: String, + filters: ClassFilters + ) { + ReportApi.xmlReport(xmlFile, title, binaryReports, classfileDirs, sourceDirs, filters.convert()) } /** @@ -84,31 +83,36 @@ public class KoverLegacyFeatures { * @param filters Filters to limit the classes that will be displayed in the report. * @throws IOException In case of a report generation error */ - public static void generateHtmlReport( - File htmlDir, - String charsetName, - List binaryReports, - List classfileDirs, - List sourceDirs, - String title, - ClassFilters filters - ) throws IOException { - // repeat reading freemarker temple from resources if error occurred, see https://github.com/Kotlin/kotlinx-kover/issues/510 - // the values are selected empirically so that the maximum report generation time is not much more than a second - ReportApi.setFreemarkerRetry(7, 150); - + @Throws(IOException::class) + public fun generateHtmlReport( + htmlDir: File, + charsetName: String?, + binaryReports: List, + classfileDirs: List, + sourceDirs: List, + title: String, + filters: ClassFilters + ) { // print to stdout only critical errors - ErrorReporter.setLogLevel(ErrorReporter.ERROR); + ErrorReporter.setLogLevel(ErrorReporter.ERROR) // disable freemarker logging to stdout for the time of report generation - String oldFreemarkerLogger = System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, "none"); + val oldFreemarkerLogger = System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, "none") try { - ReportApi.htmlReport(htmlDir, title, charsetName, binaryReports, classfileDirs, sourceDirs, Wildcards.convertFilters(filters)); + ReportApi.htmlReport( + htmlDir, + title, + charsetName, + binaryReports, + classfileDirs, + sourceDirs, + filters.convert() + ) } finally { if (oldFreemarkerLogger == null) { - System.clearProperty(FREE_MARKER_LOGGER_PROPERTY_NAME); + System.clearProperty(FREE_MARKER_LOGGER_PROPERTY_NAME) } else { - System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, oldFreemarkerLogger); + System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, oldFreemarkerLogger) } } } @@ -123,11 +127,17 @@ public class KoverLegacyFeatures { * @param classfileDirs List of root directories for compiled class-files * @return List of rule violation errors, empty list if there is no verification errors. */ - public static List verify(List rules, File tempDir, KoverLegacyFeatures.ClassFilters filters, List binaryReports, List classfileDirs) { + public fun verify( + rules: List, + tempDir: File, + filters: ClassFilters, + binaryReports: List, + classfileDirs: List + ): List { try { - return LegacyVerification.verify(rules, tempDir, filters, binaryReports, classfileDirs); - } catch (IOException e) { - throw new RuntimeException("Kover features exception occurred while verification", e); + return LegacyVerification.verify(rules, tempDir, filters, binaryReports, classfileDirs) + } catch (e: IOException) { + throw RuntimeException("Kover features exception occurred while verification", e) } } @@ -140,11 +150,17 @@ public class KoverLegacyFeatures { * @param binaryReports List of coverage binary binaryReports in IC format * @param classfileDirs List of root directories for compiled class-files */ - public static void aggregateIc(File icFile, KoverLegacyFeatures.ClassFilters filters, File tempDir, List binaryReports, List classfileDirs) { - final File smapFile = new File(tempDir, "report.smap"); + public fun aggregateIc( + icFile: File, + filters: ClassFilters, + tempDir: File, + binaryReports: List, + classfileDirs: List + ) { + val smapFile = File(tempDir, "report.smap") - Request request = new Request(Wildcards.convertFilters(filters), icFile, smapFile); - AggregatorApi.aggregate(Collections.singletonList(request), binaryReports, classfileDirs); + val request = Request(filters.convert(), icFile, smapFile) + AggregatorApi.aggregate(listOf(request), binaryReports, classfileDirs) } /** @@ -156,170 +172,45 @@ public class KoverLegacyFeatures { * @param classfileDirs List of root directories for compiled class-files * @return List of coverage values. */ - public static List evalCoverage(GroupingBy groupBy, CoverageUnit coverageUnit, AggregationType aggregationForGroup, File tempDir, KoverLegacyFeatures.ClassFilters filters, List binaryReports, List classfileDirs) { - Bound bound = new Bound(LegacyVerification.ONE_HUNDRED, BigDecimal.ZERO, coverageUnit, aggregationForGroup); - Rule rule = new Rule("", groupBy, Collections.singletonList(bound)); - - List violations = verify(Collections.singletonList(rule), tempDir, filters, binaryReports, classfileDirs); - ArrayList result = new ArrayList<>(); - - for (KoverLegacyFeatures.RuleViolations violation : violations) { - for (KoverLegacyFeatures.BoundViolation boundViolation : violation.violations) { - result.add(new KoverLegacyFeatures.CoverageValue(boundViolation.entityName, boundViolation.value)); + public fun evalCoverage( + groupBy: GroupingBy, + coverageUnit: CoverageUnit, + aggregationForGroup: AggregationType, + tempDir: File, + filters: ClassFilters, + binaryReports: List, + classfileDirs: List + ): List { + val bound = Bound(LegacyVerification.ONE_HUNDRED, BigDecimal.ZERO, coverageUnit, aggregationForGroup) + val rule = Rule("", groupBy, listOf(bound)) + + val violations = verify(listOf(rule), tempDir, filters, binaryReports, classfileDirs) + val result = ArrayList() + + for (violation in violations) { + for (boundViolation in violation.violations) { + result.add(CoverageValue(boundViolation.entityName, boundViolation.value)) } } - return result; - } - - /** - * Class filters. - */ - public static class ClassFilters { - /** - * If specified, only the classes specified in this field are filtered. - */ - public final Set includeClasses; - - /** - * The classes specified in this field are not filtered. - */ - public final Set excludeClasses; - - /** - * Classes that have at least one of the annotations specified in this field are not filtered. - */ - public final Set excludeAnnotation; - - public ClassFilters(Set includeClasses, - Set excludeClasses, - Set excludeAnnotation) { - this.includeClasses = includeClasses; - this.excludeClasses = excludeClasses; - this.excludeAnnotation = excludeAnnotation; - } - } - - /** - * Entity type for grouping code to coverage evaluation. - */ - public enum GroupingBy { - /** - * Counts the coverage values for all code. - */ - APPLICATION, - /** - * Counts the coverage values for each class separately. - */ - CLASS, - /** - * Counts the coverage values for each package that has classes separately. - */ - PACKAGE - } - - /** - * Type of the metric to evaluate code coverage. - */ - public enum CoverageUnit { - /** - * Number of lines. - */ - LINE, - /** - * Number of JVM bytecode instructions. - */ - INSTRUCTION, - /** - * Number of branches covered. - */ - BRANCH - } - - /** - * Type of counter value to compare with minimal and maximal values if them defined. - */ - public enum AggregationType { - COVERED_COUNT, - MISSED_COUNT, - COVERED_PERCENTAGE, - MISSED_PERCENTAGE - } - - public static class CoverageValue { - public final String entityName; - public final BigDecimal value; - - public CoverageValue(String entityName, BigDecimal value) { - this.entityName = entityName; - this.value = value; - } + return result } +} +/** + * Class filters. + */ +public data class ClassFilters( /** - * Describes a single bound for the verification rule to enforce + * If specified, only the classes specified in this field are filtered. */ - public static class Bound { - public final BigDecimal minValue; - public final BigDecimal maxValue; - - public final CoverageUnit coverageUnits; - public final AggregationType aggregationForGroup; - - public Bound(BigDecimal minValue, BigDecimal maxValue, CoverageUnit coverageUnits, AggregationType aggregationForGroup) { - this.minValue = minValue; - this.maxValue = maxValue; - this.coverageUnits = coverageUnits; - this.aggregationForGroup = aggregationForGroup; - } - } - + public val includeClasses: Set, /** - * Verification rule - a named set of bounds of coverage value to check. + * The classes specified in this field are not filtered. */ - public static class Rule { - public final String name; - - public final GroupingBy groupBy; - - public final List bounds; - - public Rule(String name, GroupingBy groupBy, List bounds) { - this.name = name; - this.groupBy = groupBy; - this.bounds = bounds; - } - } - - /** - * Violation of verification rule. - */ - public static class RuleViolations { - public final Rule rule; - - public final List violations; - - public RuleViolations(Rule rule, List violations) { - this.rule = rule; - this.violations = violations; - } - } - + public val excludeClasses: Set, /** - * Violation of verification bound. + * Classes that have at least one of the annotations specified in this field are not filtered. */ - public static class BoundViolation { - public final Bound bound; - public final boolean isMax; - public final BigDecimal value; - public final String entityName; - - public BoundViolation(Bound bound, boolean isMax, BigDecimal value, String entityName) { - this.bound = bound; - this.isMax = isMax; - this.value = value; - this.entityName = entityName; - } - } - -} + public val excludeAnnotation: Set +) diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java deleted file mode 100644 index 23075d92..00000000 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/LegacyVerification.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.features.jvm; - -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 com.intellij.rt.coverage.verify.Verifier; -import com.intellij.rt.coverage.verify.api.*; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.TreeMap; - -class LegacyVerification { - private LegacyVerification() { - // no-op - } - - static final BigDecimal ONE_HUNDRED = new BigDecimal(100); - - public static List verify(List rules, File tempDir, KoverLegacyFeatures.ClassFilters filters, List reports, List outputs) throws IOException { - Filters intellijFilters = Wildcards.convertFilters(filters); - - final ArrayList rulesArray = new ArrayList<>(); - - File ic = new File(tempDir, "agg-ic.ic"); - File smap = new File(tempDir, "agg-smap.smap"); - - Request requests = new Request(intellijFilters, ic, smap); - AggregatorApi.aggregate(Collections.singletonList(requests), reports, outputs); - - for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++) { - KoverLegacyFeatures.Rule rule = rules.get(ruleIndex); - - final List bounds = new ArrayList<>(); - for (int boundIndex = 0; boundIndex < rule.bounds.size(); boundIndex++) { - KoverLegacyFeatures.Bound b = rule.bounds.get(boundIndex); - - bounds.add(new Bound(boundIndex, counterToIntellij(b), valueTypeToIntellij(b), valueToIntellij(b, b.minValue), valueToIntellij(b, b.maxValue))); - } - - rulesArray.add(new Rule(ruleIndex, ic, targetToIntellij(rule), bounds)); - } - - - final Verifier verifier = new Verifier(rulesArray); - verifier.processRules(); - List violations = VerificationApi.verify(rulesArray); - - ArrayList ruleViolations = new ArrayList<>(); - for (RuleViolation ruleViolation : violations) { - TreeMap resultBounds = new TreeMap<>(); - - KoverLegacyFeatures.Rule rule = rules.get(ruleViolation.id); - for (int boundIndex = 0; boundIndex < ruleViolation.violations.size(); boundIndex++) { - BoundViolation boundViolation = ruleViolation.violations.get(boundIndex); - KoverLegacyFeatures.Bound bound = rule.bounds.get(boundViolation.id); - - for (Violation maxViolation : boundViolation.maxViolations) { - String entityName = rule.groupBy != KoverLegacyFeatures.GroupingBy.APPLICATION ? maxViolation.targetName : null; - resultBounds.put( - new ViolationId(boundViolation.id, entityName), - new KoverLegacyFeatures.BoundViolation(bound, true, intellijToValue(maxViolation.targetValue, bound), entityName) - ); - } - for (Violation minViolation : boundViolation.minViolations) { - String entityName = rule.groupBy != KoverLegacyFeatures.GroupingBy.APPLICATION ? minViolation.targetName : null; - resultBounds.put( - new ViolationId(boundViolation.id, entityName), - new KoverLegacyFeatures.BoundViolation(bound, false, intellijToValue(minViolation.targetValue, bound), entityName) - ); - } - } - - ruleViolations.add(new KoverLegacyFeatures.RuleViolations(rule, new ArrayList<>(resultBounds.values()))); - } - - return ruleViolations; - } - - private static class ViolationId implements Comparable { - private final int index; - private final String entityName; - - private ViolationId(int index, String entityName) { - this.index = index; - this.entityName = entityName; - } - - @Override - public int compareTo(@NotNull LegacyVerification.ViolationId other) { - // first compared by index - if (index != other.index) { - return Integer.compare(index, other.index); - } - - // if indexes are equals then compare by entity name - if (entityName == null) { - // bounds with empty entity names goes first - return (other.entityName == null) ? 0 : -1; - } - - if (other.entityName == null) return 1; - - return entityName.compareTo(other.entityName); - } - } - - private static BigDecimal intellijToValue(BigDecimal intellijValue, KoverLegacyFeatures.Bound bound) { - if (isPercentage(bound.aggregationForGroup)) { - return intellijValue.multiply(ONE_HUNDRED); - } else { - return intellijValue; - } - } - - private static Target targetToIntellij(KoverLegacyFeatures.Rule rule) { - switch (rule.groupBy) { - case APPLICATION: - return Target.ALL; - case CLASS: - return Target.CLASS; - case PACKAGE: - return Target.PACKAGE; - } - return null; - } - - private static Counter counterToIntellij(KoverLegacyFeatures.Bound bound) { - switch (bound.coverageUnits) { - case LINE: - return Counter.LINE; - case INSTRUCTION: - return Counter.INSTRUCTION; - case BRANCH: - return Counter.BRANCH; - } - return null; - } - - private static ValueType valueTypeToIntellij(KoverLegacyFeatures.Bound bound) { - switch (bound.aggregationForGroup) { - case COVERED_COUNT: - return ValueType.COVERED; - case MISSED_COUNT: - return ValueType.MISSED; - case COVERED_PERCENTAGE: - return ValueType.COVERED_RATE; - case MISSED_PERCENTAGE: - return ValueType.MISSED_RATE; - } - return null; - } - - private static BigDecimal valueToIntellij(KoverLegacyFeatures.Bound bound, BigDecimal value) { - if (value == null) return null; - - if (isPercentage(bound.aggregationForGroup)) { - return value.divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP); - } else { - return value; - } - } - - private static boolean isPercentage(KoverLegacyFeatures.AggregationType aggregationType) { - return aggregationType == KoverLegacyFeatures.AggregationType.COVERED_PERCENTAGE || aggregationType == KoverLegacyFeatures.AggregationType.MISSED_PERCENTAGE; - } -} diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt index 411910ba..94610b4d 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt @@ -1,7 +1,11 @@ -package kotlinx.kover.features.jvm; +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm -import java.io.IOException; -import java.io.InputStream; +import java.io.IOException +import java.io.InputStream /** * Class for instrumentation of JVM byte code of already compiled class-files. @@ -15,5 +19,5 @@ public interface OfflineInstrumenter { * @return instrumented byte code * @throws IOException in case of any instrumentation error */ - byte[] instrument(InputStream originalClass, String debugName) throws IOException; + public fun instrument(originalClass: InputStream, debugName: String): ByteArray } \ No newline at end of file diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenterImpl.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenterImpl.java deleted file mode 100644 index b48583fc..00000000 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenterImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package kotlinx.kover.features.jvm; - -import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Implementation of {@link OfflineInstrumenter}. - * The class should not be explicitly used from the outside. - */ -class OfflineInstrumenterImpl implements OfflineInstrumenter { - private final boolean countHits; - - OfflineInstrumenterImpl(boolean countHits) { - this.countHits = countHits; - } - - @Override - public byte[] instrument(InputStream originalClass, String debugName) throws IOException { - String previousConDySetting = ConDySettings.disableConDy(); - - try { - return OfflineInstrumentationApi.instrument(originalClass, countHits); - } catch (Throwable e) { - throw new IOException( - String.format("Error while instrumenting '%s' with Kover instrumenter version '%s'", - debugName, KoverFeatures.getVersion()), e); - } finally { - ConDySettings.restoreConDy(previousConDySetting); - } - } - -} diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt new file mode 100644 index 00000000..aa037798 --- /dev/null +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm + +import java.math.BigDecimal + + +/** + * Entity type for grouping code to coverage evaluation. + */ +public enum class GroupingBy { + /** + * Counts the coverage values for all code. + */ + APPLICATION, + + /** + * Counts the coverage values for each class separately. + */ + CLASS, + + /** + * Counts the coverage values for each package that has classes separately. + */ + PACKAGE +} + +/** + * Type of the metric to evaluate code coverage. + */ +public enum class CoverageUnit { + /** + * Number of lines. + */ + LINE, + + /** + * Number of JVM bytecode instructions. + */ + INSTRUCTION, + + /** + * Number of branches covered. + */ + BRANCH +} + +/** + * Type of counter value to compare with minimal and maximal values if them defined. + */ +public enum class AggregationType { + COVERED_COUNT, + MISSED_COUNT, + COVERED_PERCENTAGE, + MISSED_PERCENTAGE +} + +public data class CoverageValue(val entityName: String?, val value: BigDecimal) + +/** + * Describes a single bound for the verification rule to enforce + */ +public data class Bound( + val minValue: BigDecimal?, + val maxValue: BigDecimal?, + val coverageUnits: CoverageUnit, + val aggregationForGroup: AggregationType +) + +/** + * Verification rule - a named set of bounds of coverage value to check. + */ +public data class Rule(val name: String, val groupBy: GroupingBy, val bounds: List) + +/** + * Violation of verification rule. + */ +public data class RuleViolations(val rule: Rule, val violations: List) + +/** + * Violation of verification bound. + */ +public data class BoundViolation(val bound: Bound, val isMax: Boolean, val value: BigDecimal, val entityName: String?) diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java deleted file mode 100644 index a4f0fc19..00000000 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Wildcards.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.features.jvm; - -import com.intellij.rt.coverage.report.api.Filters; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - -class Wildcards { - private static final HashSet regexMetacharactersSet = new HashSet<>(); - - private static final String regexMetacharacters = "<([{\\^-=$!|]})+.>"; - - static { - for (int i = 0; i < regexMetacharacters.length(); i++) { - char c = regexMetacharacters.charAt(i); - regexMetacharactersSet.add(c); - } - } - - private Wildcards() { - // no-op - } - - static Filters convertFilters(KoverLegacyFeatures.ClassFilters filters) { - return new Filters( - convert(filters.includeClasses), - convert(filters.excludeClasses), - convert(filters.excludeAnnotation) - ); - } - - private static List convert(Set templates) { - ArrayList patterns = new ArrayList<>(templates.size()); - for (String template : templates) { - patterns.add(Pattern.compile(Wildcards.wildcardsToRegex(template))); - } - return patterns; - } - - /** - * Replaces characters `*` or `.` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters. - */ - static String wildcardsToRegex(String value) { - // in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance - final StringBuilder builder = new StringBuilder(value.length() * 2); - - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (regexMetacharactersSet.contains(c)) { - builder.append('\\').append(c); - } else if (c == '*') { - builder.append(".*"); - } else if (c == '?') { - builder.append('.'); - } else if (c == '#') { - builder.append("[^.]*"); - } else { - builder.append(c); - } - } - return builder.toString(); - } -} diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/ConDySettings.java b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/ConDySettings.kt similarity index 53% rename from kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/ConDySettings.java rename to kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/ConDySettings.kt index 3b913275..79b88540 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/ConDySettings.java +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/ConDySettings.kt @@ -1,36 +1,36 @@ -package kotlinx.kover.features.jvm; +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm.impl + /** * Internal class to control JVM ConDy settings. */ -final class ConDySettings { - - private ConDySettings() { - // no-op - } - - private static final String CONDY_SYSTEM_PARAM_NAME = "coverage.condy.enable"; +internal object ConDySettings { + private const val CONDY_SYSTEM_PARAM_NAME = "coverage.condy.enable" /** * Disable JVM ConDy during instrumentation. * * @return previous value of ConDy setting */ - static String disableConDy() { + fun disableConDy(): String? { // disable ConDy for offline instrumentations - return System.setProperty(CONDY_SYSTEM_PARAM_NAME, "false"); + return System.setProperty(CONDY_SYSTEM_PARAM_NAME, "false") } /** * Restore previous value of JVM ConDy setting. * - * @param prevValue new setting value + * Returns prevValue new setting value. */ - static void restoreConDy(String prevValue) { + fun restoreConDy(prevValue: String?) { if (prevValue == null) { - System.clearProperty(CONDY_SYSTEM_PARAM_NAME); + System.clearProperty(CONDY_SYSTEM_PARAM_NAME) } else { - System.setProperty(CONDY_SYSTEM_PARAM_NAME, prevValue); + System.setProperty(CONDY_SYSTEM_PARAM_NAME, prevValue) } } } diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt new file mode 100644 index 00000000..4fc47ceb --- /dev/null +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm.impl + +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 com.intellij.rt.coverage.verify.Verifier +import com.intellij.rt.coverage.verify.api.Counter +import com.intellij.rt.coverage.verify.api.Target +import com.intellij.rt.coverage.verify.api.ValueType +import com.intellij.rt.coverage.verify.api.VerificationApi +import kotlinx.kover.features.jvm.* +import java.io.File +import java.math.BigDecimal +import java.math.RoundingMode +import java.util.* + +private typealias IntellijRule = com.intellij.rt.coverage.verify.api.Rule + +private typealias IntellijBound = com.intellij.rt.coverage.verify.api.Bound + +internal object LegacyVerification { + internal val ONE_HUNDRED: BigDecimal = BigDecimal(100) + + fun verify( + rules: List, + tempDir: File, + filters: ClassFilters, + reports: List, + outputs: List + ): List { + val intellijFilters: Filters = filters.convert() + + val rulesArray = ArrayList() + + val ic = File(tempDir, "agg-ic.ic") + val smap = File(tempDir, "agg-smap.smap") + + val requests = Request(intellijFilters, ic, smap) + AggregatorApi.aggregate(listOf(requests), reports, outputs) + + for (ruleIndex in rules.indices) { + val rule = rules[ruleIndex] + + val bounds: MutableList = ArrayList() + for (boundIndex in rule.bounds.indices) { + val b = rule.bounds[boundIndex] + + bounds.add( + IntellijBound( + boundIndex, + counterToIntellij(b), + valueTypeToIntellij(b), + valueToIntellij(b, b.minValue), + valueToIntellij(b, b.maxValue) + ) + ) + } + + rulesArray.add(IntellijRule(ruleIndex, ic, targetToIntellij(rule), bounds)) + } + + + val verifier = Verifier(rulesArray) + verifier.processRules() + val violations = VerificationApi.verify(rulesArray) + + val ruleViolations = ArrayList() + for (ruleViolation in violations) { + val resultBounds = TreeMap() + + val rule = rules[ruleViolation.id] + for (boundIndex in ruleViolation.violations.indices) { + val boundViolation = ruleViolation.violations[boundIndex] + val bound = rule.bounds[boundViolation.id] + + for (maxViolation in boundViolation.maxViolations) { + val entityName = if (rule.groupBy != GroupingBy.APPLICATION) maxViolation.targetName else null + resultBounds[ViolationId(boundViolation.id, entityName)] = + BoundViolation(bound, true, intellijToValue(maxViolation.targetValue, bound), entityName) + } + for (minViolation in boundViolation.minViolations) { + val entityName = if (rule.groupBy != GroupingBy.APPLICATION) minViolation.targetName else null + resultBounds[ViolationId(boundViolation.id, entityName)] = + BoundViolation(bound, false, intellijToValue(minViolation.targetValue, bound), entityName) + } + } + + ruleViolations.add(RuleViolations(rule, ArrayList(resultBounds.values))) + } + + return ruleViolations + } + + private fun intellijToValue(intellijValue: BigDecimal, bound: Bound): BigDecimal { + return if (isPercentage(bound.aggregationForGroup)) { + intellijValue.multiply(ONE_HUNDRED) + } else { + intellijValue + } + } + + private fun targetToIntellij(rule: Rule): Target? { + return when (rule.groupBy) { + GroupingBy.APPLICATION -> Target.ALL + GroupingBy.CLASS -> Target.CLASS + GroupingBy.PACKAGE -> Target.PACKAGE + } + } + + private fun counterToIntellij(bound: Bound): Counter? { + return when (bound.coverageUnits) { + CoverageUnit.LINE -> Counter.LINE + CoverageUnit.INSTRUCTION -> Counter.INSTRUCTION + CoverageUnit.BRANCH -> Counter.BRANCH + } + } + + private fun valueTypeToIntellij(bound: Bound): ValueType? { + return when (bound.aggregationForGroup) { + AggregationType.COVERED_COUNT -> ValueType.COVERED + AggregationType.MISSED_COUNT -> ValueType.MISSED + AggregationType.COVERED_PERCENTAGE -> ValueType.COVERED_RATE + AggregationType.MISSED_PERCENTAGE -> ValueType.MISSED_RATE + } + } + + private fun valueToIntellij(bound: Bound, value: BigDecimal?): BigDecimal? { + if (value == null) return null + + return if (isPercentage(bound.aggregationForGroup)) { + value.divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP) + } else { + value + } + } + + private fun isPercentage(aggregationType: AggregationType): Boolean { + return aggregationType == AggregationType.COVERED_PERCENTAGE || aggregationType == AggregationType.MISSED_PERCENTAGE + } + + private class ViolationId(private val index: Int, private val entityName: String?) : Comparable { + override fun compareTo(other: ViolationId): Int { + // first compared by index + if (index != other.index) { + return Integer.compare(index, other.index) + } + + // if indexes are equals then compare by entity name + if (entityName == null) { + // bounds with empty entity names goes first + return if ((other.entityName == null)) 0 else -1 + } + + if (other.entityName == null) return 1 + + return entityName.compareTo(other.entityName) + } + } +} diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/OfflineInstrumenterImpl.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/OfflineInstrumenterImpl.kt new file mode 100644 index 00000000..9d5cdbd3 --- /dev/null +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/OfflineInstrumenterImpl.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm.impl + +import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi +import kotlinx.kover.features.jvm.KoverFeatures.version +import kotlinx.kover.features.jvm.OfflineInstrumenter +import java.io.IOException +import java.io.InputStream + + +/** + * Implementation of [OfflineInstrumenter]. + * The class should not be explicitly used from the outside. + */ +internal class OfflineInstrumenterImpl(private val countHits: Boolean): OfflineInstrumenter { + + override fun instrument(originalClass: InputStream, debugName: String): ByteArray { + val previousConDySetting = ConDySettings.disableConDy() + try { + return OfflineInstrumentationApi.instrument(originalClass, countHits) + } catch (e: Throwable) { + throw IOException( + String.format( + "Error while instrumenting '%s' with Kover instrumenter version '%s'", + debugName, + version + ), + e + ) + } finally { + ConDySettings.restoreConDy(previousConDySetting) + } + } +} diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/Wildcards.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/Wildcards.kt new file mode 100644 index 00000000..9f326d2e --- /dev/null +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/Wildcards.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.features.jvm.impl + +import com.intellij.rt.coverage.report.api.Filters +import kotlinx.kover.features.jvm.ClassFilters +import java.util.regex.Pattern + +internal fun ClassFilters.convert(): Filters { + return Filters( + convert(includeClasses), + convert(excludeClasses), + convert(excludeAnnotation) + ) +} + +private fun convert(templates: Set): List { + val patterns = ArrayList(templates.size) + for (template in templates) { + patterns.add(Pattern.compile(template.wildcardsToRegex())) + } + return patterns +} + +/** + * Replaces characters `*` to `.*`, `#` to `[^.]*` and `?` to `.` regexp characters and also add escape char '\' before regexp metacharacters (see [regexMetacharactersSet]). + */ +internal fun String.wildcardsToRegex(): String { + // in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance + val builder = StringBuilder(length * 2) + + forEach { char -> + when (char) { + in regexMetacharactersSet -> builder.append('\\').append(char) + '*' -> builder.append('.').append("*") + '?' -> builder.append('.') + '#' -> builder.append("[^.]*") + else -> builder.append(char) + } + } + + return builder.toString() +} + +private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet() diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Paths.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Paths.kt index 33a62a0f..699c408b 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Paths.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Paths.kt @@ -11,7 +11,7 @@ import java.io.File internal fun agentFilePath(toolVariant: CoverageToolVariant): String { return if (toolVariant.vendor == CoverageToolVendor.KOVER) { - "kover${separator}kover-jvm-agent-${KoverFeatures.getVersion()}.jar" + "kover${separator}kover-jvm-agent-${KoverFeatures.version}.jar" } else { "kover${separator}jacoco-coverage-agent-${toolVariant.version}.jar" } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt index cb508638..9bce6473 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt @@ -28,5 +28,5 @@ public object KoverVersions { * Current version of Kover Gradle Plugin */ public val version: String - get() = KoverFeatures.getVersion() + get() = KoverFeatures.version } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt index 4a76ec11..49b9cb2d 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt @@ -4,10 +4,10 @@ package kotlinx.kover.gradle.plugin.tools -import kotlinx.kover.features.jvm.KoverLegacyFeatures -import kotlinx.kover.features.jvm.KoverLegacyFeatures.BoundViolation -import kotlinx.kover.features.jvm.KoverLegacyFeatures.CoverageValue +import kotlinx.kover.features.jvm.* import kotlinx.kover.gradle.plugin.dsl.* +import kotlinx.kover.gradle.plugin.dsl.AggregationType +import kotlinx.kover.gradle.plugin.dsl.CoverageUnit import java.io.File import java.io.Serializable import java.nio.charset.Charset @@ -42,7 +42,7 @@ internal data class CoverageRequest( val lineFormat: String, ): Serializable -internal fun generateErrorMessage(violations: List): String { +internal fun generateErrorMessage(violations: List): String { val messageBuilder = StringBuilder() violations.forEach { rule -> @@ -63,27 +63,26 @@ internal fun generateErrorMessage(violations: List "lines" - KoverLegacyFeatures.CoverageUnit.INSTRUCTION -> "instructions" - KoverLegacyFeatures.CoverageUnit.BRANCH -> "branches" + kotlinx.kover.features.jvm.CoverageUnit.LINE -> "lines" + kotlinx.kover.features.jvm.CoverageUnit.INSTRUCTION -> "instructions" + kotlinx.kover.features.jvm.CoverageUnit.BRANCH -> "branches" } val valueTypeText = when (bound.aggregationForGroup) { - KoverLegacyFeatures.AggregationType.COVERED_COUNT -> "covered count" - KoverLegacyFeatures.AggregationType.MISSED_COUNT -> "missed count" - KoverLegacyFeatures.AggregationType.COVERED_PERCENTAGE -> "covered percentage" - KoverLegacyFeatures.AggregationType.MISSED_PERCENTAGE -> "missed percentage" + kotlinx.kover.features.jvm.AggregationType.COVERED_COUNT -> "covered count" + kotlinx.kover.features.jvm.AggregationType.MISSED_COUNT -> "missed count" + kotlinx.kover.features.jvm.AggregationType.COVERED_PERCENTAGE -> "covered percentage" + kotlinx.kover.features.jvm.AggregationType.MISSED_PERCENTAGE -> "missed percentage" } val entityText = when (rule.rule.groupBy) { - KoverLegacyFeatures.GroupingBy.APPLICATION -> "" - KoverLegacyFeatures.GroupingBy.CLASS -> " for class '$entityName'" - KoverLegacyFeatures.GroupingBy.PACKAGE -> " for package '$entityName'" + GroupingBy.APPLICATION -> "" + GroupingBy.CLASS -> " for class '$entityName'" + GroupingBy.PACKAGE -> " for package '$entityName'" } val expectedValue = if (isMax) bound.maxValue else bound.minValue diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt index 758254d3..18ef3369 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Evaluation.kt @@ -4,7 +4,7 @@ package kotlinx.kover.gradle.plugin.tools.jacoco -import kotlinx.kover.features.jvm.KoverLegacyFeatures.CoverageValue +import kotlinx.kover.features.jvm.CoverageValue import kotlinx.kover.gradle.plugin.commons.KoverCriticalException import kotlinx.kover.gradle.plugin.commons.ReportContext import kotlinx.kover.gradle.plugin.commons.VerificationBound diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt index 150560c8..be9b5eea 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt @@ -5,10 +5,10 @@ package kotlinx.kover.gradle.plugin.tools.jacoco import groovy.lang.GroovyObject -import kotlinx.kover.features.jvm.KoverLegacyFeatures.Bound -import kotlinx.kover.features.jvm.KoverLegacyFeatures.BoundViolation -import kotlinx.kover.features.jvm.KoverLegacyFeatures.Rule -import kotlinx.kover.features.jvm.KoverLegacyFeatures.RuleViolations +import kotlinx.kover.features.jvm.Bound +import kotlinx.kover.features.jvm.BoundViolation +import kotlinx.kover.features.jvm.Rule +import kotlinx.kover.features.jvm.RuleViolations import kotlinx.kover.gradle.plugin.commons.* import kotlinx.kover.gradle.plugin.dsl.AggregationType import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt index 84a31e7b..65a2d163 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt @@ -1,7 +1,6 @@ package kotlinx.kover.gradle.plugin.tools.kover -import kotlinx.kover.features.jvm.KoverLegacyFeatures -import kotlinx.kover.features.jvm.KoverLegacyFeatures.Bound +import kotlinx.kover.features.jvm.* import kotlinx.kover.gradle.plugin.commons.ReportFilters import kotlinx.kover.gradle.plugin.commons.VerificationBound import kotlinx.kover.gradle.plugin.commons.VerificationRule @@ -9,14 +8,14 @@ import kotlinx.kover.gradle.plugin.dsl.AggregationType import kotlinx.kover.gradle.plugin.dsl.CoverageUnit import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType -internal fun ReportFilters.toKoverFeatures() = KoverLegacyFeatures.ClassFilters( +internal fun ReportFilters.toKoverFeatures() = ClassFilters( includesClasses, excludesClasses, excludesAnnotations ) -internal fun VerificationRule.convert(): KoverLegacyFeatures.Rule { - return KoverLegacyFeatures.Rule( +internal fun VerificationRule.convert(): Rule { + return Rule( name, entityType.convert(), bounds.map { it.convert() } @@ -29,28 +28,28 @@ internal fun VerificationBound.convert(): Bound { } -internal fun GroupingEntityType.convert(): KoverLegacyFeatures.GroupingBy { +internal fun GroupingEntityType.convert(): GroupingBy { return when (this) { - GroupingEntityType.APPLICATION -> KoverLegacyFeatures.GroupingBy.APPLICATION - GroupingEntityType.CLASS -> KoverLegacyFeatures.GroupingBy.CLASS - GroupingEntityType.PACKAGE -> KoverLegacyFeatures.GroupingBy.PACKAGE + GroupingEntityType.APPLICATION -> GroupingBy.APPLICATION + GroupingEntityType.CLASS -> GroupingBy.CLASS + GroupingEntityType.PACKAGE -> GroupingBy.PACKAGE } } -internal fun CoverageUnit.convert(): KoverLegacyFeatures.CoverageUnit { +internal fun CoverageUnit.convert(): kotlinx.kover.features.jvm.CoverageUnit { return when (this) { - CoverageUnit.LINE -> KoverLegacyFeatures.CoverageUnit.LINE - CoverageUnit.BRANCH -> KoverLegacyFeatures.CoverageUnit.BRANCH - CoverageUnit.INSTRUCTION -> KoverLegacyFeatures.CoverageUnit.INSTRUCTION + CoverageUnit.LINE -> kotlinx.kover.features.jvm.CoverageUnit.LINE + CoverageUnit.BRANCH -> kotlinx.kover.features.jvm.CoverageUnit.BRANCH + CoverageUnit.INSTRUCTION -> kotlinx.kover.features.jvm.CoverageUnit.INSTRUCTION } } -internal fun AggregationType.convert(): KoverLegacyFeatures.AggregationType { +internal fun AggregationType.convert(): kotlinx.kover.features.jvm.AggregationType { return when (this) { - AggregationType.COVERED_COUNT -> KoverLegacyFeatures.AggregationType.COVERED_COUNT - AggregationType.COVERED_PERCENTAGE -> KoverLegacyFeatures.AggregationType.COVERED_PERCENTAGE - AggregationType.MISSED_COUNT -> KoverLegacyFeatures.AggregationType.MISSED_COUNT - AggregationType.MISSED_PERCENTAGE -> KoverLegacyFeatures.AggregationType.MISSED_PERCENTAGE + AggregationType.COVERED_COUNT -> kotlinx.kover.features.jvm.AggregationType.COVERED_COUNT + AggregationType.COVERED_PERCENTAGE -> kotlinx.kover.features.jvm.AggregationType.COVERED_PERCENTAGE + AggregationType.MISSED_COUNT -> kotlinx.kover.features.jvm.AggregationType.MISSED_COUNT + AggregationType.MISSED_PERCENTAGE -> kotlinx.kover.features.jvm.AggregationType.MISSED_PERCENTAGE } } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt index 683952b8..3707ad0f 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt @@ -17,7 +17,7 @@ import java.io.File internal class KoverTool(override val variant: CoverageToolVariant) : CoverageTool { - override val jvmAgentDependency: String = "org.jetbrains.kotlinx:kover-jvm-agent:${KoverFeatures.getVersion()}" + override val jvmAgentDependency: String = "org.jetbrains.kotlinx:kover-jvm-agent:${KoverFeatures.version}" // since Kover Features is in compile dependency and there is no need in additional dependency to reporter // we can't just specify null dependency, so use agent as a mock From 56bf463dac860d69946ec3d32353cc4e8815987b Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 27 Mar 2024 17:53:10 +0100 Subject: [PATCH 4/6] ~review fixes --- kover-features-jvm/api/kover-features-jvm.api | 136 ++++++++++++++++++ kover-features-jvm/build.gradle.kts | 8 +- .../kover/features/jvm/KoverLegacyFeatures.kt | 41 ++---- .../kover/features/jvm/OfflineInstrumenter.kt | 1 + .../kover/features/jvm/Verification.kt | 5 + .../features/jvm/impl/LegacyVerification.kt | 11 +- .../kover/gradle/plugin/tools/Verification.kt | 17 ++- .../tools/kover/KoverFeaturesIntegration.kt | 21 +-- 8 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 kover-features-jvm/api/kover-features-jvm.api diff --git a/kover-features-jvm/api/kover-features-jvm.api b/kover-features-jvm/api/kover-features-jvm.api new file mode 100644 index 00000000..b325d422 --- /dev/null +++ b/kover-features-jvm/api/kover-features-jvm.api @@ -0,0 +1,136 @@ +public final class kotlinx/kover/features/jvm/AggregationType : java/lang/Enum { + public static final field COVERED_COUNT Lkotlinx/kover/features/jvm/AggregationType; + public static final field COVERED_PERCENTAGE Lkotlinx/kover/features/jvm/AggregationType; + public static final field MISSED_COUNT Lkotlinx/kover/features/jvm/AggregationType; + public static final field MISSED_PERCENTAGE Lkotlinx/kover/features/jvm/AggregationType; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/kover/features/jvm/AggregationType; + public static fun values ()[Lkotlinx/kover/features/jvm/AggregationType; +} + +public final class kotlinx/kover/features/jvm/Bound { + public fun (Ljava/math/BigDecimal;Ljava/math/BigDecimal;Lkotlinx/kover/features/jvm/CoverageUnit;Lkotlinx/kover/features/jvm/AggregationType;)V + public final fun component1 ()Ljava/math/BigDecimal; + public final fun component2 ()Ljava/math/BigDecimal; + public final fun component3 ()Lkotlinx/kover/features/jvm/CoverageUnit; + public final fun component4 ()Lkotlinx/kover/features/jvm/AggregationType; + public final fun copy (Ljava/math/BigDecimal;Ljava/math/BigDecimal;Lkotlinx/kover/features/jvm/CoverageUnit;Lkotlinx/kover/features/jvm/AggregationType;)Lkotlinx/kover/features/jvm/Bound; + public static synthetic fun copy$default (Lkotlinx/kover/features/jvm/Bound;Ljava/math/BigDecimal;Ljava/math/BigDecimal;Lkotlinx/kover/features/jvm/CoverageUnit;Lkotlinx/kover/features/jvm/AggregationType;ILjava/lang/Object;)Lkotlinx/kover/features/jvm/Bound; + public fun equals (Ljava/lang/Object;)Z + public final fun getAggregationForGroup ()Lkotlinx/kover/features/jvm/AggregationType; + public final fun getCoverageUnits ()Lkotlinx/kover/features/jvm/CoverageUnit; + public final fun getMaxValue ()Ljava/math/BigDecimal; + public final fun getMinValue ()Ljava/math/BigDecimal; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class kotlinx/kover/features/jvm/BoundViolation { + public fun (Lkotlinx/kover/features/jvm/Bound;ZLjava/math/BigDecimal;Ljava/lang/String;)V + public final fun component1 ()Lkotlinx/kover/features/jvm/Bound; + public final fun component2 ()Z + public final fun component3 ()Ljava/math/BigDecimal; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Lkotlinx/kover/features/jvm/Bound;ZLjava/math/BigDecimal;Ljava/lang/String;)Lkotlinx/kover/features/jvm/BoundViolation; + public static synthetic fun copy$default (Lkotlinx/kover/features/jvm/BoundViolation;Lkotlinx/kover/features/jvm/Bound;ZLjava/math/BigDecimal;Ljava/lang/String;ILjava/lang/Object;)Lkotlinx/kover/features/jvm/BoundViolation; + public fun equals (Ljava/lang/Object;)Z + public final fun getBound ()Lkotlinx/kover/features/jvm/Bound; + public final fun getEntityName ()Ljava/lang/String; + public final fun getValue ()Ljava/math/BigDecimal; + public fun hashCode ()I + public final fun isMax ()Z + public fun toString ()Ljava/lang/String; +} + +public final class kotlinx/kover/features/jvm/ClassFilters { + public fun (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;)V + public final fun component1 ()Ljava/util/Set; + public final fun component2 ()Ljava/util/Set; + public final fun component3 ()Ljava/util/Set; + public final fun copy (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;)Lkotlinx/kover/features/jvm/ClassFilters; + public static synthetic fun copy$default (Lkotlinx/kover/features/jvm/ClassFilters;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;ILjava/lang/Object;)Lkotlinx/kover/features/jvm/ClassFilters; + public fun equals (Ljava/lang/Object;)Z + public final fun getExcludeAnnotation ()Ljava/util/Set; + public final fun getExcludeClasses ()Ljava/util/Set; + public final fun getIncludeClasses ()Ljava/util/Set; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class kotlinx/kover/features/jvm/CoverageUnit : java/lang/Enum { + public static final field BRANCH Lkotlinx/kover/features/jvm/CoverageUnit; + public static final field INSTRUCTION Lkotlinx/kover/features/jvm/CoverageUnit; + public static final field LINE Lkotlinx/kover/features/jvm/CoverageUnit; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/kover/features/jvm/CoverageUnit; + public static fun values ()[Lkotlinx/kover/features/jvm/CoverageUnit; +} + +public final class kotlinx/kover/features/jvm/CoverageValue { + public fun (Ljava/lang/String;Ljava/math/BigDecimal;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/math/BigDecimal; + public final fun copy (Ljava/lang/String;Ljava/math/BigDecimal;)Lkotlinx/kover/features/jvm/CoverageValue; + public static synthetic fun copy$default (Lkotlinx/kover/features/jvm/CoverageValue;Ljava/lang/String;Ljava/math/BigDecimal;ILjava/lang/Object;)Lkotlinx/kover/features/jvm/CoverageValue; + public fun equals (Ljava/lang/Object;)Z + public final fun getEntityName ()Ljava/lang/String; + public final fun getValue ()Ljava/math/BigDecimal; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class kotlinx/kover/features/jvm/GroupingBy : java/lang/Enum { + public static final field APPLICATION Lkotlinx/kover/features/jvm/GroupingBy; + public static final field CLASS Lkotlinx/kover/features/jvm/GroupingBy; + public static final field PACKAGE Lkotlinx/kover/features/jvm/GroupingBy; + public static fun valueOf (Ljava/lang/String;)Lkotlinx/kover/features/jvm/GroupingBy; + public static fun values ()[Lkotlinx/kover/features/jvm/GroupingBy; +} + +public final class kotlinx/kover/features/jvm/KoverFeatures { + public static final field INSTANCE Lkotlinx/kover/features/jvm/KoverFeatures; + public final fun createOfflineInstrumenter ()Lkotlinx/kover/features/jvm/OfflineInstrumenter; + public final fun getVersion ()Ljava/lang/String; + public final fun koverWildcardToRegex (Ljava/lang/String;)Ljava/lang/String; +} + +public final class kotlinx/kover/features/jvm/KoverLegacyFeatures { + public static final field INSTANCE Lkotlinx/kover/features/jvm/KoverLegacyFeatures; + public final fun aggregateIc (Ljava/io/File;Lkotlinx/kover/features/jvm/ClassFilters;Ljava/io/File;Ljava/util/List;Ljava/util/List;)V + public final fun evalCoverage (Lkotlinx/kover/features/jvm/GroupingBy;Lkotlinx/kover/features/jvm/CoverageUnit;Lkotlinx/kover/features/jvm/AggregationType;Ljava/io/File;Lkotlinx/kover/features/jvm/ClassFilters;Ljava/util/List;Ljava/util/List;)Ljava/util/List; + public final fun generateHtmlReport (Ljava/io/File;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Lkotlinx/kover/features/jvm/ClassFilters;)V + public final fun generateXmlReport (Ljava/io/File;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Lkotlinx/kover/features/jvm/ClassFilters;)V + public final fun instrument (Ljava/io/File;Ljava/util/List;Lkotlinx/kover/features/jvm/ClassFilters;Z)V + public final fun verify (Ljava/util/List;Ljava/io/File;Lkotlinx/kover/features/jvm/ClassFilters;Ljava/util/List;Ljava/util/List;)Ljava/util/List; +} + +public abstract interface class kotlinx/kover/features/jvm/OfflineInstrumenter { + public abstract fun instrument (Ljava/io/InputStream;Ljava/lang/String;)[B +} + +public final class kotlinx/kover/features/jvm/Rule { + public fun (Ljava/lang/String;Lkotlinx/kover/features/jvm/GroupingBy;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lkotlinx/kover/features/jvm/GroupingBy; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Lkotlinx/kover/features/jvm/GroupingBy;Ljava/util/List;)Lkotlinx/kover/features/jvm/Rule; + public static synthetic fun copy$default (Lkotlinx/kover/features/jvm/Rule;Ljava/lang/String;Lkotlinx/kover/features/jvm/GroupingBy;Ljava/util/List;ILjava/lang/Object;)Lkotlinx/kover/features/jvm/Rule; + public fun equals (Ljava/lang/Object;)Z + public final fun getBounds ()Ljava/util/List; + public final fun getGroupBy ()Lkotlinx/kover/features/jvm/GroupingBy; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class kotlinx/kover/features/jvm/RuleViolations { + public fun (Lkotlinx/kover/features/jvm/Rule;Ljava/util/List;)V + public final fun component1 ()Lkotlinx/kover/features/jvm/Rule; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lkotlinx/kover/features/jvm/Rule;Ljava/util/List;)Lkotlinx/kover/features/jvm/RuleViolations; + public static synthetic fun copy$default (Lkotlinx/kover/features/jvm/RuleViolations;Lkotlinx/kover/features/jvm/Rule;Ljava/util/List;ILjava/lang/Object;)Lkotlinx/kover/features/jvm/RuleViolations; + public fun equals (Ljava/lang/Object;)Z + public final fun getRule ()Lkotlinx/kover/features/jvm/Rule; + public final fun getViolations ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/kover-features-jvm/build.gradle.kts b/kover-features-jvm/build.gradle.kts index 31568e19..ef3853e8 100644 --- a/kover-features-jvm/build.gradle.kts +++ b/kover-features-jvm/build.gradle.kts @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") + alias(libs.plugins.kotlinx.binaryCompatibilityValidator) id("kover-publishing-conventions") } @@ -27,10 +28,9 @@ extensions.configure, - filters: ClassFilters, - countHits: Boolean + resultDir: File, originalDirs: List, filters: ClassFilters, countHits: Boolean ) { val outputs = ArrayList(originalDirs.size) for (i in originalDirs.indices) { @@ -100,13 +97,7 @@ public object KoverLegacyFeatures { val oldFreemarkerLogger = System.setProperty(FREE_MARKER_LOGGER_PROPERTY_NAME, "none") try { ReportApi.htmlReport( - htmlDir, - title, - charsetName, - binaryReports, - classfileDirs, - sourceDirs, - filters.convert() + htmlDir, title, charsetName, binaryReports, classfileDirs, sourceDirs, filters.convert() ) } finally { if (oldFreemarkerLogger == null) { @@ -121,18 +112,15 @@ public object KoverLegacyFeatures { /** * Verify coverage by specified verification rules. * + * @param rules List of the verification rules to check * @param tempDir Directory to create temporary files * @param filters Filters to limit the classes that will be verified - * @param binaryReports List of coverage binary binaryReports in IC format + * @param binaryReports List of coverage binary reports in IC format * @param classfileDirs List of root directories for compiled class-files * @return List of rule violation errors, empty list if there is no verification errors. */ public fun verify( - rules: List, - tempDir: File, - filters: ClassFilters, - binaryReports: List, - classfileDirs: List + rules: List, tempDir: File, filters: ClassFilters, binaryReports: List, classfileDirs: List ): List { try { return LegacyVerification.verify(rules, tempDir, filters, binaryReports, classfileDirs) @@ -147,15 +135,11 @@ public object KoverLegacyFeatures { * @param icFile Target IC report file * @param filters Filters to limit the classes that will be placed into result file * @param tempDir Directory to create temporary files - * @param binaryReports List of coverage binary binaryReports in IC format + * @param binaryReports List of coverage binary reports in IC format * @param classfileDirs List of root directories for compiled class-files */ public fun aggregateIc( - icFile: File, - filters: ClassFilters, - tempDir: File, - binaryReports: List, - classfileDirs: List + icFile: File, filters: ClassFilters, tempDir: File, binaryReports: List, classfileDirs: List ) { val smapFile = File(tempDir, "report.smap") @@ -166,10 +150,13 @@ public object KoverLegacyFeatures { /** * Get coverage values from binary reports. * - * @param tempDir Directory to create temporary files - * @param filters Filters to limit the classes that will be placed into result coverage - * @param binaryReports List of coverage binary binaryReports in IC format - * @param classfileDirs List of root directories for compiled class-files + * @param groupBy Code unit for which coverage will be aggregated + * @param coverageUnit Specify which units to measure coverage for (line, branch, etc.) + * @param aggregationForGroup Aggregation function that will be calculated over all the elements of the same group + * @param tempDir Directory to create temporary files + * @param filters Filters to limit the classes that will be placed into result coverage + * @param binaryReports List of coverage binary reports in IC format + * @param classfileDirs List of root directories for compiled class-files * @return List of coverage values. */ public fun evalCoverage( diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt index 94610b4d..301739ed 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/OfflineInstrumenter.kt @@ -19,5 +19,6 @@ public interface OfflineInstrumenter { * @return instrumented byte code * @throws IOException in case of any instrumentation error */ + @Throws(IOException::class) public fun instrument(originalClass: InputStream, debugName: String): ByteArray } \ No newline at end of file diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt index aa037798..23154c37 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/Verification.kt @@ -57,6 +57,11 @@ public enum class AggregationType { MISSED_PERCENTAGE } +/** + * Evaluated coverage [value] for given entity with name [entityName]. + * + * Entity could be class, package, etc + */ public data class CoverageValue(val entityName: String?, val value: BigDecimal) /** diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt index 4fc47ceb..81dcc4af 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt @@ -45,7 +45,7 @@ internal object LegacyVerification { for (ruleIndex in rules.indices) { val rule = rules[ruleIndex] - val bounds: MutableList = ArrayList() + val bounds: MutableList = ArrayList() for (boundIndex in rule.bounds.indices) { val b = rule.bounds[boundIndex] @@ -70,6 +70,7 @@ internal object LegacyVerification { val ruleViolations = ArrayList() for (ruleViolation in violations) { + // TreeMap is using for getting stable order in result List - in this case, it is easier to write tests and Gradle build cache will not miss val resultBounds = TreeMap() val rule = rules[ruleViolation.id] @@ -142,17 +143,15 @@ internal object LegacyVerification { return aggregationType == AggregationType.COVERED_PERCENTAGE || aggregationType == AggregationType.MISSED_PERCENTAGE } - private class ViolationId(private val index: Int, private val entityName: String?) : Comparable { + private data class ViolationId(private val index: Int, private val entityName: String?) : Comparable { override fun compareTo(other: ViolationId): Int { // first compared by index - if (index != other.index) { - return Integer.compare(index, other.index) - } + index.compareTo(other.index).takeIf { it != 0 }?.let { return it } // if indexes are equals then compare by entity name if (entityName == null) { // bounds with empty entity names goes first - return if ((other.entityName == null)) 0 else -1 + return if (other.entityName == null) 0 else -1 } if (other.entityName == null) return 1 diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt index 49b9cb2d..0e90f6b6 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt @@ -67,16 +67,16 @@ private fun BoundViolation.format(rule: RuleViolations): String { val directionText = if (isMax) "maximum" else "minimum" val metricText = when (bound.coverageUnits) { - kotlinx.kover.features.jvm.CoverageUnit.LINE -> "lines" - kotlinx.kover.features.jvm.CoverageUnit.INSTRUCTION -> "instructions" - kotlinx.kover.features.jvm.CoverageUnit.BRANCH -> "branches" + FeatureCoverageUnit.LINE -> "lines" + FeatureCoverageUnit.INSTRUCTION -> "instructions" + FeatureCoverageUnit.BRANCH -> "branches" } val valueTypeText = when (bound.aggregationForGroup) { - kotlinx.kover.features.jvm.AggregationType.COVERED_COUNT -> "covered count" - kotlinx.kover.features.jvm.AggregationType.MISSED_COUNT -> "missed count" - kotlinx.kover.features.jvm.AggregationType.COVERED_PERCENTAGE -> "covered percentage" - kotlinx.kover.features.jvm.AggregationType.MISSED_PERCENTAGE -> "missed percentage" + FeatureAggregationType.COVERED_COUNT -> "covered count" + FeatureAggregationType.MISSED_COUNT -> "missed count" + FeatureAggregationType.COVERED_PERCENTAGE -> "covered percentage" + FeatureAggregationType.MISSED_PERCENTAGE -> "missed percentage" } val entityText = when (rule.rule.groupBy) { @@ -89,3 +89,6 @@ private fun BoundViolation.format(rule: RuleViolations): String { return "$metricText $valueTypeText$entityText is $value, but expected $directionText is $expectedValue" } + +private typealias FeatureCoverageUnit = kotlinx.kover.features.jvm.CoverageUnit +private typealias FeatureAggregationType = kotlinx.kover.features.jvm.AggregationType \ No newline at end of file diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt index 65a2d163..3cc6d696 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverFeaturesIntegration.kt @@ -8,6 +8,9 @@ import kotlinx.kover.gradle.plugin.dsl.AggregationType import kotlinx.kover.gradle.plugin.dsl.CoverageUnit import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType +private typealias FeatureCoverageUnit = kotlinx.kover.features.jvm.CoverageUnit +private typealias FeatureAggregationType = kotlinx.kover.features.jvm.AggregationType + internal fun ReportFilters.toKoverFeatures() = ClassFilters( includesClasses, excludesClasses, @@ -37,19 +40,19 @@ internal fun GroupingEntityType.convert(): GroupingBy { } -internal fun CoverageUnit.convert(): kotlinx.kover.features.jvm.CoverageUnit { +internal fun CoverageUnit.convert(): FeatureCoverageUnit { return when (this) { - CoverageUnit.LINE -> kotlinx.kover.features.jvm.CoverageUnit.LINE - CoverageUnit.BRANCH -> kotlinx.kover.features.jvm.CoverageUnit.BRANCH - CoverageUnit.INSTRUCTION -> kotlinx.kover.features.jvm.CoverageUnit.INSTRUCTION + CoverageUnit.LINE -> FeatureCoverageUnit.LINE + CoverageUnit.BRANCH -> FeatureCoverageUnit.BRANCH + CoverageUnit.INSTRUCTION -> FeatureCoverageUnit.INSTRUCTION } } -internal fun AggregationType.convert(): kotlinx.kover.features.jvm.AggregationType { +internal fun AggregationType.convert(): FeatureAggregationType { return when (this) { - AggregationType.COVERED_COUNT -> kotlinx.kover.features.jvm.AggregationType.COVERED_COUNT - AggregationType.COVERED_PERCENTAGE -> kotlinx.kover.features.jvm.AggregationType.COVERED_PERCENTAGE - AggregationType.MISSED_COUNT -> kotlinx.kover.features.jvm.AggregationType.MISSED_COUNT - AggregationType.MISSED_PERCENTAGE -> kotlinx.kover.features.jvm.AggregationType.MISSED_PERCENTAGE + AggregationType.COVERED_COUNT -> FeatureAggregationType.COVERED_COUNT + AggregationType.COVERED_PERCENTAGE -> FeatureAggregationType.COVERED_PERCENTAGE + AggregationType.MISSED_COUNT -> FeatureAggregationType.MISSED_COUNT + AggregationType.MISSED_PERCENTAGE -> FeatureAggregationType.MISSED_PERCENTAGE } } From 721c7537d71f6a446be6d12429728d9345e6c844 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 27 Mar 2024 19:17:56 +0100 Subject: [PATCH 5/6] ~small fixes --- .../kotlinx/kover/features/jvm/KoverLegacyFeatures.kt | 3 ++- .../kover/features/jvm/impl/LegacyVerification.kt | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt index c8805b05..a5c53c58 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/KoverLegacyFeatures.kt @@ -73,6 +73,7 @@ public object KoverLegacyFeatures { * Generate Kover HTML report. * * @param htmlDir Output directory with result HTML report + * @param charsetName Name of charset used in HTML report * @param binaryReports List of coverage binary reports in IC format * @param classfileDirs List of root directories for compiled class-files * @param sourceDirs List of root directories for Java and Kotlin source files @@ -141,7 +142,7 @@ public object KoverLegacyFeatures { public fun aggregateIc( icFile: File, filters: ClassFilters, tempDir: File, binaryReports: List, classfileDirs: List ) { - val smapFile = File(tempDir, "report.smap") + val smapFile = tempDir.resolve("report.smap") val request = Request(filters.convert(), icFile, smapFile) AggregatorApi.aggregate(listOf(request), binaryReports, classfileDirs) diff --git a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt index 81dcc4af..a2db3199 100644 --- a/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt +++ b/kover-features-jvm/src/main/java/kotlinx/kover/features/jvm/impl/LegacyVerification.kt @@ -36,8 +36,8 @@ internal object LegacyVerification { val rulesArray = ArrayList() - val ic = File(tempDir, "agg-ic.ic") - val smap = File(tempDir, "agg-smap.smap") + val ic = tempDir.resolve("agg-ic.ic") + val smap = tempDir.resolve("agg-smap.smap") val requests = Request(intellijFilters, ic, smap) AggregatorApi.aggregate(listOf(requests), reports, outputs) @@ -104,7 +104,7 @@ internal object LegacyVerification { } } - private fun targetToIntellij(rule: Rule): Target? { + private fun targetToIntellij(rule: Rule): Target { return when (rule.groupBy) { GroupingBy.APPLICATION -> Target.ALL GroupingBy.CLASS -> Target.CLASS @@ -112,7 +112,7 @@ internal object LegacyVerification { } } - private fun counterToIntellij(bound: Bound): Counter? { + private fun counterToIntellij(bound: Bound): Counter { return when (bound.coverageUnits) { CoverageUnit.LINE -> Counter.LINE CoverageUnit.INSTRUCTION -> Counter.INSTRUCTION @@ -120,7 +120,7 @@ internal object LegacyVerification { } } - private fun valueTypeToIntellij(bound: Bound): ValueType? { + private fun valueTypeToIntellij(bound: Bound): ValueType { return when (bound.aggregationForGroup) { AggregationType.COVERED_COUNT -> ValueType.COVERED AggregationType.MISSED_COUNT -> ValueType.MISSED From 198a96d2117dc65b5956925c9737ae674b770147 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 27 Mar 2024 19:26:32 +0100 Subject: [PATCH 6/6] ~JDK version --- kover-features-jvm/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kover-features-jvm/build.gradle.kts b/kover-features-jvm/build.gradle.kts index ef3853e8..b6a0b18e 100644 --- a/kover-features-jvm/build.gradle.kts +++ b/kover-features-jvm/build.gradle.kts @@ -33,6 +33,10 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } +tasks.compileJava { + options.release.set(8) +} + // Workaround: // `kotlin-dsl` itself specifies the language version to ensure compatibility of the Kotlin DSL API // Since we ourselves guarantee and test compatibility with previous Gradle versions, we can override language version @@ -44,7 +48,7 @@ afterEvaluate { jvmTarget.set(JvmTarget.JVM_1_8) languageVersion.set(KotlinVersion.KOTLIN_1_5) apiVersion.set(KotlinVersion.KOTLIN_1_5) - freeCompilerArgs.add("-Xsuppress-version-warnings") + freeCompilerArgs.addAll("-Xsuppress-version-warnings", "-Xjdk-release=1.8") } } }