Skip to content

Commit

Permalink
Fix coverage analysis sometimes being placed incorrectly (#866)
Browse files Browse the repository at this point in the history
  • Loading branch information
hugo-vrijswijk authored May 19, 2021
1 parent ebe102f commit b467533
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ class CoverageMatchBuilder(mutationContext: ActiveMutationContext)(implicit log:
override def defaultCase(transformedMutant: TransformedMutants): Case =
withCoverage(super.defaultCase(transformedMutant), transformedMutant.mutantStatements)

/** Call coverage in a place that's always safe to call: the 'if'-statement of the default match of the mutation switch. `coverMutant` always returns true
*/
private def withCoverage(caze: Case, mutants: List[Mutant]): Case = {
val coverageStatement = mutants.map(mutant => q"_root_.stryker4s.coverage.coverMutant(${mutant.id})")
val newBody = caze.body match {
case b: Term.Block => coverageStatement ++ b.stats
case other => coverageStatement :+ other
}
caze.copy(body = Term.Block(newBody))
val coverageCond = q"_root_.stryker4s.coverage.coverMutant(..${mutants.map(_.id).map(Lit.Int(_))})"
caze.copy(cond = Some(coverageCond))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package stryker4s.mutants.applymutants

import stryker4s.extension.TreeExtensions.IsEqualExtension
import stryker4s.extension.mutationtype.GreaterThan
import stryker4s.model.{Mutant, TransformedMutants}
import stryker4s.scalatest.LogMatchers
import stryker4s.testutil.Stryker4sSuite

import scala.meta._

class CoverageMatchBuilderTest extends Stryker4sSuite with LogMatchers {
describe("buildMatch") {
it("should add coverage analysis to the default case") {
// Arrange
val ids = Iterator.from(0)
val originalStatement = q"x >= 15"
val mutants = List(q"x > 15", q"x <= 15")
.map(Mutant(ids.next(), originalStatement, _, GreaterThan))
val sut = new CoverageMatchBuilder(ActiveMutationContext.testRunner)

// Act
val result = sut.buildMatch(TransformedMutants(originalStatement, mutants)).cases.last

// Assert
assert(result.isEqual(p"case _ if _root_.stryker4s.coverage.coverMutant(0, 1) => x >= 15"), result)
}

it("should set the mutation switch match to Ints") {
// Arrange
val ids = Iterator.from(0)
val originalStatement = q"x >= 15"
val mutants = List(q"x > 15", q"x <= 15")
.map(Mutant(ids.next(), originalStatement, _, GreaterThan))
val sut = new CoverageMatchBuilder(ActiveMutationContext.testRunner)

// Act
val result = sut.buildMatch(TransformedMutants(originalStatement, mutants)).cases.init

// Assert
result.map(_.syntax) should (contain
.inOrderOnly(
p"case 0 => x > 15".syntax,
p"case 1 => x <= 15".syntax
))
}
}
}
16 changes: 10 additions & 6 deletions sbt-testrunner/src/main/scala/stryker4s/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,27 @@ package object stryker4s {

/** Add a mutant to the current coverage report
*/
def coverMutant(id: Int) = {
def coverMutant(ids: Int*): Boolean = {
if (collectCoverage.get()) {
val currentTest = activeTest.get
if (currentTest != null) {
val currentCovered = coveredTests.getOrElseUpdate(id, new ConcurrentLinkedQueue())
currentCovered.add(currentTest)
ids.foreach { id =>
val currentCovered = coveredTests.getOrElseUpdate(id, new ConcurrentLinkedQueue())
currentCovered.add(currentTest)
}
}
}
true // Always return true, `coverMutant` is called in the guard condition of the default mutation switch
}

/** Set the currently running test. This is needed to map the covered mutants with the test that was running at that time
*/
def setActiveTest(fingerPrint: Fingerprint) = if (collectCoverage.get()) activeTest.set(fingerPrint)
protected[stryker4s] def setActiveTest(fingerPrint: Fingerprint) =
if (collectCoverage.get()) activeTest.set(fingerPrint)

/** Collect coverage analysis during the provided function and return it in a tuple
*/
def collectCoverage[A](f: => A): (A, CoverageReport) = try {
protected[stryker4s] def collectCoverage[A](f: => A): (A, CoverageReport) = try {
collectCoverage.set(true)

val result = f
Expand All @@ -65,6 +69,6 @@ package object stryker4s {

def activeMutation: Int = activeMutationRef.get()

def activeMutation_=(mutation: Int): Unit = activeMutationRef.set(mutation)
protected[stryker4s] def activeMutation_=(mutation: Int): Unit = activeMutationRef.set(mutation)

}

0 comments on commit b467533

Please sign in to comment.