Skip to content

Commit 06f4de4

Browse files
authored
Fixed JaCoCo error: Can't add different class with same name
Now duplicate classes are added to the JaCoCo report only once - this behavior copies the behavior from Kover report generator Resolves #634 Resolves #613 PR #635
1 parent 46d4196 commit 06f4de4

File tree

15 files changed

+301
-21
lines changed

15 files changed

+301
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.kover.gradle.plugin.test.functional.cases
6+
7+
import kotlinx.kover.gradle.plugin.test.functional.framework.checker.CheckerContext
8+
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.TemplateTest
9+
10+
internal class ClassDupTest {
11+
/**
12+
* Checking that if there are duplicate files in different versions, the JaCoCo report still continues to be generated.
13+
* This behavior is identical to the behavior in Kover reporter.
14+
*/
15+
@TemplateTest("android-class-dup", [":app:koverXmlReport"])
16+
fun CheckerContext.test() {
17+
subproject(":app") {
18+
xmlReport {
19+
// class present in report
20+
classCounter("kotlinx.kover.test.android.DupClass").assertFullyMissed()
21+
}
22+
}
23+
}
24+
25+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
plugins {
6+
id ("org.jetbrains.kotlinx.kover")
7+
id ("com.android.application")
8+
id ("org.jetbrains.kotlin.android")
9+
}
10+
11+
android {
12+
namespace = "kotlinx.kover.test.android"
13+
compileSdk = 32
14+
15+
defaultConfig {
16+
applicationId = "kotlinx.kover.test.android"
17+
minSdk = 21
18+
targetSdk = 31
19+
versionCode = 1
20+
versionName = "1.0"
21+
22+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
23+
}
24+
25+
buildTypes {
26+
release {
27+
isMinifyEnabled = true
28+
}
29+
}
30+
compileOptions {
31+
sourceCompatibility = JavaVersion.VERSION_1_8
32+
targetCompatibility = JavaVersion.VERSION_1_8
33+
}
34+
kotlinOptions {
35+
jvmTarget = "1.8"
36+
}
37+
buildFeatures {
38+
viewBinding = true
39+
}
40+
}
41+
42+
dependencies {
43+
implementation("androidx.core:core-ktx:1.8.0")
44+
implementation("androidx.appcompat:appcompat:1.5.0")
45+
implementation("com.google.android.material:material:1.6.1")
46+
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
47+
testImplementation("junit:junit:4.13.2")
48+
}
49+
50+
51+
/*
52+
* Kover configs
53+
*/
54+
55+
kover {
56+
useJacoco()
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.kover.test.android
6+
7+
object DupClass {
8+
fun log(message: String) {
9+
println("DEBUG: $message")
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
4+
-->
5+
6+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
7+
8+
<application android:label="@string/app_name">
9+
<uses-library android:name="com.google.android.things" android:required="false" />
10+
11+
<activity android:name=".MainActivity"
12+
android:exported="true">
13+
<intent-filter>
14+
<action android:name="android.intent.action.MAIN" />
15+
16+
<category android:name="android.intent.category.LAUNCHER" />
17+
</intent-filter>
18+
<!-- Make this the first activity that is displayed when the device boots. -->
19+
<intent-filter>
20+
<action android:name="android.intent.action.MAIN" />
21+
22+
<category android:name="android.intent.category.HOME" />
23+
<category android:name="android.intent.category.DEFAULT" />
24+
</intent-filter>
25+
</activity>
26+
</application>
27+
28+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
4+
-->
5+
6+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
7+
xmlns:app="http://schemas.android.com/apk/res-auto"
8+
xmlns:tools="http://schemas.android.com/tools"
9+
android:layout_width="match_parent"
10+
android:layout_height="match_parent"
11+
tools:context=".MainActivity">
12+
13+
<TextView
14+
android:id="@+id/main_label"
15+
android:layout_width="wrap_content"
16+
android:layout_height="wrap_content"
17+
android:layout_marginStart="104dp"
18+
android:layout_marginTop="28dp"
19+
android:text="SERIALIZATION TEST"
20+
android:textSize="20sp"
21+
android:textStyle="bold"
22+
app:layout_constraintStart_toStartOf="parent"
23+
app:layout_constraintTop_toTopOf="parent" />
24+
25+
<EditText
26+
android:id="@+id/encoded_text"
27+
android:layout_width="373dp"
28+
android:layout_height="411dp"
29+
android:layout_marginStart="16dp"
30+
android:layout_marginTop="16dp"
31+
android:editable="false"
32+
android:ems="10"
33+
android:gravity="start|top"
34+
android:inputType="textMultiLine"
35+
android:textAlignment="viewStart"
36+
android:textSize="12sp"
37+
app:layout_constraintStart_toStartOf="parent"
38+
app:layout_constraintTop_toBottomOf="@+id/main_label" />
39+
40+
</androidx.constraintlayout.widget.ConstraintLayout>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
4+
-->
5+
6+
<resources>
7+
<color name="purple_200">#FFBB86FC</color>
8+
<color name="purple_500">#FF6200EE</color>
9+
<color name="purple_700">#FF3700B3</color>
10+
<color name="teal_200">#FF03DAC5</color>
11+
<color name="teal_700">#FF018786</color>
12+
<color name="black">#FF000000</color>
13+
<color name="white">#FFFFFFFF</color>
14+
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!--
2+
~ Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
-->
4+
5+
<resources>
6+
<string name="app_name">Android Test</string>
7+
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!--
2+
~ Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
-->
4+
5+
<resources>
6+
7+
<style name="Theme.App" parent="android:Theme.Material.Light.DarkActionBar" />
8+
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.kover.test.android
6+
7+
object DupClass {
8+
fun log(message: String) {
9+
println("DEBUG: $message")
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.kover.test.android
6+
7+
import org.junit.Test
8+
9+
import org.junit.Assert.*
10+
11+
12+
class LocalTests {
13+
@Test
14+
fun testDebugUtils() {
15+
println("test")
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
plugins {
6+
id("com.android.application") version "7.4.0" apply false
7+
id("com.android.library") version "7.4.0" apply false
8+
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
9+
id("org.jetbrains.kotlinx.kover") version "0.7.1" apply false
10+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
#
4+
5+
# Project-wide Gradle settings.
6+
# IDE (e.g. Android Studio) users:
7+
# Gradle settings configured through the IDE *will override*
8+
# any settings specified in this file.
9+
# For more details on how to configure your build environment visit
10+
# http://www.gradle.org/docs/current/userguide/build_environment.html
11+
# Specifies the JVM arguments used for the daemon process.
12+
# The setting is particularly useful for tweaking memory settings.
13+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
14+
# When configured, Gradle will run in incubating parallel mode.
15+
# This option should only be used with decoupled projects. More details, visit
16+
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17+
# org.gradle.parallel=true
18+
# AndroidX package structure to make it clearer which packages are bundled with the
19+
# Android operating system, and which are packaged with your app's APK
20+
# https://developer.android.com/topic/libraries/support-library/androidx-rn
21+
android.useAndroidX=true
22+
# Kotlin code style for this project: "official" or "obsolete":
23+
kotlin.code.style=official
24+
# Enables namespacing of each library's R class so that its R class includes only the
25+
# resources declared in the library itself and none from the library's dependencies,
26+
# thereby reducing the size of the R class for that library
27+
android.nonTransitiveRClass=true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pluginManagement {
2+
repositories {
3+
google()
4+
mavenCentral()
5+
gradlePluginPortal()
6+
}
7+
}
8+
9+
dependencyResolutionManagement {
10+
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11+
repositories {
12+
google()
13+
mavenCentral()
14+
gradlePluginPortal()
15+
}
16+
}
17+
18+
rootProject.name = "android_kts"
19+
include(":app")

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoAnt.kt

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package kotlinx.kover.gradle.plugin.tools.jacoco
66

77
import groovy.lang.Closure
88
import groovy.lang.GroovyObject
9-
import kotlinx.kover.features.jvm.KoverFeatures
9+
import kotlinx.kover.features.jvm.KoverFeatures.koverWildcardToRegex
1010
import kotlinx.kover.gradle.plugin.commons.ReportContext
1111
import java.io.File
1212

@@ -25,24 +25,27 @@ internal fun ReportContext.callAntReport(
2525
)
2626
)
2727

28-
29-
val filteredOutput = if (filters.excludesClasses.isNotEmpty() || filters.includesClasses.isNotEmpty()) {
30-
val excludeRegexes = filters.excludesClasses.map { Regex(it.wildcardsToClassFileRegex()) }
31-
val includeRegexes = filters.includesClasses.map { Regex(it.wildcardsToClassFileRegex()) }
32-
33-
val outputCollections = files.outputs.map { output ->
34-
services.objects.fileCollection().from(output).asFileTree.filter { file ->
35-
// the `canonicalPath` is used because a `File.separatorChar` was used to construct the class-file regex
36-
val path = file.toRelativeString(output)
37-
// if the inclusion rules are declared, then the file must fit at least one of them
38-
(includeRegexes.isEmpty() || includeRegexes.any { regex -> path.matches(regex) })
39-
// if the exclusion rules are declared, then the file should not fit any of them
40-
&& excludeRegexes.none { regex -> path.matches(regex) }
28+
val filesByClassName = mutableMapOf<String, File>()
29+
files.outputs.forEach { output ->
30+
output.walk().forEach { file ->
31+
if (file.isFile && file.name.endsWith(CLASS_FILE_EXTENSION)) {
32+
val className = file.toRelativeString(output).filenameToClassname()
33+
filesByClassName[className] = file
4134
}
4235
}
43-
services.objects.fileCollection().from(outputCollections)
36+
}
37+
38+
val classes = if (filters.excludesClasses.isNotEmpty() || filters.includesClasses.isNotEmpty()) {
39+
val excludeRegexes = filters.excludesClasses.map { koverWildcardToRegex(it).toRegex() }
40+
val includeRegexes = filters.includesClasses.map { koverWildcardToRegex(it).toRegex() }
41+
42+
filesByClassName.filterKeys { className ->
43+
((includeRegexes.isEmpty() || includeRegexes.any { regex -> className.matches(regex) })
44+
// if the exclusion rules are declared, then the file should not fit any of them
45+
&& excludeRegexes.none { regex -> className.matches(regex) })
46+
}.values
4447
} else {
45-
services.objects.fileCollection().from(files.outputs)
48+
filesByClassName.values
4649
}
4750

4851

@@ -55,7 +58,7 @@ internal fun ReportContext.callAntReport(
5558
services.objects.fileCollection().from(files.sources).addToAntBuilder(this, "resources")
5659
}
5760
invokeWithBody("classfiles") {
58-
filteredOutput.addToAntBuilder(this, "resources")
61+
services.objects.fileCollection().from(classes).addToAntBuilder(this, "resources")
5962
}
6063
}
6164
block()
@@ -84,10 +87,13 @@ internal inline fun GroovyObject.invokeWithBody(
8487
}
8588

8689
/**
87-
* Replaces characters `.` to `|` or `\` and added `.class` as postfix and `.* /` or `.*\` as prefix.
90+
* Replaces characters `|` or `\` to `.` and remove postfix `.class`.
8891
*/
89-
private fun String.wildcardsToClassFileRegex(): String {
90-
val filenameWithWildcards = this.replace('.', File.separatorChar) + ".class"
91-
return KoverFeatures.koverWildcardToRegex(filenameWithWildcards)
92+
private fun String.filenameToClassname(): String {
93+
return this.replace(File.separatorChar, '.').removeSuffix(CLASS_FILE_EXTENSION)
9294
}
9395

96+
/**
97+
* Extension of class-files.
98+
*/
99+
private const val CLASS_FILE_EXTENSION = ".class"

0 commit comments

Comments
 (0)