Kotlin 协程的 Jacoco 代码覆盖率不正确

Incorrect Jacoco code coverage for Kotlin coroutine

我正在使用 Jacoco 进行单元测试代码覆盖。 Jacoco 生成的报告显示 在我的 Kotlin 代码 中遗漏了几个分支。我注意到 coroutine code 和它后面的代码,根据 Jacoco 没有被正确覆盖。我不确定是因为协程还是其他原因。 运行 我的单元测试 IntelliJ 代码覆盖率 我的 Kotlin class 显示 100% 覆盖率.

我不知道为什么 Jacoco 显示的覆盖率较低。我已经使用 Spock (Groovy) 编写了我的单元测试。

请参考下图:

错过的分支:

原代码:

类似于“”:

JaCoCo 执行字节码分析,而不是源代码Example.ktkotlinc 1.3.10

的编译
package example

fun main(args: Array<String>) {
    kotlinx.coroutines.runBlocking { // line 4
    }
}

结果在两个文件 ExampleKt.classExampleKt$main.class 中,最后一个 (javap -v -p ExampleKt$main.class) 的字节码包含方法 invokeSuspend(Object)

  public final java.lang.Object invokeSuspend(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=3, locals=4, args_size=2
         0: invokestatic  #29                 // Method kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object;
         3: astore_3
         4: aload_0
         5: getfield      #33                 // Field label:I
         8: tableswitch   { // 0 to 0
                       0: 28
                 default: 53
            }
        28: aload_1
        29: dup
        30: instanceof    #35                 // class kotlin/Result$Failure
        33: ifeq          43
        36: checkcast     #35                 // class kotlin/Result$Failure
        39: getfield      #39                 // Field kotlin/Result$Failure.exception:Ljava/lang/Throwable;
        42: athrow
        43: pop
        44: aload_0
        45: getfield      #41                 // Field p$:Lkotlinx/coroutines/CoroutineScope;
        48: astore_2
        49: getstatic     #47                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
        52: areturn
        53: new           #49                 // class java/lang/IllegalStateException
        56: dup
        57: ldc           #51                 // String call to 'resume' before 'invoke' with coroutine
        59: invokespecial #55                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
        62: athrow
      LineNumberTable:
        line 4: 3
        line 5: 49

与源文件的第 4 行关联并包含分支(ifeqtableswitch)。

虽然截至今​​天的最新 JaCoCo 版本 (0.8.2) 具有针对各种编译器生成的工件的过滤器,例如 switch 语句中的 String,但 Kotlin 编译器为协程生成的字节码未被过滤.在 https://www.jacoco.org/jacoco/trunk/doc/changes.html And among others at https://www.jacoco.org/research/index.html there is also presentation about bytecode pattern matching 可以看到变更日志 shows/explains 许多编译器生成的工件。


您在 IntelliJ IDEA 中看到的 100% 只是行覆盖率,因此您正在尝试比较两个完全不同的事物。作为证明——这里是 IntelliJ IDEA 的屏幕截图,它显示了 100% 的行覆盖率,但只执行了 if 的一个分支(其中 args.size >= 0 计算为 true

这里是执行相同源文件的 JaCoCo 报告的相应屏幕截图

上升到包级别,您可以看到 100% 的行覆盖率,但 50% 的分支覆盖率

然后通过第一个 link ExampleKt.main.new Function2() {...} 下降到 class 级别,您可以再次看到该方法 invokeSuspend(Object) 贡献了遗漏的分支


更新 (29/01/2019)

JaCoCo version 0.8.3 具有 Kotlin 编译器为暂停 lambda 和函数添加的分支过滤器:

Jacoco 0.8.3 版本修复,已于昨天 1 月 24 日发布。

可在此处找到完整的更改日志:https://github.com/jacoco/jacoco/releases