From 17c8ace011fafe8778114cdf2caacf4beb0a8d50 Mon Sep 17 00:00:00 2001 From: Sergey Shanshin Date: Wed, 15 May 2024 01:31:05 +0300 Subject: [PATCH] Added extended reports filtering The following report filtering features have been implemented: - include filter by classes annotated with specified annotations - include and exclude filters by classes that inherit the specified class or implement the specified interface Co-authored-by: Leonid Startsev Resolves #274 Resolves #454 PR #581 --- gradle/libs.versions.toml | 2 +- kover-cli/docs/index.md | 26 +-- .../cli/commands/OfflineInstrumentCommand.kt | 5 +- .../kover/cli/commands/ReportCommand.kt | 30 +++- kover-features-jvm/api/kover-features-jvm.api | 12 +- .../kover/features/jvm/KoverLegacyFeatures.kt | 53 +++++- .../kover/features/jvm/impl/Wildcards.kt | 20 +-- .../api/kover-gradle-plugin.api | 2 + kover-gradle-plugin/docs/configuring.md | 73 +++++++- .../cases/ReportAnnotationFilterTests.kt | 39 +++- .../cases/ReportInheritedFromFilterTests.kt | 170 ++++++++++++++++++ .../annotations-mix/main/kotlin/Sources.kt | 55 ++++++ .../annotations-mix/test/kotlin/TestClass.kt | 13 ++ .../inherited-main/main/kotlin/Sources.kt | 81 +++++++++ .../inherited-main/test/kotlin/TestClass.kt | 13 ++ .../appliers/tasks/VariantReportsSet.kt | 11 +- .../kover/gradle/plugin/commons/Types.kt | 8 +- .../gradle/plugin/dsl/KoverReportsConfig.kt | 67 +++++++ .../kover/gradle/plugin/dsl/KoverVersions.kt | 2 +- .../gradle/plugin/dsl/internal/ReportsImpl.kt | 12 ++ .../tools/kover/KoverFeaturesIntegration.kt | 5 +- 21 files changed, 649 insertions(+), 50 deletions(-) create mode 100644 kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportInheritedFromFilterTests.kt create mode 100644 kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/main/kotlin/Sources.kt create mode 100644 kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/test/kotlin/TestClass.kt create mode 100644 kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/main/kotlin/Sources.kt create mode 100644 kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/test/kotlin/TestClass.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6317df87..2d9caa9a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -intellij-coverage = "1.0.752" +intellij-coverage = "1.0.753" junit = "5.9.0" kotlinx-bcv = "0.13.2" kotlinx-dokka = "1.8.10" diff --git a/kover-cli/docs/index.md b/kover-cli/docs/index.md index 1e051fc5..b9d001c7 100644 --- a/kover-cli/docs/index.md +++ b/kover-cli/docs/index.md @@ -22,18 +22,22 @@ For information about offline instrumentation, [see](../offline-instrumentation# | --include | instrument only specified classes, wildcards `*` and `?` are acceptable | | + | ### Generating reports + Allows you to generate HTML and XML reports from the existing binary report. `java -jar kover-cli.jar report [ ...] --classfiles [--exclude ] [--excludeAnnotation ] [--html ] [--include ] --src [--title ] [--xml ]` -| Option | Description | Required | Multiple | -|---------------------------------------|---------------------------------------------------------------------------------------------------------|:--------:|:--------:| -| `` | list of binary reports files | | + | -| --classfiles | location of the compiled class-files root (must be original and not instrumented) | + | + | -| --exclude | filter to exclude classes, wildcards `*` and `?` are acceptable | | + | -| --excludeAnnotation | filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable | | + | -| --html | generate a HTML report in the specified path | | | -| --include | filter to include classes, wildcards `*` and `?` are acceptable | | + | -| --src | location of the source files root | + | + | -| --title | title in the HTML report | | | -| --xml | generate a XML report in the specified path | | | +| Option | Description | Required | Multiple | +|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:| +| `` | list of binary reports files | | + | +| --classfiles | location of the compiled class-files root (must be original and not instrumented) | + | + | +| --exclude | filter to exclude classes, wildcards `*` and `?` are acceptable | | + | +| --include | filter to include classes, wildcards `*` and `?` are acceptable | | + | +| --excludeAnnotation | filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable. Excludes have priority over includes | | + | +| --includeAnnotation | filter to include classes marked by this annotation, wildcards `*` and `?` are acceptable | | + | +| --excludeInheritedFrom | filter to exclude classes extending the specified class or implementing an interface, wildcards `*` and `?` are acceptable | | + | +| --includeInheritedFrom | filter to include only classes extending the specified class or implementing an interface, wildcards `*` and `?` are acceptable | | + | +| --html | generate a HTML report in the specified path | | | +| --src | location of the source files root | + | + | +| --title | title in the HTML report | | | +| --xml | generate a XML report in the specified path | | | 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 86187a12..e6ccb87d 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 @@ -65,7 +65,10 @@ internal class OfflineInstrumentCommand : Command { val filters = ClassFilters( includeClasses.toSet(), excludeClasses.toSet(), - excludeAnnotation.toSet() + excludeAnnotation.toSet(), + emptySet(), + emptySet(), + emptySet() ) 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 7f11be76..b015d4bb 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 @@ -65,11 +65,32 @@ internal class ReportCommand : Command { @Option( name = "--excludeAnnotation", - usage = "filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable", - metaVar = "" + usage = "filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable. Excludes have priority over includes", + metaVar = "" ) private var excludeAnnotation: MutableList = ArrayList() + @Option( + name = "--includeAnnotation", + usage = "filter to include classes marked by this annotation, wildcards `*` and `?` are acceptable. Excludes have priority over includes.", + metaVar = "" + ) + private var includeAnnotation: MutableList = ArrayList() + + @Option( + name = "--excludeInheritedFrom", + usage = "filter to exclude classes extending the specified class or implementing an interface, wildcards `*` and `?` are acceptable. Excludes have priority over includes", + metaVar = "" + ) + private var excludeInheritedFrom: MutableList = ArrayList() + + @Option( + name = "--includeInheritedFrom", + usage = "filter to include only classes extending the specified class or implementing an interface, wildcards `*` and `?` are acceptable. Excludes have priority over includes", + metaVar = "" + ) + private var includeInheritedFrom: MutableList = ArrayList() + override val name: String = "report" override val description: String = "Generates human-readable reports in various formats from binary report files" @@ -79,7 +100,10 @@ internal class ReportCommand : Command { val filters = ClassFilters( includeClasses.toSet(), excludeClasses.toSet(), - excludeAnnotation.toSet() + includeAnnotation.toSet(), + excludeAnnotation.toSet(), + includeInheritedFrom.toSet(), + excludeInheritedFrom.toSet() ) var fail = false diff --git a/kover-features-jvm/api/kover-features-jvm.api b/kover-features-jvm/api/kover-features-jvm.api index b325d422..bc735c6c 100644 --- a/kover-features-jvm/api/kover-features-jvm.api +++ b/kover-features-jvm/api/kover-features-jvm.api @@ -42,16 +42,22 @@ public final class kotlinx/kover/features/jvm/BoundViolation { } public final class kotlinx/kover/features/jvm/ClassFilters { - public fun (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;)V + public fun (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;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 final fun component4 ()Ljava/util/Set; + public final fun component5 ()Ljava/util/Set; + public final fun component6 ()Ljava/util/Set; + public final fun copy (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;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;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 getExcludeInheritedFrom ()Ljava/util/Set; + public final fun getIncludeAnnotation ()Ljava/util/Set; public final fun getIncludeClasses ()Ljava/util/Set; + public final fun getIncludeInheritedFrom ()Ljava/util/Set; public fun hashCode ()I public fun toString ()Ljava/lang/String; } 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 a5c53c58..7b22402e 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 @@ -198,7 +198,56 @@ public data class ClassFilters( */ public val excludeClasses: Set, /** - * Classes that have at least one of the annotations specified in this field are not filtered. + * Classes that have at least one of the annotations specified in this field are present in the report. + * All other classes that are not marked with at least one of the specified annotations are not included in the report. + * + * + * If inclusion and exclusion rules are specified at the same time, then excludes have priority over includes. + * This means that even if a class is annotated with both annotations from 'exclude' and 'include', it will be excluded from the report. + * + */ + public val includeAnnotation: Set, + /** + * Classes that have at least one of the annotations specified in this field are not present in the report. + * + * + * If inclusion and exclusion rules are specified at the same time, then excludes have priority over includes. + * This means that even if a class is annotated with both annotations from 'exclude' and 'include', it will be excluded from the report. + * + */ + public val excludeAnnotation: Set, + /** + * + * Include only classes extending at least one of the specified classes or implementing at least one of the interfaces. + * The class itself with the specified name is not included in the report. + * + * + * The entire inheritance tree is analyzed; a class may inherit the specified class/interface indirectly and still be included in the report, unless the specified class/interface is located outside of the application (see below). + * + * + * The following classes and interfaces can be specified in arguments: + * + * * classes and interfaces declared in the application + * * classes and interfaces declared outside the application, if they are directly inherited or implemented by any type from the application + * + * + * Due to technical limitations, if a specified class or interface is not declared in the application and not extended/implemented directly by one of the application types, such a filter will have no effect. + */ + public val includeInheritedFrom: Set, + /** + * + * Exclude classes extending at least one of the specified classes or implementing at least one of the interfaces. + * The class itself with the specified name is not excluded from the report. + * + * The entire inheritance tree is analyzed; a class may inherit the specified class/interface indirectly and still be included in the report, unless the specified class/interface is located outside of the application (see below). + * + * The following classes and interfaces can be specified in arguments: + * + * * classes and interfaces declared in the application + * * classes and interfaces declared outside the application, however they are directly inherited or implemented by any type from the application + * + * + * Due to technical limitations, if a specified class or interface is not declared in the application and not extended/implemented directly by one of the application types, such a filter will have no effect. */ - public val excludeAnnotation: Set + public val excludeInheritedFrom: Set ) 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 index 6adfb842..4d7384ed 100644 --- 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 @@ -10,21 +10,17 @@ import java.util.regex.Pattern internal fun ClassFilters.convert(): Filters { return Filters( - convert(includeClasses), - convert(excludeClasses), - emptyList(), - convert(excludeAnnotation), - emptyList(), - emptyList() + includeClasses.toRegexp(), + excludeClasses.toRegexp(), + includeAnnotation.toRegexp(), + excludeAnnotation.toRegexp(), + includeInheritedFrom.toRegexp(), + excludeInheritedFrom.toRegexp() ) } -private fun convert(templates: Set): List { - val patterns = ArrayList(templates.size) - for (template in templates) { - patterns.add(Pattern.compile(template.wildcardsToRegex())) - } - return patterns +private fun Collection.toRegexp(): List { + return map { template -> Pattern.compile(template.wildcardsToRegex()) } } /** diff --git a/kover-gradle-plugin/api/kover-gradle-plugin.api b/kover-gradle-plugin/api/kover-gradle-plugin.api index 5d3c3f38..77fb0ea4 100644 --- a/kover-gradle-plugin/api/kover-gradle-plugin.api +++ b/kover-gradle-plugin/api/kover-gradle-plugin.api @@ -173,6 +173,8 @@ public abstract interface class kotlinx/kover/gradle/plugin/dsl/KoverReportFilte public abstract fun classes ([Ljava/lang/String;)V public abstract fun classes ([Lorg/gradle/api/provider/Provider;)V public abstract fun getProjects ()Lorg/gradle/api/provider/SetProperty; + public abstract fun inheritedFrom ([Ljava/lang/String;)V + public abstract fun inheritedFrom ([Lorg/gradle/api/provider/Provider;)V public abstract fun packages (Ljava/lang/Iterable;)V public abstract fun packages (Lorg/gradle/api/provider/Provider;)V public abstract fun packages ([Ljava/lang/String;)V diff --git a/kover-gradle-plugin/docs/configuring.md b/kover-gradle-plugin/docs/configuring.md index 912f7438..1daf06ab 100644 --- a/kover-gradle-plugin/docs/configuring.md +++ b/kover-gradle-plugin/docs/configuring.md @@ -49,12 +49,20 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } includes { // includes class by fully-qualified JVM class name, wildcards '*' and '?' are available classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then classes that are not marked with an annotation will not be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface. + // other classes will not be included. + inheritedFrom("*MyInterface") } } @@ -113,6 +121,8 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } // inclusions for XML reports @@ -121,6 +131,11 @@ koverReport { classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } } @@ -149,6 +164,8 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } // inclusions for HTML reports @@ -157,6 +174,11 @@ koverReport { classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } } @@ -185,6 +207,8 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } // inclusions for logging reports @@ -193,6 +217,11 @@ koverReport { classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } // Add a header line to the output before the lines with coverage @@ -248,12 +277,19 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } includes { // includes class by fully-qualified JVM class name, wildcards '*' and '?' are available classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } @@ -311,6 +347,8 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } // inclusions for XML reports @@ -319,6 +357,11 @@ koverReport { classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } } @@ -347,6 +390,8 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } // inclusions for HTML reports @@ -355,6 +400,11 @@ koverReport { classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } } @@ -385,12 +435,19 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } includes { // includes class by fully-qualified JVM class name, wildcards '*' and '?' are available classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } @@ -432,6 +489,8 @@ koverReport { packages("com.another.subpackage") // excludes all classes and functions, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available annotatedBy("*Generated*") + // exclude all classes that extend the specified class or implement the specified interface + inheritedFrom("*Repository") } // inclusions for logging reports @@ -440,6 +499,11 @@ koverReport { classes("com.example.*") // includes all classes located in specified package and it subpackages packages("com.another.subpackage") + // include all classes, annotated by specified annotations (with BINARY or RUNTIME AnnotationRetention), wildcards '*' and '?' are available + // if this is specified, then no class that is not marked with an annotation will be included in the report + annotatedBy("*MarkerToKover*") + // include all classes that extend the specified class or implement the specified interface + inheritedFrom("*MyInterface") } } // Add a header line to the output before the lines with coverage @@ -466,11 +530,16 @@ Exclusion rules are names of the classes that must be excluded from the report. If inclusion and exclusion rules are specified at the same time, then excludes have priority over includes. This means that even if a class is specified in both the inclusion and exclusion rules, it will be excluded from the report (e.g. class `com.example.Class1` above). -It is acceptable to filter a class from the report by its fully-qualified name - using `classes` or `packages`. Also acceptable to filter a class, function or getter marked with the specified annotation - `annotatedBy`. +It is acceptable to filter a class from the report by its fully-qualified name - using `classes`. +Also, you can have additional filter types: + - declarations marked with the specified annotation - `annotatedBy` + - classes extending specified class or implementing specified interface - `inheritedFrom` + +**_Additional filters do not work for JaCoCo coverage library_** **Kover supports filtering by annotations having `AnnotationRetention` `BINARY` or `RUNTIME`.** -[Wildcards](#class-name-with-wildcards) `*` and `?` are allowed in class names. +[Wildcards](#class-name-with-wildcards) `*` and `?` are allowed in filters. There are several levels where you can define filters. Each of the levels has its own priority. ```kotlin diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportAnnotationFilterTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportAnnotationFilterTests.kt index 1c73addf..da4f7f20 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportAnnotationFilterTests.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportAnnotationFilterTests.kt @@ -8,6 +8,7 @@ import kotlinx.kover.gradle.plugin.dsl.* import kotlinx.kover.gradle.plugin.dsl.CoverageUnit.LINE import kotlinx.kover.gradle.plugin.test.functional.framework.configurator.* import kotlinx.kover.gradle.plugin.test.functional.framework.starter.* +import java.io.Closeable internal class ReportAnnotationFilterTests { @@ -37,18 +38,40 @@ internal class ReportAnnotationFilterTests { } } } + } - run("koverXmlReport", "check") { + @GeneratedTest + fun BuildConfigurator.testInclusions() { + addProjectWithKover { + sourcesFrom("annotations-mix") + kover { + reports { + filters { + excludes { + classes("*ByName") + annotatedBy("org.jetbrains.Exclude") + } + includes { + annotatedBy("*.Include") + } + } + } + } + } + + run("koverXmlReport") { xmlReport { - methodCounter("org.jetbrains.NotExcludedClass", "function").assertFullyCovered() + classCounter("org.jetbrains.NotAnnotatedClass").assertAbsent() classCounter("org.jetbrains.ExcludedClass").assertAbsent() - methodCounter("org.jetbrains.PartiallyExcludedClass", "function1").assertFullyCovered() - methodCounter("org.jetbrains.PartiallyExcludedClass", "function2").assertAbsent() - methodCounter("org.jetbrains.PartiallyExcludedClass", "inlined").assertAbsent() + classCounter("org.jetbrains.ExcludedByName").assertAbsent() + classCounter("org.jetbrains.TogetherClass").assertAbsent() + + classCounter("org.jetbrains.IncludedClass").assertFullyMissed() + methodCounter("org.jetbrains.IncludedClass", "function").assertFullyMissed() - methodCounter("org.jetbrains.SourcesKt", "inlinedExcluded").assertAbsent() - methodCounter("org.jetbrains.SourcesKt", "inlinedNotExcluded").assertFullyCovered() - methodCounter("org.jetbrains.SourcesKt", "notExcluded").assertFullyCovered() + classCounter("org.jetbrains.MixedClass").assertFullyMissed() + methodCounter("org.jetbrains.MixedClass", "function1").assertFullyMissed() + methodCounter("org.jetbrains.MixedClass", "function2").assertAbsent() } } } diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportInheritedFromFilterTests.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportInheritedFromFilterTests.kt new file mode 100644 index 00000000..af317a5e --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportInheritedFromFilterTests.kt @@ -0,0 +1,170 @@ +/* + * Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.gradle.plugin.test.functional.cases + +import kotlinx.kover.gradle.plugin.test.functional.framework.configurator.BuildConfigurator +import kotlinx.kover.gradle.plugin.test.functional.framework.starter.GeneratedTest + +internal class ReportInheritedFromFilterTests { + @GeneratedTest + fun BuildConfigurator.testExclusions() { + addProjectWithKover { + sourcesFrom("inherited-main") + kover { + reports { + filters { + excludes { + inheritedFrom("*.Interface", "org.jetbrains.A", "*AutoCloseable") + } + } + } + } + } + + run("koverXmlReport") { + xmlReport { + classCounter("org.jetbrains.RegularClass").assertFullyMissed() + classCounter("org.jetbrains.CloseableClass").assertFullyMissed() + classCounter("org.jetbrains.A").assertFullyMissed() + + classCounter("org.jetbrains.B").assertAbsent() + classCounter("org.jetbrains.C").assertAbsent() + classCounter("org.jetbrains.D").assertAbsent() + classCounter("org.jetbrains.AChild").assertAbsent() + classCounter("org.jetbrains.BChild").assertAbsent() + classCounter("org.jetbrains.CChild").assertAbsent() + classCounter("org.jetbrains.DChild").assertAbsent() + } + } + } + + @GeneratedTest + fun BuildConfigurator.testInclusions() { + addProjectWithKover { + sourcesFrom("inherited-main") + kover { + reports { + filters { + includes { + inheritedFrom("*.Interface", "org.jetbrains.A", "*AutoCloseable") + } + } + } + } + } + + run("koverXmlReport") { + xmlReport { + classCounter("org.jetbrains.RegularClass").assertAbsent() + classCounter("org.jetbrains.CloseableClass").assertAbsent() + classCounter("org.jetbrains.A").assertAbsent() + + classCounter("org.jetbrains.B").assertFullyMissed() + classCounter("org.jetbrains.C").assertFullyMissed() + classCounter("org.jetbrains.D").assertFullyMissed() + classCounter("org.jetbrains.AChild").assertFullyMissed() + classCounter("org.jetbrains.BChild").assertFullyMissed() + classCounter("org.jetbrains.CChild").assertFullyMissed() + classCounter("org.jetbrains.DChild").assertFullyMissed() + } + } + } + + @GeneratedTest + fun BuildConfigurator.testIncludeAndExclude() { + addProjectWithKover { + sourcesFrom("inherited-main") + kover { + reports { + filters { + includes { + inheritedFrom("*.Interface", "org.jetbrains.A", "*AutoCloseable") + } + + excludes { + inheritedFrom("*.B", "*.AutoCloseable") + } + } + } + } + } + + run("koverXmlReport") { + xmlReport { + classCounter("org.jetbrains.RegularClass").assertAbsent() + classCounter("org.jetbrains.CloseableClass").assertAbsent() + classCounter("org.jetbrains.BChild").assertAbsent() + classCounter("org.jetbrains.A").assertAbsent() + classCounter("org.jetbrains.D").assertAbsent() + classCounter("org.jetbrains.DChild").assertAbsent() + + classCounter("org.jetbrains.B").assertFullyMissed() + classCounter("org.jetbrains.C").assertFullyMissed() + classCounter("org.jetbrains.AChild").assertFullyMissed() + classCounter("org.jetbrains.CChild").assertFullyMissed() + } + } + } + + @GeneratedTest + fun BuildConfigurator.testDifferentIncludeFilters() { + addProjectWithKover { + sourcesFrom("inherited-main") + kover { + reports { + filters { + includes { + // for includes 'AND' rule should work + inheritedFrom("org.jetbrains.A") + classes("*.*Child") + } + } + } + } + } + + run("koverXmlReport") { + xmlReport { + classCounter("org.jetbrains.A").assertAbsent() + classCounter("org.jetbrains.B").assertAbsent() + + classCounter("org.jetbrains.BChild").assertFullyMissed() + classCounter("org.jetbrains.AChild").assertFullyMissed() + } + } + } + @GeneratedTest + fun BuildConfigurator.testDifferentExcludeFilters() { + addProjectWithKover { + sourcesFrom("inherited-main") + kover { + reports { + filters { + excludes { + // for excludes 'OR' rule should work + inheritedFrom("org.jetbrains.A") + classes("*.*Child") + } + } + } + } + } + + run("koverXmlReport") { + xmlReport { + classCounter("org.jetbrains.A").assertFullyMissed() + + // excluded as inheritor of A + classCounter("org.jetbrains.B").assertAbsent() + + // excluded by name + classCounter("org.jetbrains.AChild").assertAbsent() + classCounter("org.jetbrains.BChild").assertAbsent() + classCounter("org.jetbrains.CChild").assertAbsent() + } + } + } + +} \ No newline at end of file diff --git a/kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/main/kotlin/Sources.kt b/kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/main/kotlin/Sources.kt new file mode 100644 index 00000000..d6e8e597 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/main/kotlin/Sources.kt @@ -0,0 +1,55 @@ +package org.jetbrains + +annotation class Exclude +annotation class Include + +class NotAnnotatedClass { + fun function() { + println("function") + } +} + +@Exclude +class ExcludedClass { + fun function() { + println("function") + } +} + +@Include +class IncludedClass { + fun function() { + println("function") + } +} + +@Include +class ExcludedByName { + fun function() { + println("function") + } +} + + +@Exclude +@Include +class TogetherClass { + fun function() { + println("function") + } +} + +@Include +class MixedClass { + fun function1() { + println("function1") + } + + @Exclude + fun function2() { + println("function1") + } +} + + + diff --git a/kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/test/kotlin/TestClass.kt b/kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/test/kotlin/TestClass.kt new file mode 100644 index 00000000..ce737b21 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/sources/annotations-mix/test/kotlin/TestClass.kt @@ -0,0 +1,13 @@ +package org.jetbrains.serialuser + +import org.jetbrains.* +import kotlin.test.Test + +class TestClass { + + @Test + fun test() { + println("Do nothing") + } + +} diff --git a/kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/main/kotlin/Sources.kt b/kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/main/kotlin/Sources.kt new file mode 100644 index 00000000..f048e960 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/main/kotlin/Sources.kt @@ -0,0 +1,81 @@ +package org.jetbrains + +import java.io.Closeable +import java.lang.Cloneable + +// inheritable types +interface Interface + +open class A + + +// children +class RegularClass { + fun function() { + println("function") + } +} + +// direct inheritance of project class +open class B: A() { + fun functionB() { + println("function") + } +} + +// direct implementation of project interface +open class C : Interface { + fun functionC() { + println("function") + } +} + +// direct implementation of non-project interface +open class D: AutoCloseable { + fun functionD() { + println("function") + } + + override fun close() { + println("foo") + } +} + +// direct inheritance of project class +class AChild: A() { + fun functionAA() { + println("function") + } +} + +// indirect inheritance of project class +class BChild: B() { + fun functionBB() { + println("function") + } +} + +// indirect implementation of project interface +class CChild: C() { + fun functionCC() { + println("function") + } +} + +// indirect implementation of non-project interface +class DChild: D() { + fun functionDD() { + println("function") + } +} + +// indirect implementation of twice indirect non-project interface (AutoCloseable) +class CloseableClass : Closeable { + fun functionCC() { + println("function") + } + + override fun close() { + println("foo") + } +} diff --git a/kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/test/kotlin/TestClass.kt b/kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/test/kotlin/TestClass.kt new file mode 100644 index 00000000..ce737b21 --- /dev/null +++ b/kover-gradle-plugin/src/functionalTest/templates/sources/inherited-main/test/kotlin/TestClass.kt @@ -0,0 +1,13 @@ +package org.jetbrains.serialuser + +import org.jetbrains.* +import kotlin.test.Test + +class TestClass { + + @Test + fun test() { + println("Do nothing") + } + +} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt index 005ae6b7..19f4ac87 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt @@ -212,9 +212,14 @@ internal class VariantReportsSet( private fun KoverReportFiltersConfigImpl.convert(): Provider { return project.provider { ReportFilters( - includesImpl.classes.get(), includesImpl.annotations.get(), - excludesImpl.classes.get(), excludesImpl.annotations.get(), - includesImpl.projects.get(), excludesImpl.projects.get(), + includesImpl.classes.get(), + includesImpl.annotations.get(), + includesImpl.inheritedFrom.get(), + includesImpl.projects.get(), + excludesImpl.classes.get(), + excludesImpl.annotations.get(), + excludesImpl.inheritedFrom.get(), + excludesImpl.projects.get() ) } } 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 915317b3..a16baff9 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 @@ -73,13 +73,17 @@ internal data class ReportFilters( @get:Input val includesAnnotations: Set = emptySet(), @get:Input + val includeInheritedFrom: Set = emptySet(), + @get:Input + val includeProjects: Set = emptySet(), + @get:Input val excludesClasses: Set = emptySet(), @get:Input val excludesAnnotations: Set = emptySet(), @get:Input - val includeProjects: Set = emptySet(), + val excludeInheritedFrom: Set = emptySet(), @get:Input - val excludeProjects: Set = emptySet(), + val excludeProjects: Set = emptySet() ): Serializable internal open class VerificationRule @Inject constructor( diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt index 3c6d696d..ea774a30 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt @@ -483,6 +483,7 @@ public interface KoverReportFiltersConfig { * includes { * classes("com.example.FooBar?", "com.example.*Bar") * packages("com.example.subpackage") + * annotatedBy("*ClassToReport*") * projects.add(":my:lib*") * } * ``` @@ -650,9 +651,15 @@ public interface KoverReportFilter { /** * Add to filters all classes and functions marked by specified annotations. * + * Use cases: + * - in case of exclusion filters all classes or function marked by at least one of the specified annotation will be excluded from the report + * - in case of inclusion filters only classes marked by at least one of the specified annotations will be included in the report + * * It is acceptable to use `*` and `?` wildcards, * `*` means any number of arbitrary characters (including no chars), `?` means one arbitrary character. * + * **_Does not work for JaCoCo_** + * * Example: * ``` * annotatedBy("*Generated*", "com.example.KoverExclude") @@ -663,11 +670,17 @@ public interface KoverReportFilter { /** * Add to filters all classes and functions marked by specified annotations. * + * Use cases: + * - in case of exclusion filters all classes or function marked by at least one of the specified annotation will be excluded from the report + * - in case of inclusion filters only classes marked by at least one of the specified annotations will be included in the report + * * Used for lazy setup. * * It is acceptable to use `*` and `?` wildcards, * `*` means any number of arbitrary characters (including no chars), `?` means one arbitrary character. * + * **_Does not work for JaCoCo_** + * * Example: * ``` * val annotation: Provider = ... @@ -689,6 +702,60 @@ public interface KoverReportFilter { */ val projects: SetProperty + /** + * Filter classes extending at least one of the specified classes or implementing at least one of the interfaces. + * The class itself with the specified name is not taken into account. + * + * The entire inheritance tree is analyzed; a class may inherit the specified class/interface indirectly and still be included in the report, unless the specified class/interface is located outside of the application (see below). + * + * The following classes and interfaces can be specified in arguments: + * - classes and interfaces declared in the application + * - classes and interfaces declared outside the application, however they are directly inherited or implemented by any type from the application + * + * Due to technical limitations, if a specified class or interface is not declared in the application and not extended/implemented directly by one of the application types, such a filter will have no effect. + * + * If this filter is specified, then the generation of the report may slow down, because it becomes necessary to analyze the inheritance tree. + * + * It is acceptable to use `*` and `?` wildcards, + * `*` means any number of arbitrary characters (including no chars), `?` means one arbitrary character. + * + * **_Does not work for JaCoCo_** + * + * Example: + * ``` + * inheritedFrom("*Repository") + * ``` + */ + public fun inheritedFrom(vararg typeName: String) + + /** + * Filter classes extending at least one of the specified classes or implementing at least one of the interfaces. + * The class itself with the specified name is not taken into account. + * + * The entire inheritance tree is analyzed; a class may inherit the specified class/interface indirectly and still be included in the report, unless the specified class/interface is located outside of the application (see below). + * + * The following classes and interfaces can be specified in arguments: + * - classes and interfaces declared in the application + * - classes and interfaces declared outside the application, however they are directly inherited or implemented by any type from the application + * + * Due to technical limitations, if a specified class or interface is not declared in the application and not extended/implemented directly by one of the application types, such a filter will have no effect. + * + * If this filter is specified, then the generation of the report may slow down, because it becomes necessary to analyze the inheritance tree. + * + * Used for lazy setup. + * + * It is acceptable to use `*` and `?` wildcards, + * `*` means any number of arbitrary characters (including no chars), `?` means one arbitrary character. + * + * **_Does not work for JaCoCo_** + * + * Example: + * ``` + * inheritedFrom("*Repository") + * ``` + */ + public fun inheritedFrom(vararg typeName: Provider) + /** * Add all classes generated by Android plugin to filters. * 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 8a6b7a70..dd85c71a 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 @@ -17,7 +17,7 @@ public object KoverVersions { /** * Kover coverage tool version. */ - public const val KOVER_TOOL_VERSION = "1.0.752" + public const val KOVER_TOOL_VERSION = "1.0.753" /** * JaCoCo coverage tool version used by default. diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt index e117a420..61848d9a 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt @@ -270,6 +270,7 @@ internal open class KoverReportFiltersConfigImpl @Inject constructor( internal abstract class KoverReportFilterImpl: KoverReportFilter { internal abstract val classes: SetProperty internal abstract val annotations: SetProperty + internal abstract val inheritedFrom: SetProperty override fun classes(vararg names: String) { classes.addAll(*names) @@ -322,16 +323,27 @@ internal abstract class KoverReportFilterImpl: KoverReportFilter { annotations.add(nameProvider) } } + override fun inheritedFrom(vararg typeName: String) { + inheritedFrom.addAll(*typeName) + } + + override fun inheritedFrom(vararg typeName: Provider) { + typeName.forEach { nameProvider -> + inheritedFrom.add(nameProvider) + } + } internal fun extendsFrom(other: KoverReportFilterImpl) { classes.addAll(other.classes) annotations.addAll(other.annotations) projects.addAll(other.projects) + inheritedFrom.addAll(other.inheritedFrom) } internal fun clean() { classes.empty() annotations.empty() + inheritedFrom.empty() projects.empty() } 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 3cc6d696..1fc85389 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 @@ -14,7 +14,10 @@ private typealias FeatureAggregationType = kotlinx.kover.features.jvm.Aggregatio internal fun ReportFilters.toKoverFeatures() = ClassFilters( includesClasses, excludesClasses, - excludesAnnotations + includesAnnotations, + excludesAnnotations, + includeInheritedFrom, + excludeInheritedFrom ) internal fun VerificationRule.convert(): Rule {