Skip to content

Commit

Permalink
Added smart file matches for --filesToMutate parameter (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
RCMartins committed Mar 24, 2021
1 parent 124df9e commit cfeebd1
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 85 deletions.
9 changes: 9 additions & 0 deletions blinky-cli/src/main/scala/blinky/run/Instruction.scala
Expand Up @@ -76,6 +76,12 @@ object Instruction {
next: Option[Boolean] => Instruction[A]
) extends Instruction[A]

final case class GrepFiles[+A](
basePath: Path,
fileName: String,
next: Seq[String] => Instruction[A]
) extends Instruction[A]

def succeed[A](value: => A): Return[A] =
Return(() => value)

Expand Down Expand Up @@ -161,4 +167,7 @@ object Instruction {
): Timeout[Option[Boolean]] =
Timeout(runFunction, millis, succeed(_: Option[Boolean]))

def grepFiles(basePath: Path, fileName: String): GrepFiles[Seq[String]] =
GrepFiles(basePath, fileName, succeed(_: Seq[String]))

}
3 changes: 3 additions & 0 deletions blinky-cli/src/main/scala/blinky/run/Interpreter.scala
Expand Up @@ -87,6 +87,9 @@ object Interpreter {
case CopyRelativeFiles(filesToCopy, fromPath, toPath, next) =>
val result = externalCalls.copyRelativeFiles(filesToCopy, fromPath, toPath)
interpreterNext(next(result))
case GrepFiles(basePath, fileName, next) =>
val result = externalCalls.grepFiles(basePath, fileName)
interpreterNext(next(result))
case timeout @ Timeout(_, _, _) =>
Left(timeout)
}
Expand Down
164 changes: 114 additions & 50 deletions blinky-cli/src/main/scala/blinky/run/Run.scala
Expand Up @@ -3,7 +3,7 @@ package blinky.run
import ammonite.ops.{Path, RelPath}
import blinky.BuildInfo
import blinky.run.Instruction._
import blinky.run.config.{MutationsConfigValidated, SimpleBlinkyConfig}
import blinky.run.config.{FileFilter, MutationsConfigValidated, SimpleBlinkyConfig}
import blinky.run.modules.CliModule
import blinky.v0.BlinkyConfig
import zio.{ExitCode, RIO}
Expand Down Expand Up @@ -71,66 +71,59 @@ object Run {
.split(System.lineSeparator())
.toSeq
.map(file => cloneProjectBaseFolder / RelPath(file))
.filter(file => file.ext == "scala" || file.ext == "sbt")
.filter(_.ext == "scala")
.map(_.toString)

processResult <-
processFilesToMutate(projectRealPath, config.filesToMutate)

result <-
if (base.isEmpty)
succeed(Right(base))
else
for {
copyResult <- copyFilesToTempFolder(
originalProjectRoot,
originalProjectPath,
projectRealPath
)
result <- copyResult match {
case Left(result) =>
succeed(Left(result))
case Right(_) =>
// This part is just an optimization of 'base'
val configFileOrFolderToMutate: Path =
Try(Path(config.filesToMutate))
.getOrElse(
projectRealPath / RelPath(config.filesToMutate)
)

val configFileOrFolderToMutateStr =
configFileOrFolderToMutate.toString

IsFile(
configFileOrFolderToMutate,
if (_)
if (base.contains(configFileOrFolderToMutateStr))
succeed(Seq(configFileOrFolderToMutateStr))
else
succeed(Seq.empty[String])
else
succeed(
base.filter(
_.startsWith(configFileOrFolderToMutateStr)
)
)
).map(Right(_))
}
} yield result
processResult match {
case Left(value) =>
succeed(Left(value))
case Right(filesToMutateStr) =>
if (base.isEmpty)
succeed(Right((filesToMutateStr, base)))
else
for {
copyResult <- copyFilesToTempFolder(
originalProjectRoot,
originalProjectPath,
projectRealPath
)
result <- optimiseFilesToMutate(
base,
copyResult,
projectRealPath,
config.filesToMutate
)
} yield result
}
} yield result
}
else
copyFilesToTempFolder(
originalProjectRoot,
originalProjectPath,
projectRealPath
).map(_ => Right(Seq("all")))
for {
_ <- copyFilesToTempFolder(
originalProjectRoot,
originalProjectPath,
projectRealPath
)
processResult <- processFilesToMutate(
projectRealPath,
config.filesToMutate
)
} yield processResult.map { filesToMutateStr =>
(filesToMutateStr, Seq("all"))
}
}

