Search code examples
javajunitswitch-statementjacococodecov

Why is JaCoCo not covering my String switch statements?


I have a switch statement that extracts an addressing mode from a String and I've written unit tests to cover, what I thought was every eventuality but JaCoCo seems to skip my switch statements, resulting in lower coverage.

Why, if all my case statements, including a default are being executed in tests, would the switch statement not be counted as hit?

enter image description here


Solution

  • For the switch by String

    class Fun  {
      static int fun(String s) {
        switch (s) {
          case "I":
            return 1;
          case "A":
            return 2;
          case "Z":
            return 3;
          case "ABS":
            return 4;
          case "IND":
            return 5;
          default:
            return 6;
        }
      }
    }
    

    Oracle Java compiler generates bytecode similar to the following code (Eclipse Compiler for Java generates slightly different bytecode)

        int c = -1;
        switch (s.hashCode()) {
          case 65: // +1 branch
            if (s.equals("I")) // +2 branches
              c = 0;
            break;
          case 73: // +1 branch
            if (s.equals("A")) // +2 branches
              c = 1;
            break;
          case 90: // +1 branch
            if (s.equals("Z")) // +2 branches
              c = 2;
            break;
          case 64594: // +1 branch
            if (s.equals("ABS")) // +2 branches
              c = 3;
            break;
          case 72639: // +1 branch
            if (s.equals("IND")) // +2 branches
              c = 4;
            break;
          default: // +1 branch
        }
        switch (c) {
          case 0: // +1 branch
            return 1;
          case 1: // +1 branch
            return 2;
          case 2: // +1 branch
            return 3;
          case 3: // +1 branch
            return 4;
          case 4: // +1 branch
            return 5;
          default: // +1 branch
            return 6;
        }
    

    So that original switch-statement with 6 cases is represented in bytecode by a switch with 6 cases for hashCode of String plus 5 if-statements plus another switch with 6 cases. To see this bytecode you can use javap -c.

    JaCoCo performs analysis of bytecode and in versions lower than 0.8.0 has no filter for switch by string. Your tests cover cases, where conditions in if-statements evaluate to true, but not the cases where they evaluate to false. Personally I would advise to simply ignore missing cases, because the goal is not to test that compiler generates proper code, but to test that your application behaves correctly. But for a sake of completeness of this answer - here is tests that cover all bytecode branches:

    import org.junit.Test;
    import static org.junit.Assert.*;
    
    public class FunTest {
      @Test
      public void test() {
        // original strings:
        assertEquals(1, Fun.fun("I"));
        assertEquals(2, Fun.fun("A"));
        assertEquals(3, Fun.fun("Z"));
        assertEquals(4, Fun.fun("ABS"));
        assertEquals(5, Fun.fun("IND"));
    
        // same hash codes, but different strings:
        assertEquals(6, Fun.fun("\0I"));
        assertEquals(6, Fun.fun("\0A"));
        assertEquals(6, Fun.fun("\0Z"));
        assertEquals(6, Fun.fun("\0ABS"));
        assertEquals(6, Fun.fun("\0IND"));
    
        // distinct hash code to cover default cases of switches
        assertEquals(6, Fun.fun(""));
      }
    }
    

    And report generated by JaCoCo 0.7.9 as a proof:

    coverage report

    JaCoCo version 0.8.0 provides filters, including filter for bytecode that javac produces for switch by string. And so generates following report even without additional tests:

    coverage report