15

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

3 Answers 3

18

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the detailed explanation. Although, I figured this out already and thus bypassed coverage for these kotlin compiler generated code with Function2 keyword. I hope that was the right approach as I should not be bothered for the code coverage of the compiler generated code. What’s your opinion?
@SahilChhabra I doubt that this is correct, because as far as I can see - "body" of runBlocking { /* body, e.g. println("hello") */ } is inside of method invokeSuspend together with additional branches.
I got your point. That seems correct. But then how should I cover those extra lines or branches of invokeSuspend.
@SahilChhabra for the time being you need to put up with this limitation. You can focus on line coverage - quoting eclemma.org/jacoco/trunk/doc/counters.html "A source line is considered executed when at least one instruction that is assigned to this line has been executed." As was already shown above metric "line coverage" is identical to metric shown by IntelliJ IDEA and 100% for this metric is perfectly reachable.
1

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

3 Comments

Is there something you have to do? I still have this issue with version 0.8.8.202204050719
Me to facing the same issue
Yeah not fixed. runBlocking reports as yellow (partially covered)
0

Don't use Jacoco for Kotlin project with suspended functions and coroutines. Use https://github.com/Kotlin/kotlinx-kover from Jetbrains. Will save much of your time.

2 Comments

Not even Jacoco 0.8.12 handles suspended functions properly.
kover is even worse. btw it uses jacoco under the hood, so no sense in recommending it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.