Gus*_*Gus 60 java code-coverage bytecode try-with-resources jacoco
我有一些代码使用try资源和jacoco它只有一半覆盖.所有的源代码行都是绿色的,但我得到一个黄色的小符号告诉我8个分支中只有4个被覆盖.

我无法弄清楚所有分支是什么,以及如何编写涵盖它们的代码.扔三个可能的地方PipelineException.这些createStageList(),processItem()以及隐含的close()
createStageList()processItem()close()processItem()和抛出异常close()我想不出任何其他情况,但我仍然只有8个中有4个被覆盖.
有人可以向我解释为什么它是8中的4,并且无论如何都可以击中所有8个分支?我不熟悉decyrpting /阅读/解释字节码,但也许你是...... :)我已经看过https://github.com/jacoco/jacoco/issues/82,但它既不是也不是问题它非常引用帮助(除了注意这是由于编译器生成的块)
嗯,就在我写完这篇文章的时候,我想到了上面提到的那些案例可能没有被测试过......如果我做对了,我会发一个答案.我确信这个问题和答案在任何情况下都会对某人有所帮助.
编辑:没有,我没有找到它.抛出RuntimeExceptions(不由catch块处理)不包括任何更多分支
Ant*_*ony 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控制流.
如您所见,有很多情况可以处理抑制异常的各种可能性.能够涵盖所有这些案件是不合理的.实际上,goto L59第一个try块上的分支是不可能达到的,因为第一个捕获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)
}
小智 8
我可以覆盖所有8个分支,所以我的答案是肯定的.看看下面的代码,这只是一个快速尝试,但它的工作原理(或者看看我的github:https://github.com/bachoreczm/basicjava和'trywithresources'包,你可以找到,如何尝试 -资源有效,请参阅'explainOfTryWithResources'类):
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;
  }
}
没有真正的问题,但想要在那里投入更多的研究.tl; dr =看起来你可以为try-finally实现100%的覆盖率,但不能用于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%行,指令和分支覆盖:
分析:java-7示例:
如果针对Java7样式示例运行相同的4个测试,则jacoco表示覆盖了6/8个分支(在try本身上)和在try中的null-check上的2/2.我尝试了一些额外的测试以增加覆盖率,但我发现没有办法比6/8更好.正如其他人所指出的那样,java-7示例的反编译代码(我也看到了)表明java编译器正在为try-with-resource生成无法访问的段.Jacoco正在(准确地)报告存在此类细分市场.
更新:使用Java7的编码风格,你也许可以得到100%的覆盖率IF使用Java7 JRE(见下文马加什响应).但是,使用带有Java8 JRE的Java7编码风格,我相信你会覆盖6/8分支机构.相同的代码,只是不同的JRE.看起来两个JRE之间的字节代码创建方式不同,Java8创建了无法访问的路径.
四岁了,但还是......
AutoCloseableAutoCloseabletry块中抛出但是AutoCloseable为空以上列出了所有7个条件 - 8个分支的原因是由于重复条件.
可以到达所有分支,try-with-resources相当简单的编译器糖(至少比较switch-on-string) - 如果它们无法到达,那么它根据定义是一个编译器bug.
只有6单元测试实际需要(在下面的示例代码,throwsOnClose是@Ingored和分支覆盖是8/8.
另请注意,Throwable.addSuppressed(Throwable)无法抑制自身,因此生成的字节码包含一个额外的保护(IF_ACMPEQ - 引用相等)来防止这种情况.幸运的是,这个分支由throw-on-write,throw-on-close和throw-on-write-and-close情况所覆盖,因为字节码变量槽由3个异常处理程序区域中的外部2个重用.
这不是 Jacoco的问题 - 实际上链接问题#82中的示例代码是不正确的,因为没有重复的空检查,并且关闭周围没有嵌套的catch块.
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)));
        }
    }
}
虽然不在OP的示例代码中,但有一个案例无法通过AFAIK进行测试.
如果将资源引用作为参数传递,那么在Java 7/8中,您必须具有要分配的局部变量:
    void someMethod(AutoCloseable arg)
    {
        try(AutoCloseable pfft = arg) {
            //...
        }
    }
在这种情况下,生成的代码仍将保护资源引用.合成糖在Java 9中更新,其中不再需要局部变量:try(arg){ /*...*/ }
不可否认,这些分支中的一些可以被写为不切实际 - 即try块使用AutoCloseable无空值检查或资源引用(with)不能为空的位置.
通常,您的应用程序并不关心它失败的地方 - 打开文件,写入文件或关闭它 - 失败的粒度是无关紧要的(除非应用程序特别关注文件,例如文件浏览器或文字处理器).
此外,在OP的代码中,要测试null可关闭路径 - 您必须将try块重构为受保护的方法,子类并提供NOOP实现 - 所有这些只是覆盖了永远不会在野外采用的分支.
我写了一个小的Java 8库io.earcam.unexceptional(在Maven Central中),它处理大多数已检查的异常样板.
与此问题相关:它为AutoCloseables 提供了一堆零分支单行,将已检查的异常转换为未选中.
示例:免费端口查找器
int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);
| 归档时间: | 
 | 
| 查看次数: | 20946 次 | 
| 最近记录: |