runResult <- filesToMutateEither match {
case Left(exitCode) =>
succeed(exitCode)
case Right(Seq()) =>
case Right((_, Seq())) =>
ConsoleReporter.filesToMutateIsEmpty
.map(_ => ExitCode.success)
case Right(filesToMutate) =>
case Right((filesToMutateStr, filesToMutateSeq)) =>
for {
coursier <- Setup.setupCoursier(projectRealPath)
_ <- Setup.sbtCompileWithSemanticDB(projectRealPath)
Expand All @@ -139,7 +132,7 @@ object Run {
// Setup BlinkyConfig object
blinkyConf: BlinkyConfig = BlinkyConfig(
mutantsOutputFile = (projectRealPath / "blinky.mutants").toString,
filesToMutate = filesToMutate,
filesToMutate = filesToMutateSeq,
specificMutants = config.options.mutant,
enabledMutators = config.mutators.enabled,
disabledMutators = config.mutators.disabled
Expand Down Expand Up @@ -180,7 +173,7 @@ object Run {
s"--exclude=${config.filesToExclude}"
else "",
s"--tool-classpath=$toolPath",
s"--files=${config.filesToMutate}",
s"--files=$filesToMutateStr",
s"--config=$scalafixConfFile",
"--auto-classpath=target"
).filter(_.nonEmpty)
Expand All @@ -198,6 +191,77 @@ object Run {
}
} yield inst

private def filterFiles(
files: Seq[String],
fileName: String
): Instruction[Either[ExitCode, String]] = {
val filesFiltered = files.collect { case file if file.endsWith(fileName) => file }
filesFiltered match {
case List(singleFile) =>
succeed(Right(singleFile))
case Nil =>
printLine(s"--filesToMutate '$fileName' does not exist.")
.map(_ => Left(ExitCode.failure))
case _ =>
printLine(s"--filesToMutate is ambiguous.").map(_ => Left(ExitCode.failure))
}
}

def processFilesToMutate(
projectRealPath: Path,
filesToMutate: FileFilter
): Instruction[Either[ExitCode, String]] =
filesToMutate match {
case FileFilter.SingleFileOrFolder(fileOrFolder) =>
succeed(Right(fileOrFolder.toString))
case FileFilter.FileName(fileName) =>
grepFiles(
projectRealPath,
fileName
).flatMap(filterFiles(_, fileName))
}

def optimiseFilesToMutate(
base: Seq[String],
copyResult: Either[ExitCode, Unit],
projectRealPath: Path,
filesToMutate: FileFilter
): Instruction[Either[ExitCode, (String, Seq[String])]] =
copyResult match {
case Left(result) =>
succeed(Left(result))
case Right(_) => // This part is just an optimization of 'base'
val fileToMutateInst: Instruction[Either[ExitCode, Path]] =
filesToMutate match {
case FileFilter.SingleFileOrFolder(fileOrFolder) =>
succeed(Right(projectRealPath / fileOrFolder))
case FileFilter.FileName(fileName) =>
filterFiles(base, fileName).map(_.map(Path(_)))
}

for {
fileToMutateResult <- fileToMutateInst
result <-
fileToMutateResult match {
case Left(exitCode) =>
succeed(Left(exitCode))
case Right(configFileOrFolderToMutate) =>
val configFileOrFolderToMutateStr =
configFileOrFolderToMutate.toString
IsFile(
configFileOrFolderToMutate,
if (_)
if (base.contains(configFileOrFolderToMutateStr))
succeed(Seq(configFileOrFolderToMutateStr))
else
succeed(Seq.empty[String])
else
succeed(base.filter(_.startsWith(configFileOrFolderToMutateStr)))
).map(baseFiltered => Right((configFileOrFolderToMutateStr, baseFiltered)))
}
} yield result
}

def copyFilesToTempFolder(
originalProjectRoot: Path,
originalProjectPath: Path,
Expand Down
13 changes: 13 additions & 0 deletions blinky-cli/src/main/scala/blinky/run/config/FileFilter.scala
@@ -0,0 +1,13 @@
package blinky.run.config

import ammonite.ops.RelPath

sealed trait FileFilter

object FileFilter {

case class SingleFileOrFolder(fileOrFolder: RelPath) extends FileFilter

case class FileName(fileName: String) extends FileFilter

}
@@ -1,12 +1,12 @@
package blinky.run.config

import ammonite.ops.RelPath
import better.files.File

import scala.util.Try
import blinky.run.config.FileFilter.{FileName, SingleFileOrFolder}

case class MutationsConfigValidated(
projectPath: File,
filesToMutate: String,
filesToMutate: FileFilter,
filesToExclude: String,
mutators: SimpleBlinkyConfig,
options: OptionsConfig
Expand All @@ -21,21 +21,23 @@ object MutationsConfigValidated {
else if (!projectPath.exists)
Left(s"--projectPath '$projectPath' does not exist.")
else {
val filesToMutateEither: Either[String, String] = {
val filesToMutate: File =
Try(File(config.filesToMutate))
.filter(_.exists)
.getOrElse(projectPath / config.filesToMutate)
val filesToMutate: FileFilter = {
val filesToMutate: File = projectPath / config.filesToMutate

if (filesToMutate.exists)
Right(config.filesToMutate)
else if (filesToMutate.extension.isEmpty && File(filesToMutate.toString + ".scala").exists)
Right(config.filesToMutate + ".scala")
else
Left(s"--filesToMutate '${config.filesToMutate}' does not exist.")
SingleFileOrFolder(RelPath(config.filesToMutate))
else if (filesToMutate.extension.isEmpty) {
if (File(filesToMutate.toString + ".scala").exists)
SingleFileOrFolder(RelPath(config.filesToMutate + ".scala"))
else {
FileName(config.filesToMutate + ".scala")
}
} else {
FileName(config.filesToMutate)
}
}

filesToMutateEither.map(filesToMutate =>
Right(
MutationsConfigValidated(
projectPath,
filesToMutate,
Expand Down
Expand Up @@ -67,4 +67,12 @@ object AmmoniteExternalCalls extends ExternalCalls {
}
).toEither

def grepFiles(
basePath: Path,
fileName: String
): Seq[String] =
Try(
ls.rec(basePath).map(_.toString)
).getOrElse(Seq.empty)

}
Expand Up @@ -36,4 +36,9 @@ trait ExternalCalls {
toPath: Path
): Either[Throwable, Unit]

def grepFiles(
basePath: Path,
fileName: String
): Seq[String]

}
2 changes: 2 additions & 0 deletions blinky-cli/src/main/scala/blinky/run/package.scala
Expand Up @@ -52,6 +52,8 @@ package object run {
)
case Timeout(runFunction, millis, next) =>
Timeout(runFunction, millis, next(_: Option[Boolean]).flatMap(f))
case GrepFiles(basePath, fileName, next) =>
GrepFiles(basePath, fileName, next(_: Seq[String]).flatMap(f))
}
}

Expand Down

0 comments on commit cfeebd1

Please sign in to comment.