有资源尝试的8个分支机构 - 可能覆盖jacoco吗?

时间:2013-06-27 22:16:05

标签: java code-coverage bytecode jacoco try-with-resources

我有一些代码使用try资源和jacoco它只是一半覆盖。所有的源代码行都是绿色的,但是我得到一个黄色的小符号,告诉我8个分支中只有4个被覆盖。

enter image description here

我无法弄清楚所有分支是什么,以及如何编写涵盖它们的代码。三个可能的地方抛出PipelineException。这些是createStageList()processItem()和隐含的close()

  1. 不抛出任何例外,
  2. createStageList()
  3. 中抛出异常
  4. processItem()
  5. 中抛出异常
  6. close()
  7. 中抛出异常
  8. processItem()close()
  9. 中抛出异常

    我想不出任何其他情况,但我仍然只有8个中的4个。

    有人可以向我解释为什么它是8中的4,并且无论如何都要击中所有8个分支?我不熟悉decyrpting /阅读/解释字节码,但也许你是...... :)我已经看过https://github.com/jacoco/jacoco/issues/82,但是它和它引用的问题都没有帮助(除了注意到这是由于编译器生成的块)

    嗯,就在我写完这篇文章的时候,我想到了上面提到的那些案例可能没有被测试过......如果我做对了,我会发一个答案。我相信这个问题,它的答案在任何情况下都会帮助别人。

    编辑:不,我没找到。抛出RuntimeExceptions(不由catch块处理)没有涵盖任何更多的分支

6 个答案:

答案 0 :(得分:56)

嗯,我不能告诉你Jacoco的确切问题是什么,但我可以告诉你如何编译Try With Resources。基本上,有很多编译器生成的开关来处理在各个点抛出的异常。

如果我们采用以下代码并编译它

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";
}

然后反汇编,我们得到

.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_1
L3:
    new java/io/CharArrayWriter
    dup
    invokespecial java/io/CharArrayWriter <init> ()V
    astore_2
    aconst_null
    astore_3
L13:
    aload_2
    aconst_null
    invokevirtual java/io/CharArrayWriter writeTo (Ljava/io/Writer;)V
L18:
    aload_2
    ifnull L94
    aload_3
    ifnull L44
L26:
    aload_2
    invokevirtual java/io/CharArrayWriter close ()V
L30:
    goto L94
L33:
.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 L94
L44:
.stack same
    aload_2
    invokevirtual java/io/CharArrayWriter close ()V
    goto L94
L51:
.stack same_locals_1_stack_item
    stack Object java/lang/Throwable
.end stack
    astore 4
    aload 4
    astore_3
    aload 4
    athrow
L59:
.stack same_locals_1_stack_item
    stack Object java/lang/Throwable
.end stack
    astore 5
L61:
    aload_2
    ifnull L91
    aload_3
    ifnull L87
L69:
    aload_2
    invokevirtual java/io/CharArrayWriter close ()V
L73:
    goto L91
L76:
.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 L91
L87:
.stack same
    aload_2
    invokevirtual java/io/CharArrayWriter close ()V
L91:
.stack same
    aload 5
    athrow
L94:
.stack full
    locals Object [Ljava/lang/String; Object java/lang/String
    stack 
.end stack
    goto L108
L97:
.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;)V
L108:
.stack same
    ldc 'after'
    astore_2
    return
.end method

对于那些不会说字节码的人来说,这大致相当于下面的伪Java。我不得不使用gotos,因为字节码并不真正对应Java控制流。

正如您所看到的,有很多情况可以处理抑制异常的各种可能性。能够涵盖所有这些案件是不合理的。实际上,第一个try块上的goto L59分支是不可能达到的,因为第一个捕获Throwable将捕获所有异常。

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)
}

答案 1 :(得分:8)

enter image description here

我可以覆盖所有8个分支,所以我的答案是肯定的。看看下面的代码,这只是一个快速的尝试,但它可以工作(或者看看我的github:https://github.com/bachoreczm/basicjava和'trywithresources'包,你可以找到,try-with-resources如何工作,请参阅'ExplaipOfTryWithResources '班级):

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;
  }
}

答案 2 :(得分:6)

没有真正的问题,但想在那里投入更多的研究。 tl; dr =看起来你可以实现100%覆盖try-finally,但不能用于try-with-resource。

可以理解的是,老派的try-finally和Java7 try-with-resources之间存在差异。这是两个使用替代方法显示相同内容的等效示例。

旧派示例(尝试终极方法):

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

Java7示例(尝试资源方法):

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

