Menu

Incorrect Branch Coverage for Java 7 Switch

2011-08-31
2013-04-24
  • Jeff Campbell

    Jeff Campbell - 2011-08-31

    For the following Java 7 code:

    public static String decodeGender(String gender) {
        if (gender == null) {
          return null;
        }
        switch (gender) {
          case "Male":
            return "M";
          case "Female":
            return "F";
          case "Unknown":
            return "U";
          default:
            throw new IllegalStateException("Unknown gender type ");
        }
      }

    And, the following JUnit test:
    public void testDecodeGender() {
        assertNull(decodeGender(null));
        assertEquals("M", decodeGender("Male"));
        assertEquals("F", decodeGender("Female"));
        assertEquals("U", decodeGender("Unknown"));
        try {
          decodeGender("?");
          fail("decodeGender should have failed");
        } catch (Exception e) {
        }
    }

    Jacoco Reports:
    Line Coverage: 100%
    Branch Coverage: 81%

    Branch coverage is 81% because: on the "switch (gender) {" line Jacoco indicates that "3 of 14 branches missed".  I'm assuming that the byte code that Java 7 produces make Jacoco think that some branches of this switch statement is missed?  Am I really missing 3 branches!?

     
  • Marc R. Hoffmann

    This is quite an interesting observation. From looking at the byte code one
    can see how the Java compiler handles the switch on strings. Actually it is an
    3-step process:

      1. Switch on the hash code (3 Branches, 1 Default)
      2. For each hash code do an equals (3 * 2 branches)
      3. Do an final switch for the actual execution of the cases (3 Branches, 1 Default)
     
    So we have an total of 14 branches which looks strange from the source code's
    point of view. What looks even more strange is that you're missing three of
    them. The explanation is step 2 where the equals method is applied additionally
    after the hash code. To cover these branches also you would need to find other
    strings with the same hash code. This is definitely something that might be
    filtered from coverage reports in future versions of JaCoCo:

      https://sourceforge.net/apps/trac/eclemma/wiki/FilteringOptions
     
    Cheers,
    -marc

     
  • Marc R. Hoffmann

    BTW, here is what the Java compiler produces from your example:

      public static java.lang.String decodeGender(java.lang.String gender);
          0  aload_0 [gender]
          1  ifnonnull 6
          4  aconst_null
          5  areturn
          6  aload_0 [gender]
          7  astore_1
          8  iconst_m1
          9  istore_2
         10  aload_1
         11  invokevirtual java.lang.String.hashCode() : int [17]
         14  lookupswitch default: 87
              case 2390573: 48
              case 1379812394: 76
              case 2100660076: 62
         48  aload_1
         49  ldc <String "Male"> [18]
         51  invokevirtual java.lang.String.equals(java.lang.Object) : boolean [19]
         54  ifeq 87
         57  iconst_0
         58  istore_2
         59  goto 87
         62  aload_1
         63  ldc <String "Female"> [20]
         65  invokevirtual java.lang.String.equals(java.lang.Object) : boolean [19]
         68  ifeq 87
         71  iconst_1
         72  istore_2
         73  goto 87
         76  aload_1
         77  ldc <String "Unknown"> [21]
         79  invokevirtual java.lang.String.equals(java.lang.Object) : boolean [19]
         82  ifeq 87
         85  iconst_2
         86  istore_2
         87  iload_2
         88  tableswitch default: 125
              case 0: 116
              case 1: 119
              case 2: 122
        116  ldc <String "M"> [22]
        118  areturn
        119  ldc <String "F"> [23]
        121  areturn
        122  ldc <String "U"> [24]
        124  areturn
        125  new java.lang.IllegalStateException [25]
        128  dup
        129  new java.lang.StringBuilder [7]
        132  dup
        133  invokespecial java.lang.StringBuilder() [8]
        136  ldc <String "Unknown gender type ["> [26]
        138  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [9]
        141  aload_0 [gender]
        142  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [9]
        145  ldc <String "]"> [27]
        147  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [9]
        150  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [12]
        153  invokespecial java.lang.IllegalStateException(java.lang.String) [28]
        156  athrow