2

The following simple code snippet contains a while-loop that looks as if it could be infinite:

  def findDivisor(n: Int): Int = {
    require(n >= 2)
    var i = 2
    while (true) {
      if (n % i == 0) {
        return i
      } else {
        // do-nothing branch
      }
      i += 1
    }
    // $COVERAGE-OFF$
    throw new Error("unreachable")
    // $COVERAGE-ON$
  }

Basic math guarantees that this method always terminates (even if it cannot find a proper divisor, it must stop at n).

Despite the $COVERAGE-OFF$ right after the while-loop, Scoverage (and maybe some other coverage tools) will complain, and compute only 75% branch coverage (because while counts as a branch point, and the false branch is never taken before return).

Moving the // $COVERAGE-OFF$ around, e.g. before the closing } of the while-body does not help either.

How do I force it to ignore the impossible branch?

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • I'm aware that in this simple case, the code could be rewritten without any loops at all, e.g. using tail-recursion: `@annotation.tailrec def findDivisor(n: Int, i: Int = 2): Int = if (n % i == 0) i else findDivisor(n, i +1)`. The example is deliberately simplified. The question is specifically about how to silence the coverage tool without changing the control structures. – Andrey Tyukin Sep 28 '18 at 11:53

2 Answers2

2

Just wrap the while(true) { loop head into a separate pair of $COVERAGE-OFF$-$COVERAGE-ON$ comments:

  def findDivisor(n: Int): Int = {
    require(n >= 2)
    var i = 2
    // $COVERAGE-OFF$
    while (true) {
      // $COVERAGE-ON$
      if (n % i == 0) {
        return i
      } else {
        // do-nothing branch
      }
      i += 1
      // $COVERAGE-OFF$
    }
    throw new Error("unreachable")
    // $COVERAGE-ON$
  }

Now Scoverage makes sure that each statement in the body of the while-loop is covered, but it ignores the false-branch, and reports 100% test coverage e.g. after the following simple test:

  "Whatever" should "do something" in {
    MyObjectName.findDivisor(57) should be(3)
  }
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
2

I'd suggest that, instead of working against the compiler, you provide your code in terms the compiler can understand. The compiler understands infinite recursion.

@tailrec def forever(op: => Unit): Nothing = {
  op
  forever(op)
}

def findDivisor(n: Int): Int = {
  require(n >= 2)
  var i = 2
  forever {
    if (n % i == 0) {
      return i
    } else {
      // do-nothing branch
    }
    i += 1
  }
}

forever has no branches, so your coverage tool ought to be happy, and, as a bonus, you no longer need a dummy exception.

Brian McCutchon
  • 8,354
  • 3
  • 33
  • 45
  • That's all true... But sometimes you really want to preserve every single line and every single `while`-loop exactly from some older code written in C, or some ancient imperative pseudocode. Or maybe you want to write the code in a way so it can be easily translated into another language that does not support tail recursion. That clearly does not result in idiomatic Scala code, but nevertheless, it should be possible to force the code-coverage tool to ignore certain branches even in not-entirely-idiomatic Scala code. Thanks anyway. – Andrey Tyukin Sep 28 '18 at 12:03