分析:老派的例子:
使用Jacoco 0.7.4.201502262128和JDK 1.8.0_45,我可以使用以下4个测试获得Old School示例的100%行,指令和分支覆盖:

        
  • 基本润滑脂路径(声明不为空,执行()正常运行)
  •     
  • execute()抛出异常
  •     
  • foo()抛出异常AND语句返回为null
  •     
  • 语句返回为null
Jacoco在&#39;尝试&#39;内部指示2个分支。 (在空检查上)和最终内部的4(在空检查上)。所有内容都完全涵盖。

分析:java-7示例:
如果针对Java7样式示例运行相同的4个测试,则jacoco表示覆盖6/8个分支(在try本身上)和在try中的null-check上2/2。我尝试了一些额外的测试以增加覆盖率,但我发现没有办法比6/8更好。正如其他人所指出的那样,java-7示例的反编译代码(我也看到了)表明java编译器正在为try-with-resource生成无法访问的段。 Jacoco正在(准确地)报告此类细分市场存在。

更新:使用Java7编码样式,您可以使用Java7 JRE获得100%覆盖 IF (请参阅下面的Matyas响应)。但是,使用带有Java8 JRE的Java7编码风格,我相信你会覆盖6/8分支机构。相同的代码,只是不同的JRE。看起来两个JRE之间的字节代码创建方式不同,Java8创建了无法访问的路径。

答案 3 :(得分:5)

