mirror of https://github.com/sbt/sbt.git
Merge pull request #118 from dwijnand/scalafmt
Add, configure & enable Scalafmt
This commit is contained in:
commit
1527acc6c1
|
|
@ -0,0 +1,10 @@
|
|||
maxColumn = 100
|
||||
project.git = true
|
||||
project.excludeFilters = [ /sbt-test/, /input_sources/, /contraband-scala/ ]
|
||||
|
||||
# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style.
|
||||
# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala
|
||||
docstrings = JavaDoc
|
||||
|
||||
# This also seems more idiomatic to include whitespace in import x.{ yyy }
|
||||
spaces.inImportCurlyBraces = true
|
||||
|
|
@ -6,7 +6,7 @@ scala:
|
|||
- 2.12.3
|
||||
|
||||
script:
|
||||
- sbt -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M "++$TRAVIS_SCALA_VERSION" mimaReportBinaryIssues test
|
||||
- sbt -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M "++$TRAVIS_SCALA_VERSION" mimaReportBinaryIssues scalafmt::test test:scalafmt::test sbt:scalafmt::test test
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
|
|
|||
149
build.sbt
149
build.sbt
|
|
@ -3,7 +3,7 @@ import Util._
|
|||
//import com.typesafe.tools.mima.core._, ProblemFilters._
|
||||
|
||||
def baseVersion = "1.0.0-SNAPSHOT"
|
||||
def internalPath = file("internal")
|
||||
def internalPath = file("internal")
|
||||
|
||||
def commonSettings: Seq[Setting[_]] = Seq(
|
||||
scalaVersion := scala212,
|
||||
|
|
@ -18,42 +18,53 @@ def commonSettings: Seq[Setting[_]] = Seq(
|
|||
scalacOptions := {
|
||||
val old = scalacOptions.value
|
||||
scalaVersion.value match {
|
||||
case sv if sv.startsWith("2.10") => old diff List("-Xfuture", "-Ywarn-unused", "-Ywarn-unused-import")
|
||||
case sv if sv.startsWith("2.10") =>
|
||||
old diff List("-Xfuture", "-Ywarn-unused", "-Ywarn-unused-import")
|
||||
case sv if sv.startsWith("2.11") => old ++ List("-Ywarn-unused", "-Ywarn-unused-import")
|
||||
case _ => old ++ List("-Ywarn-unused", "-Ywarn-unused-import", "-YdisableFlatCpCaching")
|
||||
}
|
||||
},
|
||||
scalacOptions in console in Compile -= "-Ywarn-unused-import",
|
||||
scalacOptions in console in Test -= "-Ywarn-unused-import",
|
||||
scalacOptions in console in Test -= "-Ywarn-unused-import",
|
||||
publishArtifact in Compile := true,
|
||||
publishArtifact in Test := false
|
||||
)
|
||||
|
||||
val mimaSettings = Def settings (
|
||||
mimaPreviousArtifacts := Set(organization.value % moduleName.value % "1.0.0-RC3"
|
||||
cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled)
|
||||
mimaPreviousArtifacts := Set(
|
||||
organization.value % moduleName.value % "1.0.0-RC3"
|
||||
cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled)
|
||||
)
|
||||
)
|
||||
|
||||
lazy val utilRoot: Project = (project in file(".")).
|
||||
aggregate(
|
||||
utilInterface, utilControl, utilPosition,
|
||||
utilLogging, utilRelation, utilCache, utilTracking, utilTesting,
|
||||
lazy val utilRoot: Project = (project in file("."))
|
||||
.aggregate(
|
||||
utilInterface,
|
||||
utilControl,
|
||||
utilPosition,
|
||||
utilLogging,
|
||||
utilRelation,
|
||||
utilCache,
|
||||
utilTracking,
|
||||
utilTesting,
|
||||
utilScripted
|
||||
).
|
||||
settings(
|
||||
inThisBuild(Seq(
|
||||
git.baseVersion := baseVersion,
|
||||
version := {
|
||||
val v = version.value
|
||||
if (v contains "SNAPSHOT") git.baseVersion.value
|
||||
else v
|
||||
},
|
||||
bintrayPackage := "util",
|
||||
homepage := Some(url("https://github.com/sbt/util")),
|
||||
description := "Util module for sbt",
|
||||
scmInfo := Some(ScmInfo(url("https://github.com/sbt/util"), "git@github.com:sbt/util.git"))
|
||||
)),
|
||||
)
|
||||
.settings(
|
||||
inThisBuild(
|
||||
Seq(
|
||||
git.baseVersion := baseVersion,
|
||||
version := {
|
||||
val v = version.value
|
||||
if (v contains "SNAPSHOT") git.baseVersion.value
|
||||
else v
|
||||
},
|
||||
bintrayPackage := "util",
|
||||
homepage := Some(url("https://github.com/sbt/util")),
|
||||
description := "Util module for sbt",
|
||||
scmInfo := Some(ScmInfo(url("https://github.com/sbt/util"), "git@github.com:sbt/util.git")),
|
||||
scalafmtOnCompile := true,
|
||||
scalafmtVersion := "1.1.0",
|
||||
)),
|
||||
commonSettings,
|
||||
name := "Util Root",
|
||||
publish := {},
|
||||
|
|
@ -67,21 +78,19 @@ lazy val utilRoot: Project = (project in file(".")).
|
|||
// defines Java structures used across Scala versions, such as the API structures and relationships extracted by
|
||||
// the analysis compiler phases and passed back to sbt. The API structures are defined in a simple
|
||||
// format from which Java sources are generated by the datatype generator Projproject
|
||||
lazy val utilInterface = (project in internalPath / "util-interface").
|
||||
settings(
|
||||
commonSettings,
|
||||
javaOnlySettings,
|
||||
name := "Util Interface",
|
||||
exportJars := true,
|
||||
mimaSettings,
|
||||
)
|
||||
lazy val utilInterface = (project in internalPath / "util-interface").settings(
|
||||
commonSettings,
|
||||
javaOnlySettings,
|
||||
name := "Util Interface",
|
||||
exportJars := true,
|
||||
mimaSettings,
|
||||
)
|
||||
|
||||
lazy val utilControl = (project in internalPath / "util-control").
|
||||
settings(
|
||||
commonSettings,
|
||||
name := "Util Control",
|
||||
mimaSettings,
|
||||
)
|
||||
lazy val utilControl = (project in internalPath / "util-control").settings(
|
||||
commonSettings,
|
||||
name := "Util Control",
|
||||
mimaSettings,
|
||||
)
|
||||
|
||||
val utilPosition = (project in file("internal") / "util-position").settings(
|
||||
commonSettings,
|
||||
|
|
@ -90,14 +99,15 @@ val utilPosition = (project in file("internal") / "util-position").settings(
|
|||
)
|
||||
|
||||
// logging
|
||||
lazy val utilLogging = (project in internalPath / "util-logging").
|
||||
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
|
||||
dependsOn(utilInterface, utilTesting % Test).
|
||||
settings(
|
||||
lazy val utilLogging = (project in internalPath / "util-logging")
|
||||
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
||||
.dependsOn(utilInterface, utilTesting % Test)
|
||||
.settings(
|
||||
commonSettings,
|
||||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||
name := "Util Logging",
|
||||
libraryDependencies ++= Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson.value, scalaReflect.value),
|
||||
libraryDependencies ++=
|
||||
Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson.value, scalaReflect.value),
|
||||
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
|
||||
contrabandFormatsForType in generateContrabands in Compile := { tpe =>
|
||||
val old = (contrabandFormatsForType in generateContrabands in Compile).value
|
||||
|
|
@ -109,68 +119,69 @@ lazy val utilLogging = (project in internalPath / "util-logging").
|
|||
)
|
||||
|
||||
// Relation
|
||||
lazy val utilRelation = (project in internalPath / "util-relation").
|
||||
dependsOn(utilTesting % Test).
|
||||
settings(
|
||||
lazy val utilRelation = (project in internalPath / "util-relation")
|
||||
.dependsOn(utilTesting % Test)
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "Util Relation",
|
||||
mimaSettings,
|
||||
)
|
||||
|
||||
// Persisted caching based on sjson-new
|
||||
lazy val utilCache = (project in file("util-cache")).
|
||||
dependsOn(utilTesting % Test).
|
||||
settings(
|
||||
lazy val utilCache = (project in file("util-cache"))
|
||||
.dependsOn(utilTesting % Test)
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "Util Cache",
|
||||
libraryDependencies ++= Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value),
|
||||
libraryDependencies ++=
|
||||
Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value),
|
||||
mimaSettings,
|
||||
).
|
||||
configure(addSbtIO)
|
||||
)
|
||||
.configure(addSbtIO)
|
||||
|
||||
// Builds on cache to provide caching for filesystem-related operations
|
||||
lazy val utilTracking = (project in file("util-tracking")).
|
||||
dependsOn(utilCache, utilTesting % Test).
|
||||
settings(
|
||||
lazy val utilTracking = (project in file("util-tracking"))
|
||||
.dependsOn(utilCache, utilTesting % Test)
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "Util Tracking",
|
||||
mimaSettings,
|
||||
).
|
||||
configure(addSbtIO)
|
||||
)
|
||||
.configure(addSbtIO)
|
||||
|
||||
// Internal utility for testing
|
||||
lazy val utilTesting = (project in internalPath / "util-testing").
|
||||
settings(
|
||||
lazy val utilTesting = (project in internalPath / "util-testing")
|
||||
.settings(
|
||||
commonSettings,
|
||||
crossScalaVersions := Seq(scala210, scala211, scala212),
|
||||
name := "Util Testing",
|
||||
libraryDependencies ++= Seq(scalaCheck, scalatest),
|
||||
mimaSettings,
|
||||
).
|
||||
configure(addSbtIO)
|
||||
)
|
||||
.configure(addSbtIO)
|
||||
|
||||
lazy val utilScripted = (project in internalPath / "util-scripted").
|
||||
dependsOn(utilLogging, utilInterface).
|
||||
settings(
|
||||
lazy val utilScripted = (project in internalPath / "util-scripted")
|
||||
.dependsOn(utilLogging, utilInterface)
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "Util Scripted",
|
||||
libraryDependencies ++= {
|
||||
scalaVersion.value match {
|
||||
case sv if sv startsWith "2.11" => Seq(parserCombinator211)
|
||||
case sv if sv startsWith "2.12" => Seq(parserCombinator211)
|
||||
case _ => Seq()
|
||||
case _ => Seq()
|
||||
}
|
||||
},
|
||||
mimaSettings,
|
||||
).
|
||||
configure(addSbtIO)
|
||||
)
|
||||
.configure(addSbtIO)
|
||||
|
||||
def customCommands: Seq[Setting[_]] = Seq(
|
||||
commands += Command.command("release") { state =>
|
||||
// "clean" ::
|
||||
"+compile" ::
|
||||
"+publishSigned" ::
|
||||
"reload" ::
|
||||
state
|
||||
"+publishSigned" ::
|
||||
"reload" ::
|
||||
state
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,23 +7,20 @@ import java.io.IOException
|
|||
|
||||
object ErrorHandling {
|
||||
def translate[T](msg: => String)(f: => T) =
|
||||
try { f }
|
||||
catch {
|
||||
try { f } catch {
|
||||
case e: IOException => throw new TranslatedIOException(msg + e.toString, e)
|
||||
case e: Exception => throw new TranslatedException(msg + e.toString, e)
|
||||
}
|
||||
|
||||
def wideConvert[T](f: => T): Either[Throwable, T] =
|
||||
try { Right(f) }
|
||||
catch {
|
||||
try { Right(f) } catch {
|
||||
case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
|
||||
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
|
||||
case x: Throwable => Left(x)
|
||||
}
|
||||
|
||||
def convert[T](f: => T): Either[Exception, T] =
|
||||
try { Right(f) }
|
||||
catch { case e: Exception => Left(e) }
|
||||
try { Right(f) } catch { case e: Exception => Left(e) }
|
||||
|
||||
def reducedToString(e: Throwable): String =
|
||||
if (e.getClass == classOf[RuntimeException]) {
|
||||
|
|
@ -32,7 +29,11 @@ object ErrorHandling {
|
|||
} else
|
||||
e.toString
|
||||
}
|
||||
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable) extends RuntimeException(msg, cause) {
|
||||
|
||||
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable)
|
||||
extends RuntimeException(msg, cause) {
|
||||
override def toString = msg
|
||||
}
|
||||
final class TranslatedIOException private[sbt] (msg: String, cause: IOException) extends TranslatedException(msg, cause)
|
||||
|
||||
final class TranslatedIOException private[sbt] (msg: String, cause: IOException)
|
||||
extends TranslatedException(msg, cause)
|
||||
|
|
|
|||
|
|
@ -5,16 +5,20 @@ package sbt.internal.util
|
|||
|
||||
/** Defines a function to call as sbt exits.*/
|
||||
trait ExitHook {
|
||||
|
||||
/** Subclasses should implement this method, which is called when this hook is executed. */
|
||||
def runBeforeExiting(): Unit
|
||||
|
||||
}
|
||||
|
||||
object ExitHook {
|
||||
def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f }
|
||||
}
|
||||
|
||||
object ExitHooks {
|
||||
|
||||
/** Calls each registered exit hook, trapping any exceptions so that each hook is given a chance to run. */
|
||||
def runExitHooks(exitHooks: Seq[ExitHook]): Seq[Throwable] =
|
||||
exitHooks.flatMap(hook =>
|
||||
ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
|
||||
}
|
||||
exitHooks.flatMap(hook => ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,41 +13,43 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||
object BufferedAppender {
|
||||
def generateName: String =
|
||||
"buffered-" + generateId.incrementAndGet
|
||||
|
||||
private val generateId: AtomicInteger = new AtomicInteger
|
||||
|
||||
def apply(delegate: Appender): BufferedAppender =
|
||||
apply(generateName, delegate)
|
||||
def apply(name: String, delegate: Appender): BufferedAppender =
|
||||
{
|
||||
val appender = new BufferedAppender(name, delegate)
|
||||
appender.start
|
||||
appender
|
||||
}
|
||||
|
||||
def apply(name: String, delegate: Appender): BufferedAppender = {
|
||||
val appender = new BufferedAppender(name, delegate)
|
||||
appender.start
|
||||
appender
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Am appender that can buffer the logging done on it and then can flush the buffer
|
||||
* An appender that can buffer the logging done on it and then can flush the buffer
|
||||
* to the delegate appender provided in the constructor. Use 'record()' to
|
||||
* start buffering and then 'play' to flush the buffer to the backing appender.
|
||||
* The logging level set at the time a message is originally logged is used, not
|
||||
* the level at the time 'play' is called.
|
||||
*/
|
||||
class BufferedAppender private[BufferedAppender] (name: String, delegate: Appender) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
|
||||
class BufferedAppender private[BufferedAppender] (name: String, delegate: Appender)
|
||||
extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
|
||||
|
||||
private[this] val buffer = new ListBuffer[XLogEvent]
|
||||
private[this] var recording = false
|
||||
|
||||
def append(event: XLogEvent): Unit =
|
||||
{
|
||||
if (recording) {
|
||||
buffer += event.toImmutable
|
||||
} else delegate.append(event)
|
||||
}
|
||||
def append(event: XLogEvent): Unit = {
|
||||
if (recording) {
|
||||
buffer += event.toImmutable
|
||||
} else delegate.append(event)
|
||||
}
|
||||
|
||||
/** Enables buffering. */
|
||||
def record() = synchronized { recording = true }
|
||||
def buffer[T](f: => T): T = {
|
||||
record()
|
||||
try { f }
|
||||
finally { stopQuietly() }
|
||||
try { f } finally { stopQuietly() }
|
||||
}
|
||||
def bufferQuietly[T](f: => T): T = {
|
||||
record()
|
||||
|
|
@ -70,10 +72,13 @@ class BufferedAppender private[BufferedAppender] (name: String, delegate: Append
|
|||
}
|
||||
buffer.clear()
|
||||
}
|
||||
|
||||
/** Clears buffered events and disables buffering. */
|
||||
def clearBuffer(): Unit = synchronized { buffer.clear(); recording = false }
|
||||
|
||||
/** Plays buffered events and disables buffering. */
|
||||
def stopBuffer(): Unit = synchronized { play(); clearBuffer() }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -93,8 +98,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
def record() = synchronized { recording = true }
|
||||
def buffer[T](f: => T): T = {
|
||||
record()
|
||||
try { f }
|
||||
finally { stopQuietly() }
|
||||
try { f } finally { stopQuietly() }
|
||||
}
|
||||
def bufferQuietly[T](f: => T): T = {
|
||||
record()
|
||||
|
|
@ -111,8 +115,10 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
* so that the messages are written consecutively. The buffer is cleared in the process.
|
||||
*/
|
||||
def play(): Unit = synchronized { delegate.logAll(buffer.toList); buffer.clear() }
|
||||
|
||||
/** Clears buffered events and disables buffering. */
|
||||
def clear(): Unit = synchronized { buffer.clear(); recording = false }
|
||||
|
||||
/** Plays buffered events and disables buffering. */
|
||||
def stop(): Unit = synchronized { play(); clear() }
|
||||
|
||||
|
|
@ -127,6 +133,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
delegate.setLevel(newLevel)
|
||||
()
|
||||
}
|
||||
|
||||
override def setSuccessEnabled(flag: Boolean): Unit = synchronized {
|
||||
super.setSuccessEnabled(flag)
|
||||
if (recording)
|
||||
|
|
@ -135,6 +142,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
delegate.setSuccessEnabled(flag)
|
||||
()
|
||||
}
|
||||
|
||||
override def setTrace(level: Int): Unit = synchronized {
|
||||
super.setTrace(level)
|
||||
if (recording)
|
||||
|
|
@ -144,12 +152,14 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
()
|
||||
}
|
||||
|
||||
def trace(t: => Throwable): Unit =
|
||||
doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
|
||||
def trace(t: => Throwable): Unit = doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
|
||||
|
||||
def success(message: => String): Unit =
|
||||
doBufferable(Level.Info, new Success(message), _.success(message))
|
||||
|
||||
def log(level: Level.Value, message: => String): Unit =
|
||||
doBufferable(level, new Log(level, message), _.log(level, message))
|
||||
|
||||
def logAll(events: Seq[LogEvent]): Unit = synchronized {
|
||||
if (recording)
|
||||
buffer ++= events
|
||||
|
|
@ -157,11 +167,22 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
delegate.logAll(events)
|
||||
()
|
||||
}
|
||||
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
|
||||
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
|
||||
|
||||
private def doBufferable(
|
||||
level: Level.Value,
|
||||
appendIfBuffered: => LogEvent,
|
||||
doUnbuffered: AbstractLogger => Unit
|
||||
): Unit =
|
||||
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
|
||||
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit = synchronized {
|
||||
|
||||
private def doBufferableIf(
|
||||
condition: => Boolean,
|
||||
appendIfBuffered: => LogEvent,
|
||||
doUnbuffered: AbstractLogger => Unit
|
||||
): Unit = synchronized {
|
||||
if (condition) {
|
||||
if (recording)
|
||||
buffer += appendIfBuffered
|
||||
|
|
|
|||
|
|
@ -51,10 +51,13 @@ object ConsoleLogger {
|
|||
* @param suppressedMessage How to show suppressed stack traces.
|
||||
* @return A new `ConsoleLogger` that logs to `out`.
|
||||
*/
|
||||
def apply(out: ConsoleOut = ConsoleOut.systemOut,
|
||||
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
|
||||
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger =
|
||||
def apply(
|
||||
out: ConsoleOut = ConsoleOut.systemOut,
|
||||
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
|
||||
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String] =
|
||||
ConsoleAppender.noSuppressedMessage
|
||||
): ConsoleLogger =
|
||||
new ConsoleLogger(out, ansiCodesSupported, useFormat, suppressedMessage)
|
||||
}
|
||||
|
||||
|
|
@ -62,10 +65,12 @@ object ConsoleLogger {
|
|||
* A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
*/
|
||||
class ConsoleLogger private[ConsoleLogger] (out: ConsoleOut,
|
||||
override val ansiCodesSupported: Boolean,
|
||||
useFormat: Boolean,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
|
||||
class ConsoleLogger private[ConsoleLogger] (
|
||||
out: ConsoleOut,
|
||||
override val ansiCodesSupported: Boolean,
|
||||
useFormat: Boolean,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
) extends BasicLogger {
|
||||
|
||||
private[sbt] val appender: ConsoleAppender =
|
||||
ConsoleAppender(generateName(), out, ansiCodesSupported, useFormat, suppressedMessage)
|
||||
|
|
@ -160,7 +165,11 @@ object ConsoleAppender {
|
|||
* @param suppressedMessage How to handle stack traces.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(name: String, out: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender =
|
||||
def apply(
|
||||
name: String,
|
||||
out: ConsoleOut,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
): ConsoleAppender =
|
||||
apply(name, out, formatEnabledInEnv, formatEnabledInEnv, suppressedMessage)
|
||||
|
||||
/**
|
||||
|
|
@ -184,14 +193,16 @@ object ConsoleAppender {
|
|||
* formatting.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(name: String,
|
||||
out: ConsoleOut,
|
||||
ansiCodesSupported: Boolean,
|
||||
useFormat: Boolean,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = {
|
||||
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
|
||||
appender.start
|
||||
appender
|
||||
def apply(
|
||||
name: String,
|
||||
out: ConsoleOut,
|
||||
ansiCodesSupported: Boolean,
|
||||
useFormat: Boolean,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
): ConsoleAppender = {
|
||||
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
|
||||
appender.start
|
||||
appender
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,7 +253,9 @@ object ConsoleAppender {
|
|||
// this results in a linkage error as detected below. The detection is likely jvm specific, but the priority
|
||||
// is avoiding mistakenly identifying something as a launcher incompatibility when it is not
|
||||
case e: IncompatibleClassChangeError if e.getMessage == jline1to2CompatMsg =>
|
||||
throw new IncompatibleClassChangeError("JLine incompatibility detected. Check that the sbt launcher is version 0.13.x or later.")
|
||||
throw new IncompatibleClassChangeError(
|
||||
"JLine incompatibility detected. Check that the sbt launcher is version 0.13.x or later."
|
||||
)
|
||||
}
|
||||
|
||||
private[this] def os = System.getProperty("os.name")
|
||||
|
|
@ -262,11 +275,11 @@ object ConsoleAppender {
|
|||
* This logger is not thread-safe.
|
||||
*/
|
||||
class ConsoleAppender private[ConsoleAppender] (
|
||||
name: String,
|
||||
out: ConsoleOut,
|
||||
ansiCodesSupported: Boolean,
|
||||
useFormat: Boolean,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
name: String,
|
||||
out: ConsoleOut,
|
||||
ansiCodesSupported: Boolean,
|
||||
useFormat: Boolean,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
) extends AbstractAppender(name, null, LogExchange.dummyLayout, true) {
|
||||
import scala.Console.{ BLUE, GREEN, RED, YELLOW }
|
||||
|
||||
|
|
@ -275,12 +288,12 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
else ""
|
||||
}
|
||||
|
||||
private val SUCCESS_LABEL_COLOR = GREEN
|
||||
private val SUCCESS_LABEL_COLOR = GREEN
|
||||
private val SUCCESS_MESSAGE_COLOR = reset
|
||||
private val NO_COLOR = reset
|
||||
private val NO_COLOR = reset
|
||||
|
||||
private var traceEnabledVar: Int = Int.MaxValue
|
||||
|
||||
|
||||
def setTrace(level: Int): Unit = synchronized { traceEnabledVar = level }
|
||||
|
||||
/**
|
||||
|
|
@ -307,9 +320,11 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
out.lockObject.synchronized {
|
||||
if (traceLevel >= 0)
|
||||
write(StackTrace.trimmed(t, traceLevel))
|
||||
if (traceLevel <= 2)
|
||||
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)))
|
||||
if (traceLevel <= 2) {
|
||||
val ctx = new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)
|
||||
for (msg <- suppressedMessage(ctx))
|
||||
appendLog(NO_COLOR, "trace", NO_COLOR, msg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -365,10 +380,16 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
* @param messageColor The color to use to format the message.
|
||||
* @param message The message to write.
|
||||
*/
|
||||
private def appendLog(labelColor: String, label: String, messageColor: String, message: String): Unit =
|
||||
private def appendLog(
|
||||
labelColor: String,
|
||||
label: String,
|
||||
messageColor: String,
|
||||
message: String
|
||||
): Unit =
|
||||
out.lockObject.synchronized {
|
||||
message.lines.foreach { line =>
|
||||
val labeledLine = s"$reset[${formatted(labelColor, label)}] ${formatted(messageColor, line)}"
|
||||
val labeledLine =
|
||||
s"$reset[${formatted(labelColor, label)}] ${formatted(messageColor, line)}"
|
||||
write(labeledLine)
|
||||
}
|
||||
}
|
||||
|
|
@ -395,31 +416,30 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
private def appendTraceEvent(te: TraceEvent): Unit = {
|
||||
val traceLevel = getTrace
|
||||
val throwableShowLines: ShowLines[Throwable] =
|
||||
ShowLines[Throwable]( (t: Throwable) => {
|
||||
ShowLines[Throwable]((t: Throwable) => {
|
||||
List(StackTrace.trimmed(t, traceLevel))
|
||||
})
|
||||
val codec: ShowLines[TraceEvent] =
|
||||
ShowLines[TraceEvent]( (t: TraceEvent) => {
|
||||
ShowLines[TraceEvent]((t: TraceEvent) => {
|
||||
throwableShowLines.showLines(t.message)
|
||||
})
|
||||
codec.showLines(te).toVector foreach { appendLog(Level.Error, _) }
|
||||
}
|
||||
|
||||
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = {
|
||||
def appendEvent(oe: ObjectEvent[_]): Unit =
|
||||
{
|
||||
val contentType = oe.contentType
|
||||
if (contentType == "sbt.internal.util.TraceEvent") {
|
||||
appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
|
||||
}
|
||||
else LogExchange.stringCodec[AnyRef](contentType) match {
|
||||
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = {
|
||||
def appendEvent(oe: ObjectEvent[_]): Unit = {
|
||||
val contentType = oe.contentType
|
||||
if (contentType == "sbt.internal.util.TraceEvent") {
|
||||
appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
|
||||
} else
|
||||
LogExchange.stringCodec[AnyRef](contentType) match {
|
||||
case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" =>
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) }
|
||||
case Some(codec) =>
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { appendLog(level, _) }
|
||||
case _ => appendLog(level, oe.message.toString)
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach (appendLog(level, _))
|
||||
case _ => appendLog(level, oe.message.toString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o match {
|
||||
case x: StringEvent => Vector(x.message) foreach { appendLog(level, _) }
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ sealed trait ConsoleOut {
|
|||
object ConsoleOut {
|
||||
def systemOut: ConsoleOut = printStreamOut(System.out)
|
||||
|
||||
def overwriteContaining(s: String): (String, String) => Boolean = (cur, prev) =>
|
||||
cur.contains(s) && prev.contains(s)
|
||||
def overwriteContaining(s: String): (String, String) => Boolean =
|
||||
(cur, prev) => cur.contains(s) && prev.contains(s)
|
||||
|
||||
/** Move to beginning of previous line and clear the line. */
|
||||
private[this] final val OverwriteLine = "\u001B[A\r\u001B[2K"
|
||||
|
|
|
|||
|
|
@ -16,10 +16,21 @@ import org.apache.logging.log4j.core.Appender
|
|||
* `backing` tracks the files that persist the global logging.
|
||||
* `newLogger` creates a new global logging configuration from a sink and backing configuration.
|
||||
*/
|
||||
final case class GlobalLogging(full: ManagedLogger, console: ConsoleOut, backed: Appender,
|
||||
backing: GlobalLogBacking, newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging)
|
||||
final case class GlobalLogging(
|
||||
full: ManagedLogger,
|
||||
console: ConsoleOut,
|
||||
backed: Appender,
|
||||
backing: GlobalLogBacking,
|
||||
newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging
|
||||
)
|
||||
|
||||
final case class GlobalLogging1(full: Logger, console: ConsoleOut, backed: AbstractLogger, backing: GlobalLogBacking, newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1)
|
||||
final case class GlobalLogging1(
|
||||
full: Logger,
|
||||
console: ConsoleOut,
|
||||
backed: AbstractLogger,
|
||||
backing: GlobalLogBacking,
|
||||
newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1
|
||||
)
|
||||
|
||||
/**
|
||||
* Tracks the files that persist the global logging.
|
||||
|
|
@ -27,6 +38,7 @@ final case class GlobalLogging1(full: Logger, console: ConsoleOut, backed: Abstr
|
|||
* `newBackingFile` creates a new temporary location for the next backing file.
|
||||
*/
|
||||
final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile: () => File) {
|
||||
|
||||
/** Shifts the current backing file to `last` and sets the current backing to `newFile`. */
|
||||
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file), newBackingFile)
|
||||
|
||||
|
|
@ -38,32 +50,38 @@ final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile
|
|||
* Otherwise, no changes are made.
|
||||
*/
|
||||
def unshift = GlobalLogBacking(last getOrElse file, None, newBackingFile)
|
||||
|
||||
}
|
||||
|
||||
object GlobalLogBacking {
|
||||
def apply(newBackingFile: => File): GlobalLogBacking = GlobalLogBacking(newBackingFile, None, newBackingFile _)
|
||||
def apply(newBackingFile: => File): GlobalLogBacking =
|
||||
GlobalLogBacking(newBackingFile, None, newBackingFile _)
|
||||
}
|
||||
|
||||
object GlobalLogging {
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
private def generateName: String =
|
||||
"GlobalLogging" + generateId.incrementAndGet
|
||||
|
||||
private def generateName: String = "GlobalLogging" + generateId.incrementAndGet
|
||||
private val generateId: AtomicInteger = new AtomicInteger
|
||||
|
||||
def initial1(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1, newBackingFile: => File, console: ConsoleOut): GlobalLogging1 =
|
||||
{
|
||||
val log = ConsoleLogger(console)
|
||||
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
|
||||
}
|
||||
def initial1(
|
||||
newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1,
|
||||
newBackingFile: => File,
|
||||
console: ConsoleOut
|
||||
): GlobalLogging1 = {
|
||||
val log = ConsoleLogger(console)
|
||||
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
|
||||
}
|
||||
|
||||
def initial(newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File, console: ConsoleOut): GlobalLogging =
|
||||
{
|
||||
val loggerName = generateName
|
||||
val log = LogExchange.logger(loggerName)
|
||||
val appender = ConsoleAppender(ConsoleAppender.generateName, console)
|
||||
LogExchange.bindLoggerAppenders(
|
||||
loggerName, List(appender -> Level.Info)
|
||||
)
|
||||
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
|
||||
}
|
||||
def initial(
|
||||
newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging,
|
||||
newBackingFile: => File,
|
||||
console: ConsoleOut
|
||||
): GlobalLogging = {
|
||||
val loggerName = generateName
|
||||
val log = LogExchange.logger(loggerName)
|
||||
val appender = ConsoleAppender(ConsoleAppender.generateName, console)
|
||||
LogExchange.bindLoggerAppenders(loggerName, List(appender -> Level.Info))
|
||||
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ import sbt.util._
|
|||
* Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`.
|
||||
* A line is delimited by `nl`, which is by default the platform line separator.
|
||||
*/
|
||||
class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: String = System.getProperty("line.separator")) extends java.io.Writer {
|
||||
class LoggerWriter(
|
||||
delegate: Logger,
|
||||
unbufferedLevel: Option[Level.Value],
|
||||
nl: String = System.getProperty("line.separator")
|
||||
) extends java.io.Writer {
|
||||
def this(delegate: Logger, level: Level.Value) = this(delegate, Some(level))
|
||||
def this(delegate: Logger) = this(delegate, None)
|
||||
|
||||
|
|
@ -17,6 +21,7 @@ class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: S
|
|||
private[this] val lines = new collection.mutable.ListBuffer[String]
|
||||
|
||||
override def close() = flush()
|
||||
|
||||
override def flush(): Unit =
|
||||
synchronized {
|
||||
if (buffer.nonEmpty) {
|
||||
|
|
@ -24,12 +29,14 @@ class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: S
|
|||
buffer.clear()
|
||||
}
|
||||
}
|
||||
|
||||
def flushLines(level: Level.Value): Unit =
|
||||
synchronized {
|
||||
for (line <- lines)
|
||||
delegate.log(level, line)
|
||||
lines.clear()
|
||||
}
|
||||
|
||||
override def write(content: Array[Char], offset: Int, length: Int): Unit =
|
||||
synchronized {
|
||||
buffer.appendAll(content, offset, length)
|
||||
|
|
@ -44,6 +51,7 @@ class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: S
|
|||
process()
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def log(s: String): Unit = unbufferedLevel match {
|
||||
case None =>
|
||||
lines += s; ()
|
||||
|
|
|
|||
|
|
@ -10,47 +10,78 @@ object MainAppender {
|
|||
"GlobalBacking" + generateId.incrementAndGet
|
||||
private val generateId: AtomicInteger = new AtomicInteger
|
||||
|
||||
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger =
|
||||
{
|
||||
import config._
|
||||
// TODO
|
||||
// console setTrace screenTrace
|
||||
// backed setTrace backingTrace
|
||||
// multi: Logger
|
||||
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger = {
|
||||
import config._
|
||||
// TODO
|
||||
// console setTrace screenTrace
|
||||
// backed setTrace backingTrace
|
||||
// multi: Logger
|
||||
|
||||
// val log = LogExchange.logger(loggerName)
|
||||
LogExchange.unbindLoggerAppenders(log.name)
|
||||
LogExchange.bindLoggerAppenders(
|
||||
log.name,
|
||||
(consoleOpt.toList map { _ -> screenLevel }) :::
|
||||
List(backed -> backingLevel) :::
|
||||
(extra map { x => (x -> Level.Info) })
|
||||
)
|
||||
log
|
||||
}
|
||||
// val log = LogExchange.logger(loggerName)
|
||||
LogExchange.unbindLoggerAppenders(log.name)
|
||||
LogExchange.bindLoggerAppenders(
|
||||
log.name,
|
||||
(consoleOpt.toList map { _ -> screenLevel }) :::
|
||||
List(backed -> backingLevel) :::
|
||||
(extra map { x =>
|
||||
(x -> Level.Info)
|
||||
})
|
||||
)
|
||||
log
|
||||
}
|
||||
|
||||
def globalDefault(console: ConsoleOut): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging =
|
||||
{
|
||||
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = (log, writer, backing) => {
|
||||
def globalDefault(
|
||||
console: ConsoleOut
|
||||
): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = {
|
||||
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging =
|
||||
(log, writer, backing) => {
|
||||
val backed: Appender = defaultBacked(generateGlobalBackingName)(writer)
|
||||
val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil))
|
||||
GlobalLogging(full, console, backed, backing, newAppender)
|
||||
}
|
||||
newAppender
|
||||
}
|
||||
newAppender
|
||||
}
|
||||
|
||||
def defaultMultiConfig(consoleOpt: Option[ConsoleOut], backing: Appender, extra: List[Appender]): MainAppenderConfig =
|
||||
MainAppenderConfig(consoleOpt map { defaultScreen(_, ConsoleAppender.noSuppressedMessage) }, backing, extra,
|
||||
Level.Info, Level.Debug, -1, Int.MaxValue)
|
||||
def defaultScreen(console: ConsoleOut): Appender = ConsoleAppender(ConsoleAppender.generateName, console)
|
||||
def defaultScreen(console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender =
|
||||
def defaultMultiConfig(
|
||||
consoleOpt: Option[ConsoleOut],
|
||||
backing: Appender,
|
||||
extra: List[Appender]
|
||||
): MainAppenderConfig =
|
||||
MainAppenderConfig(
|
||||
consoleOpt map { defaultScreen(_, ConsoleAppender.noSuppressedMessage) },
|
||||
backing,
|
||||
extra,
|
||||
Level.Info,
|
||||
Level.Debug,
|
||||
-1,
|
||||
Int.MaxValue
|
||||
)
|
||||
|
||||
def defaultScreen(console: ConsoleOut): Appender =
|
||||
ConsoleAppender(ConsoleAppender.generateName, console)
|
||||
|
||||
def defaultScreen(
|
||||
console: ConsoleOut,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
): Appender =
|
||||
ConsoleAppender(ConsoleAppender.generateName, console, suppressedMessage = suppressedMessage)
|
||||
def defaultScreen(name: String, console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender =
|
||||
|
||||
def defaultScreen(
|
||||
name: String,
|
||||
console: ConsoleOut,
|
||||
suppressedMessage: SuppressedTraceContext => Option[String]
|
||||
): Appender =
|
||||
ConsoleAppender(name, console, suppressedMessage = suppressedMessage)
|
||||
|
||||
def defaultBacked: PrintWriter => Appender = defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv)
|
||||
def defaultBacked(loggerName: String): PrintWriter => Appender = defaultBacked(loggerName, ConsoleAppender.formatEnabledInEnv)
|
||||
def defaultBacked(useFormat: Boolean): PrintWriter => Appender = defaultBacked(generateGlobalBackingName, useFormat)
|
||||
def defaultBacked: PrintWriter => Appender =
|
||||
defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv)
|
||||
|
||||
def defaultBacked(loggerName: String): PrintWriter => Appender =
|
||||
defaultBacked(loggerName, ConsoleAppender.formatEnabledInEnv)
|
||||
|
||||
def defaultBacked(useFormat: Boolean): PrintWriter => Appender =
|
||||
defaultBacked(generateGlobalBackingName, useFormat)
|
||||
|
||||
def defaultBacked(loggerName: String, useFormat: Boolean): PrintWriter => Appender =
|
||||
to => {
|
||||
ConsoleAppender(
|
||||
|
|
@ -61,7 +92,12 @@ object MainAppender {
|
|||
}
|
||||
|
||||
final case class MainAppenderConfig(
|
||||
consoleOpt: Option[Appender], backed: Appender, extra: List[Appender],
|
||||
screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int
|
||||
consoleOpt: Option[Appender],
|
||||
backed: Appender,
|
||||
extra: List[Appender],
|
||||
screenLevel: Level.Value,
|
||||
backingLevel: Level.Value,
|
||||
screenTrace: Int,
|
||||
backingTrace: Int
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,33 +14,31 @@ import sbt.internal.util.codec.JsonProtocol._
|
|||
* Delegates log events to the associated LogExchange.
|
||||
*/
|
||||
class ManagedLogger(
|
||||
val name: String,
|
||||
val channelName: Option[String],
|
||||
val execId: Option[String],
|
||||
xlogger: XLogger
|
||||
val name: String,
|
||||
val channelName: Option[String],
|
||||
val execId: Option[String],
|
||||
xlogger: XLogger
|
||||
) extends Logger {
|
||||
override def trace(t: => Throwable): Unit =
|
||||
logEvent(Level.Error, TraceEvent("Error", t, channelName, execId))
|
||||
override def log(level: Level.Value, message: => String): Unit =
|
||||
{
|
||||
xlogger.log(
|
||||
ConsoleAppender.toXLevel(level),
|
||||
new ObjectMessage(StringEvent(level.toString, message, channelName, execId))
|
||||
)
|
||||
}
|
||||
|
||||
override def log(level: Level.Value, message: => String): Unit = {
|
||||
xlogger.log(
|
||||
ConsoleAppender.toXLevel(level),
|
||||
new ObjectMessage(StringEvent(level.toString, message, channelName, execId))
|
||||
)
|
||||
}
|
||||
|
||||
// send special event for success since it's not a real log level
|
||||
override def success(message: => String): Unit = {
|
||||
infoEvent[SuccessEvent](SuccessEvent(message))
|
||||
}
|
||||
|
||||
def registerStringCodec[A: ShowLines: TypeTag]: Unit =
|
||||
{
|
||||
val tag = StringTypeTag[A]
|
||||
val ev = implicitly[ShowLines[A]]
|
||||
// println(s"registerStringCodec ${tag.key}")
|
||||
val _ = LogExchange.getOrElseUpdateStringCodec(tag.key, ev)
|
||||
}
|
||||
def registerStringCodec[A: ShowLines: TypeTag]: Unit = {
|
||||
val tag = StringTypeTag[A]
|
||||
val ev = implicitly[ShowLines[A]]
|
||||
// println(s"registerStringCodec ${tag.key}")
|
||||
val _ = LogExchange.getOrElseUpdateStringCodec(tag.key, ev)
|
||||
}
|
||||
registerStringCodec[Throwable]
|
||||
registerStringCodec[TraceEvent]
|
||||
registerStringCodec[SuccessEvent]
|
||||
|
|
@ -48,18 +46,17 @@ class ManagedLogger(
|
|||
final def infoEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Info, event)
|
||||
final def warnEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Warn, event)
|
||||
final def errorEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Error, event)
|
||||
def logEvent[A: JsonFormat: TypeTag](level: Level.Value, event: => A): Unit =
|
||||
{
|
||||
val v: A = event
|
||||
val tag = StringTypeTag[A]
|
||||
LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]])
|
||||
// println("logEvent " + tag.key)
|
||||
val entry: ObjectEvent[A] = ObjectEvent(level, v, channelName, execId, tag.key)
|
||||
xlogger.log(
|
||||
ConsoleAppender.toXLevel(level),
|
||||
new ObjectMessage(entry)
|
||||
)
|
||||
}
|
||||
def logEvent[A: JsonFormat: TypeTag](level: Level.Value, event: => A): Unit = {
|
||||
val v: A = event
|
||||
val tag = StringTypeTag[A]
|
||||
LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]])
|
||||
// println("logEvent " + tag.key)
|
||||
val entry: ObjectEvent[A] = ObjectEvent(level, v, channelName, execId, tag.key)
|
||||
xlogger.log(
|
||||
ConsoleAppender.toXLevel(level),
|
||||
new ObjectMessage(entry)
|
||||
)
|
||||
}
|
||||
|
||||
@deprecated("No longer used.", "1.0.0")
|
||||
override def ansiCodesSupported = ConsoleAppender.formatEnabledInEnv
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah
|
||||
*/
|
||||
|
|
@ -17,19 +16,25 @@ class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger {
|
|||
super.setLevel(newLevel)
|
||||
dispatch(new SetLevel(newLevel))
|
||||
}
|
||||
|
||||
override def setTrace(level: Int): Unit = {
|
||||
super.setTrace(level)
|
||||
dispatch(new SetTrace(level))
|
||||
}
|
||||
|
||||
override def setSuccessEnabled(flag: Boolean): Unit = {
|
||||
super.setSuccessEnabled(flag)
|
||||
dispatch(new SetSuccess(flag))
|
||||
}
|
||||
|
||||
def trace(t: => Throwable): Unit = dispatch(new Trace(t))
|
||||
def log(level: Level.Value, message: => String): Unit = dispatch(new Log(level, message))
|
||||
def success(message: => String): Unit = dispatch(new Success(message))
|
||||
def logAll(events: Seq[LogEvent]): Unit = delegates.foreach(_.logAll(events))
|
||||
def control(event: ControlEvent.Value, message: => String): Unit = delegates.foreach(_.control(event, message))
|
||||
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
delegates.foreach(_.control(event, message))
|
||||
|
||||
private[this] def dispatch(event: LogEvent): Unit = {
|
||||
for (d <- delegates) {
|
||||
d.log(event)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import sjsonnew.support.scalajson.unsafe.Converter
|
|||
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||
|
||||
final class ObjectEvent[A](
|
||||
val level: Level.Value,
|
||||
val message: A,
|
||||
val channelName: Option[String],
|
||||
val execId: Option[String],
|
||||
val contentType: String,
|
||||
val json: JValue
|
||||
val level: Level.Value,
|
||||
val message: A,
|
||||
val channelName: Option[String],
|
||||
val execId: Option[String],
|
||||
val contentType: String,
|
||||
val json: JValue
|
||||
) extends Serializable {
|
||||
override def toString: String =
|
||||
s"ObjectEvent($level, $message, $channelName, $execId, $contentType, $json)"
|
||||
|
|
@ -21,12 +21,18 @@ final class ObjectEvent[A](
|
|||
|
||||
object ObjectEvent {
|
||||
def apply[A: JsonFormat](
|
||||
level: Level.Value,
|
||||
message: A,
|
||||
channelName: Option[String],
|
||||
execId: Option[String],
|
||||
contentType: String
|
||||
level: Level.Value,
|
||||
message: A,
|
||||
channelName: Option[String],
|
||||
execId: Option[String],
|
||||
contentType: String
|
||||
): ObjectEvent[A] =
|
||||
new ObjectEvent(level, message, channelName, execId, contentType,
|
||||
Converter.toJsonUnsafe(message))
|
||||
new ObjectEvent(
|
||||
level,
|
||||
message,
|
||||
channelName,
|
||||
execId,
|
||||
contentType,
|
||||
Converter.toJsonUnsafe(message)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package sbt.internal.util
|
|||
|
||||
object StackTrace {
|
||||
def isSbtClass(name: String) = name.startsWith("sbt") || name.startsWith("xsbt")
|
||||
|
||||
/**
|
||||
* Return a printable representation of the stack trace associated
|
||||
* with t. Information about t and its Throwable causes is included.
|
||||
|
|
@ -59,6 +60,6 @@ object StackTrace {
|
|||
appendStackTrace(c, false)
|
||||
}
|
||||
b.toString()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@ final case class StringTypeTag[A](key: String) {
|
|||
}
|
||||
|
||||
object StringTypeTag {
|
||||
def apply[A: TypeTag]: StringTypeTag[A] =
|
||||
{
|
||||
val tag = implicitly[TypeTag[A]]
|
||||
val tpe = tag.tpe
|
||||
val k = typeToString(tpe)
|
||||
// println(tpe.getClass.toString + " " + k)
|
||||
StringTypeTag[A](k)
|
||||
}
|
||||
def apply[A: TypeTag]: StringTypeTag[A] = {
|
||||
val tag = implicitly[TypeTag[A]]
|
||||
val tpe = tag.tpe
|
||||
val k = typeToString(tpe)
|
||||
// println(tpe.getClass.toString + " " + k)
|
||||
StringTypeTag[A](k)
|
||||
}
|
||||
def typeToString(tpe: Type): String =
|
||||
tpe match {
|
||||
case TypeRef(_, sym, args) =>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
|
|||
|
||||
implicit val JBooleanFormat: JF[JBoolean] = projectFormat(_.get, (x: Boolean) => JBoolean(x))
|
||||
implicit val JStringFormat: JF[JString] = projectFormat(_.value, (x: String) => JString(x))
|
||||
implicit val JNumberFormat: JF[JNumber] = projectFormat(x => BigDecimal(x.value), (x: BigDecimal) => JNumber(x.toString))
|
||||
|
||||
implicit val JNumberFormat: JF[JNumber] =
|
||||
projectFormat(x => BigDecimal(x.value), (x: BigDecimal) => JNumber(x.toString))
|
||||
|
||||
implicit val JArrayFormat: JF[JArray] = projectFormat[JArray, Array[JValue]](_.value, JArray(_))
|
||||
|
||||
implicit lazy val JObjectJsonWriter: JW[JObject] = new JW[JObject] {
|
||||
|
|
@ -43,5 +46,6 @@ trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
|
|||
def read[J](j: Option[J], u: Unbuilder[J]) = ??? // Is this even possible? with no Manifest[J]?
|
||||
}
|
||||
|
||||
implicit lazy val JValueFormat: JF[JValue] = jsonFormat[JValue](JValueJsonReader, JValueJsonWriter)
|
||||
implicit lazy val JValueFormat: JF[JValue] =
|
||||
jsonFormat[JValue](JValueJsonReader, JValueJsonWriter)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
package sbt.internal.util.codec
|
||||
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
|
||||
import xsbti.Position
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
package sbt.internal.util.codec
|
||||
|
||||
import xsbti.{ Problem, Severity, Position }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
package sbt.internal.util.codec
|
||||
|
||||
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import sbt.internal.util.SuccessEvent
|
|||
|
||||
trait SuccessEventShowLines {
|
||||
implicit val sbtSuccessEventShowLines: ShowLines[SuccessEvent] =
|
||||
ShowLines[SuccessEvent]( (e: SuccessEvent) => {
|
||||
ShowLines[SuccessEvent]((e: SuccessEvent) => {
|
||||
Vector(e.message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import sbt.internal.util.{ StackTrace, TraceEvent }
|
|||
|
||||
trait ThrowableShowLines {
|
||||
implicit val sbtThrowableShowLines: ShowLines[Throwable] =
|
||||
ShowLines[Throwable]( (t: Throwable) => {
|
||||
ShowLines[Throwable]((t: Throwable) => {
|
||||
// 0 means enabled with default behavior. See StackTrace.scala.
|
||||
val traceLevel = 0
|
||||
List(StackTrace.trimmed(t, traceLevel))
|
||||
|
|
@ -17,7 +17,7 @@ object ThrowableShowLines extends ThrowableShowLines
|
|||
|
||||
trait TraceEventShowLines {
|
||||
implicit val sbtTraceEventShowLines: ShowLines[TraceEvent] =
|
||||
ShowLines[TraceEvent]( (t: TraceEvent) => {
|
||||
ShowLines[TraceEvent]((t: TraceEvent) => {
|
||||
ThrowableShowLines.sbtThrowableShowLines.showLines(t.message)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ abstract class AbstractLogger extends Logger {
|
|||
def control(event: ControlEvent.Value, message: => String): Unit
|
||||
|
||||
def logAll(events: Seq[LogEvent]): Unit
|
||||
|
||||
/** Defined in terms of other methods in Logger and should not be called from them. */
|
||||
final def log(event: LogEvent): Unit = {
|
||||
event match {
|
||||
|
|
|
|||
|
|
@ -36,8 +36,15 @@ object InterfaceUtil {
|
|||
case None => Optional.empty[A]()
|
||||
}
|
||||
|
||||
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer],
|
||||
pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position =
|
||||
def position(
|
||||
line0: Option[Integer],
|
||||
content: String,
|
||||
offset0: Option[Integer],
|
||||
pointer0: Option[Integer],
|
||||
pointerSpace0: Option[String],
|
||||
sourcePath0: Option[String],
|
||||
sourceFile0: Option[File]
|
||||
): Position =
|
||||
new ConcretePosition(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0)
|
||||
|
||||
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
|
||||
|
|
@ -53,23 +60,22 @@ object InterfaceUtil {
|
|||
this.get2 == o.get2
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int =
|
||||
{
|
||||
var hash = 1
|
||||
hash = hash * 31 + this.get1.##
|
||||
hash = hash * 31 + this.get2.##
|
||||
hash
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
var hash = 1
|
||||
hash = hash * 31 + this.get1.##
|
||||
hash = hash * 31 + this.get2.##
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
private final class ConcretePosition(
|
||||
line0: Option[Integer],
|
||||
content: String,
|
||||
offset0: Option[Integer],
|
||||
pointer0: Option[Integer],
|
||||
pointerSpace0: Option[String],
|
||||
sourcePath0: Option[String],
|
||||
sourceFile0: Option[File]
|
||||
line0: Option[Integer],
|
||||
content: String,
|
||||
offset0: Option[Integer],
|
||||
pointer0: Option[Integer],
|
||||
pointerSpace0: Option[String],
|
||||
sourcePath0: Option[String],
|
||||
sourceFile0: Option[File]
|
||||
) extends Position {
|
||||
val line = o2jo(line0)
|
||||
val lineContent = content
|
||||
|
|
@ -81,10 +87,10 @@ object InterfaceUtil {
|
|||
}
|
||||
|
||||
private final class ConcreteProblem(
|
||||
cat: String,
|
||||
pos: Position,
|
||||
msg: String,
|
||||
sev: Severity
|
||||
cat: String,
|
||||
pos: Position,
|
||||
msg: String,
|
||||
sev: Severity
|
||||
) extends Problem {
|
||||
val category = cat
|
||||
val position = pos
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ object Level extends Enumeration {
|
|||
val Info = Value(2, "info")
|
||||
val Warn = Value(3, "warn")
|
||||
val Error = Value(4, "error")
|
||||
|
||||
/**
|
||||
* Defines the label to use for success messages.
|
||||
* Because the label for levels is defined in this module, the success label is also defined here.
|
||||
|
|
@ -23,6 +24,7 @@ object Level extends Enumeration {
|
|||
|
||||
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
|
||||
def apply(s: String) = values.find(s == _.toString)
|
||||
|
||||
/** Same as apply, defined for use in pattern matching. */
|
||||
private[sbt] def unapply(s: String) = apply(s)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends
|
|||
|
||||
object ControlEvent extends Enumeration {
|
||||
val Start, Header, Finish = Value
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,16 @@ sealed abstract class LogExchange {
|
|||
val _ = context
|
||||
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
|
||||
val config = ctx.getConfiguration
|
||||
val loggerConfig = LoggerConfig.createLogger(false, XLevel.DEBUG, name,
|
||||
"true", Array[AppenderRef](), null, config, null)
|
||||
val loggerConfig = LoggerConfig.createLogger(
|
||||
false,
|
||||
XLevel.DEBUG,
|
||||
name,
|
||||
"true",
|
||||
Array[AppenderRef](),
|
||||
null,
|
||||
config,
|
||||
null
|
||||
)
|
||||
config.addLogger(name, loggerConfig)
|
||||
ctx.updateLoggers
|
||||
val logger = ctx.getLogger(name)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ abstract class Logger extends xLogger {
|
|||
final def info(message: => String): Unit = log(Level.Info, message)
|
||||
final def warn(message: => String): Unit = log(Level.Warn, message)
|
||||
final def error(message: => String): Unit = log(Level.Error, message)
|
||||
|
||||
// Added by sys.process.ProcessLogger
|
||||
final def err(message: => String): Unit = log(Level.Error, message)
|
||||
// sys.process.ProcessLogger
|
||||
|
|
@ -62,12 +63,16 @@ object Logger {
|
|||
def log(level: Level.Value, message: => String): Unit = ()
|
||||
}
|
||||
|
||||
implicit def absLog2PLog(log: AbstractLogger): ProcessLogger = new BufferedLogger(log) with ProcessLogger
|
||||
implicit def absLog2PLog(log: AbstractLogger): ProcessLogger =
|
||||
new BufferedLogger(log) with ProcessLogger
|
||||
|
||||
implicit def log2PLog(log: Logger): ProcessLogger = absLog2PLog(new FullLogger(log))
|
||||
|
||||
implicit def xlog2Log(lg: xLogger): Logger = lg match {
|
||||
case l: Logger => l
|
||||
case _ => wrapXLogger(lg)
|
||||
}
|
||||
|
||||
private[this] def wrapXLogger(lg: xLogger): Logger = new Logger {
|
||||
import InterfaceUtil.toSupplier
|
||||
override def debug(msg: Supplier[String]): Unit = lg.debug(msg)
|
||||
|
|
@ -78,23 +83,39 @@ object Logger {
|
|||
override def log(level: Level.Value, msg: Supplier[String]): Unit = lg.log(level, msg)
|
||||
def trace(t: => Throwable): Unit = trace(toSupplier(t))
|
||||
def success(s: => String): Unit = info(toSupplier(s))
|
||||
def log(level: Level.Value, msg: => String): Unit =
|
||||
{
|
||||
val fmsg = toSupplier(msg)
|
||||
level match {
|
||||
case Level.Debug => lg.debug(fmsg)
|
||||
case Level.Info => lg.info(fmsg)
|
||||
case Level.Warn => lg.warn(fmsg)
|
||||
case Level.Error => lg.error(fmsg)
|
||||
}
|
||||
def log(level: Level.Value, msg: => String): Unit = {
|
||||
val fmsg = toSupplier(msg)
|
||||
level match {
|
||||
case Level.Debug => lg.debug(fmsg)
|
||||
case Level.Info => lg.info(fmsg)
|
||||
case Level.Warn => lg.warn(fmsg)
|
||||
case Level.Error => lg.error(fmsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def jo2o[A](o: Optional[A]): Option[A] = InterfaceUtil.jo2o(o)
|
||||
def o2jo[A](o: Option[A]): Optional[A] = InterfaceUtil.o2jo(o)
|
||||
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer],
|
||||
pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position =
|
||||
InterfaceUtil.position(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0)
|
||||
|
||||
def position(
|
||||
line0: Option[Integer],
|
||||
content: String,
|
||||
offset0: Option[Integer],
|
||||
pointer0: Option[Integer],
|
||||
pointerSpace0: Option[String],
|
||||
sourcePath0: Option[String],
|
||||
sourceFile0: Option[File]
|
||||
): Position =
|
||||
InterfaceUtil.position(
|
||||
line0,
|
||||
content,
|
||||
offset0,
|
||||
pointer0,
|
||||
pointerSpace0,
|
||||
sourcePath0,
|
||||
sourceFile0
|
||||
)
|
||||
|
||||
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
|
||||
InterfaceUtil.problem(cat, pos, msg, sev)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,26 +8,25 @@ import EscHelpers.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequ
|
|||
|
||||
object Escapes extends Properties("Escapes") {
|
||||
property("genTerminator only generates terminators") =
|
||||
forAllNoShrink(genTerminator) { (c: Char) => isEscapeTerminator(c) }
|
||||
forAllNoShrink(genTerminator)((c: Char) => isEscapeTerminator(c))
|
||||
|
||||
property("genWithoutTerminator only generates terminators") =
|
||||
forAllNoShrink(genWithoutTerminator) { (s: String) =>
|
||||
s.forall { c => !isEscapeTerminator(c) }
|
||||
s.forall(c => !isEscapeTerminator(c))
|
||||
}
|
||||
|
||||
property("hasEscapeSequence is false when no escape character is present") = forAllNoShrink(genWithoutEscape) { (s: String) =>
|
||||
!hasEscapeSequence(s)
|
||||
}
|
||||
property("hasEscapeSequence is false when no escape character is present") =
|
||||
forAllNoShrink(genWithoutEscape)((s: String) => !hasEscapeSequence(s))
|
||||
|
||||
property("hasEscapeSequence is true when escape character is present") = forAllNoShrink(genWithRandomEscapes) { (s: String) =>
|
||||
hasEscapeSequence(s)
|
||||
}
|
||||
property("hasEscapeSequence is true when escape character is present") =
|
||||
forAllNoShrink(genWithRandomEscapes)((s: String) => hasEscapeSequence(s))
|
||||
|
||||
property("removeEscapeSequences is the identity when no escape character is present") = forAllNoShrink(genWithoutEscape) { (s: String) =>
|
||||
val removed: String = removeEscapeSequences(s)
|
||||
("Escape sequence removed: '" + removed + "'") |:
|
||||
(removed == s)
|
||||
}
|
||||
property("removeEscapeSequences is the identity when no escape character is present") =
|
||||
forAllNoShrink(genWithoutEscape) { (s: String) =>
|
||||
val removed: String = removeEscapeSequences(s)
|
||||
("Escape sequence removed: '" + removed + "'") |:
|
||||
(removed == s)
|
||||
}
|
||||
|
||||
property("No escape characters remain after removeEscapeSequences") = forAll { (s: String) =>
|
||||
val removed: String = removeEscapeSequences(s)
|
||||
|
|
@ -36,22 +35,26 @@ object Escapes extends Properties("Escapes") {
|
|||
}
|
||||
|
||||
property("removeEscapeSequences returns string without escape sequences") =
|
||||
forAllNoShrink(genWithoutEscape, genEscapePairs) { (start: String, escapes: List[EscapeAndNot]) =>
|
||||
val withEscapes: String = start + (escapes.map { ean => ean.escape.makeString + ean.notEscape }).mkString("")
|
||||
val removed: String = removeEscapeSequences(withEscapes)
|
||||
val original = start + escapes.map(_.notEscape).mkString("")
|
||||
val diffCharString = diffIndex(original, removed)
|
||||
("Input string : '" + withEscapes + "'") |:
|
||||
("Expected : '" + original + "'") |:
|
||||
("Escapes removed : '" + removed + "'") |:
|
||||
(diffCharString) |:
|
||||
(original == removed)
|
||||
forAllNoShrink(genWithoutEscape, genEscapePairs) {
|
||||
(start: String, escapes: List[EscapeAndNot]) =>
|
||||
val withEscapes: String =
|
||||
start + escapes.map(ean => ean.escape.makeString + ean.notEscape).mkString("")
|
||||
val removed: String = removeEscapeSequences(withEscapes)
|
||||
val original = start + escapes.map(_.notEscape).mkString("")
|
||||
val diffCharString = diffIndex(original, removed)
|
||||
("Input string : '" + withEscapes + "'") |:
|
||||
("Expected : '" + original + "'") |:
|
||||
("Escapes removed : '" + removed + "'") |:
|
||||
(diffCharString) |:
|
||||
(original == removed)
|
||||
}
|
||||
|
||||
def diffIndex(expect: String, original: String): String = {
|
||||
var i = 0;
|
||||
while (i < expect.length && i < original.length) {
|
||||
if (expect.charAt(i) != original.charAt(i)) return ("Differing character, idx: " + i + ", char: " + original.charAt(i) + ", expected: " + expect.charAt(i))
|
||||
if (expect.charAt(i) != original.charAt(i))
|
||||
return ("Differing character, idx: " + i + ", char: " + original.charAt(i) +
|
||||
", expected: " + expect.charAt(i))
|
||||
i += 1
|
||||
}
|
||||
if (expect.length != original.length) return s"Strings are different lengths!"
|
||||
|
|
@ -59,13 +62,21 @@ object Escapes extends Properties("Escapes") {
|
|||
}
|
||||
|
||||
final case class EscapeAndNot(escape: EscapeSequence, notEscape: String) {
|
||||
override def toString = s"EscapeAntNot(escape = [$escape], notEscape = [${notEscape.map(_.toInt)}])"
|
||||
override def toString =
|
||||
s"EscapeAntNot(escape = [$escape], notEscape = [${notEscape.map(_.toInt)}])"
|
||||
}
|
||||
|
||||
// 2.10.5 warns on "implicit numeric widening" but it looks like a bug: https://issues.scala-lang.org/browse/SI-8450
|
||||
final case class EscapeSequence(content: String, terminator: Char) {
|
||||
if (!content.isEmpty) {
|
||||
assert(content.tail.forall(c => !isEscapeTerminator(c)), "Escape sequence content contains an escape terminator: '" + content + "'")
|
||||
assert((content.head == '[') || !isEscapeTerminator(content.head), "Escape sequence content contains an escape terminator: '" + content.headOption + "'")
|
||||
assert(
|
||||
content.tail.forall(c => !isEscapeTerminator(c)),
|
||||
"Escape sequence content contains an escape terminator: '" + content + "'"
|
||||
)
|
||||
assert(
|
||||
(content.head == '[') || !isEscapeTerminator(content.head),
|
||||
"Escape sequence content contains an escape terminator: '" + content.headOption + "'"
|
||||
)
|
||||
}
|
||||
assert(isEscapeTerminator(terminator))
|
||||
def makeString: String = ESC + content + terminator
|
||||
|
|
@ -74,14 +85,20 @@ object Escapes extends Properties("Escapes") {
|
|||
if (content.isEmpty) s"ESC (${terminator.toInt})"
|
||||
else s"ESC ($content) (${terminator.toInt})"
|
||||
}
|
||||
|
||||
private[this] def noEscape(s: String): String = s.replace(ESC, ' ')
|
||||
|
||||
lazy val genEscapeSequence: Gen[EscapeSequence] = oneOf(genKnownSequence, genTwoCharacterSequence, genArbitraryEscapeSequence)
|
||||
lazy val genEscapePair: Gen[EscapeAndNot] = for (esc <- genEscapeSequence; not <- genWithoutEscape) yield EscapeAndNot(esc, not)
|
||||
lazy val genEscapeSequence: Gen[EscapeSequence] =
|
||||
oneOf(genKnownSequence, genTwoCharacterSequence, genArbitraryEscapeSequence)
|
||||
|
||||
lazy val genEscapePair: Gen[EscapeAndNot] =
|
||||
for (esc <- genEscapeSequence; not <- genWithoutEscape) yield EscapeAndNot(esc, not)
|
||||
|
||||
lazy val genEscapePairs: Gen[List[EscapeAndNot]] = listOf(genEscapePair)
|
||||
|
||||
lazy val genArbitraryEscapeSequence: Gen[EscapeSequence] =
|
||||
for (content <- genWithoutTerminator if !content.isEmpty; term <- genTerminator) yield new EscapeSequence("[" + content, term)
|
||||
for (content <- genWithoutTerminator if !content.isEmpty; term <- genTerminator)
|
||||
yield new EscapeSequence("[" + content, term)
|
||||
|
||||
lazy val genKnownSequence: Gen[EscapeSequence] =
|
||||
oneOf((misc ++ setGraphicsMode ++ setMode ++ resetMode).map(toEscapeSequence))
|
||||
|
|
@ -91,14 +108,15 @@ object Escapes extends Properties("Escapes") {
|
|||
lazy val misc = Seq("14;23H", "5;3f", "2A", "94B", "19C", "85D", "s", "u", "2J", "K")
|
||||
|
||||
lazy val setGraphicsMode: Seq[String] =
|
||||
for (txt <- 0 to 8; fg <- 30 to 37; bg <- 40 to 47) yield txt.toString + ";" + fg.toString + ";" + bg.toString + "m"
|
||||
for (txt <- 0 to 8; fg <- 30 to 37; bg <- 40 to 47)
|
||||
yield txt.toString + ";" + fg.toString + ";" + bg.toString + "m"
|
||||
|
||||
lazy val resetMode = setModeLike('I')
|
||||
lazy val setMode = setModeLike('h')
|
||||
def setModeLike(term: Char): Seq[String] = (0 to 19).map(i => "=" + i.toString + term)
|
||||
|
||||
lazy val genWithoutTerminator =
|
||||
genRawString.map(_.filter { c => !isEscapeTerminator(c) && (c != '[') })
|
||||
genRawString.map(_.filter(c => !isEscapeTerminator(c) && (c != '[')))
|
||||
|
||||
lazy val genTwoCharacterSequence =
|
||||
// 91 == [ which is the CSI escape sequence.
|
||||
|
|
@ -108,7 +126,8 @@ object Escapes extends Properties("Escapes") {
|
|||
lazy val genWithoutEscape: Gen[String] = genRawString.map(noEscape)
|
||||
|
||||
def genWithRandomEscapes: Gen[String] =
|
||||
for (ls <- listOf(genRawString); end <- genRawString) yield ls.mkString("", ESC.toString, ESC.toString + end)
|
||||
for (ls <- listOf(genRawString); end <- genRawString)
|
||||
yield ls.mkString("", ESC.toString, ESC.toString + end)
|
||||
|
||||
private def genRawString = Arbitrary.arbString.arbitrary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")"
|
||||
case _ => "Not Log"
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are
|
||||
* represented as separately written segments (ToLog instances). ToLog.`byCharacter`
|
||||
|
|
@ -46,7 +47,7 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
val content = section.content
|
||||
val normalized = Escape.newline(content, newLine)
|
||||
if (section.byCharacter)
|
||||
normalized.foreach { c => writer.write(c.toInt) }
|
||||
normalized.foreach(c => writer.write(c.toInt))
|
||||
else
|
||||
writer.write(normalized)
|
||||
}
|
||||
|
|
@ -56,6 +57,7 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
/** Converts the given lines in segments to lines as Strings for checking the results of the test.*/
|
||||
def toLines(lines: List[List[ToLog]]): List[String] =
|
||||
lines.map(_.map(_.contentOnly).mkString)
|
||||
|
||||
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/
|
||||
def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean =
|
||||
(lines zip events) forall {
|
||||
|
|
@ -64,10 +66,10 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
}
|
||||
|
||||
/* The following are implicit generators to build up a write sequence.
|
||||
* ToLog represents a written segment. NewLine represents one of the possible
|
||||
* newline separators. A List[ToLog] represents a full line and always includes a
|
||||
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in
|
||||
* the `content` of a ToLog instance.*/
|
||||
* ToLog represents a written segment. NewLine represents one of the possible
|
||||
* newline separators. A List[ToLog] represents a full line and always includes a
|
||||
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in
|
||||
* the `content` of a ToLog instance.*/
|
||||
|
||||
implicit lazy val arbOut: Arbitrary[Output] = Arbitrary(genOutput)
|
||||
implicit lazy val arbLog: Arbitrary[ToLog] = Arbitrary(genLog)
|
||||
|
|
@ -76,7 +78,8 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
implicit lazy val arbLevel: Arbitrary[Level.Value] = Arbitrary(genLevel)
|
||||
|
||||
implicit def genLine(implicit logG: Gen[ToLog]): Gen[List[ToLog]] =
|
||||
for (l <- listOf[ToLog](MaxSegments); last <- logG) yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
|
||||
for (l <- listOf[ToLog](MaxSegments); last <- logG)
|
||||
yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
|
||||
|
||||
implicit def genLog(implicit content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] =
|
||||
for (c <- content.arbitrary; by <- byChar.arbitrary) yield {
|
||||
|
|
@ -98,7 +101,7 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines
|
||||
|
||||
def listOf[T](max: Int)(implicit content: Arbitrary[T]): Gen[List[T]] =
|
||||
Gen.choose(0, max) flatMap { sz => listOfN(sz, content.arbitrary) }
|
||||
Gen.choose(0, max) flatMap (sz => listOfN(sz, content.arbitrary))
|
||||
}
|
||||
|
||||
/* Helper classes*/
|
||||
|
|
@ -107,35 +110,43 @@ final class Output(val lines: List[List[ToLog]], val level: Level.Value) {
|
|||
override def toString =
|
||||
"Level: " + level + "\n" + lines.map(_.mkString).mkString("\n")
|
||||
}
|
||||
|
||||
final class NewLine(val str: String) {
|
||||
override def toString = Escape(str)
|
||||
}
|
||||
|
||||
final class ToLog(val content: String, val byCharacter: Boolean) {
|
||||
def contentOnly = Escape.newline(content, "")
|
||||
override def toString = if (content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
|
||||
|
||||
override def toString =
|
||||
if (content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
|
||||
}
|
||||
|
||||
/** Defines some utility methods for escaping unprintable characters.*/
|
||||
object Escape {
|
||||
|
||||
/** Escapes characters with code less than 20 by printing them as unicode escapes.*/
|
||||
def apply(s: String): String =
|
||||
{
|
||||
val builder = new StringBuilder(s.length)
|
||||
for (c <- s) {
|
||||
val char = c.toInt
|
||||
def escaped = pad(char.toHexString.toUpperCase, 4, '0')
|
||||
if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
|
||||
}
|
||||
builder.toString
|
||||
}
|
||||
def pad(s: String, minLength: Int, extra: Char) =
|
||||
{
|
||||
val diff = minLength - s.length
|
||||
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
|
||||
def apply(s: String): String = {
|
||||
val builder = new StringBuilder(s.length)
|
||||
for (c <- s) {
|
||||
val char = c.toInt
|
||||
def escaped = pad(char.toHexString.toUpperCase, 4, '0')
|
||||
if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
|
||||
}
|
||||
builder.toString
|
||||
}
|
||||
|
||||
def pad(s: String, minLength: Int, extra: Char) = {
|
||||
val diff = minLength - s.length
|
||||
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
|
||||
}
|
||||
|
||||
/** Replaces a \n character at the end of a string `s` with `nl`.*/
|
||||
def newline(s: String, nl: String): String =
|
||||
if (s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s
|
||||
|
||||
}
|
||||
|
||||
/** Records logging events for later retrieval.*/
|
||||
final class RecordingLogger extends BasicLogger {
|
||||
private var events: List[LogEvent] = Nil
|
||||
|
|
@ -147,6 +158,7 @@ final class RecordingLogger extends BasicLogger {
|
|||
def log(level: Level.Value, message: => String): Unit = { events ::= new Log(level, message) }
|
||||
def success(message: => String): Unit = { events ::= new Success(message) }
|
||||
def logAll(es: Seq[LogEvent]): Unit = { events :::= es.toList }
|
||||
def control(event: ControlEvent.Value, message: => String): Unit = { events ::= new ControlEvent(event, message) }
|
||||
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
events ::= new ControlEvent(event, message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
import sjsonnew.BasicJsonProtocol._
|
||||
val log = LogExchange.logger("foo")
|
||||
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
|
||||
implicit val intShow: ShowLines[Int] = ShowLines({ (x: Int) => Vector(s"String representation of $x") })
|
||||
implicit val intShow: ShowLines[Int] =
|
||||
ShowLines((x: Int) => Vector(s"String representation of $x"))
|
||||
log.registerStringCodec[Int]
|
||||
log.infoEvent(1)
|
||||
}
|
||||
|
|
@ -33,7 +34,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
import sjsonnew.BasicJsonProtocol._
|
||||
val log = LogExchange.logger("foo")
|
||||
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
|
||||
implicit val intArrayShow: ShowLines[Array[Int]] = ShowLines({ (x: Array[Int]) => Vector(s"String representation of ${x.mkString}") })
|
||||
implicit val intArrayShow: ShowLines[Array[Int]] =
|
||||
ShowLines((x: Array[Int]) => Vector(s"String representation of ${x.mkString}"))
|
||||
log.registerStringCodec[Array[Int]]
|
||||
log.infoEvent(Array(1, 2, 3))
|
||||
}
|
||||
|
|
@ -42,7 +44,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
import sjsonnew.BasicJsonProtocol._
|
||||
val log = LogExchange.logger("foo")
|
||||
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
|
||||
implicit val intVectorShow: ShowLines[Vector[Vector[Int]]] = ShowLines({ (xss: Vector[Vector[Int]]) => Vector(s"String representation of $xss") })
|
||||
implicit val intVectorShow: ShowLines[Vector[Vector[Int]]] =
|
||||
ShowLines((xss: Vector[Vector[Int]]) => Vector(s"String representation of $xss"))
|
||||
log.registerStringCodec[Vector[Vector[Int]]]
|
||||
log.infoEvent(Vector(Vector(1, 2, 3)))
|
||||
}
|
||||
|
|
@ -51,7 +54,9 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
// this is passed into State normally
|
||||
val global0 = initialGlobalLogging
|
||||
val full = global0.full
|
||||
(1 to 3).toList foreach { x => full.info(s"test$x") }
|
||||
(1 to 3).toList foreach { x =>
|
||||
full.info(s"test$x")
|
||||
}
|
||||
}
|
||||
|
||||
// This is done in Mainloop.scala
|
||||
|
|
@ -62,7 +67,7 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
val out = new PrintWriter(writer)
|
||||
val g = global0.newAppender(global0.full, out, logBacking0)
|
||||
val full = g.full
|
||||
(1 to 3).toList foreach { x => full.info(s"newAppender $x") }
|
||||
(1 to 3).toList foreach (x => full.info(s"newAppender $x"))
|
||||
assert(logBacking0.file.exists)
|
||||
g
|
||||
}
|
||||
|
|
@ -71,7 +76,7 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
val out = new PrintWriter(writer)
|
||||
val g = global1.newAppender(global1.full, out, logBacking1)
|
||||
val full = g.full
|
||||
(1 to 3).toList foreach { x => full.info(s"newAppender $x") }
|
||||
(1 to 3).toList foreach (x => full.info(s"newAppender $x"))
|
||||
// println(logBacking.file)
|
||||
// print("Press enter to continue. ")
|
||||
// System.console.readLine
|
||||
|
|
@ -81,6 +86,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
|
|||
|
||||
val console = ConsoleOut.systemOut
|
||||
def initialGlobalLogging: GlobalLogging = GlobalLogging.initial(
|
||||
MainAppender.globalDefault(console), File.createTempFile("sbt", ".log"), console
|
||||
MainAppender.globalDefault(console),
|
||||
File.createTempFile("sbt", ".log"),
|
||||
console
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ package sbt.internal.util
|
|||
import sbt.util._
|
||||
|
||||
object TestLogger {
|
||||
def apply[T](f: Logger => T): T =
|
||||
{
|
||||
val log = new BufferedLogger(ConsoleLogger())
|
||||
log.setLevel(Level.Debug)
|
||||
log.bufferQuietly(f(log))
|
||||
}
|
||||
}
|
||||
def apply[T](f: Logger => T): T = {
|
||||
val log = new BufferedLogger(ConsoleLogger())
|
||||
log.setLevel(Level.Debug)
|
||||
log.bufferQuietly(f(log))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package sbt.internal.util
|
|||
import Relation._
|
||||
|
||||
object Relation {
|
||||
|
||||
/** Constructs a new immutable, finite relation that is initially empty. */
|
||||
def empty[A, B]: Relation[A, B] = make(Map.empty, Map.empty)
|
||||
|
||||
|
|
@ -13,17 +14,18 @@ object Relation {
|
|||
* Constructs a [[Relation]] from underlying `forward` and `reverse` representations, without checking that they are consistent.
|
||||
* This is a low-level constructor and the alternatives [[empty]] and [[reconstruct]] should be preferred.
|
||||
*/
|
||||
def make[A, B](forward: Map[A, Set[B]], reverse: Map[B, Set[A]]): Relation[A, B] = new MRelation(forward, reverse)
|
||||
def make[A, B](forward: Map[A, Set[B]], reverse: Map[B, Set[A]]): Relation[A, B] =
|
||||
new MRelation(forward, reverse)
|
||||
|
||||
/** Constructs a relation such that for every entry `_1 -> _2s` in `forward` and every `_2` in `_2s`, `(_1, _2)` is in the relation. */
|
||||
def reconstruct[A, B](forward: Map[A, Set[B]]): Relation[A, B] =
|
||||
{
|
||||
val reversePairs = for ((a, bs) <- forward.view; b <- bs.view) yield (b, a)
|
||||
val reverse = (Map.empty[B, Set[A]] /: reversePairs) { case (m, (b, a)) => add(m, b, a :: Nil) }
|
||||
make(forward filter { case (a, bs) => bs.nonEmpty }, reverse)
|
||||
}
|
||||
def reconstruct[A, B](forward: Map[A, Set[B]]): Relation[A, B] = {
|
||||
val reversePairs = for ((a, bs) <- forward.view; b <- bs.view) yield (b, a)
|
||||
val reverse = (Map.empty[B, Set[A]] /: reversePairs) { case (m, (b, a)) => add(m, b, a :: Nil) }
|
||||
make(forward filter { case (a, bs) => bs.nonEmpty }, reverse)
|
||||
}
|
||||
|
||||
def merge[A, B](rels: Traversable[Relation[A, B]]): Relation[A, B] = (Relation.empty[A, B] /: rels)(_ ++ _)
|
||||
def merge[A, B](rels: Traversable[Relation[A, B]]): Relation[A, B] =
|
||||
(Relation.empty[A, B] /: rels)(_ ++ _)
|
||||
|
||||
private[sbt] def remove[X, Y](map: M[X, Y], from: X, to: Y): M[X, Y] =
|
||||
map.get(from) match {
|
||||
|
|
@ -34,46 +36,61 @@ object Relation {
|
|||
}
|
||||
|
||||
private[sbt] def combine[X, Y](a: M[X, Y], b: M[X, Y]): M[X, Y] =
|
||||
(a /: b) { (map, mapping) => add(map, mapping._1, mapping._2) }
|
||||
(a /: b)((map, mapping) => add(map, mapping._1, mapping._2))
|
||||
|
||||
private[sbt] def add[X, Y](map: M[X, Y], from: X, to: Traversable[Y]): M[X, Y] =
|
||||
map.updated(from, get(map, from) ++ to)
|
||||
|
||||
private[sbt] def get[X, Y](map: M[X, Y], t: X): Set[Y] = map.getOrElse(t, Set.empty[Y])
|
||||
|
||||
private[sbt]type M[X, Y] = Map[X, Set[Y]]
|
||||
private[sbt] type M[X, Y] = Map[X, Set[Y]]
|
||||
}
|
||||
|
||||
/** Binary relation between A and B. It is a set of pairs (_1, _2) for _1 in A, _2 in B. */
|
||||
trait Relation[A, B] {
|
||||
|
||||
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
|
||||
def forward(_1: A): Set[B]
|
||||
|
||||
/** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */
|
||||
def reverse(_2: B): Set[A]
|
||||
|
||||
/** Includes `pair` in the relation. */
|
||||
def +(pair: (A, B)): Relation[A, B]
|
||||
|
||||
/** Includes `(a, b)` in the relation. */
|
||||
def +(a: A, b: B): Relation[A, B]
|
||||
|
||||
/** Includes in the relation `(a, b)` for all `b` in `bs`. */
|
||||
def +(a: A, bs: Traversable[B]): Relation[A, B]
|
||||
|
||||
/** Returns the union of the relation `r` with this relation. */
|
||||
def ++(r: Relation[A, B]): Relation[A, B]
|
||||
|
||||
/** Includes the given pairs in this relation. */
|
||||
def ++(rs: Traversable[(A, B)]): Relation[A, B]
|
||||
|
||||
/** Removes all elements `(_1, _2)` for all `_1` in `_1s` from this relation. */
|
||||
def --(_1s: Traversable[A]): Relation[A, B]
|
||||
|
||||
/** Removes all `pairs` from this relation. */
|
||||
def --(pairs: TraversableOnce[(A, B)]): Relation[A, B]
|
||||
|
||||
/** Removes all `relations` from this relation. */
|
||||
def --(relations: Relation[A, B]): Relation[A, B]
|
||||
|
||||
/** Removes all pairs `(_1, _2)` from this relation. */
|
||||
def -(_1: A): Relation[A, B]
|
||||
|
||||
/** Removes `pair` from this relation. */
|
||||
def -(pair: (A, B)): Relation[A, B]
|
||||
|
||||
/** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */
|
||||
def _1s: collection.Set[A]
|
||||
|
||||
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
|
||||
def _2s: collection.Set[B]
|
||||
|
||||
/** Returns the number of pairs in this relation */
|
||||
def size: Int
|
||||
|
||||
|
|
@ -110,10 +127,12 @@ trait Relation[A, B] {
|
|||
* The value associated with a given `_2` is the set of all `_1`s such that `(_1, _2)` is in this relation.
|
||||
*/
|
||||
def reverseMap: Map[B, Set[A]]
|
||||
|
||||
}
|
||||
|
||||
// Note that we assume without checking that fwd and rev are consistent.
|
||||
private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) extends Relation[A, B] {
|
||||
private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]])
|
||||
extends Relation[A, B] {
|
||||
def forwardMap = fwd
|
||||
def reverseMap = rev
|
||||
|
||||
|
|
@ -125,25 +144,30 @@ private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ex
|
|||
|
||||
def size = (fwd.valuesIterator map (_.size)).sum
|
||||
|
||||
def all: Traversable[(A, B)] = fwd.iterator.flatMap { case (a, bs) => bs.iterator.map(b => (a, b)) }.toTraversable
|
||||
def all: Traversable[(A, B)] =
|
||||
fwd.iterator.flatMap { case (a, bs) => bs.iterator.map(b => (a, b)) }.toTraversable
|
||||
|
||||
def +(pair: (A, B)) = this + (pair._1, Set(pair._2))
|
||||
def +(from: A, to: B) = this + (from, to :: Nil)
|
||||
def +(from: A, to: Traversable[B]) = if (to.isEmpty) this else
|
||||
new MRelation(add(fwd, from, to), (rev /: to) { (map, t) => add(map, t, from :: Nil) })
|
||||
def +(from: A, to: Traversable[B]) =
|
||||
if (to.isEmpty) this
|
||||
else new MRelation(add(fwd, from, to), (rev /: to)((map, t) => add(map, t, from :: Nil)))
|
||||
|
||||
def ++(rs: Traversable[(A, B)]) = ((this: Relation[A, B]) /: rs) { _ + _ }
|
||||
def ++(other: Relation[A, B]) = new MRelation[A, B](combine(fwd, other.forwardMap), combine(rev, other.reverseMap))
|
||||
def ++(other: Relation[A, B]) =
|
||||
new MRelation[A, B](combine(fwd, other.forwardMap), combine(rev, other.reverseMap))
|
||||
|
||||
def --(ts: Traversable[A]): Relation[A, B] = ((this: Relation[A, B]) /: ts) { _ - _ }
|
||||
def --(pairs: TraversableOnce[(A, B)]): Relation[A, B] = ((this: Relation[A, B]) /: pairs) { _ - _ }
|
||||
def --(pairs: TraversableOnce[(A, B)]): Relation[A, B] = ((this: Relation[A, B]) /: pairs)(_ - _)
|
||||
def --(relations: Relation[A, B]): Relation[A, B] = --(relations.all)
|
||||
|
||||
def -(pair: (A, B)): Relation[A, B] =
|
||||
new MRelation(remove(fwd, pair._1, pair._2), remove(rev, pair._2, pair._1))
|
||||
|
||||
def -(t: A): Relation[A, B] =
|
||||
fwd.get(t) match {
|
||||
case Some(rs) =>
|
||||
val upRev = (rev /: rs) { (map, r) => remove(map, r, t) }
|
||||
val upRev = (rev /: rs)((map, r) => remove(map, r, t))
|
||||
new MRelation(fwd - t, upRev)
|
||||
case None => this
|
||||
}
|
||||
|
|
@ -155,18 +179,21 @@ private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ex
|
|||
(Relation.empty[A, B] ++ y, Relation.empty[A, B] ++ n)
|
||||
}
|
||||
|
||||
def groupBy[K](discriminator: ((A, B)) => K): Map[K, Relation[A, B]] = all.groupBy(discriminator) mapValues { Relation.empty[A, B] ++ _ }
|
||||
def groupBy[K](discriminator: ((A, B)) => K): Map[K, Relation[A, B]] =
|
||||
all.groupBy(discriminator) mapValues { Relation.empty[A, B] ++ _ }
|
||||
|
||||
def contains(a: A, b: B): Boolean = forward(a)(b)
|
||||
|
||||
override def equals(other: Any) = other match {
|
||||
// We assume that the forward and reverse maps are consistent, so we only use the forward map
|
||||
// for equality. Note that key -> Empty is semantically the same as key not existing.
|
||||
case o: MRelation[A, B] => forwardMap.filterNot(_._2.isEmpty) == o.forwardMap.filterNot(_._2.isEmpty)
|
||||
case _ => false
|
||||
case o: MRelation[A, B] =>
|
||||
forwardMap.filterNot(_._2.isEmpty) == o.forwardMap.filterNot(_._2.isEmpty)
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def hashCode = fwd.filterNot(_._2.isEmpty).hashCode()
|
||||
|
||||
override def toString = all.map { case (a, b) => a + " -> " + b }.mkString("Relation [", ", ", "]")
|
||||
override def toString =
|
||||
all.map { case (a, b) => a + " -> " + b }.mkString("Relation [", ", ", "]")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,21 +11,20 @@ object RelationTest extends Properties("Relation") {
|
|||
val r = Relation.empty[Int, Double] ++ pairs
|
||||
check(r, pairs)
|
||||
}
|
||||
def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) =
|
||||
{
|
||||
val _1s = pairs.map(_._1).toSet
|
||||
val _2s = pairs.map(_._2).toSet
|
||||
def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) = {
|
||||
val _1s = pairs.map(_._1).toSet
|
||||
val _2s = pairs.map(_._2).toSet
|
||||
|
||||
r._1s == _1s && r.forwardMap.keySet == _1s &&
|
||||
r._2s == _2s && r.reverseMap.keySet == _2s &&
|
||||
pairs.forall {
|
||||
case (a, b) =>
|
||||
(r.forward(a) contains b) &&
|
||||
(r.reverse(b) contains a) &&
|
||||
(r.forwardMap(a) contains b) &&
|
||||
(r.reverseMap(b) contains a)
|
||||
}
|
||||
r._1s == _1s && r.forwardMap.keySet == _1s &&
|
||||
r._2s == _2s && r.reverseMap.keySet == _2s &&
|
||||
pairs.forall {
|
||||
case (a, b) =>
|
||||
(r.forward(a) contains b) &&
|
||||
(r.reverse(b) contains a) &&
|
||||
(r.forwardMap(a) contains b) &&
|
||||
(r.reverseMap(b) contains a)
|
||||
}
|
||||
}
|
||||
|
||||
property("Does not contain removed entries") = forAll { (pairs: List[(Int, Double, Boolean)]) =>
|
||||
val add = pairs.map { case (a, b, c) => (a, b) }
|
||||
|
|
@ -39,17 +38,17 @@ object RelationTest extends Properties("Relation") {
|
|||
|
||||
all(removeCoarse) { rem =>
|
||||
("_1s does not contain removed" |: (!r._1s.contains(rem))) &&
|
||||
("Forward does not contain removed" |: r.forward(rem).isEmpty) &&
|
||||
("Forward map does not contain removed" |: !r.forwardMap.contains(rem)) &&
|
||||
("Removed is not a value in reverse map" |: !r.reverseMap.values.toSet.contains(rem))
|
||||
("Forward does not contain removed" |: r.forward(rem).isEmpty) &&
|
||||
("Forward map does not contain removed" |: !r.forwardMap.contains(rem)) &&
|
||||
("Removed is not a value in reverse map" |: !r.reverseMap.values.toSet.contains(rem))
|
||||
} &&
|
||||
all(removeFine) {
|
||||
case (a, b) =>
|
||||
("Forward does not contain removed" |: (!r.forward(a).contains(b))) &&
|
||||
("Reverse does not contain removed" |: (!r.reverse(b).contains(a))) &&
|
||||
("Forward map does not contain removed" |: (notIn(r.forwardMap, a, b))) &&
|
||||
("Reverse map does not contain removed" |: (notIn(r.reverseMap, b, a)))
|
||||
}
|
||||
all(removeFine) {
|
||||
case (a, b) =>
|
||||
("Forward does not contain removed" |: (!r.forward(a).contains(b))) &&
|
||||
("Reverse does not contain removed" |: (!r.reverse(b).contains(a))) &&
|
||||
("Forward map does not contain removed" |: (notIn(r.forwardMap, a, b))) &&
|
||||
("Reverse map does not contain removed" |: (notIn(r.reverseMap, b, a)))
|
||||
}
|
||||
}
|
||||
|
||||
property("Groups correctly") = forAll { (entries: List[(Int, Double)], randomInt: Int) =>
|
||||
|
|
@ -75,10 +74,10 @@ object RelationTest extends Properties("Relation") {
|
|||
object EmptyRelationTest extends Properties("Empty relation") {
|
||||
lazy val e = Relation.empty[Int, Double]
|
||||
|
||||
property("Forward empty") = forAll { (i: Int) => e.forward(i).isEmpty }
|
||||
property("Reverse empty") = forAll { (i: Double) => e.reverse(i).isEmpty }
|
||||
property("Forward empty") = forAll((i: Int) => e.forward(i).isEmpty)
|
||||
property("Reverse empty") = forAll((i: Double) => e.reverse(i).isEmpty)
|
||||
property("Forward map empty") = e.forwardMap.isEmpty
|
||||
property("Reverse map empty") = e.reverseMap.isEmpty
|
||||
property("_1 empty") = e._1s.isEmpty
|
||||
property("_2 empty") = e._2s.isEmpty
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ package scripted
|
|||
|
||||
object CommentHandler extends BasicStatementHandler {
|
||||
def apply(command: String, args: List[String]) = ()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,18 +58,20 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
|
|||
val lines1 = IO.readLines(fromString(file1))
|
||||
val lines2 = IO.readLines(fromString(file2))
|
||||
if (lines1 != lines2)
|
||||
scriptError("File contents are different:\n" + lines1.mkString("\n") + "\nAnd:\n" + lines2.mkString("\n"))
|
||||
scriptError(
|
||||
"File contents are different:\n" + lines1.mkString("\n") +
|
||||
"\nAnd:\n" + lines2.mkString("\n")
|
||||
)
|
||||
}
|
||||
|
||||
def newer(a: String, b: String): Unit =
|
||||
{
|
||||
val pathA = fromString(a)
|
||||
val pathB = fromString(b)
|
||||
val isNewer = pathA.exists && (!pathB.exists || pathA.lastModified > pathB.lastModified)
|
||||
if (!isNewer) {
|
||||
scriptError(s"$pathA is not newer than $pathB")
|
||||
}
|
||||
def newer(a: String, b: String): Unit = {
|
||||
val pathA = fromString(a)
|
||||
val pathB = fromString(b)
|
||||
val isNewer = pathA.exists && (!pathB.exists || pathA.lastModified > pathB.lastModified)
|
||||
if (!isNewer) {
|
||||
scriptError(s"$pathA is not newer than $pathB")
|
||||
}
|
||||
}
|
||||
def exists(paths: List[String]): Unit = {
|
||||
val notPresent = fromStrings(paths).filter(!_.exists)
|
||||
if (notPresent.nonEmpty)
|
||||
|
|
@ -127,9 +129,16 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
|
|||
IO.copy(mapped.init pair map)
|
||||
()
|
||||
}
|
||||
|
||||
def wrongArguments(args: List[String]): Unit =
|
||||
scriptError("Command '" + commandName + "' does not accept arguments (found '" + spaced(args) + "').")
|
||||
scriptError(
|
||||
"Command '" + commandName + "' does not accept arguments (found '" + spaced(args) + "')."
|
||||
)
|
||||
|
||||
def wrongArguments(requiredArgs: String, args: List[String]): Unit =
|
||||
scriptError("Wrong number of arguments to " + commandName + " command. " + requiredArgs + " required, found: '" + spaced(args) + "'.")
|
||||
scriptError(
|
||||
"Wrong number of arguments to " + commandName + " command. " +
|
||||
requiredArgs + " required, found: '" + spaced(args) + "'."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ package scripted
|
|||
|
||||
final class FilteredLoader(parent: ClassLoader) extends ClassLoader(parent) {
|
||||
@throws(classOf[ClassNotFoundException])
|
||||
override final def loadClass(className: String, resolve: Boolean): Class[_] =
|
||||
{
|
||||
if (className.startsWith("java.") || className.startsWith("javax."))
|
||||
super.loadClass(className, resolve)
|
||||
else
|
||||
throw new ClassNotFoundException(className)
|
||||
}
|
||||
override final def loadClass(className: String, resolve: Boolean): Class[_] = {
|
||||
if (className.startsWith("java.") || className.startsWith("javax."))
|
||||
super.loadClass(className, resolve)
|
||||
else
|
||||
throw new ClassNotFoundException(className)
|
||||
}
|
||||
override def getResources(name: String) = null
|
||||
override def getResource(name: String) = null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ package sbt.internal.scripted
|
|||
|
||||
trait HandlersProvider {
|
||||
def getHandlers(config: ScriptConfig): Map[Char, StatementHandler]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package internal
|
|||
package scripted
|
||||
|
||||
final class TestException(statement: Statement, msg: String, exception: Throwable)
|
||||
extends RuntimeException(statement.linePrefix + " " + msg, exception)
|
||||
extends RuntimeException(statement.linePrefix + " " + msg, exception)
|
||||
|
||||
class ScriptRunner {
|
||||
import scala.collection.mutable.HashMap
|
||||
|
|
@ -15,14 +15,16 @@ class ScriptRunner {
|
|||
def processStatement(handler: StatementHandler, statement: Statement): Unit = {
|
||||
val state = states(handler).asInstanceOf[handler.State]
|
||||
val nextState =
|
||||
try { Right(handler(statement.command, statement.arguments, state)) }
|
||||
catch { case e: Exception => Left(e) }
|
||||
try { Right(handler(statement.command, statement.arguments, state)) } catch {
|
||||
case e: Exception => Left(e)
|
||||
}
|
||||
nextState match {
|
||||
case Left(err) =>
|
||||
if (statement.successExpected) {
|
||||
err match {
|
||||
case t: TestFailed => throw new TestException(statement, "Command failed: " + t.getMessage, null)
|
||||
case _ => throw new TestException(statement, "Command failed", err)
|
||||
case t: TestFailed =>
|
||||
throw new TestException(statement, "Command failed: " + t.getMessage, null)
|
||||
case _ => throw new TestException(statement, "Command failed", err)
|
||||
}
|
||||
} else
|
||||
()
|
||||
|
|
@ -36,13 +38,12 @@ class ScriptRunner {
|
|||
val handlers = Set() ++ statements.map(_._1)
|
||||
|
||||
try {
|
||||
handlers.foreach { handler => states(handler) = handler.initialState }
|
||||
handlers.foreach(handler => states(handler) = handler.initialState)
|
||||
statements foreach (Function.tupled(processStatement))
|
||||
} finally {
|
||||
for (handler <- handlers; state <- states.get(handler)) {
|
||||
try { handler.finish(state.asInstanceOf[handler.State]) }
|
||||
catch { case e: Exception => () }
|
||||
try { handler.finish(state.asInstanceOf[handler.State]) } catch { case e: Exception => () }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,12 @@ import sbt.internal.io.Resources
|
|||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
object ScriptedRunnerImpl {
|
||||
def run(resourceBaseDirectory: File, bufferLog: Boolean, tests: Array[String], handlersProvider: HandlersProvider): Unit = {
|
||||
def run(
|
||||
resourceBaseDirectory: File,
|
||||
bufferLog: Boolean,
|
||||
tests: Array[String],
|
||||
handlersProvider: HandlersProvider
|
||||
): Unit = {
|
||||
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, handlersProvider)
|
||||
val logger = newLogger
|
||||
val allTests = get(tests, resourceBaseDirectory, logger) flatMap {
|
||||
|
|
@ -36,15 +41,18 @@ object ScriptedRunnerImpl {
|
|||
ScriptedTest(group, name)
|
||||
}
|
||||
private[sbt] val generateId: AtomicInteger = new AtomicInteger
|
||||
private[sbt] def newLogger: ManagedLogger =
|
||||
{
|
||||
val loggerName = "scripted-" + generateId.incrementAndGet
|
||||
val x = LogExchange.logger(loggerName)
|
||||
x
|
||||
}
|
||||
private[sbt] def newLogger: ManagedLogger = {
|
||||
val loggerName = "scripted-" + generateId.incrementAndGet
|
||||
val x = LogExchange.logger(loggerName)
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handlersProvider: HandlersProvider) {
|
||||
final class ScriptedTests(
|
||||
resourceBaseDirectory: File,
|
||||
bufferLog: Boolean,
|
||||
handlersProvider: HandlersProvider
|
||||
) {
|
||||
private val testResources = new Resources(resourceBaseDirectory)
|
||||
private val consoleAppender: ConsoleAppender = ConsoleAppender()
|
||||
|
||||
|
|
@ -53,85 +61,96 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
|
|||
|
||||
def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[() => Option[String]] =
|
||||
scriptedTest(group, name, Logger.xlog2Log(log))
|
||||
|
||||
def scriptedTest(group: String, name: String, log: ManagedLogger): Seq[() => Option[String]] =
|
||||
scriptedTest(group, name, { _ => () }, log)
|
||||
def scriptedTest(group: String, name: String, prescripted: File => Unit, log: ManagedLogger): Seq[() => Option[String]] = {
|
||||
scriptedTest(group, name, (_ => ()), log)
|
||||
|
||||
def scriptedTest(
|
||||
group: String,
|
||||
name: String,
|
||||
prescripted: File => Unit,
|
||||
log: ManagedLogger
|
||||
): Seq[() => Option[String]] = {
|
||||
for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield {
|
||||
val g = groupDir.getName
|
||||
val n = nme.getName
|
||||
val str = s"$g / $n"
|
||||
() => {
|
||||
println("Running " + str)
|
||||
testResources.readWriteResourceDirectory(g, n) { testDirectory =>
|
||||
val disabled = new File(testDirectory, "disabled").isFile
|
||||
if (disabled) {
|
||||
log.info("D " + str + " [DISABLED]")
|
||||
None
|
||||
} else {
|
||||
try { scriptedTest(str, testDirectory, prescripted, log); None }
|
||||
catch { case _: TestException | _: PendingTestSuccessException => Some(str) }
|
||||
() =>
|
||||
{
|
||||
println("Running " + str)
|
||||
testResources.readWriteResourceDirectory(g, n) { testDirectory =>
|
||||
val disabled = new File(testDirectory, "disabled").isFile
|
||||
if (disabled) {
|
||||
log.info("D " + str + " [DISABLED]")
|
||||
None
|
||||
} else {
|
||||
try { scriptedTest(str, testDirectory, prescripted, log); None } catch {
|
||||
case _: TestException | _: PendingTestSuccessException => Some(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def scriptedTest(label: String, testDirectory: File, prescripted: File => Unit, log: ManagedLogger): Unit =
|
||||
{
|
||||
val buffered = BufferedAppender(consoleAppender)
|
||||
LogExchange.unbindLoggerAppenders(log.name)
|
||||
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
|
||||
if (bufferLog) {
|
||||
buffered.record()
|
||||
}
|
||||
def createParser() =
|
||||
{
|
||||
// val fileHandler = new FileCommands(testDirectory)
|
||||
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
|
||||
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
|
||||
val scriptConfig = new ScriptConfig(label, testDirectory, log)
|
||||
new TestScriptParser(handlersProvider getHandlers scriptConfig)
|
||||
}
|
||||
val (file, pending) = {
|
||||
val normal = new File(testDirectory, ScriptFilename)
|
||||
val pending = new File(testDirectory, PendingScriptFilename)
|
||||
if (pending.isFile) (pending, true) else (normal, false)
|
||||
}
|
||||
val pendingString = if (pending) " [PENDING]" else ""
|
||||
|
||||
def runTest() =
|
||||
{
|
||||
val run = new ScriptRunner
|
||||
val parser = createParser()
|
||||
run(parser.parse(file))
|
||||
}
|
||||
def testFailed(): Unit = {
|
||||
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
|
||||
log.error("x " + label + pendingString)
|
||||
}
|
||||
|
||||
try {
|
||||
prescripted(testDirectory)
|
||||
runTest()
|
||||
log.info("+ " + label + pendingString)
|
||||
if (pending) throw new PendingTestSuccessException(label)
|
||||
} catch {
|
||||
case e: TestException =>
|
||||
testFailed()
|
||||
e.getCause match {
|
||||
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
|
||||
case _ => if (!pending) e.printStackTrace
|
||||
}
|
||||
if (!pending) throw e
|
||||
case e: PendingTestSuccessException =>
|
||||
testFailed()
|
||||
log.error(" Mark as passing to remove this failure.")
|
||||
throw e
|
||||
case e: Exception =>
|
||||
testFailed()
|
||||
if (!pending) throw e
|
||||
} finally { buffered.clearBuffer() }
|
||||
private def scriptedTest(
|
||||
label: String,
|
||||
testDirectory: File,
|
||||
prescripted: File => Unit,
|
||||
log: ManagedLogger
|
||||
): Unit = {
|
||||
val buffered = BufferedAppender(consoleAppender)
|
||||
LogExchange.unbindLoggerAppenders(log.name)
|
||||
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
|
||||
if (bufferLog) {
|
||||
buffered.record()
|
||||
}
|
||||
def createParser() = {
|
||||
// val fileHandler = new FileCommands(testDirectory)
|
||||
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
|
||||
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
|
||||
val scriptConfig = new ScriptConfig(label, testDirectory, log)
|
||||
new TestScriptParser(handlersProvider getHandlers scriptConfig)
|
||||
}
|
||||
val (file, pending) = {
|
||||
val normal = new File(testDirectory, ScriptFilename)
|
||||
val pending = new File(testDirectory, PendingScriptFilename)
|
||||
if (pending.isFile) (pending, true) else (normal, false)
|
||||
}
|
||||
val pendingString = if (pending) " [PENDING]" else ""
|
||||
|
||||
def runTest() = {
|
||||
val run = new ScriptRunner
|
||||
val parser = createParser()
|
||||
run(parser.parse(file))
|
||||
}
|
||||
def testFailed(): Unit = {
|
||||
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
|
||||
log.error("x " + label + pendingString)
|
||||
}
|
||||
|
||||
try {
|
||||
prescripted(testDirectory)
|
||||
runTest()
|
||||
log.info("+ " + label + pendingString)
|
||||
if (pending) throw new PendingTestSuccessException(label)
|
||||
} catch {
|
||||
case e: TestException =>
|
||||
testFailed()
|
||||
e.getCause match {
|
||||
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
|
||||
case _ => if (!pending) e.printStackTrace
|
||||
}
|
||||
if (!pending) throw e
|
||||
case e: PendingTestSuccessException =>
|
||||
testFailed()
|
||||
log.error(" Mark as passing to remove this failure.")
|
||||
throw e
|
||||
case e: Exception =>
|
||||
testFailed()
|
||||
if (!pending) throw e
|
||||
} finally { buffered.clearBuffer() }
|
||||
}
|
||||
}
|
||||
|
||||
// object ScriptedTests extends ScriptedRunner {
|
||||
|
|
@ -148,31 +167,30 @@ object ListTests {
|
|||
import ListTests._
|
||||
final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) {
|
||||
def filter = DirectoryFilter -- HiddenFileFilter
|
||||
def listTests: Seq[ScriptedTest] =
|
||||
{
|
||||
list(baseDirectory, filter) flatMap { group =>
|
||||
val groupName = group.getName
|
||||
listTests(group).map(ScriptedTest(groupName, _))
|
||||
}
|
||||
}
|
||||
private[this] def listTests(group: File): Seq[String] =
|
||||
{
|
||||
def listTests: Seq[ScriptedTest] = {
|
||||
list(baseDirectory, filter) flatMap { group =>
|
||||
val groupName = group.getName
|
||||
val allTests = list(group, filter).sortBy(_.getName)
|
||||
if (allTests.isEmpty) {
|
||||
log.warn("No tests in test group " + groupName)
|
||||
Seq.empty
|
||||
} else {
|
||||
val (included, skipped) = allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
|
||||
if (included.isEmpty)
|
||||
log.warn("Test group " + groupName + " skipped.")
|
||||
else if (skipped.nonEmpty) {
|
||||
log.warn("Tests skipped in group " + group.getName + ":")
|
||||
skipped.foreach(testName => log.warn(" " + testName.getName))
|
||||
}
|
||||
Seq(included.map(_.getName): _*)
|
||||
}
|
||||
listTests(group).map(ScriptedTest(groupName, _))
|
||||
}
|
||||
}
|
||||
private[this] def listTests(group: File): Seq[String] = {
|
||||
val groupName = group.getName
|
||||
val allTests = list(group, filter).sortBy(_.getName)
|
||||
if (allTests.isEmpty) {
|
||||
log.warn("No tests in test group " + groupName)
|
||||
Seq.empty
|
||||
} else {
|
||||
val (included, skipped) =
|
||||
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
|
||||
if (included.isEmpty)
|
||||
log.warn("Test group " + groupName + " skipped.")
|
||||
else if (skipped.nonEmpty) {
|
||||
log.warn("Tests skipped in group " + group.getName + ":")
|
||||
skipped.foreach(testName => log.warn(" " + testName.getName))
|
||||
}
|
||||
Seq(included.map(_.getName): _*)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PendingTestSuccessException(label: String) extends Exception {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ trait StatementHandler {
|
|||
trait BasicStatementHandler extends StatementHandler {
|
||||
final type State = Unit
|
||||
final def initialState = ()
|
||||
final def apply(command: String, arguments: List[String], state: Unit): Unit = apply(command, arguments)
|
||||
|
||||
final def apply(command: String, arguments: List[String], state: Unit): Unit =
|
||||
apply(command, arguments)
|
||||
|
||||
def apply(command: String, arguments: List[String]): Unit
|
||||
def finish(state: Unit) = ()
|
||||
}
|
||||
|
|
@ -23,4 +26,4 @@ trait BasicStatementHandler extends StatementHandler {
|
|||
/** Use when a stack trace is not useful */
|
||||
final class TestFailed(msg: String) extends RuntimeException(msg) {
|
||||
override def fillInStackTrace = this
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,13 @@ successChar ::= '+' | '-'
|
|||
word ::= [^ \[\]]+
|
||||
comment ::= '#' \S* nl
|
||||
nl ::= '\r' \'n' | '\n' | '\r' | eof
|
||||
*/
|
||||
final case class Statement(command: String, arguments: List[String], successExpected: Boolean, line: Int) {
|
||||
*/
|
||||
final case class Statement(
|
||||
command: String,
|
||||
arguments: List[String],
|
||||
successExpected: Boolean,
|
||||
line: Int
|
||||
) {
|
||||
def linePrefix = "{line " + line + "} "
|
||||
}
|
||||
|
||||
|
|
@ -41,43 +46,50 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
|
|||
if (handlers.keys.exists(key => key == '+' || key == '-'))
|
||||
sys.error("Start characters cannot be '+' or '-'")
|
||||
|
||||
def parse(scriptFile: File): List[(StatementHandler, Statement)] = parse(read(scriptFile), Some(scriptFile.getAbsolutePath))
|
||||
def parse(scriptFile: File): List[(StatementHandler, Statement)] =
|
||||
parse(read(scriptFile), Some(scriptFile.getAbsolutePath))
|
||||
def parse(script: String): List[(StatementHandler, Statement)] = parse(script, None)
|
||||
private def parse(script: String, label: Option[String]): List[(StatementHandler, Statement)] =
|
||||
{
|
||||
parseAll(statements, script) match {
|
||||
case Success(result, next) => result
|
||||
case err: NoSuccess =>
|
||||
{
|
||||
val labelString = label.map("'" + _ + "' ").getOrElse("")
|
||||
sys.error("Could not parse test script, " + labelString + err.toString)
|
||||
}
|
||||
private def parse(script: String, label: Option[String]): List[(StatementHandler, Statement)] = {
|
||||
parseAll(statements, script) match {
|
||||
case Success(result, next) => result
|
||||
case err: NoSuccess => {
|
||||
val labelString = label.map("'" + _ + "' ").getOrElse("")
|
||||
sys.error("Could not parse test script, " + labelString + err.toString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy val statements = rep1(space ~> statement <~ newline)
|
||||
def statement: Parser[(StatementHandler, Statement)] =
|
||||
{
|
||||
trait PositionalStatement extends Positional {
|
||||
def tuple: (StatementHandler, Statement)
|
||||
}
|
||||
positioned {
|
||||
val command = (word | err("expected command"))
|
||||
val arguments = rep(space ~> (word | failure("expected argument")))
|
||||
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^
|
||||
{
|
||||
case successExpected ~ start ~ command ~ arguments =>
|
||||
new PositionalStatement {
|
||||
def tuple = (handlers(start), new Statement(command, arguments, successExpected, pos.line))
|
||||
}
|
||||
}
|
||||
} ^^ (_.tuple)
|
||||
|
||||
def statement: Parser[(StatementHandler, Statement)] = {
|
||||
trait PositionalStatement extends Positional {
|
||||
def tuple: (StatementHandler, Statement)
|
||||
}
|
||||
positioned {
|
||||
val command = (word | err("expected command"))
|
||||
val arguments = rep(space ~> (word | failure("expected argument")))
|
||||
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^ {
|
||||
case successExpected ~ start ~ command ~ arguments =>
|
||||
new PositionalStatement {
|
||||
def tuple =
|
||||
(handlers(start), new Statement(command, arguments, successExpected, pos.line))
|
||||
}
|
||||
}
|
||||
} ^^ (_.tuple)
|
||||
}
|
||||
|
||||
def successParser: Parser[Boolean] = ('+' ^^^ true) | ('-' ^^^ false) | success(true)
|
||||
def space: Parser[String] = """[ \t]*""".r
|
||||
lazy val word: Parser[String] = ("\'" ~> "[^'\n\r]*".r <~ "\'") | ("\"" ~> "[^\"\n\r]*".r <~ "\"") | WordRegex
|
||||
def startCharacterParser: Parser[Char] = elem("start character", handlers.contains _) |
|
||||
((newline | err("expected start character " + handlers.keys.mkString("(", "", ")"))) ~> failure("end of input"))
|
||||
|
||||
lazy val word: Parser[String] =
|
||||
("\'" ~> "[^'\n\r]*".r <~ "\'") | ("\"" ~> "[^\"\n\r]*".r <~ "\"") | WordRegex
|
||||
|
||||
def startCharacterParser: Parser[Char] =
|
||||
elem("start character", handlers.contains _) |
|
||||
(
|
||||
(newline | err("expected start character " + handlers.keys.mkString("(", "", ")")))
|
||||
~> failure("end of input")
|
||||
)
|
||||
|
||||
def newline = """\s*([\n\r]|$)""".r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,15 @@ object Dependencies {
|
|||
val scalatest = "org.scalatest" %% "scalatest" % "3.0.1"
|
||||
val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
|
||||
|
||||
val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value }
|
||||
val sjsonnewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value }
|
||||
val sjsonnewMurmurhash = Def.setting { "com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value }
|
||||
val sjsonnew = Def.setting {
|
||||
"com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value
|
||||
}
|
||||
val sjsonnewScalaJson = Def.setting {
|
||||
"com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value
|
||||
}
|
||||
val sjsonnewMurmurhash = Def.setting {
|
||||
"com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value
|
||||
}
|
||||
|
||||
def log4jVersion = "2.8.1"
|
||||
val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.3")
|
||||
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0")
|
||||
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.17")
|
||||
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.17")
|
||||
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.10")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ case class Miss[O](update: O => Unit) extends CacheResult[O]
|
|||
* A simple cache with keys of type `I` and values of type `O`
|
||||
*/
|
||||
trait Cache[I, O] {
|
||||
|
||||
/**
|
||||
* Queries the cache backed with store `store` for key `key`.
|
||||
*/
|
||||
|
|
@ -59,7 +60,7 @@ object Cache {
|
|||
val result = default(key)
|
||||
update(result)
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
def debug[I](label: String, cache: SingletonCache[I]): SingletonCache[I] =
|
||||
new SingletonCache[I] {
|
||||
|
|
|
|||
|
|
@ -3,5 +3,4 @@ package sbt.util
|
|||
import sjsonnew.BasicJsonProtocol
|
||||
|
||||
object CacheImplicits extends CacheImplicits
|
||||
trait CacheImplicits extends BasicCacheImplicits
|
||||
with BasicJsonProtocol
|
||||
trait CacheImplicits extends BasicCacheImplicits with BasicJsonProtocol
|
||||
|
|
|
|||
|
|
@ -9,12 +9,15 @@ import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
|||
|
||||
/** A `CacheStore` is used by the caching infrastructure to persist cached information. */
|
||||
abstract class CacheStore extends Input with Output {
|
||||
|
||||
/** Delete the persisted information. */
|
||||
def delete(): Unit
|
||||
|
||||
}
|
||||
|
||||
object CacheStore {
|
||||
implicit lazy val jvalueIsoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
implicit lazy val jvalueIsoString: IsoString[JValue] =
|
||||
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
|
||||
/** Returns file-based CacheStore using standard JSON converter. */
|
||||
def apply(cacheFile: File): CacheStore = file(cacheFile)
|
||||
|
|
@ -25,6 +28,7 @@ object CacheStore {
|
|||
|
||||
/** Factory that can make new stores. */
|
||||
abstract class CacheStoreFactory {
|
||||
|
||||
/** Create a new store. */
|
||||
def make(identifier: String): CacheStore
|
||||
|
||||
|
|
@ -36,7 +40,8 @@ abstract class CacheStoreFactory {
|
|||
}
|
||||
|
||||
object CacheStoreFactory {
|
||||
implicit lazy val jvalueIsoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
implicit lazy val jvalueIsoString: IsoString[JValue] =
|
||||
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
|
||||
/** Returns directory-based CacheStoreFactory using standard JSON converter. */
|
||||
def apply(base: File): CacheStoreFactory = directory(base)
|
||||
|
|
@ -46,29 +51,38 @@ object CacheStoreFactory {
|
|||
}
|
||||
|
||||
/** A factory that creates new stores persisted in `base`. */
|
||||
class DirectoryStoreFactory[J: IsoString](base: File, converter: SupportConverter[J]) extends CacheStoreFactory {
|
||||
class DirectoryStoreFactory[J: IsoString](base: File, converter: SupportConverter[J])
|
||||
extends CacheStoreFactory {
|
||||
IO.createDirectory(base)
|
||||
|
||||
def make(identifier: String): CacheStore = new FileBasedStore(base / identifier, converter)
|
||||
|
||||
def sub(identifier: String): CacheStoreFactory = new DirectoryStoreFactory(base / identifier, converter)
|
||||
def sub(identifier: String): CacheStoreFactory =
|
||||
new DirectoryStoreFactory(base / identifier, converter)
|
||||
}
|
||||
|
||||
/** A `CacheStore` that persists information in `file`. */
|
||||
class FileBasedStore[J: IsoString](file: File, converter: SupportConverter[J]) extends CacheStore {
|
||||
IO.touch(file, setModified = false)
|
||||
|
||||
def read[T: JsonReader]() = Using.fileInputStream(file)(stream => new PlainInput(stream, converter).read())
|
||||
def read[T: JsonReader]() =
|
||||
Using.fileInputStream(file)(stream => new PlainInput(stream, converter).read())
|
||||
|
||||
def write[T: JsonWriter](value: T) =
|
||||
Using.fileOutputStream(append = false)(file)(stream => new PlainOutput(stream, converter).write(value))
|
||||
Using.fileOutputStream(append = false)(file) { stream =>
|
||||
new PlainOutput(stream, converter).write(value)
|
||||
}
|
||||
|
||||
def delete() = IO.delete(file)
|
||||
def close() = ()
|
||||
}
|
||||
|
||||
/** A store that reads from `inputStream` and writes to `outputStream`. */
|
||||
class StreamBasedStore[J: IsoString](inputStream: InputStream, outputStream: OutputStream, converter: SupportConverter[J]) extends CacheStore {
|
||||
class StreamBasedStore[J: IsoString](
|
||||
inputStream: InputStream,
|
||||
outputStream: OutputStream,
|
||||
converter: SupportConverter[J]
|
||||
) extends CacheStore {
|
||||
def read[T: JsonReader]() = new PlainInput(inputStream, converter).read()
|
||||
def write[T: JsonWriter](value: T) = new PlainOutput(outputStream, converter).write(value)
|
||||
def delete() = ()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ object HashModifiedFileInfo {
|
|||
private final case class PlainFile(file: File, exists: Boolean) extends PlainFileInfo
|
||||
private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
|
||||
private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo
|
||||
private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo
|
||||
private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long)
|
||||
extends HashModifiedFileInfo
|
||||
|
||||
final case class FilesInfo[F <: FileInfo] private (files: Set[F])
|
||||
object FilesInfo {
|
||||
|
|
@ -52,7 +53,8 @@ object FileInfo {
|
|||
type F <: FileInfo
|
||||
|
||||
implicit def format: JsonFormat[F]
|
||||
implicit def formats: JsonFormat[FilesInfo[F]] = projectFormat(_.files, (fs: Set[F]) => FilesInfo(fs))
|
||||
implicit def formats: JsonFormat[FilesInfo[F]] =
|
||||
projectFormat(_.files, (fs: Set[F]) => FilesInfo(fs))
|
||||
|
||||
def apply(file: File): F
|
||||
def apply(files: Set[File]): FilesInfo[F] = FilesInfo(files map apply)
|
||||
|
|
@ -113,7 +115,9 @@ object FileInfo {
|
|||
|
||||
implicit def apply(file: File): HashFileInfo = FileHash(file.getAbsoluteFile, computeHash(file))
|
||||
|
||||
private def computeHash(file: File): List[Byte] = try Hash(file).toList catch { case NonFatal(_) => Nil }
|
||||
private def computeHash(file: File): List[Byte] =
|
||||
try Hash(file).toList
|
||||
catch { case NonFatal(_) => Nil }
|
||||
}
|
||||
|
||||
object lastModified extends Style {
|
||||
|
|
@ -140,7 +144,8 @@ object FileInfo {
|
|||
}
|
||||
}
|
||||
|
||||
implicit def apply(file: File): ModifiedFileInfo = FileModified(file.getAbsoluteFile, file.lastModified)
|
||||
implicit def apply(file: File): ModifiedFileInfo =
|
||||
FileModified(file.getAbsoluteFile, file.lastModified)
|
||||
}
|
||||
|
||||
object exists extends Style {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import sbt.io.{ IO, Using }
|
|||
|
||||
trait Input extends Closeable {
|
||||
def read[T: JsonReader](): T
|
||||
def read[T: JsonReader](default: => T): T = try read[T]() catch { case NonFatal(_) => default }
|
||||
def read[T: JsonReader](default: => T): T =
|
||||
try read[T]()
|
||||
catch { case NonFatal(_) => default }
|
||||
}
|
||||
|
||||
class PlainInput[J: IsoString](input: InputStream, converter: SupportConverter[J]) extends Input {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ trait Output extends Closeable {
|
|||
def write[T: JsonWriter](value: T): Unit
|
||||
}
|
||||
|
||||
class PlainOutput[J: IsoString](output: OutputStream, converter: SupportConverter[J]) extends Output {
|
||||
class PlainOutput[J: IsoString](output: OutputStream, converter: SupportConverter[J])
|
||||
extends Output {
|
||||
val isoFormat: IsoString[J] = implicitly
|
||||
|
||||
def write[T: JsonWriter](value: T) = {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ import CacheImplicits._
|
|||
* A cache that stores a single value.
|
||||
*/
|
||||
trait SingletonCache[A] {
|
||||
|
||||
/** Reads the cache from the backing `from`. */
|
||||
def read(from: Input): A
|
||||
|
||||
/** Writes `value` to the backing `to`. */
|
||||
def write(to: Output, value: A): Unit
|
||||
|
||||
}
|
||||
|
||||
object SingletonCache {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,17 @@ object StampedFormat extends BasicJsonProtocol {
|
|||
withStamp(stamp(format))(format)
|
||||
}
|
||||
|
||||
def withStamp[T, S](stamp: S)(format: JsonFormat[T])(implicit formatStamp: JsonFormat[S], equivStamp: Equiv[S]): JsonFormat[T] =
|
||||
def withStamp[T, S](stamp: S)(format: JsonFormat[T])(
|
||||
implicit formatStamp: JsonFormat[S],
|
||||
equivStamp: Equiv[S]
|
||||
): JsonFormat[T] =
|
||||
new JsonFormat[T] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
val stampedLength = unbuilder.beginArray(js)
|
||||
if (stampedLength != 2) sys.error(s"Expected JsArray of size 2, found JsArray of size $stampedLength.")
|
||||
if (stampedLength != 2)
|
||||
sys.error(s"Expected JsArray of size 2, found JsArray of size $stampedLength.")
|
||||
val readStamp = unbuilder.nextElement
|
||||
val readValue = unbuilder.nextElement
|
||||
val actualStamp = formatStamp.read(Some(readStamp), unbuilder)
|
||||
|
|
@ -34,7 +38,10 @@ object StampedFormat extends BasicJsonProtocol {
|
|||
builder.endArray()
|
||||
}
|
||||
}
|
||||
private def stamp[T](format: JsonFormat[T])(implicit mf: Manifest[JsonFormat[T]]): Int = typeHash(mf)
|
||||
|
||||
private def stamp[T](format: JsonFormat[T])(implicit mf: Manifest[JsonFormat[T]]): Int =
|
||||
typeHash(mf)
|
||||
|
||||
private def typeHash[T](implicit mf: Manifest[T]) = mf.toString.hashCode
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import sbt.internal.util.UnitSpec
|
|||
|
||||
class CacheSpec extends UnitSpec {
|
||||
|
||||
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
implicit val isoString: IsoString[JValue] =
|
||||
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
|
||||
"A cache" should "NOT throw an exception if read without being written previously" in {
|
||||
testCache[String, Int] {
|
||||
|
|
@ -68,10 +69,12 @@ class CacheSpec extends UnitSpec {
|
|||
}
|
||||
}
|
||||
|
||||
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(implicit cache: Cache[K, V]): Unit =
|
||||
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(
|
||||
implicit cache: Cache[K, V]
|
||||
): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store", Converter)
|
||||
f(cache, store)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ class SingletonCacheSpec extends UnitSpec {
|
|||
}
|
||||
}
|
||||
|
||||
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
implicit val isoString: IsoString[JValue] =
|
||||
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
|
||||
|
||||
"A singleton cache" should "throw an exception if read without being written previously" in {
|
||||
testCache[Int] {
|
||||
|
|
@ -83,10 +84,12 @@ class SingletonCacheSpec extends UnitSpec {
|
|||
}
|
||||
}
|
||||
|
||||
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(implicit cache: SingletonCache[T]): Unit =
|
||||
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(
|
||||
implicit cache: SingletonCache[T]
|
||||
): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store", Converter)
|
||||
f(cache, store)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,28 +10,36 @@ object ChangeReport {
|
|||
override def modified = files
|
||||
override def markAllModified = this
|
||||
}
|
||||
|
||||
def unmodified[T](files: Set[T]): ChangeReport[T] =
|
||||
new EmptyChangeReport[T] {
|
||||
override def checked = files
|
||||
override def unmodified = files
|
||||
}
|
||||
}
|
||||
|
||||
/** The result of comparing some current set of objects against a previous set of objects.*/
|
||||
trait ChangeReport[T] {
|
||||
|
||||
/** The set of all of the objects in the current set.*/
|
||||
def checked: Set[T]
|
||||
|
||||
/** All of the objects that are in the same state in the current and reference sets.*/
|
||||
def unmodified: Set[T]
|
||||
|
||||
/**
|
||||
* All checked objects that are not in the same state as the reference. This includes objects that are in both
|
||||
* sets but have changed and files that are only in one set.
|
||||
*/
|
||||
def modified: Set[T] // all changes, including added
|
||||
|
||||
/** All objects that are only in the current set.*/
|
||||
def added: Set[T]
|
||||
|
||||
/** All objects only in the previous set*/
|
||||
def removed: Set[T]
|
||||
def +++(other: ChangeReport[T]): ChangeReport[T] = new CompoundChangeReport(this, other)
|
||||
|
||||
/**
|
||||
* Generate a new report with this report's unmodified set included in the new report's modified set. The new report's
|
||||
* unmodified set is empty. The new report's added, removed, and checked sets are the same as in this report.
|
||||
|
|
@ -45,14 +53,16 @@ trait ChangeReport[T] {
|
|||
def removed = ChangeReport.this.removed
|
||||
override def markAllModified = this
|
||||
}
|
||||
override def toString =
|
||||
{
|
||||
val labels = List("Checked", "Modified", "Unmodified", "Added", "Removed")
|
||||
val sets = List(checked, modified, unmodified, added, removed)
|
||||
val keyValues = labels.zip(sets).map { case (label, set) => label + ": " + set.mkString(", ") }
|
||||
keyValues.mkString("Change report:\n\t", "\n\t", "")
|
||||
}
|
||||
|
||||
override def toString = {
|
||||
val labels = List("Checked", "Modified", "Unmodified", "Added", "Removed")
|
||||
val sets = List(checked, modified, unmodified, added, removed)
|
||||
val keyValues = labels.zip(sets).map { case (label, set) => label + ": " + set.mkString(", ") }
|
||||
keyValues.mkString("Change report:\n\t", "\n\t", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EmptyChangeReport[T] extends ChangeReport[T] {
|
||||
def checked = Set.empty[T]
|
||||
def unmodified = Set.empty[T]
|
||||
|
|
@ -61,7 +71,9 @@ class EmptyChangeReport[T] extends ChangeReport[T] {
|
|||
def removed = Set.empty[T]
|
||||
override def toString = "No changes"
|
||||
}
|
||||
private class CompoundChangeReport[T](a: ChangeReport[T], b: ChangeReport[T]) extends ChangeReport[T] {
|
||||
|
||||
private class CompoundChangeReport[T](a: ChangeReport[T], b: ChangeReport[T])
|
||||
extends ChangeReport[T] {
|
||||
lazy val checked = a.checked ++ b.checked
|
||||
lazy val unmodified = a.unmodified ++ b.unmodified
|
||||
lazy val modified = a.modified ++ b.modified
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ object FileFunction {
|
|||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style)(action: Set[File] => Set[File]): Set[File] => Set[File] =
|
||||
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style)(
|
||||
action: Set[File] => Set[File]
|
||||
): Set[File] => Set[File] =
|
||||
cached(cacheBaseDirectory, inStyle = inStyle, outStyle = defaultOutStyle)(action)
|
||||
|
||||
/**
|
||||
|
|
@ -64,8 +66,12 @@ object FileFunction {
|
|||
* @param outStyle The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(action: Set[File] => Set[File]): Set[File] => Set[File] =
|
||||
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)((in, out) => action(in.checked))
|
||||
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
|
||||
action: Set[File] => Set[File]
|
||||
): Set[File] => Set[File] =
|
||||
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)(
|
||||
(in, out) => action(in.checked)
|
||||
)
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
|
|
@ -103,7 +109,9 @@ object FileFunction {
|
|||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style)(action: UpdateFunction): Set[File] => Set[File] =
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style)(
|
||||
action: UpdateFunction
|
||||
): Set[File] => Set[File] =
|
||||
cached(storeFactory, inStyle = inStyle, outStyle = defaultOutStyle)(action)
|
||||
|
||||
/**
|
||||
|
|
@ -124,20 +132,21 @@ object FileFunction {
|
|||
* @param outStyle The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(action: UpdateFunction): Set[File] => Set[File] =
|
||||
{
|
||||
lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle)
|
||||
lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle)
|
||||
inputs =>
|
||||
{
|
||||
inCache(inputs) { inReport =>
|
||||
outCache { outReport =>
|
||||
if (inReport.modified.isEmpty && outReport.modified.isEmpty)
|
||||
outReport.checked
|
||||
else
|
||||
action(inReport, outReport)
|
||||
}
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
|
||||
action: UpdateFunction
|
||||
): Set[File] => Set[File] = {
|
||||
lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle)
|
||||
lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle)
|
||||
inputs =>
|
||||
{
|
||||
inCache(inputs) { inReport =>
|
||||
outCache { outReport =>
|
||||
if (inReport.modified.isEmpty && outReport.modified.isEmpty)
|
||||
outReport.checked
|
||||
else
|
||||
action(inReport, outReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import sjsonnew.JsonFormat
|
|||
import sjsonnew.support.murmurhash.Hasher
|
||||
|
||||
object Tracked {
|
||||
|
||||
/**
|
||||
* Creates a tracker that provides the last time it was evaluated.
|
||||
* If the function throws an exception.
|
||||
|
|
@ -42,21 +43,24 @@ object Tracked {
|
|||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||
* In both cases, the timestamp is not updated if the function throws an exception.
|
||||
*/
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp = tstamp(CacheStore(cacheFile), useStartTime)
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp =
|
||||
tstamp(CacheStore(cacheFile), useStartTime)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
|
||||
def diffInputs(store: CacheStore, style: FileInfo.Style): Difference =
|
||||
Difference.inputs(store, style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
|
||||
def diffInputs(cacheFile: File, style: FileInfo.Style): Difference = diffInputs(CacheStore(cacheFile), style)
|
||||
def diffInputs(cacheFile: File, style: FileInfo.Style): Difference =
|
||||
diffInputs(CacheStore(cacheFile), style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
|
||||
def diffOutputs(store: CacheStore, style: FileInfo.Style): Difference =
|
||||
Difference.outputs(store, style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
|
||||
def diffOutputs(cacheFile: File, style: FileInfo.Style): Difference = diffOutputs(CacheStore(cacheFile), style)
|
||||
def diffOutputs(cacheFile: File, style: FileInfo.Style): Difference =
|
||||
diffOutputs(CacheStore(cacheFile), style)
|
||||
|
||||
/** Creates a tracker that provides the output of the most recent invocation of the function */
|
||||
def lastOutput[I, O: JsonFormat](store: CacheStore)(f: (I, Option[O]) => O): I => O = { in =>
|
||||
|
|
@ -84,7 +88,9 @@ object Tracked {
|
|||
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
|
||||
* }}}
|
||||
*/
|
||||
def outputChanged[A1: JsonFormat, A2](store: CacheStore)(f: (Boolean, A1) => A2): (() => A1) => A2 = p => {
|
||||
def outputChanged[A1: JsonFormat, A2](store: CacheStore)(
|
||||
f: (Boolean, A1) => A2
|
||||
): (() => A1) => A2 = p => {
|
||||
val cache: SingletonCache[Long] = {
|
||||
import CacheImplicits.LongJsonFormat
|
||||
implicitly
|
||||
|
|
@ -131,7 +137,9 @@ object Tracked {
|
|||
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
|
||||
* }}}
|
||||
*/
|
||||
def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(f: (Boolean, I) => O): I => O = { in =>
|
||||
def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(
|
||||
f: (Boolean, I) => O
|
||||
): I => O = { in =>
|
||||
val cache: SingletonCache[Long] = {
|
||||
import CacheImplicits.LongJsonFormat
|
||||
implicitly
|
||||
|
|
@ -159,7 +167,9 @@ object Tracked {
|
|||
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
|
||||
* }}}
|
||||
*/
|
||||
def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(f: (Boolean, I) => O): I => O =
|
||||
def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(
|
||||
f: (Boolean, I) => O
|
||||
): I => O =
|
||||
inputChanged(CacheStore(cacheFile))(f)
|
||||
|
||||
private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) {
|
||||
|
|
@ -186,23 +196,29 @@ object Tracked {
|
|||
}
|
||||
|
||||
trait Tracked {
|
||||
|
||||
/** Cleans outputs and clears the cache.*/
|
||||
def clean(): Unit
|
||||
|
||||
}
|
||||
class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: JsonFormat[Long]) extends Tracked {
|
||||
|
||||
class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: JsonFormat[Long])
|
||||
extends Tracked {
|
||||
def clean() = store.delete()
|
||||
|
||||
/**
|
||||
* Reads the previous timestamp, evaluates the provided function,
|
||||
* and then updates the timestamp if the function completes normally.
|
||||
*/
|
||||
def apply[T](f: Long => T): T =
|
||||
{
|
||||
val start = now()
|
||||
val result = f(readTimestamp)
|
||||
store.write(if (useStartTime) start else now())
|
||||
result
|
||||
}
|
||||
def apply[T](f: Long => T): T = {
|
||||
val start = now()
|
||||
val result = f(readTimestamp)
|
||||
store.write(if (useStartTime) start else now())
|
||||
result
|
||||
}
|
||||
|
||||
private def now() = System.currentTimeMillis
|
||||
|
||||
def readTimestamp: Long =
|
||||
Try { store.read[Long] } getOrElse 0
|
||||
}
|
||||
|
|
@ -210,24 +226,30 @@ class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: J
|
|||
@deprecated("Use Tracked.inputChanged and Tracked.outputChanged instead", "1.0.1")
|
||||
class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
|
||||
def clean() = store.delete()
|
||||
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value =>
|
||||
{
|
||||
if (uptodate(value))
|
||||
ifUnchanged(value)
|
||||
else {
|
||||
update(value)
|
||||
ifChanged(value)
|
||||
}
|
||||
}
|
||||
|
||||
def update(value: O): Unit = store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
||||
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value => {
|
||||
if (uptodate(value))
|
||||
ifUnchanged(value)
|
||||
else {
|
||||
update(value)
|
||||
ifChanged(value)
|
||||
}
|
||||
}
|
||||
|
||||
def update(value: O): Unit =
|
||||
store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
||||
|
||||
def uptodate(value: O): Boolean = {
|
||||
val equiv: Equiv[O] = implicitly
|
||||
equiv.equiv(value, store.read[O])
|
||||
}
|
||||
}
|
||||
|
||||
object Difference {
|
||||
def constructor(defineClean: Boolean, filesAreOutputs: Boolean): (CacheStore, FileInfo.Style) => Difference =
|
||||
def constructor(
|
||||
defineClean: Boolean,
|
||||
filesAreOutputs: Boolean
|
||||
): (CacheStore, FileInfo.Style) => Difference =
|
||||
(store, style) => new Difference(store, style, defineClean, filesAreOutputs)
|
||||
|
||||
/**
|
||||
|
|
@ -236,55 +258,63 @@ object Difference {
|
|||
* before and after running the function.
|
||||
*/
|
||||
val outputs = constructor(true, true)
|
||||
|
||||
/**
|
||||
* Provides a constructor for a Difference that does nothing on a call to 'clean' and saves the
|
||||
* hash/last modified time of the files as they were prior to running the function.
|
||||
*/
|
||||
val inputs = constructor(false, false)
|
||||
|
||||
}
|
||||
class Difference(val store: CacheStore, val style: FileInfo.Style, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked {
|
||||
def clean() =
|
||||
{
|
||||
if (defineClean) IO.delete(raw(cachedFilesInfo)) else ()
|
||||
clearCache()
|
||||
}
|
||||
|
||||
class Difference(
|
||||
val store: CacheStore,
|
||||
val style: FileInfo.Style,
|
||||
val defineClean: Boolean,
|
||||
val filesAreOutputs: Boolean
|
||||
) extends Tracked {
|
||||
def clean() = {
|
||||
if (defineClean) IO.delete(raw(cachedFilesInfo)) else ()
|
||||
clearCache()
|
||||
}
|
||||
|
||||
private def clearCache() = store.delete()
|
||||
|
||||
private def cachedFilesInfo = store.read(default = FilesInfo.empty[style.F])(style.formats).files
|
||||
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
|
||||
|
||||
def apply[T](files: Set[File])(f: ChangeReport[File] => T): T =
|
||||
{
|
||||
val lastFilesInfo = cachedFilesInfo
|
||||
apply(files, lastFilesInfo)(f)(_ => files)
|
||||
}
|
||||
def apply[T](files: Set[File])(f: ChangeReport[File] => T): T = {
|
||||
val lastFilesInfo = cachedFilesInfo
|
||||
apply(files, lastFilesInfo)(f)(_ => files)
|
||||
}
|
||||
|
||||
def apply[T](f: ChangeReport[File] => T)(implicit toFiles: T => Set[File]): T =
|
||||
{
|
||||
val lastFilesInfo = cachedFilesInfo
|
||||
apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles)
|
||||
}
|
||||
def apply[T](f: ChangeReport[File] => T)(implicit toFiles: T => Set[File]): T = {
|
||||
val lastFilesInfo = cachedFilesInfo
|
||||
apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles)
|
||||
}
|
||||
|
||||
private def abs(files: Set[File]) = files.map(_.getAbsoluteFile)
|
||||
private[this] def apply[T](files: Set[File], lastFilesInfo: Set[style.F])(f: ChangeReport[File] => T)(extractFiles: T => Set[File]): T =
|
||||
{
|
||||
val lastFiles = raw(lastFilesInfo)
|
||||
val currentFiles = abs(files)
|
||||
val currentFilesInfo = style(currentFiles)
|
||||
|
||||
val report = new ChangeReport[File] {
|
||||
lazy val checked = currentFiles
|
||||
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
|
||||
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
|
||||
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
|
||||
lazy val unmodified = checked -- modified
|
||||
}
|
||||
private[this] def apply[T](files: Set[File], lastFilesInfo: Set[style.F])(
|
||||
f: ChangeReport[File] => T
|
||||
)(extractFiles: T => Set[File]): T = {
|
||||
val lastFiles = raw(lastFilesInfo)
|
||||
val currentFiles = abs(files)
|
||||
val currentFilesInfo = style(currentFiles)
|
||||
|
||||
val result = f(report)
|
||||
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
|
||||
|
||||
store.write(info)(style.formats)
|
||||
|
||||
result
|
||||
val report = new ChangeReport[File] {
|
||||
lazy val checked = currentFiles
|
||||
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
|
||||
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
|
||||
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
|
||||
lazy val unmodified = checked -- modified
|
||||
}
|
||||
|
||||
val result = f(report)
|
||||
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
|
||||
|
||||
store.write(info)(style.formats)
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import sbt.internal.util.UnitSpec
|
|||
class TrackedSpec extends UnitSpec {
|
||||
"lastOutput" should "store the last output" in {
|
||||
withStore { store =>
|
||||
|
||||
val value = 5
|
||||
val otherValue = 10
|
||||
|
||||
|
|
@ -132,7 +131,6 @@ class TrackedSpec extends UnitSpec {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
"tstamp tracker" should "have a timestamp of 0 on first invocation" in {
|
||||
withStore { store =>
|
||||
Tracked.tstamp(store) { last =>
|
||||
|
|
@ -143,7 +141,6 @@ class TrackedSpec extends UnitSpec {
|
|||
|
||||
it should "provide the last time a function has been evaluated" in {
|
||||
withStore { store =>
|
||||
|
||||
Tracked.tstamp(store) { last =>
|
||||
assert(last === 0)
|
||||
}
|
||||
|
|
@ -161,4 +158,4 @@ class TrackedSpec extends UnitSpec {
|
|||
f(store)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue