Kotlin JaCoCo - IllegalClassFormatException。请提供原装非仪表类
Kotlin JaCoCo - IllegalClassFormatException. Please supply original non-instrumented classes
我正在尝试获取 Android 应用程序模块的测试覆盖率报告并执行 testVariantBuildTypeUnitTest 任务。
尽管所有测试都已通过,但 TEST-classNameTest.xml 文件包含以下错误消息。此错误仅针对包含来自 Kotlin 的 调用 Java 的方法。这个问题有解决办法吗?
**Caused by: java.lang.IllegalStateException: Cannot process instrumented class... Please supply original non-instrumented classes.
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.InstrSupport.assertNotInstrumented(InstrSupport.java:238)
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.ClassInstrumenter.visitField(ClassInstrumenter.java:56)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassVisitor.visitField(ClassVisitor.java:339)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.readField(ClassReader.java:1111)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:713)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:401)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:90)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:108)
我们有多模块 android 项目,因此我们使用这个自定义 Jacoco 任务来获取覆盖率报告:
project.afterEvaluate {
(android.hasProperty('applicationVariants')
? android.'applicationVariants'
: android.'libraryVariants')
.all { variant ->
def variantName = variant.name
def unitTestTask = "test${variantName.capitalize()}UnitTest"
def jacocoReportName = unitTestTask + project.name
tasks.create(name: jacocoReportName, type: JacocoReport, dependsOn: [
"$unitTestTask"
]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"
reports {
html.enabled = true
xml.enabled = true
}
def fileFilter = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/databinding',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*'
]
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: fileFilter)
def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}",
excludes: fileFilter)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
if (isAndroidLibrary(project)) {
executionData(files([
"${projectDir}/jacoco.exec"
]))
}else{
executionData(files([
"$project.buildDir/jacoco/${project.name}.exec"
]))
}
}
}
}
此致,
解决方案
问题很可能出在您的 模块级别 build.gradle 文件中。您需要删除 testCoverageEnabled 或将其设置为 false。
android {
...
buildTypes {
release {
minifyEnabled true
}
debug {
// testCoverageEnabled true
minifyEnabled false
}
}
解释和参考
首先,多年来我一直在与 JaCoCo 一起努力解决 android 的单元测试覆盖率问题,并且作为 AGP (Android Gradle 插件)团队增强了 AGP(例如 Similar issue)因此此解决方案可能会在 AGP 的未来版本中发生变化。
来自 AGP 团队的 release of AGP 4.1 the AGP team changed the way the plugin deals with unit tests. I raised an enter link description here 问题,他们解释说插件 (AGP 4.1+) 有自己的仪器形式来获得覆盖,这无疑与 JaCoCo 不兼容。正是在这个问题上向我强调了解决方案。
注1:这个问题在DSL文档中not documented!
注意 2:还应注意,截至我撰写本文时,AGP 7 正在开发中,但会出现相同的问题。
注意事项
此解决方案将导致生成插装测试覆盖率的问题,因为 AGP 生成插装测试覆盖率(以 .ec 文件的形式),这意味着要获得 插装测试覆盖率,您必须拥有testCoverageEnabled true
。
作为解决方法,您可以:
- 将您的单元测试放入版本 build 变体
- 将您的仪器测试放在调试 build 变体中。
仍然可以将覆盖范围合并到一份报告中:例如:
task jacocoCombinedUnitTestAndroidTestReport(type: JacocoReport) {
group = "Reporting"
reports {
xml.enabled = true
html.enabled = true
}
def mainSrc = "$project.projectDir/src/main/java"
def releaseKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/release", excludes:["com/example/ui**"])
def debugKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug/com/example/ui/")
def releaseJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/release", excludes: ["com/example/ui**"])
def debugJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/debug/com/example/ui/" )
def mainClasses = releaseKotlinClasses + debugKotlinClasses
def javaClasses = releaseJavaClasses + debugJavaClasses
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([mainClasses, javaClasses])
executionData.from = fileTree(dir: buildDir, include: ["jacoco/testReleaseUnitTest.exec",
"outputs/code_coverage/debugAndroidTest/connected/**.ec"])
}
- 这假设您在 ui 目录中有任何 UI 代码。
- 您包括所有版本 类,不包括 ui 目录
- 您只包含 ui 调试 类。
请注意,这仅在撰写此答案时有效,希望 Google 将来能解决此问题。
我正在尝试获取 Android 应用程序模块的测试覆盖率报告并执行 testVariantBuildTypeUnitTest 任务。
尽管所有测试都已通过,但 TEST-classNameTest.xml 文件包含以下错误消息。此错误仅针对包含来自 Kotlin 的 调用 Java 的方法。这个问题有解决办法吗?
**Caused by: java.lang.IllegalStateException: Cannot process instrumented class... Please supply original non-instrumented classes.
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.InstrSupport.assertNotInstrumented(InstrSupport.java:238)
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.ClassInstrumenter.visitField(ClassInstrumenter.java:56)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassVisitor.visitField(ClassVisitor.java:339)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.readField(ClassReader.java:1111)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:713)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:401)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:90)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:108)
我们有多模块 android 项目,因此我们使用这个自定义 Jacoco 任务来获取覆盖率报告:
project.afterEvaluate {
(android.hasProperty('applicationVariants')
? android.'applicationVariants'
: android.'libraryVariants')
.all { variant ->
def variantName = variant.name
def unitTestTask = "test${variantName.capitalize()}UnitTest"
def jacocoReportName = unitTestTask + project.name
tasks.create(name: jacocoReportName, type: JacocoReport, dependsOn: [
"$unitTestTask"
]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"
reports {
html.enabled = true
xml.enabled = true
}
def fileFilter = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/databinding',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*'
]
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: fileFilter)
def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}",
excludes: fileFilter)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
if (isAndroidLibrary(project)) {
executionData(files([
"${projectDir}/jacoco.exec"
]))
}else{
executionData(files([
"$project.buildDir/jacoco/${project.name}.exec"
]))
}
}
}
}
此致,
解决方案
问题很可能出在您的 模块级别 build.gradle 文件中。您需要删除 testCoverageEnabled 或将其设置为 false。
android {
...
buildTypes {
release {
minifyEnabled true
}
debug {
// testCoverageEnabled true
minifyEnabled false
}
}
解释和参考
首先,多年来我一直在与 JaCoCo 一起努力解决 android 的单元测试覆盖率问题,并且作为 AGP (Android Gradle 插件)团队增强了 AGP(例如 Similar issue)因此此解决方案可能会在 AGP 的未来版本中发生变化。
来自 AGP 团队的 release of AGP 4.1 the AGP team changed the way the plugin deals with unit tests. I raised an enter link description here 问题,他们解释说插件 (AGP 4.1+) 有自己的仪器形式来获得覆盖,这无疑与 JaCoCo 不兼容。正是在这个问题上向我强调了解决方案。
注1:这个问题在DSL文档中not documented!
注意 2:还应注意,截至我撰写本文时,AGP 7 正在开发中,但会出现相同的问题。
注意事项
此解决方案将导致生成插装测试覆盖率的问题,因为 AGP 生成插装测试覆盖率(以 .ec 文件的形式),这意味着要获得 插装测试覆盖率,您必须拥有testCoverageEnabled true
。
作为解决方法,您可以:
- 将您的单元测试放入版本 build 变体
- 将您的仪器测试放在调试 build 变体中。 仍然可以将覆盖范围合并到一份报告中:例如:
task jacocoCombinedUnitTestAndroidTestReport(type: JacocoReport) {
group = "Reporting"
reports {
xml.enabled = true
html.enabled = true
}
def mainSrc = "$project.projectDir/src/main/java"
def releaseKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/release", excludes:["com/example/ui**"])
def debugKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug/com/example/ui/")
def releaseJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/release", excludes: ["com/example/ui**"])
def debugJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/debug/com/example/ui/" )
def mainClasses = releaseKotlinClasses + debugKotlinClasses
def javaClasses = releaseJavaClasses + debugJavaClasses
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([mainClasses, javaClasses])
executionData.from = fileTree(dir: buildDir, include: ["jacoco/testReleaseUnitTest.exec",
"outputs/code_coverage/debugAndroidTest/connected/**.ec"])
}
- 这假设您在 ui 目录中有任何 UI 代码。
- 您包括所有版本 类,不包括 ui 目录
- 您只包含 ui 调试 类。
请注意,这仅在撰写此答案时有效,希望 Google 将来能解决此问题。