Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix coverage analysis sometimes being placed incorrectly #866

Merged
merged 2 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

}