Partially covered InputStream.bufferedReader()

39 views
Skip to first unread message

Giacomo Boccardo

unread,
Apr 25, 2020, 8:19:37 AM4/25/20
to JaCoCo and EclEmma Users
Hi,

   JaCoCo against the following Kotlin code

fun foo(inputStream: java.io.InputStream) {
    val x = inputStream.bufferedReader()
}

reports two branches on the single line in the foo fun.

Testing the method only one branch is covered.

I don't understand what's the other branch and how should I test it.

Thanks,
  Giacomo

drek...@gmail.com

unread,
Apr 26, 2020, 3:40:16 PM4/26/20
to JaCoCo and EclEmma Users
It's probably not quite the one-liner it looks like:
@kotlin.internal.InlineOnly
public inline fun InputStream.bufferedReader(charset: Charset = Charsets.UTF_8): BufferedReader = reader(charset).buffered()

That's inlined so is (at bytecode level) part of _your_ method.  Further, called by that is the buffered() method:
@kotlin.internal.InlineOnly
public inline fun Reader.buffered(bufferSize: Int = DEFAULT_BUFFER_SIZE): BufferedReader =
if (this is BufferedReader) this else BufferedReader(this, bufferSize)

This is also part of _your_ method and certainly has a conditional in it. Just my guess but that doesn't get compiled out as it's inlined. You'd have to look at the bytecode
to verify anything like that though.

Giacomo Boccardo

unread,
Apr 26, 2020, 5:53:43 PM4/26/20
to JaCoCo and EclEmma Users
Hi,

   I saw that, but I think that, without modifying the function, I cannot cover the "(this is BufferedReader)" condition because neither I can force that branch from the argument "inputStream" nor I can mock inline functions.

Thanks,
  Giacomo

drek...@gmail.com

unread,
Apr 27, 2020, 6:39:50 AM4/27/20
to JaCoCo and EclEmma Users
One of things I've found with higher-level language features in the JVM are instances of this impossible-to-cover scenario (I seem to remember Java try-with-resources is/was much the same). It's really a "limitation" of the compiler not inferring that condition has only one possible outcome, not of Jacoco for correctly interpreting the bytecode it saw.
Message has been deleted

Evgeny Mandrikov

unread,
Apr 27, 2020, 6:58:55 AM4/27/20
to JaCoCo and EclEmma Users
As correctly pointed by drekbour :

JaCoCo performs analysis of bytecode - after compilation by Kotlin compiler 1.3.72 of

fun foo(inputStream: java.io.InputStream) {
    val x = inputStream.bufferedReader()
}

output of javap

  public static final void foo(java.io.InputStream);
    descriptor: (Ljava/io/InputStream;)V
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=4, locals=8, args_size=1
         0: aload_0
         1: ldc           #9                  // String inputStream
         3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: aload_0
         7: astore_2
         8: getstatic     #21                 // Field kotlin/text/Charsets.UTF_8:Ljava/nio/charset/Charset;
        11: astore_3
        12: iconst_0
        13: istore        4
        15: aload_2
        16: astore        5
        18: iconst_0
        19: istore        6
        21: new           #23                 // class java/io/InputStreamReader
        24: dup
        25: aload         5
        27: aload_3
        28: invokespecial #27                 // Method java/io/InputStreamReader."<init>":(Ljava/io/InputStream;Ljava/nio/charset/Charset;)V
        31: checkcast     #29                 // class java/io/Reader
        34: astore        5
        36: sipush        8192
        39: istore        6
        41: iconst_0
        42: istore        7
        44: aload         5
        46: instanceof    #31                 // class java/io/BufferedReader
        49: ifeq          60
        52: aload         5
        54: checkcast     #31                 // class java/io/BufferedReader
        57: goto          71
        60: new           #31                 // class java/io/BufferedReader
        63: dup
        64: aload         5
        66: iload         6
        68: invokespecial #34                 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;I)V
        71: astore_1
        72: return

clearly shows that there is a branch - see offsets 46 and 49 above.

Compilation of

fun foo(inputStream: java.io.InputStream) {
    val temp = inputStream.reader(); val x = if (temp is java.io.BufferedReader) temp else java.io.BufferedReader(temp, DEFAULT_BUFFER_SIZE)
}

produces almost the same bytecode.

AFAIK there are no markers in bytecode to realize that kotlin.internal.InlineOnly function was inlined.

So there is no way for JaCoCo to distinguish whether bytecode was produced from source code as in the first example or as in second.

On Sunday, April 26, 2020 at 11:53:43 PM UTC+2, Giacomo Boccardo wrote:
I saw that, but I think that, without modifying the function, I cannot cover the "(this is BufferedReader)" condition because neither I can force that branch from the argument "inputStream" nor I can mock inline functions.


Again as correctly pointed by

On Monday, April 27, 2020 at 12:39:50 PM UTC+2, drek...@gmail.com wrote:
One of things I've found with higher-level language features in the JVM are instances of this impossible-to-cover scenario (I seem to remember Java try-with-resources is/was much the same). It's really a "limitation" of the compiler not inferring that condition has only one possible outcome, not of Jacoco for correctly interpreting the bytecode it saw.

Existence of such unreachable bytecode is bad not only for code coverage measurement but also for the size of class files and performance. So please report this to the developers of Kotlin. Either standard library functions or compiler should be improved to not produce such unreachable bytecode. Or for JaCoCo there should be a way to identify bytecode produced by kotlin.internal.InlineOnly.


Regards,
Evgeny

Reply all
Reply to author
Forward
0 new messages