diff --git a/core/src/main/scala/stryker4s/mutants/applymutants/CoverageMatchBuilder.scala b/core/src/main/scala/stryker4s/mutants/applymutants/CoverageMatchBuilder.scala index 6b4a20e9a..499c69c26 100644 --- a/core/src/main/scala/stryker4s/mutants/applymutants/CoverageMatchBuilder.scala +++ b/core/src/main/scala/stryker4s/mutants/applymutants/CoverageMatchBuilder.scala @@ -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)) } } diff --git a/core/src/test/scala/stryker4s/mutants/applymutants/CoverageMatchBuilderTest.scala b/core/src/test/scala/stryker4s/mutants/applymutants/CoverageMatchBuilderTest.scala new file mode 100644 index 000000000..c5f83c361 --- /dev/null +++ b/core/src/test/scala/stryker4s/mutants/applymutants/CoverageMatchBuilderTest.scala @@ -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 + )) + } + } +} diff --git a/sbt-testrunner/src/main/scala/stryker4s/package.scala b/sbt-testrunner/src/main/scala/stryker4s/package.scala index 4960064d6..baa127313 100644 --- a/sbt-testrunner/src/main/scala/stryker4s/package.scala +++ b/sbt-testrunner/src/main/scala/stryker4s/package.scala @@ -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 @@ -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) }