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";
}
Run Code Online (Sandbox Code Playgroud)
然后拆卸,我们得到
.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
Run Code Online (Sandbox Code Playgroud)
对于那些不会说字节码的人来说,这大致相当于下面的伪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)
}
Run Code Online (Sandbox Code Playgroud)
小智 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;
}
}
Run Code Online (Sandbox Code Playgroud)
没有真正的问题,但想要在那里投入更多的研究.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();
}
Run Code Online (Sandbox Code Playgroud)
Java7示例(尝试使用资源的方法):
try (final Statement stmt = conn.createStatement()) {
foo();
if (stmt != null) {
stmt.execute("SELECT 1");
}
}
Run Code Online (Sandbox Code Playgroud)
分析:老派示例:
使用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创建了无法访问的路径.
四岁了,但还是......
AutoCloseable
AutoCloseable
try
块中抛出但是AutoCloseable
为空以上列出了所有7个条件 - 8个分支的原因是由于重复条件.
可以到达所有分支,try-with-resources
相当简单的编译器糖(至少比较switch-on-string
) - 如果它们无法到达,那么它根据定义是一个编译器bug.
只有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的问题 - 实际上链接问题#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)));
}
}
}
Run Code Online (Sandbox Code Playgroud)
虽然不在OP的示例代码中,但有一个案例无法通过AFAIK进行测试.
如果将资源引用作为参数传递,那么在Java 7/8中,您必须具有要分配的局部变量:
void someMethod(AutoCloseable arg)
{
try(AutoCloseable pfft = arg) {
//...
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,生成的代码仍将保护资源引用.合成糖在Java 9中更新,其中不再需要局部变量:try(arg){ /*...*/ }
不可否认,这些分支中的一些可以被写为不切实际 - 即try块使用AutoCloseable
无空值检查或资源引用(with
)不能为空的位置.
通常,您的应用程序并不关心它失败的地方 - 打开文件,写入文件或关闭它 - 失败的粒度是无关紧要的(除非应用程序特别关注文件,例如文件浏览器或文字处理器).
此外,在OP的代码中,要测试null可关闭路径 - 您必须将try块重构为受保护的方法,子类并提供NOOP实现 - 所有这些只是覆盖了永远不会在野外采用的分支.
我写了一个小的Java 8库io.earcam.unexceptional(在Maven Central中),它处理大多数已检查的异常样板.
与此问题相关:它为AutoCloseable
s 提供了一堆零分支单行,将已检查的异常转换为未选中.
示例:免费端口查找器
int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
20946 次 |
最近记录: |