四岁,但仍然......

  1. 非空AutoCloseable
  2. 的快乐路径
  3. 带有AutoCloseable
  4. 的快乐路径
  5. 谨慎行事
  6. 关闭
  7. 投掷并关闭
  8. 引入资源规范( with 部分,例如构造函数调用)
  9. try区块中投掷,但AutoCloseable为空
  10. 上面列出了所有7个条件 - 8个分支的原因是由于重复的条件。

    可以到达所有分支,try-with-resources是相当简单的编译器糖(至少与switch-on-string相比) - 如果无法访问它们,那么根据定义它就是编译器错误。

    实际上只需要6个单元测试(在下面的示例代码中,throwsOnClose@Ingore d,分支覆盖范围是8/8。

    另请注意Throwable.addSuppressed(Throwable)无法抑制自身,因此生成的字节码包含一个额外的保护(IF_ACMPEQ - 引用相等)来防止这种情况。幸运的是,这个分支由throw-on-write,throw-on-close和throw-on-write-and-close情况所覆盖,因为字节码变量槽由3个异常处理程序区域中的外部2个重用。

    Jacoco的问题 - 实际上链接的issue #82中的示例代码不正确,因为没有重复的空检查,并且关闭周围没有嵌套的catch块

    JUnit测试展示了8个分支中的8个

    import static org.hamcrest.Matchers.arrayContaining;
    import static org.hamcrest.Matchers.is;
    import static org.hamcrest.Matchers.sameInstance;
    import static org.junit.Assert.assertThat;
    import static org.junit.Assert.fail;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.UncheckedIOException;
    
    import org.junit.Ignore;
    import org.junit.Test;
    
    public class FullBranchCoverageOnTryWithResourcesTest {
    
        private static class DummyOutputStream extends OutputStream {
    
            private final IOException thrownOnWrite;
            private final IOException thrownOnClose;
    
    
            public DummyOutputStream(IOException thrownOnWrite, IOException thrownOnClose)
            {
                this.thrownOnWrite = thrownOnWrite;
                this.thrownOnClose = thrownOnClose;
            }
    
    
            @Override
            public void write(int b) throws IOException
            {
                if(thrownOnWrite != null) {
                    throw thrownOnWrite;
                }
            }
    
    
            @Override
            public void close() throws IOException
            {
                if(thrownOnClose != null) {
                    throw thrownOnClose;
                }
            }
        }
    
        private static class Subject {
    
            private OutputStream closeable;
            private IOException exception;
    
    
            public Subject(OutputStream closeable)
            {
                this.closeable = closeable;
            }
    
    
            public Subject(IOException exception)
            {
                this.exception = exception;
            }
    
    
            public void scrutinize(String text)
            {
                try(OutputStream closeable = create()) {
                    process(closeable);
                } catch(IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
    
    
            protected void process(OutputStream closeable) throws IOException
            {
                if(closeable != null) {
                    closeable.write(1);
                }
            }
    
    
            protected OutputStream create() throws IOException
            {
                if(exception != null) {
                    throw exception;
                }
                return closeable;
            }
        }
    
        private final IOException onWrite = new IOException("Two writes don't make a left");
        private final IOException onClose = new IOException("Sorry Dave, we're open 24/7");
    
    
        /**
         * Covers one branch
         */
        @Test
        public void happyPath()
        {
            Subject subject = new Subject(new DummyOutputStream(null, null));
    
            subject.scrutinize("text");
        }
    
    
        /**
         * Covers one branch
         */
        @Test
        public void happyPathWithNullCloseable()
        {
            Subject subject = new Subject((OutputStream) null);
    
            subject.scrutinize("text");
        }
    
    
        /**
         * Covers one branch
         */
        @Test
        public void throwsOnCreateResource()
        {
            IOException chuck = new IOException("oom?");
            Subject subject = new Subject(chuck);
            try {
                subject.scrutinize("text");
                fail();
            } catch(UncheckedIOException e) {
                assertThat(e.getCause(), is(sameInstance(chuck)));
            }
        }
    
    
        /**
         * Covers three branches
         */
        @Test
        public void throwsOnWrite()
        {
            Subject subject = new Subject(new DummyOutputStream(onWrite, null));
            try {
                subject.scrutinize("text");
                fail();
            } catch(UncheckedIOException e) {
                assertThat(e.getCause(), is(sameInstance(onWrite)));
            }
        }
    
    
        /**
         * Covers one branch - Not needed for coverage if you have the other tests
         */
        @Ignore
        @Test
        public void throwsOnClose()
        {
            Subject subject = new Subject(new DummyOutputStream(null, onClose));
            try {
                subject.scrutinize("text");
                fail();
            } catch(UncheckedIOException e) {
                assertThat(e.getCause(), is(sameInstance(onClose)));
            }
        }
    
    
        /**
         * Covers two branches
         */
        @SuppressWarnings("unchecked")
        @Test
        public void throwsOnWriteAndClose()
        {
            Subject subject = new Subject(new DummyOutputStream(onWrite, onClose));
            try {
                subject.scrutinize("text");
                fail();
            } catch(UncheckedIOException e) {
                assertThat(e.getCause(), is(sameInstance(onWrite)));
                assertThat(e.getCause().getSuppressed(), is(arrayContaining(sameInstance(onClose))));
            }
        }
    
    
        /**
         * Covers three branches
         */
        @Test
        public void throwsInTryBlockButCloseableIsNull() throws Exception
        {
            IOException chucked = new IOException("ta-da");
            Subject subject = new Subject((OutputStream) null) {
                @Override
                protected void process(OutputStream closeable) throws IOException
                {
                    throw chucked;
                }
            };
    
            try {
                subject.scrutinize("text");
                fail();
            } catch(UncheckedIOException e) {
                assertThat(e.getCause(), is(sameInstance(chucked)));
            }
    
        }
    }
    

    Eclipse Coverage

    买者

    虽然不在OP的示例代码中,但有一个案例无法通过AFAIK进行测试。

    如果您将资源引用作为参数传递,那么在Java 7/8中,您必须有一个要分配的局部变量:

        void someMethod(AutoCloseable arg)
        {
            try(AutoCloseable pfft = arg) {
                //...
            }
        }
    

    在这种情况下,生成的代码仍将保护资源引用。合成糖为updated in Java 9,其中不再需要局部变量:try(arg){ /*...*/ }

    补充 - 建议使用库来完全避免分支

    不可否认,其中一些分支可能被写为不切实际 - 即try块使用AutoCloseable而不进行空检查或资源引用(with)不能为空。

    通常您的应用程序并不关心它失败的地方 - 打开文件,写入文件或关闭文件 - 失败的粒度无关紧要(除非应用程序特别关注文件,例如文件浏览器或文字处理器。)

    此外,在OP的代码中,要测试null可关闭路径 - 您必须将try块重构为受保护的方法,子类并提供NOOP实现 - 所有这些只是得到覆盖永远不会被野外采取的树枝。

    我写了一个小的Java 8库io.earcam.unexceptional(在Maven Central中),它处理大多数已检查的异常样板。

    与此问题相关:它为AutoCloseable提供了大量零分支单行,将已检查的异常转换为未选中。

    示例:免费端口查找器

    int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);
    

答案 4 :(得分:2)

Jacoco最近解决了这个问题,版本0.8.0(2018/01/02)

&#34;在创建报告期间,会过滤掉各种编译器生成的工件,否则需要不必要的,有时甚至是不可能的技巧,以避免部分或错过覆盖:

  • try-with-resources语句的部分字节码(GitHub#500)。&#34;

http://www.jacoco.org/jacoco/trunk/doc/changes.html

答案 5 :(得分:1)

我遇到类似的问题:

try {
...
} finally {
 if (a && b) {
  ...
 }
}

它抱怨说8个分支中有2个没有被覆盖。最后这样做了:

try {
...
} finally {
 ab(a,b);
}

void ab(a, b) {
 if (a && b) {
...
 }
}

没有其他变化,我现在达到了100%......