Skip to content

Commit

Permalink
Merge a14aaf6 into 9813a81
Browse files Browse the repository at this point in the history
  • Loading branch information
RCMartins committed Mar 29, 2021
2 parents 9813a81 + a14aaf6 commit 7b44261
Show file tree
Hide file tree
Showing 32 changed files with 955 additions and 120 deletions.
198 changes: 168 additions & 30 deletions blinky-core/src/main/scala/blinky/internal/Blinky.scala
Expand Up @@ -2,25 +2,29 @@ package blinky.internal

import ammonite.ops._
import better.files.File
import blinky.internal.MutatedTerms._
import blinky.v0.{BlinkyConfig, MutantRange}
import metaconfig.Configured
import play.api.libs.json.Json
import scalafix.v1._

import java.util.concurrent.atomic.AtomicInteger
import scala.meta.Term.{Apply, If, Name, Placeholder, Select}
import scala.meta._
import scala.meta.inputs.Input.VirtualFile
import scala.util.Try

class Blinky(config: BlinkyConfig) extends SemanticRule("Blinky") {
private val mutationId: AtomicInteger = new AtomicInteger(1)
private val mutantId: AtomicInteger = new AtomicInteger(1)
private val tempVarId: AtomicInteger = new AtomicInteger(1)
private val mutantsOutputFileOpt: Option[File] =
Some(config.mutantsOutputFile).filter(_.nonEmpty).map(File(_))
mutantsOutputFileOpt.foreach(_.createFileIfNotExists())
private val specificMutants: Seq[MutantRange] =
config.specificMutants

private def nextIndex: Int = mutationId.getAndIncrement()
private def nextMutantIndex: Int = mutantId.getAndIncrement()
private def nextTempVarIndex: Int = tempVarId.getAndIncrement()

private val fileShouldBeMutated: String => Boolean =
if (config.filesToMutate == Seq("all"))
Expand All @@ -41,40 +45,158 @@ class Blinky(config: BlinkyConfig) extends SemanticRule("Blinky") {
if (!fileShouldBeMutated(fileName))
Patch.empty
else {
val findMutations: FindMutations = new FindMutations(config.activeMutators, doc)
val placeholders: Placeholders =
new Placeholders(() => Name(s"_BLINKY_TEMP_${nextTempVarIndex}_"))
val findMutations: FindMutations =
new FindMutations(config.activeMutators, placeholders, doc)

def createPatch(
original: Term,
mutantSeq: Seq[Mutant],
placeholderFunction: Term => Term,
placeholderLocation: Option[Term],
needsParens: Boolean
): Option[(Patch, Seq[Mutant])] =
mutantSeq.headOption.map(_.original).map { original =>
val (_, mutatedStr) =
mutantSeq.map(mutant => (mutant.id, mutant.mutated)).foldRight((0, original)) {
mutantSeq.headOption.map(_.original).map { originalReplaced =>
def replacer(initialTerm: Term): Term = initialTerm
// replaceTempVars
// .foldLeft(initialTerm: Tree) { case (updatedTerm, (from, to)) =>
// updatedTerm.transform { case Term.Name(`from`) => to }
// }
// .asInstanceOf[Term]

// println("#" * 50)
// println(mutantSeq.mkString("\n"))
// println("#" * 50)

val (_, mutatedStrBefore) =
mutantSeq.map(mutant => (mutant.id, mutant.mutated)).foldRight((0, originalReplaced)) {
case ((id, mutatedTerm), (_, originalTerm)) =>
val mutantId = Lit.String(s"BLINKY_MUTATION_$id")
val envContains =
Apply(
Select(
Select(
Select(Select(Name("_root_"), Name("scala")), Name("sys")),
Name("env")
),
Name("contains")
),
List(Lit.String(s"BLINKY_MUTATION_$id"))
)
val result =
q"""if (_root_.scala.sys.env.contains($mutantId)) ($mutatedTerm) else ($originalTerm)"""
If(envContains, mutatedTerm, originalTerm)
(0, result)
}

(Patch.replaceTree(original, syntaxParens(mutatedStr, needsParens)), mutantSeq)
placeholderLocation match {
case None =>
val mutatedStr = replacer(placeholderFunction(mutatedStrBefore))
(Patch.replaceTree(original, syntaxParens(mutatedStr, needsParens)), mutantSeq)
case Some(placeholderLocation) =>
val mutatedStr = replacer(mutatedStrBefore)
val placeholderFunctionStr =
replacer(placeholderFunction(Term.Name("@"))).syntax.takeWhile(_ != '`')
(
Patch.replaceTree(original, syntaxParens(mutatedStr, needsParens)) +
Patch.addLeft(placeholderLocation, placeholderFunctionStr),
mutantSeq
)
}
}

val (finalPatch, mutantsFound): (Seq[Patch], Seq[Seq[Mutant]]) =
findMutations
.topTreeMutations(doc.tree)
.flatMap { case (original, MutatedTerms(mutantsFound, needsParens)) =>
val mutantsSeq =
mutantsFound
.filterNot(_.structure == original.structure)
.flatMap { mutated =>
val mutantIndex = nextIndex
if (specificMutants.exists(_.contains(mutantIndex)))
Some(createMutant(original, mutated, needsParens, fileName, mutantIndex))
else
None
.flatMap {
case (original, StandardMutatedTerms(mutantsFound, needsParens)) =>
val mutantsSeq =
mutantsFound
.filterNot(_.structure == original.structure)
.flatMap { mutated =>
val mutantIndex = nextMutantIndex
if (specificMutants.exists(_.contains(mutantIndex)))
Some(
createMutant(
original,
original,
mutated,
mutated,
needsParens,
fileName,
mutantIndex
)
)
else
None
}
createPatch(
original,
mutantsSeq,
identity,
// Seq.empty,
None,
needsParens = needsParens
)
case (
originalWithP,
PlaceholderMutatedTerms(
originalWithoutP,
placeholderFunction,
mutationsFoundNotFiltered,
newVars,
placeholderLocation,
needsParens
)
) =>
val mutationsFound = {
var unique: Set[String] = Set.empty
mutationsFoundNotFiltered.filter { term =>
val syntax = term._2.syntax
if (unique(syntax))
false
else {
unique = unique + syntax
true
}
}
createPatch(mutantsSeq, needsParens = needsParens)
}

val mutantSeq: Seq[Mutant] =
mutationsFound
.filterNot { case (termWithP, _) =>
termWithP.structure == originalWithP.structure
}
.flatMap { case (termWithP, termWithoutP) =>
val mutantIndex = nextMutantIndex
if (specificMutants.exists(_.contains(mutantIndex)))
Some(
createMutant(
originalWithoutP,
originalWithP,
termWithoutP,
termWithP,
needsParens,
fileName,
mutantIndex
)
)
else
None
}

// val tempVarsReplaces =
// newVars.map {
// (_, Term.Name(s"_BLINKY_TEMP_${nextTempVarIndex}_"))
// }

createPatch(
originalWithP,
mutantSeq,
placeholderFunction,
// tempVarsReplaces,
placeholderLocation,
needsParens = needsParens
)
}
.unzip

Expand All @@ -85,22 +207,40 @@ class Blinky(config: BlinkyConfig) extends SemanticRule("Blinky") {

def createMutant(
original: Term,
originalForDiff: Term,
mutated: Term,
mutatedForDiffOriginal: Term,
needsParens: Boolean,
fileName: String,
mutantIndex: Int
): Mutant = {
val pos = original.pos
val pos = originalForDiff.pos
val input = pos.input.text

val mutatedSyntax = syntaxParens(mutated, needsParens)
val mutatedInput = input.substring(0, pos.start) + mutatedSyntax + input.substring(pos.end)
val mutatedForDiff =
mutatedForDiffOriginal match {
case Placeholder() => Term.Name("identity")
case other => other
}

val mutatedSyntax = syntaxParens(mutatedForDiff, needsParens)
val mutatedStr = input.substring(0, pos.start) + mutatedSyntax + input.substring(pos.end)

val gitDiff: String = calculateGitDiff(originalForDiff, mutatedStr)

val gitDiff: String = calculateGitDiff(original, mutatedInput)
Mutant(mutantIndex, gitDiff, fileName, original, mutated, needsParens)
Mutant(
mutantIndex,
gitDiff,
fileName,
original,
originalForDiff,
mutated,
mutatedForDiff,
needsParens
)
}

def calculateGitDiff(original: Term, mutatedInput: String): String =
def calculateGitDiff(original: Term, mutatedStr: String): String =
mutantsOutputFileOpt match {
case None =>
""
Expand All @@ -109,7 +249,7 @@ class Blinky(config: BlinkyConfig) extends SemanticRule("Blinky") {
originalFile.writeText(original.pos.input.text)

File.temporaryFile() { mutatedFile =>
mutatedFile.writeText(mutatedInput)
mutatedFile.writeText(mutatedStr)

val gitDiff =
Try(
Expand All @@ -119,9 +259,7 @@ class Blinky(config: BlinkyConfig) extends SemanticRule("Blinky") {
"--no-index",
originalFile.toString,
mutatedFile.toString
)(
pwd
)
)(pwd)
).failed.get.toString
.split("\n")
.drop(5)
Expand Down

0 comments on commit 7b44261

Please sign in to comment.