mirror of
https://github.com/sbt/sbt.git
synced 2026-09-02 11:07:30 +02:00
Getting scripted testing working with using-xsbt:
* uses new scripted test framework in xsbt * adds ability to provide commands over loopback interface with <port command * only some tests have been updated
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
|
||||
package sbt.test
|
||||
|
||||
import java.io.{File, IOException}
|
||||
import xsbt.IPC
|
||||
import xsbt.test.{StatementHandler, TestFailed}
|
||||
|
||||
final class SbtHandler(directory: File, log: Logger, server: IPC.Server) extends StatementHandler
|
||||
{
|
||||
type State = Process
|
||||
def initialState = newRemote
|
||||
def apply(command: String, arguments: List[String], p: Process): Process =
|
||||
{
|
||||
send((command :: arguments).mkString(" "))
|
||||
receive(command + " failed")
|
||||
p
|
||||
}
|
||||
def finish(state: Process) =
|
||||
try {
|
||||
server.connection { _.send("exit") }
|
||||
state.exitValue()
|
||||
} catch {
|
||||
case e: IOException => state.destroy()
|
||||
}
|
||||
def send(message: String) = server.connection { _.send(message) }
|
||||
def receive(errorMessage: String) =
|
||||
server.connection { ipc =>
|
||||
val resultMessage = ipc.receive
|
||||
if(!resultMessage.toBoolean) throw new TestFailed(errorMessage)
|
||||
}
|
||||
def newRemote =
|
||||
{
|
||||
val builder = new java.lang.ProcessBuilder("xsbt", "<" + server.port).directory(directory)
|
||||
val io = BasicIO(log, false).withInput(_.close())
|
||||
val p = Process(builder) run( io )
|
||||
Spawn { p.exitValue(); server.close() }
|
||||
try { receive("Remote sbt initialization failed") }
|
||||
catch { case e: java.net.SocketException => error("Remote sbt initialization failed") }
|
||||
p
|
||||
}
|
||||
// Process("java" :: "-classpath" :: classpath.map(_.getAbsolutePath).mkString(File.pathSeparator) :: "xsbt.boot.Boot" :: ( "<" + server.port) :: Nil) run log
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
package sbt.test
|
||||
|
||||
import Scripted._
|
||||
import FileUtilities.{classLocation, sbtJar, scalaCompilerJar, scalaLibraryJar, wrapNull}
|
||||
import FileUtilities.{sbtJar, scalaCompilerJar, scalaLibraryJar, wrapNull}
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
@@ -17,18 +17,10 @@ trait ScalaScripted extends BasicScalaProject with Scripted with MavenStyleScala
|
||||
override def testAction = testNoScripted dependsOn(scripted)
|
||||
|
||||
lazy val scriptedOnly = scriptedMethodTask(scriptedDependencies : _*)
|
||||
|
||||
override def scriptedClasspath = runClasspath +++ Path.lazyPathFinder { Path.fromFile(sbtJar) :: Nil }
|
||||
}
|
||||
trait SbtScripted extends ScalaScripted
|
||||
{
|
||||
override def scriptedDependencies = testCompile :: `package` :: Nil
|
||||
override def scriptedClasspath =
|
||||
Path.lazyPathFinder {
|
||||
val ivy = runClasspath.get.filter(_.asFile.getName.startsWith("ivy-")).toList
|
||||
val builtSbtJar = (outputPath / defaultJarName)
|
||||
builtSbtJar :: ivy
|
||||
}
|
||||
override def scriptedDependencies = publishLocal :: Nil
|
||||
}
|
||||
final case class ScriptedTest(group: String, name: String) extends NotNull
|
||||
{
|
||||
@@ -36,6 +28,11 @@ final case class ScriptedTest(group: String, name: String) extends NotNull
|
||||
}
|
||||
trait Scripted extends Project with MultiTaskProject
|
||||
{
|
||||
def scriptedCompatibility = CompatibilityLevel.Minimal
|
||||
def scriptedDefScala = scalaVersion.value.toString
|
||||
def scriptedSbt = projectVersion.value.toString
|
||||
def scriptedBufferLog = true
|
||||
|
||||
def sbtTests: Path
|
||||
def scriptedTask(dependencies: ManagedTask*) = dynamic(scriptedTests(listTests)) dependsOn(dependencies : _*)
|
||||
def scriptedMethodTask(dependencies: ManagedTask*) = multiTask(listTests.map(_.toString).toList) { includeFunction =>
|
||||
@@ -44,41 +41,21 @@ trait Scripted extends Project with MultiTaskProject
|
||||
def listTests = (new ListTests(sbtTests.asFile, include _, log)).listTests
|
||||
def scriptedTests(tests: Seq[ScriptedTest], dependencies: ManagedTask*) =
|
||||
{
|
||||
val localLogger = new LocalLogger(log)
|
||||
lazy val runner =
|
||||
{
|
||||
// load ScriptedTests using a ClassLoader that loads from the project classpath so that the version
|
||||
// of sbt being built is tested, not the one doing the building.
|
||||
val filtered = new FilteredLoader(ClassLoader.getSystemClassLoader, Seq("sbt.", "scala.", "ch.epfl.", "org.apache.", "org.jsch."))
|
||||
val loader = new URLClassLoader(_scriptedClasspath.toArray, filtered)
|
||||
val scriptedClass = Class.forName(ScriptedClassName, true, loader)
|
||||
val scriptedConstructor = scriptedClass.getConstructor(classOf[File], classOf[ClassLoader])
|
||||
val rawRunner = scriptedConstructor.newInstance(sbtTests.asFile, loader)
|
||||
rawRunner.asInstanceOf[{def scriptedTest(group: String, name: String, log: Reflected.Logger): String}]
|
||||
}
|
||||
val runner = new ScriptedTests(sbtTests.asFile, scriptedBufferLog, scriptedSbt, scriptedDefScala, scriptedCompatibility)
|
||||
|
||||
val startTask = task { None } named("scripted-test-start") dependsOn(dependencies : _*)
|
||||
def scriptedTest(test: ScriptedTest) =
|
||||
task { unwrapOption(runner.scriptedTest(test.group, test.name, localLogger)) } named test.toString dependsOn(startTask)
|
||||
task { runner.scriptedTest(test.group, test.name, log) } named test.toString dependsOn(startTask)
|
||||
val testTasks = tests.map(scriptedTest)
|
||||
task { None } named("scripted-test-complete") dependsOn(testTasks : _*)
|
||||
}
|
||||
private def unwrapOption[T](s: T): Option[T] = if(s == null) None else Some(s)
|
||||
/** The classpath to use for scripted tests. This ensures that the version of sbt being built is the one used for testing.*/
|
||||
private def _scriptedClasspath =
|
||||
{
|
||||
val buildClasspath = classLocation[Scripted]
|
||||
val scalaJars = List(scalaLibraryJar, scalaCompilerJar).map(_.toURI.toURL).toList
|
||||
buildClasspath :: scalaJars ::: scriptedClasspath.get.map(_.asURL).toList
|
||||
}
|
||||
def scriptedClasspath: PathFinder = Path.emptyPathFinder
|
||||
|
||||
def include(test: ScriptedTest) = true
|
||||
}
|
||||
import scala.collection.mutable
|
||||
private[test] object Scripted
|
||||
{
|
||||
val ScriptedClassName = "sbt.test.ScriptedTests"
|
||||
val SbtTestDirectoryName = "sbt-test"
|
||||
def list(directory: File, filter: java.io.FileFilter) = wrapNull(directory.listFiles(filter))
|
||||
}
|
||||
@@ -87,7 +64,6 @@ private[test] final class ListTests(baseDirectory: File, accept: ScriptedTest =>
|
||||
def filter = DirectoryFilter -- HiddenFileFilter
|
||||
def listTests: Seq[ScriptedTest] =
|
||||
{
|
||||
System.setProperty("sbt.scala.version", "")
|
||||
list(baseDirectory, filter) flatMap { group =>
|
||||
val groupName = group.getName
|
||||
listTests(group).map(ScriptedTest(groupName, _))
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package sbt.test
|
||||
|
||||
object Reflected
|
||||
{
|
||||
type Logger =
|
||||
{
|
||||
def enableTrace(flag: Boolean): Unit
|
||||
def traceEnabled: Boolean
|
||||
def getLevel: Int
|
||||
def setLevel(level: Int): Unit
|
||||
|
||||
def trace(t: F0[Throwable]): Unit
|
||||
def success(message: F0[String]): Unit
|
||||
def log(level: Int, message: F0[String]): Unit
|
||||
def control(event: Int, message: F0[String]): Unit
|
||||
}
|
||||
type F0[T] =
|
||||
{
|
||||
def apply(): T
|
||||
}
|
||||
}
|
||||
|
||||
final class LocalLogger(logger: Logger) extends NotNull
|
||||
{
|
||||
import Reflected.F0
|
||||
def enableTrace(flag: Boolean) = logger.enableTrace(flag)
|
||||
def traceEnabled = logger.traceEnabled
|
||||
def getLevel = logger.getLevel.id
|
||||
def setLevel(level: Int) = logger.setLevel(Level(level))
|
||||
|
||||
def trace(t: F0[Throwable]) = logger.trace(t())
|
||||
def success(message: F0[String]) = logger.success(message())
|
||||
def log(level: Int, message: F0[String]) = logger.log(Level(level), message())
|
||||
def control(event: Int, message: F0[String]) = logger.control(ControlEvent(event), message())
|
||||
}
|
||||
|
||||
final class RemoteLogger(logger: Reflected.Logger) extends Logger
|
||||
{
|
||||
private final class F0[T](s: => T) extends NotNull { def apply(): T = s }
|
||||
def getLevel: Level.Value = Level(logger.getLevel)
|
||||
def setLevel(newLevel: Level.Value) = logger.setLevel(newLevel.id)
|
||||
def enableTrace(flag: Boolean) = logger.enableTrace(flag)
|
||||
def traceEnabled = logger.traceEnabled
|
||||
|
||||
def trace(t: => Throwable) = logger.trace(new F0(t))
|
||||
def success(message: => String) = logger.success(new F0(message))
|
||||
def log(level: Level.Value, message: => String) = logger.log(level.id, new F0(message))
|
||||
def control(event: ControlEvent.Value, message: => String) = logger.control(event.id, new F0(message))
|
||||
def logAll(events: Seq[LogEvent]) = events.foreach(log)
|
||||
}
|
||||
@@ -5,59 +5,100 @@
|
||||
package sbt.test
|
||||
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
|
||||
final class ScriptedTests(testResources: Resources) extends NotNull
|
||||
import xsbt.IPC
|
||||
import xsbt.test.{CommentHandler, FileCommands, ScriptRunner, TestScriptParser}
|
||||
|
||||
final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, sbtVersion: String, defScalaVersion: String, level: CompatibilityLevel.Value) extends NotNull
|
||||
{
|
||||
def this(resourceBaseDirectory: File, additional: ClassLoader) = this(new Resources(resourceBaseDirectory, additional))
|
||||
def this(resourceBaseDirectory: File) = this(new Resources(resourceBaseDirectory))
|
||||
private val testResources = new Resources(resourceBaseDirectory)
|
||||
|
||||
val ScriptFilename = "test"
|
||||
import testResources._
|
||||
|
||||
private def printClass(c: Class[_]) = println(c.getName + " loader=" +c.getClassLoader + " location=" + FileUtilities.classLocationFile(c))
|
||||
|
||||
def scriptedTest(group: String, name: String, logger: Reflected.Logger): String =
|
||||
{
|
||||
val log = new RemoteLogger(logger)
|
||||
val result = readOnlyResourceDirectory(group, name).fold(err => Some(err), testDirectory => scriptedTest(testDirectory, log))
|
||||
translateOption(result)
|
||||
}
|
||||
private def scriptedTest(testDirectory: File, log: Logger): Option[String] =
|
||||
def scriptedTest(group: String, name: String, log: Logger): Option[String] =
|
||||
testResources.readWriteResourceDirectory(group, name, log) { testDirectory =>
|
||||
scriptedTest(group + " / " + name, testDirectory, log).toLeft(())
|
||||
}.left.toOption
|
||||
private def scriptedTest(label: String, testDirectory: File, log: Logger): Option[String] =
|
||||
IPC.pullServer( scriptedTest0(label, testDirectory, log) )
|
||||
private def scriptedTest0(label: String, testDirectory: File, log: Logger)(server: IPC.Server): Option[String] =
|
||||
{
|
||||
FillProperties(testDirectory, sbtVersion, defScalaVersion, level)
|
||||
val buffered = new BufferedLogger(log)
|
||||
//buffered.startRecording()
|
||||
val filtered = new FilterLogger(buffered)
|
||||
val parsedScript = (new TestScriptParser(testDirectory, filtered)).parse(new File(testDirectory, ScriptFilename))
|
||||
val result = parsedScript.right.flatMap(withProject(testDirectory, filtered))
|
||||
//result.left.foreach(x => buffered.playAll())
|
||||
//buffered.clearAll()
|
||||
result.left.toOption
|
||||
if(bufferLog)
|
||||
buffered.recordAll
|
||||
|
||||
def createParser() =
|
||||
{
|
||||
val fileHandler = new FileCommands(testDirectory)
|
||||
val sbtHandler = new SbtHandler(testDirectory, buffered, server)
|
||||
new TestScriptParser(Map('$' -> fileHandler, '>' -> sbtHandler, '#' -> CommentHandler))
|
||||
}
|
||||
def runTest() =
|
||||
{
|
||||
val run = new ScriptRunner
|
||||
val parser = createParser()
|
||||
run(parser.parse(new File(testDirectory, ScriptFilename)))
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
runTest()
|
||||
buffered.info("+ " + label)
|
||||
None
|
||||
}
|
||||
catch
|
||||
{
|
||||
case e: xsbt.test.TestException =>
|
||||
buffered.playAll()
|
||||
buffered.error("x " + label)
|
||||
if(e.getCause eq null)
|
||||
buffered.error(" " + e.getMessage)
|
||||
else
|
||||
e.printStackTrace
|
||||
Some(e.toString)
|
||||
case e: Exception =>
|
||||
buffered.playAll()
|
||||
buffered.error("x " + label)
|
||||
throw e
|
||||
}
|
||||
finally { buffered.clearAll() }
|
||||
}
|
||||
private[this] def translateOption[T >: Null](s: Option[T]): T = s match { case Some(t) => t; case None => null }
|
||||
}
|
||||
|
||||
// TODO: remove for sbt 0.5.3
|
||||
final class FilterLogger(delegate: Logger) extends BasicLogger
|
||||
object CompatibilityLevel extends Enumeration
|
||||
{
|
||||
def trace(t: => Throwable)
|
||||
{
|
||||
if(traceEnabled)
|
||||
delegate.trace(t)
|
||||
}
|
||||
def log(level: Level.Value, message: => String)
|
||||
{
|
||||
if(atLevel(level))
|
||||
delegate.log(level, message)
|
||||
}
|
||||
def success(message: => String)
|
||||
{
|
||||
if(atLevel(Level.Info))
|
||||
delegate.success(message)
|
||||
}
|
||||
def control(event: ControlEvent.Value, message: => String)
|
||||
{
|
||||
if(atLevel(Level.Info))
|
||||
delegate.control(event, message)
|
||||
}
|
||||
def logAll(events: Seq[LogEvent]): Unit = events.foreach(delegate.log)
|
||||
val Full, Basic, Minimal, Minimal27, Minimal28 = Value
|
||||
}
|
||||
object FillProperties
|
||||
{
|
||||
def apply(projectDirectory: File, sbtVersion: String, defScalaVersion: String, level: CompatibilityLevel.Value): Unit =
|
||||
{
|
||||
import xsbt.Paths._
|
||||
fill(projectDirectory / "project" / "build.properties", sbtVersion, defScalaVersion, getVersions(level))
|
||||
}
|
||||
def fill(properties: File, sbtVersion: String, defScalaVersion: String, buildScalaVersions: String)
|
||||
{
|
||||
val toAppend = extraProperties(sbtVersion, defScalaVersion, buildScalaVersions)
|
||||
xsbt.OpenResource.fileWriter(Charset.forName("ISO-8859-1"), true)(properties) { _.write(toAppend) }
|
||||
}
|
||||
def getVersions(level: CompatibilityLevel.Value) =
|
||||
{
|
||||
import CompatibilityLevel._
|
||||
level match
|
||||
{
|
||||
case Full => "2.7.2 2.7.3 2.7.5 2.7.7 2.8.0.Beta1-RC2 2.8.0-SNAPSHOT"
|
||||
case Basic => "2.7.7 2.7.2 2.8.0.Beta1-RC2"
|
||||
case Minimal => "2.7.7 2.8.0.Beta1-RC2"
|
||||
case Minimal27 => "2.7.7"
|
||||
case Minimal28 => "2.8.0.Beta1-RC2"
|
||||
}
|
||||
}
|
||||
def extraProperties(sbtVersion: String, defScalaVersion: String, buildScalaVersions: String) =
|
||||
<x>
|
||||
sbt.version={sbtVersion}
|
||||
def.scala.version={defScalaVersion}
|
||||
build.scala.versions={buildScalaVersions}
|
||||
</x>.text
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
|
||||
package sbt.test
|
||||
|
||||
import java.io.{BufferedReader, File, InputStreamReader}
|
||||
|
||||
/*
|
||||
statement*
|
||||
statement ::= ('$' | '>') word+ '[' word ']'
|
||||
word ::= [^ \[\]]+
|
||||
comment ::= '#' [^ \n\r]* ('\n' | '\r' | eof)
|
||||
*/
|
||||
import scala.util.parsing.combinator._
|
||||
import scala.util.parsing.input.Positional
|
||||
|
||||
import TestScriptParser._
|
||||
private class TestScriptParser(baseDirectory: File, log: Logger) extends RegexParsers with NotNull
|
||||
{
|
||||
type Statement = Project => Either[String, ReloadProject]
|
||||
type PStatement = Statement with Positional
|
||||
|
||||
private def evaluateList(list: List[PStatement])(p: Project): WithProjectResult[Unit] =
|
||||
list match
|
||||
{
|
||||
case Nil => ValueResult(())
|
||||
case head :: tail =>
|
||||
head(p) match
|
||||
{
|
||||
case Left(msg) => new ErrorResult(msg)
|
||||
case Right(reload) => ContinueResult(p =>evaluateList(tail)(p), reload)
|
||||
}
|
||||
}
|
||||
|
||||
def script: Parser[Project => WithProjectResult[Unit]] = rep1(space ~> statement <~ space) ^^ evaluateList
|
||||
def statement: Parser[PStatement] =
|
||||
positioned
|
||||
{
|
||||
(StartRegex ~! rep1(word) ~! "[" ~! word ~! "]") ^^
|
||||
{
|
||||
case start ~ command ~ open ~ result ~ close =>
|
||||
val successExpected = result.toLowerCase == SuccessLiteral.toLowerCase
|
||||
new Statement with Positional
|
||||
{ selfPositional =>
|
||||
def apply(p: Project) =
|
||||
{
|
||||
val result =
|
||||
try
|
||||
{
|
||||
start match
|
||||
{
|
||||
case CommandStart => evaluateCommand(command, successExpected, selfPositional)(p)
|
||||
case ActionStart => evaluateAction(command, successExpected)(p).toLeft(NoReload)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
case e: Exception =>
|
||||
log.trace(e)
|
||||
Left(e.toString)
|
||||
}
|
||||
result.left.map(message => linePrefix(this) + message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private def linePrefix(p: Positional) = "{line " + p.pos.line + "} "
|
||||
def space = """(\s+|(\#[^\n\r]*))*""".r
|
||||
def word: Parser[String] = ("\'" ~> "[^'\n\r]*".r <~ "\'") | ("\"" ~> "[^\"\n\r]*".r <~ "\"") | WordRegex
|
||||
def parse(scriptFile: File): Either[String, Project => WithProjectResult[Unit]] =
|
||||
{
|
||||
def parseReader(reader: java.io.Reader) =
|
||||
parseAll(script, reader) match
|
||||
{
|
||||
case Success(result, next) => Right(result)
|
||||
case err: NoSuccess =>
|
||||
{
|
||||
val pos = err.next.pos
|
||||
Left("Could not parse test script '" + scriptFile.getCanonicalPath +
|
||||
"' (" + pos.line + "," + pos.column + "): " + err.msg)
|
||||
}
|
||||
}
|
||||
FileUtilities.readValue(scriptFile, log)(parseReader)
|
||||
}
|
||||
|
||||
private def scriptError(message: String): Some[String] = Some("Test script error: " + message)
|
||||
private def wrongArguments(commandName: String, args: List[String]): Some[String] =
|
||||
scriptError("Command '" + commandName + "' does not accept arguments (found '" + spacedString(args) + "').")
|
||||
private def wrongArguments(commandName: String, requiredArgs: String, args: List[String]): Some[String] =
|
||||
scriptError("Wrong number of arguments to " + commandName + " command. " + requiredArgs + " required, found: '" + spacedString(args) + "'.")
|
||||
private def evaluateCommand(command: List[String], successExpected: Boolean, position: Positional)(project: Project): Either[String, ReloadProject] =
|
||||
{
|
||||
command match
|
||||
{
|
||||
case "reload" :: Nil => Right(if(successExpected) new ReloadSuccessExpected(linePrefix(position)) else ReloadErrorExpected)
|
||||
case x => evaluateCommandNoReload(x, successExpected)(project).toLeft(NoReload)
|
||||
}
|
||||
}
|
||||
private def evaluateCommandNoReload(command: List[String], successExpected: Boolean)(project: Project): Option[String] =
|
||||
{
|
||||
evaluate(successExpected, "Command '" + command.firstOption.getOrElse("") + "'", project)
|
||||
{
|
||||
command match
|
||||
{
|
||||
case Nil => scriptError("No command specified.")
|
||||
case "touch" :: paths => touch(paths, project)
|
||||
case "delete" :: paths => delete(paths, project)
|
||||
case "mkdir" :: paths => makeDirectories(paths, project)
|
||||
case "copy-file" :: from :: to :: Nil => copyFile(from, to, project)
|
||||
case "copy-file" :: args => wrongArguments("copy-file", "Two paths", args)
|
||||
case "sync" :: from :: to :: Nil => sync(from, to, project)
|
||||
case "sync" :: args => wrongArguments("sync", "Two directory paths", args)
|
||||
case "copy" :: paths => copy(paths, project)
|
||||
case "exists" :: paths => exists(paths, project)
|
||||
case "absent" :: paths => absent(paths, project)
|
||||
case "pause" :: Nil => readLine("Press enter to continue. "); println(); None
|
||||
case "pause" :: args => wrongArguments("pause", args)
|
||||
case "newer" :: a :: b :: Nil => newer(a, b, project)
|
||||
case "newer" :: args => wrongArguments("newer", "Two paths", args)
|
||||
case "sleep" :: time :: Nil => trap("Error while sleeping:") { Thread.sleep(time.toLong) }
|
||||
case "sleep" :: args => wrongArguments("sleep", "Time in milliseconds", args)
|
||||
case "exec" :: command :: args => execute(command, args, project)
|
||||
case "exec" :: other => wrongArguments("exec", "Command and arguments", other)
|
||||
case "reload" :: args => wrongArguments("reload", args)
|
||||
case unknown :: arguments => scriptError("Unknown command " + unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
private def foreachBufferedLogger(project: Project)(f: BufferedLogger => Unit)
|
||||
{
|
||||
project.topologicalSort.foreach(p => p.log match { case buffered: BufferedLogger => f(buffered); case _ => () })
|
||||
}
|
||||
private def evaluate(successExpected: Boolean, label: String, project: Project)(body: => Option[String]): Option[String] =
|
||||
{
|
||||
def startRecordingLog() { foreachBufferedLogger(project)(_.startRecording()) }
|
||||
def clearLog() { foreachBufferedLogger(project)(_.clearAll()) }
|
||||
def playLog(message: String) =
|
||||
{
|
||||
foreachBufferedLogger(project)(_.playAll())
|
||||
Some(message)
|
||||
}
|
||||
|
||||
startRecordingLog()
|
||||
try
|
||||
{
|
||||
val result = body
|
||||
if(result.isEmpty == successExpected)
|
||||
None
|
||||
else
|
||||
{
|
||||
val mainMessage = result.map("failed (expected success): " + _).getOrElse("succeeded (expected failure).")
|
||||
playLog(label + " " + mainMessage)
|
||||
}
|
||||
}
|
||||
finally { clearLog() }
|
||||
}
|
||||
private def evaluateAction(action: List[String], successExpected: Boolean)(project: Project): Option[String] =
|
||||
{
|
||||
def actionToString = action.mkString(" ")
|
||||
action match
|
||||
{
|
||||
case Nil => scriptError("No action specified.")
|
||||
case head :: Nil if project.taskNames.toSeq.contains(head)=>
|
||||
evaluate(successExpected, "Action '" + actionToString + "'", project)(project.act(head))
|
||||
case head :: tail =>
|
||||
evaluate(successExpected, "Method '" + actionToString + "'", project)(project.call(head, tail.toArray))
|
||||
}
|
||||
}
|
||||
private def spacedString[T](l: Seq[T]) = l.mkString(" ")
|
||||
private def wrap(result: Option[String]) = result.flatMap(scriptError)
|
||||
private def trap(errorPrefix: String)(action: => Unit) = wrap( Control.trapUnit(errorPrefix, log) { action; None } )
|
||||
|
||||
private def fromStrings(paths: List[String], project: Project) = paths.map(path => fromString(path, project))
|
||||
private def fromString(path: String, project: Project) = Path.fromString(project.info.projectPath, path)
|
||||
private def touch(paths: List[String], project: Project) =
|
||||
if(paths.isEmpty)
|
||||
scriptError("No paths specified for touch command.")
|
||||
else
|
||||
wrap(lazyFold(paths) { path => FileUtilities.touch(fromString(path, project), log) })
|
||||
|
||||
private def delete(paths: List[String], project: Project) =
|
||||
if(paths.isEmpty)
|
||||
scriptError("No paths specified for delete command.")
|
||||
else
|
||||
wrap(FileUtilities.clean(fromStrings(paths, project), true, log))
|
||||
private def sync(from: String, to: String, project: Project) =
|
||||
wrap(FileUtilities.sync(fromString(from, project), fromString(to, project), log))
|
||||
private def copyFile(from: String, to: String, project: Project) =
|
||||
wrap(FileUtilities.copyFile(fromString(from, project), fromString(to, project), log))
|
||||
private def copy(paths: List[String], project: Project) =
|
||||
paths match
|
||||
{
|
||||
case Nil => scriptError("No paths specified for copy command.")
|
||||
case path :: Nil => scriptError("No destination specified for copy command.")
|
||||
case _ =>
|
||||
val mapped = fromStrings(paths, project).toArray
|
||||
val last = mapped.length - 1
|
||||
wrap(FileUtilities.copy(mapped.take(last), mapped(last), log).left.toOption)
|
||||
}
|
||||
private def makeDirectories(paths: List[String], project: Project) =
|
||||
fromStrings(paths, project) match
|
||||
{
|
||||
case Nil => scriptError("No paths specified for mkdir command.")
|
||||
case p => FileUtilities.createDirectories(p, project.log)
|
||||
}
|
||||
private def newer(a: String, b: String, project: Project) =
|
||||
trap("Error testing if '" + a + "' is newer than '" + b + "'")
|
||||
{
|
||||
val pathA = fromString(a, project)
|
||||
val pathB = fromString(b, project)
|
||||
pathA.exists && (!pathB.exists || pathA.lastModified > pathB.lastModified)
|
||||
}
|
||||
private def exists(paths: List[String], project: Project) =
|
||||
fromStrings(paths, project).filter(!_.exists) match
|
||||
{
|
||||
case Nil => None
|
||||
case x => Some("File(s) did not exist: " + x.mkString("[ ", " , ", " ]"))
|
||||
}
|
||||
private def absent(paths: List[String], project: Project) =
|
||||
fromStrings(paths, project).filter(_.exists) match
|
||||
{
|
||||
case Nil => None
|
||||
case x => Some("File(s) existed: " + x.mkString("[ ", " , ", " ]"))
|
||||
}
|
||||
private def execute(command: String, args: List[String], project: Project) =
|
||||
{
|
||||
if(command.trim.isEmpty)
|
||||
Some("Command was empty.")
|
||||
else
|
||||
{
|
||||
Control.trapUnit("Error running command: ", project.log)
|
||||
{
|
||||
val builder = new java.lang.ProcessBuilder((command :: args).toArray : _*).directory(project.info.projectDirectory)
|
||||
val exitValue = Process(builder) ! log
|
||||
if(exitValue == 0)
|
||||
None
|
||||
else
|
||||
Some("Nonzero exit value (" + exitValue + ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private object TestScriptParser
|
||||
{
|
||||
val SuccessLiteral = "success"
|
||||
val Failure = "error"
|
||||
val CommandStart = "$"
|
||||
val ActionStart = ">"
|
||||
val WordRegex = """[^ \[\]\s'\"][^ \[\]\s]*""".r
|
||||
val StartRegex = ("[" + CommandStart + ActionStart + "]").r
|
||||
|
||||
final def lazyFold[T](list: List[T])(f: T => Option[String]): Option[String] =
|
||||
list match
|
||||
{
|
||||
case Nil => None
|
||||
case head :: tail =>
|
||||
f(head) match
|
||||
{
|
||||
case None => lazyFold(tail)(f)
|
||||
case x => x
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user