Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incorrect Jacoco code coverage for Kotlin coroutine

I am using Jacoco for unit test code coverage. Jacoco's generated report shows that few branches are missed in my Kotlin code. I noticed that the coroutine code and the code after it, is not properly covered according to Jacoco. I am not sure if it is because of coroutine or something else. While running my unit test with the IntelliJ Code Coverage my Kotlin class shows 100% coverage.

I don't know why Jacoco is showing lesser coverage. I have written my Unit Tests using Spock (Groovy).

Please refer the below images:

Missed Branches: enter image description here

enter image description here

Original Code: enter image description here

like image 894
Sahil Chhabra Avatar asked Nov 26 '18 16:11

Sahil Chhabra


2 Answers

Similarly to "Why is JaCoCo not covering my String switch statements?" :

JaCoCo performs analysis of bytecode, not source code. Compilation of Example.kt with kotlinc 1.3.10

package example

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

results in two files ExampleKt.class and ExampleKt$main$1.class, bytecode of last one (javap -v -p ExampleKt$main$1.class) contains method 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

which is associated with line 4 of source file and contains branches (ifeq, tableswitch).

While latest as of today JaCoCo version (0.8.2) has filters for various compiler-generated artifacts such as String in switch statement, bytecode that Kotlin compiler generates for coroutines is not filtered. Changelog can be seen at 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 that shows/explains many compiler-generated artifacts.


What you see in IntelliJ IDEA as 100% - is only line coverage, so you are trying to compare two completely different things. As a proof - here is screenshot of IntelliJ IDEA which shows 100% line coverage, but only one branch of if was executed (where args.size >= 0 evaluates to true)

intellij

And here is corresponding screenshots of JaCoCo report for execution of the same source file

jacoco source level

Going up to the package level you can see 100% line coverage, but 50% branch coverage

jacoco package level

And then going down to the class level via the first link ExampleKt.main.new Function2() {...} you can again see that method invokeSuspend(Object) contributes missed branches

jacoco class level


Update (29/01/2019)

JaCoCo version 0.8.3 has filter for branches added by the Kotlin compiler for suspending lambdas and functions:

before

after

like image 131
Godin Avatar answered Sep 24 '22 18:09

Godin


Jacoco version 0.8.3 fixes it, it has been released yesterday January 24th.

Full change-log can be found here: https://github.com/jacoco/jacoco/releases

like image 26
Pozzo Apps Avatar answered Sep 22 '22 18:09

Pozzo Apps