10

Consider the following minimal Kotlin example:

fun <U> someWrapper(supplier: () -> U): () -> (U) {
    return { supplier() }
}

fun foo(taskExecutor: TaskExecutor): Int {
    val future = CompletableFuture.supplyAsync(someWrapper {
        42
    }, taskExecutor::execute)
    return future.join()
}

@Test
public void shouldFoo() {
    assertThat(foo(), is(42));
}

I have branch coverage rules in Jacoco, which fail for the code above, saying 1 of 2 branches is not covered on the line of the someWrapper call. Unfortunately, it is not an option for me to exclude all classes from which someWrapper is called.

Looking at the decompiled Java code:

public final int foo(TaskExecutor taskExecutor) {
    Object var10000 = WrappersKt.someWrapper((Function0)null.INSTANCE);
    if (var10000 != null) {
        Object var2 = var10000;
        var10000 = new Foo$sam$java_util_function_Supplier$0((Function0)var2);
    }

    Supplier var3 = (Supplier)var10000;
    Function1 var4 = (Function1)(new Function1(this.taskExecutor) {
        // $FF: synthetic method
        // $FF: bridge method
        public Object invoke(Object var1) {
        this.invoke((Runnable)var1);
        return Unit.INSTANCE;
        }

        public final void invoke(Runnable p1) {
        ((TaskExecutor)this.receiver).execute(p1);
        }

        public final KDeclarationContainer getOwner() {
        return Reflection.getOrCreateKotlinClass(TaskExecutor.class);
        }

        public final String getName() {
        return "execute";
        }

        public final String getSignature() {
        return "execute(Ljava/lang/Runnable;)V";
        }
    });
    CompletableFuture future = CompletableFuture.supplyAsync(var3, (Executor)(new Foo$sam$java_util_concurrent_Executor$0(var4)));
    var10000 = future.join();
    Intrinsics.checkExpressionValueIsNotNull(var10000, "future.join()");
    return ((Number)var10000).intValue();
}

I think, the problem is the if (var10000 != null) branch, which is even marked by the IDE to be unnecessary (always true).

Is it somehow possible to adjust the code such that it is possible to cover all branches, eg. by making sure the compiler does not generate that extra null check? I can change the code of both foo(..) and someWrapper(..) as long as I am able to supply a decorated lambda.

I use Kotlin 1.3.50 and Jacoco 0.8.4.

EDIT.

One obvious workaround is to extract supplyAsync(someWrapper { ... }) to some utils class and exclude that class only, ie.:

fun <U> supplyAsync(supplier: () -> U, executor: TaskExecutor): CompletableFuture<U> {
    return CompletableFuture.supplyAsync(someWrapper { supplier() }, executor::execute)
}

This would be good enough for me, though I am still curious why the branch is added by Kotlin, where no branch need to be.

Michiel Leegwater
  • 1,172
  • 4
  • 11
  • 27
BKE
  • 573
  • 2
  • 16
  • I get `Type inference failed` when trying to make your sample code compile. Would be great if you could provide sample code that works out of the box! For example, `taskExecutor` and `controller` are unknowns. – Enselic Oct 09 '19 at 16:50
  • @Enselic added small edit to remove distracting errors. I'm not going to expand it further to full fledged code as this should be enough to get the idea across. – BKE Oct 09 '19 at 17:44
  • 1
    Looking at how JaCoCo progressively adapts to support Kotlin (see https://github.com/jacoco/jacoco/releases and search for "added by the Kotlin compiler"), I think this is just another gap that is going to be fixed sooner or later. If you feel serious about topping your coverage, I suggest reporting an issue. – PiotrK Feb 18 '20 at 19:57

1 Answers1

1

If the return value of someWrapper is only meant to be used as an instance of Supplier, then you can remove the unnecessary null check by explicitly using Supplier as the return type.

fun <U> someWrapper(supplier: () -> U): Supplier<U> {
    return Supplier { supplier() }
}
Leo Aso
  • 11,898
  • 3
  • 25
  • 46