Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-10878

Improve JaCoCo's branch code coverage of a Groovy assert statement

    XMLWordPrintableJSON

Details

    • Improvement
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 4.0.6
    • 4.0.7
    • None
    • None

    Description

      At the moment, there is no way to have full branch coverage for Groovy assert statements. In a larger project, this is very distractive when trying to find the pieces of actual logic that should be better covered with tests. I believe a slight change in assert statement code generation can significantly improve the situation.

      JaCoCo has long-standing issues with covering calls of methods that throw exceptions. When such methods are called inside of, if/else branches, for example, the result is partial coverage reported for those branches.

      However, there is a JaCoCo idiom (https://github.com/jacoco/jacoco/issues/370#issuecomment-267854179) that can be used to avoid uncovered code in those cases. The basic idea is to create and return an exception from a called method and throw that exception from a caller, like in:

      void fail() {
        throw create();
      }
      
      RuntimeException create() {
        return new RuntimeException();
      }
      

      How this relates to the Groovy assert statement? For example, for a simple assert statement like

      assert firstName != null
      

      Groovy generates something like

      ValueRecorder var1 = new ValueRecorder();
      
      try {
        String var10000 = this.firstName;
        var1.record(var10000, 8);
        var1.record(var10000, 8);
        if (var10000 != null) {
          var1.clear();
        } else {
          ScriptBytecodeAdapter.assertFailed(AssertionRenderer.render("assert firstName != null", var1), (Object)null);
        }
      
      } catch (Throwable var3) {
        var1.clear();
        throw var3;
      }
      

      The problem with generated code is a

      ScriptBytecodeAdapter.assertFailed(AssertionRenderer.render("assert firstName != null", var1), (Object)null);
      

      method call. Inside that method, an exception is created and thrown. Since JaCoCo cannot cover that line completely, the branch

      if (var10000 != null)
      

      will be reported as partially covered.

      To avoid those issues, ScriptBytecodeAdapter.assertFailed() can be adapted (or a new method can be introduced like in the example below) to return the exception instead of throwing it. And then, the calling generated code can throw that returned exception:

      try {
        ...
        if (var10000 != null) {
          ...
        } else {
          Throwable throwable = ScriptBytecodeAdapter.createAssertionError(AssertionRenderer.render("assert firstName != null", var1), (Object)null);
          throw throwable
        }
      
      } catch (Throwable var3) {
        ...
      }
      

      I have a small project demonstrating the issue and a possible solution here: https://github.com/dmurat/groovy-assert-jacoco-coverage-problem

      Tnx

      Attachments

        1. screenshot-1.png
          153 kB
          Damir Murat
        2. screenshot-2.png
          24 kB
          Eric Milles

        Activity

          People

            emilles Eric Milles
            dmurat Damir Murat
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: