Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

8 branches for try with resources - jacoco coverage possible?

I've got some code that uses try with resources and in jacoco it's coming up as only half covered. All the source code lines are green, but I get a little yellow symbol telling me that only 4 of 8 branches are covered.

enter image description here

I'm having trouble figuring out what all the branches are, and how to write code that covers them. Three possible places throw PipelineException. These are createStageList(), processItem() and the implied close()

  1. Not throwing any exceptions,
  2. throwing an exception from createStageList()
  3. throwing an exception from processItem()
  4. throwing an exception from close()
  5. throwing an exception from processItem() and close()

I can't think of any other cases, yet I still only have 4 of 8 covered.

Can someone explain to me why it's 4 of 8 and is there anyway to hit all 8 branches? I'm not skilled with decyrpting/reading/interpreting byte code, but maybe you are... :) I've already seen https://github.com/jacoco/jacoco/issues/82, but neither it nor the issue it references help very much (other than noting that this is due to compiler generated blocks)

Hmm, just as I finish writing this I had a thought on what case(s) might not be not tested by what I mention above... I'll post an answer if I got it right. I'm sure this question and it's answer will help someone in any case.

EDIT: Nope, I didn't find it. Throwing RuntimeExceptions (not handled by the catch block) didn't cover any more branches

like image 947
Gus Avatar asked Jun 27 '13 22:06

Gus


People also ask

How does JaCoCo calculate branch coverage?

JaCoCo also calculates branch coverage for all if and switch statements. This metric counts the total number of such branches in a method and determines the number of executed or missed branches. Branch coverage is always available, even in absence of debug information in the class files.

What is JaCoCo branch coverage?

JaCoCo mainly provides three important metrics: Lines coverage reflects the amount of code that has been exercised based on the number of Java byte code instructions called by the tests. Branches coverage shows the percent of exercised branches in the code, typically related to if/else and switch statements.

How do I increase my JaCoCo coverage?

For the code coverage to increase , one would need to run the tests with the coverage enabled and then view the report generated locally to see the areas covered by jacoco during its coverage parse, then from these one would see the methods (per class) that needs to be covered from the view of the jacoco agent.

Is JaCoCo a code coverage tool?

JaCoCo is a free code coverage library for Java, which has been created by the EclEmma team based on the lessons learned from using and integration existing libraries for many years.


1 Answers

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_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 

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) } 
like image 124
Antimony Avatar answered Sep 28 '22 06:09

Antimony