8 branches for try with resources - jacoco coverage possible? 8 branches for try with resources - jacoco coverage possible? java java

8 branches for try with resources - jacoco coverage possible?


Well I can't tell you what the exact problem with Jacoco is, but I can show you how Try With Resources is compiled. Basically, there are a lot of compiler generated switches to handle exceptions thrown at various points.

If we take the following code and compile it

public static void main(String[] args){    String a = "before";    try (CharArrayWriter br = new CharArrayWriter()) {        br.writeTo(null);    } catch (IOException e){        System.out.println(e.getMessage());    }    String a2 = "after";}

And then disassemble, we get

.method static public main : ([Ljava/lang/String;)V    .limit stack 2    .limit locals 7    .catch java/lang/Throwable from L26 to L30 using L33    .catch java/lang/Throwable from L13 to L18 using L51    .catch [0] from L13 to L18 using L59    .catch java/lang/Throwable from L69 to L73 using L76    .catch [0] from L51 to L61 using L59    .catch java/io/IOException from L3 to L94 using L97    ldc 'before'    astore_1L3:    new java/io/CharArrayWriter    dup    invokespecial java/io/CharArrayWriter <init> ()V    astore_2    aconst_null    astore_3L13:    aload_2    aconst_null    invokevirtual java/io/CharArrayWriter writeTo (Ljava/io/Writer;)VL18:    aload_2    ifnull L94    aload_3    ifnull L44L26:    aload_2    invokevirtual java/io/CharArrayWriter close ()VL30:    goto L94L33:.stack full    locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable    stack Object java/lang/Throwable.end stack    astore 4    aload_3    aload 4    invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V    goto L94L44:.stack same    aload_2    invokevirtual java/io/CharArrayWriter close ()V    goto L94L51:.stack same_locals_1_stack_item    stack Object java/lang/Throwable.end stack    astore 4    aload 4    astore_3    aload 4    athrowL59:.stack same_locals_1_stack_item    stack Object java/lang/Throwable.end stack    astore 5L61:    aload_2    ifnull L91    aload_3    ifnull L87L69:    aload_2    invokevirtual java/io/CharArrayWriter close ()VL73:    goto L91L76:.stack full    locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable Top Object java/lang/Throwable    stack Object java/lang/Throwable.end stack    astore 6    aload_3    aload 6    invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V    goto L91L87:.stack same    aload_2    invokevirtual java/io/CharArrayWriter close ()VL91:.stack same    aload 5    athrowL94:.stack full    locals Object [Ljava/lang/String; Object java/lang/String    stack .end stack    goto L108L97:.stack same_locals_1_stack_item    stack Object java/io/IOException.end stack    astore_2    getstatic java/lang/System out Ljava/io/PrintStream;    aload_2    invokevirtual java/io/IOException getMessage ()Ljava/lang/String;    invokevirtual java/io/PrintStream println (Ljava/lang/String;)VL108:.stack same    ldc 'after'    astore_2    return.end method

For those who don't speak bytecode, this is roughly equivalent to the following pseudo Java. I had to use gotos because the bytecode doesn't really correspond to Java control flow.

As you can see, there are a lot of cases to handle the various possibilities of suppressed exceptions. It's not reasonable to be able to cover all these cases. In fact, the goto L59 branch on the first try block is impossible to reach, since the first catch Throwable will catch all exceptions.

try{    CharArrayWriter br = new CharArrayWriter();    Throwable x = null;    try{        br.writeTo(null);    } catch (Throwable t) {goto L51;}    catch (Throwable t) {goto L59;}    if (br != null) {        if (x != null) {            try{                br.close();            } catch (Throwable t) {                x.addSuppressed(t);            }        } else {br.close();}    }    break;    try{        L51:        x = t;        throw t;        L59:        Throwable t2 = t;    } catch (Throwable t) {goto L59;}    if (br != null) {        if (x != null) {            try{                br.close();            } catch (Throwable t){                x.addSuppressed(t);            }        } else {br.close();}    }    throw t2;} catch (IOException e) {    System.out.println(e)}


enter image description here

I can cover all 8 branches, so my answer is YES. Look at the following code, this is only a fast try, but it works (or see my github: https://github.com/bachoreczm/basicjava and the 'trywithresources' package, there you can find, how try-with-resources works, see 'ExplanationOfTryWithResources' class):

import java.io.ByteArrayInputStream;import java.io.IOException;import org.junit.Test;public class TestAutoClosable {  private boolean isIsNull = false;  private boolean logicThrowsEx = false;  private boolean closeThrowsEx = false;  private boolean getIsThrowsEx = false;  private void autoClose() throws Throwable {    try (AutoCloseable is = getIs()) {        doSomething();    } catch (Throwable t) {        System.err.println(t);    }  }  @Test  public void test() throws Throwable {    try {      getIsThrowsEx = true;      autoClose();    } catch (Throwable ex) {      getIsThrowsEx = false;    }  }  @Test  public void everythingOk() throws Throwable {    autoClose();  }  @Test  public void logicThrowsException() {    try {      logicThrowsEx = true;      everythingOk();    } catch (Throwable ex) {      logicThrowsEx = false;    }  }  @Test  public void isIsNull() throws Throwable {    isIsNull = true;    everythingOk();    isIsNull = false;  }  @Test  public void closeThrow() {    try {      closeThrowsEx = true;      logicThrowsEx = true;      everythingOk();      closeThrowsEx = false;    } catch (Throwable ex) {    }  }  @Test  public void test2() throws Throwable {    try {      isIsNull = true;      logicThrowsEx = true;      everythingOk();    } catch (Throwable ex) {      isIsNull = false;      logicThrowsEx = false;    }  }  private void doSomething() throws IOException {    if (logicThrowsEx) {      throw new IOException();    }  }  private AutoCloseable getIs() throws IOException {    if (getIsThrowsEx) {      throw new IOException();    }    if (closeThrowsEx) {      return new ByteArrayInputStream("".getBytes()) {        @Override        public void close() throws IOException {          throw new IOException();        }      };    }    if (!isIsNull) {      return new ByteArrayInputStream("".getBytes());    }    return null;  }}


No real question, but wanted to throw more research out there. tl;dr = It looks like you can achieve 100% coverage for try-finally, but not for try-with-resource.

Understandably, there's a difference between old-school try-finally and Java7 try-with-resources. Here's two equivalent examples showing the same thing using alternate approaches.

Old School example (a try-finally approach):

final Statement stmt = conn.createStatement();try {    foo();    if (stmt != null) {        stmt.execute("SELECT 1");    }} finally {    if (stmt != null)        stmt.close();}

Java7 example (a try-with-resource approach):

try (final Statement stmt = conn.createStatement()) {    foo();    if (stmt != null) {        stmt.execute("SELECT 1");    }}

Analysis: old-school example:
Using Jacoco 0.7.4.201502262128 and JDK 1.8.0_45, I was able to get 100% line, instruction and branch coverage on the Old School example using the following 4 tests:

  • Basic grease path (statement not null, and execute() is exercised normally)
  • execute() throws exception
  • foo() throws exception AND statement returned as null
  • statement returned as null
Jacoco indicates 2 branches inside the 'try' (on the null check) and 4 inside the finally (on the null check). All are covered fully.

Analysis: java-7 example:
If the same 4 tests run against the Java7 style example, jacoco indicates 6/8 branches are covered (on the try itself) and 2/2 on the null-check within the try. I tried a number of additional tests to increase coverage, but I can find no way to get better than 6/8. As others have indicated, the decompiled code (which I did also look at) for the java-7 example suggests that the java compiler is generating unreachable segments for try-with-resource. Jacoco is reporting (accurately) that such segments exist.

Update: Using Java7 coding style, you might be able to get 100% coverage IF using a Java7 JRE (see Matyas response below). However, using Java7 coding style with a Java8 JRE, I believe you will hit the 6/8 branches covered. Same code, just different JRE. Seems like the byte code is being created differently between the two JREs with the Java8 one creating unreachable pathways.