Skip to content

Commit

Permalink
Added extended reports filtering
Browse files Browse the repository at this point in the history
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 <sandwwraith@users.noreply.github.com>

Resolves #274
Resolves #454
PR #581
  • Loading branch information
shanshin authored May 14, 2024
1 parent 742d2ab commit 17c8ace
Show file tree
Hide file tree
Showing 21 changed files with 649 additions and 50 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
26 changes: 15 additions & 11 deletions kover-cli/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,22 @@ For information about offline instrumentation, [see](../offline-instrumentation#
| --include <class-name> | 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 [<binary-report-path> ...] --classfiles <class-file-path> [--exclude <class-name>] [--excludeAnnotation <annotation-name>] [--html <html-dir>] [--include <class-name>] --src <sources-path> [--title <html-title>] [--xml <xml-file-path>]`

| Option | Description | Required | Multiple |
|---------------------------------------|---------------------------------------------------------------------------------------------------------|:--------:|:--------:|
| `<binary-report-path>` | list of binary reports files | | + |
| --classfiles <class-file-path> | location of the compiled class-files root (must be original and not instrumented) | + | + |
| --exclude <class-name> | filter to exclude classes, wildcards `*` and `?` are acceptable | | + |
| --excludeAnnotation <annotation-name> | filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable | | + |
| --html <html-dir> | generate a HTML report in the specified path | | |
| --include <class-name> | filter to include classes, wildcards `*` and `?` are acceptable | | + |
| --src <sources-path> | location of the source files root | + | + |
| --title <html-title> | title in the HTML report | | |
| --xml <xml-file-path> | generate a XML report in the specified path | | |
| Option | Description | Required | Multiple |
|------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|:--------:|:--------:|
| `<binary-report-path>` | list of binary reports files | | + |
| --classfiles <class-file-path> | location of the compiled class-files root (must be original and not instrumented) | + | + |
| --exclude <class-name> | filter to exclude classes, wildcards `*` and `?` are acceptable | | + |
| --include <class-name> | filter to include classes, wildcards `*` and `?` are acceptable | | + |
| --excludeAnnotation <exclude-annotation-name> | filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable. Excludes have priority over includes | | + |
| --includeAnnotation <include-annotation-name> | filter to include classes marked by this annotation, wildcards `*` and `?` are acceptable | | + |
| --excludeInheritedFrom <exclude-ancestor-name> | filter to exclude classes extending the specified class or implementing an interface, wildcards `*` and `?` are acceptable | | + |
| --includeInheritedFrom <include-ancestor-name> | filter to include only classes extending the specified class or implementing an interface, wildcards `*` and `?` are acceptable | | + |
| --html <html-dir> | generate a HTML report in the specified path | | |
| --src <sources-path> | location of the source files root | + | + |
| --title <html-title> | title in the HTML report | | |
| --xml <xml-file-path> | generate a XML report in the specified path | | |
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ internal class OfflineInstrumentCommand : Command {
val filters = ClassFilters(
includeClasses.toSet(),
excludeClasses.toSet(),
excludeAnnotation.toSet()
excludeAnnotation.toSet(),
emptySet(),
emptySet(),
emptySet()
)

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<annotation-name>"
usage = "filter to exclude classes and functions marked by this annotation, wildcards `*` and `?` are acceptable. Excludes have priority over includes",
metaVar = "<exclude-annotation-name>"
)
private var excludeAnnotation: MutableList<String> = ArrayList()

@Option(
name = "--includeAnnotation",
usage = "filter to include classes marked by this annotation, wildcards `*` and `?` are acceptable. Excludes have priority over includes.",
metaVar = "<include-annotation-name>"
)
private var includeAnnotation: MutableList<String> = 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 = "<exclude-ancestor-name>"
)
private var excludeInheritedFrom: MutableList<String> = 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 = "<include-ancestor-name>"
)
private var includeInheritedFrom: MutableList<String> = ArrayList()

override val name: String = "report"

override val description: String = "Generates human-readable reports in various formats from binary report files"
Expand All @@ -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
Expand Down
12 changes: 9 additions & 3 deletions kover-features-jvm/api/kover-features-jvm.api
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,22 @@ public final class kotlinx/kover/features/jvm/BoundViolation {
}

public final class kotlinx/kover/features/jvm/ClassFilters {
public fun <init> (Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;)V
public fun <init> (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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,56 @@ public data class ClassFilters(
*/
public val excludeClasses: Set<String>,
/**
* 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<String>,
/**
* 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<String>,
/**
*
* 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<String>,
/**
*
* 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<String>
public val excludeInheritedFrom: Set<String>
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>): List<Pattern> {
val patterns = ArrayList<Pattern>(templates.size)
for (template in templates) {
patterns.add(Pattern.compile(template.wildcardsToRegex()))
}
return patterns
private fun Collection<String>.toRegexp(): List<Pattern> {
return map { template -> Pattern.compile(template.wildcardsToRegex()) }
}

/**
Expand Down
2 changes: 2 additions & 0 deletions kover-gradle-plugin/api/kover-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 17c8ace

Please sign in to comment.