summaryrefslogtreecommitdiff
path: root/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala')
-rw-r--r--examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala190
1 files changed, 190 insertions, 0 deletions
diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala
new file mode 100644
index 0000000..9aad956
--- /dev/null
+++ b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/testing/TestOutputConsole.scala
@@ -0,0 +1,190 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.sbtplugin.testing
+
+import sbt.testing.Logger
+import sbt.testing.EventHandler
+
+import scala.scalajs.tools.env.JSConsole
+import scala.scalajs.tools.sourcemap.SourceMapper
+import scala.scalajs.tools.classpath.{CompleteClasspath, IRClasspath}
+
+import scala.collection.mutable.ArrayBuffer
+
+import scala.util.Try
+
+import java.util.regex._
+
+/** This parses the messages sent from the test bridge and forwards
+ * the calls to SBT. It also buffers all log messages and allows to
+ * pipe them to multiple loggers in a synchronized fashion. This
+ * ensures that log messages aren't interleaved due to parallelism.
+ */
+class TestOutputConsole(
+ base: JSConsole,
+ handler: EventHandler,
+ events: Events,
+ classpath: CompleteClasspath,
+ noSourceMap: Boolean) extends JSConsole {
+
+ import TestOutputConsole._
+ import events._
+
+ private val traceBuf = new ArrayBuffer[StackTraceElement]
+ private val logBuffer = new ArrayBuffer[LogElement]
+
+ /* See #727: source mapping does not work with CompleteIRClasspath, so
+ * don't bother to try.
+ */
+ private val ignoreSourceMapping =
+ noSourceMap || classpath.isInstanceOf[IRClasspath]
+
+ private lazy val sourceMapper = new SourceMapper(classpath)
+
+ override def log(msg: Any): Unit = {
+ val data = msg.toString
+ val sepPos = data.indexOf("|")
+
+ if (sepPos == -1)
+ log(_.error, s"Malformed message: $data")
+ else {
+ val op = data.substring(0, sepPos)
+ val message = unescape(data.substring(sepPos + 1))
+
+ op match {
+ case "console-log" =>
+ base.log(message)
+ case "error" =>
+ val trace = getTrace()
+ logWithEvent(_.error,
+ messageWithStack(message, trace),
+ Error(new TestException(message, trace))
+ )
+ case "failure" =>
+ val trace = getTrace()
+ logWithEvent(_.error,
+ messageWithStack(message, trace),
+ Failure(new TestException(message, trace))
+ )
+ case "succeeded" =>
+ noTrace()
+ logWithEvent(_.info, message, Succeeded)
+ case "skipped" =>
+ noTrace()
+ logWithEvent(_.info, message, Skipped)
+ case "pending" =>
+ noTrace()
+ logWithEvent(_.info, message, Pending)
+ case "ignored" =>
+ noTrace()
+ logWithEvent(_.info, message, Ignored)
+ case "canceled" =>
+ noTrace()
+ logWithEvent(_.info, message, Canceled)
+ case "error-log" =>
+ noTrace()
+ log(_.error, message)
+ case "info" =>
+ noTrace()
+ log(_.info, message)
+ case "warn" =>
+ noTrace()
+ log(_.warn, message)
+ case "trace" =>
+ val Array(className, methodName, fileName,
+ lineNumberStr, columnNumberStr) = message.split('|')
+
+ def tryParse(num: String, name: String) = Try(num.toInt).getOrElse {
+ log(_.warn, s"Couldn't parse $name number in StackTrace: $num")
+ -1
+ }
+
+ val lineNumber = tryParse(lineNumberStr, "line")
+ val columnNumber = tryParse(columnNumberStr, "column")
+
+ val ste =
+ new StackTraceElement(className, methodName, fileName, lineNumber)
+
+ if (ignoreSourceMapping)
+ traceBuf += ste
+ else
+ traceBuf += sourceMapper.map(ste, columnNumber)
+ case _ =>
+ noTrace()
+ log(_.error, s"Unknown op: $op. Originating log message: $data")
+ }
+ }
+ }
+
+ private def noTrace() = {
+ if (traceBuf.nonEmpty)
+ log(_.warn, s"Discarding ${traceBuf.size} stack elements")
+ traceBuf.clear()
+ }
+
+ private def getTrace() = {
+ val res = traceBuf.toArray
+ traceBuf.clear()
+ res
+ }
+
+ private def messageWithStack(message: String, stack: Array[StackTraceElement]): String =
+ message + stack.mkString("\n", "\n", "")
+
+ private def log(method: LogMethod, message: String): Unit =
+ logBuffer.append(LogElement(method, message))
+
+ private def logWithEvent(method: LogMethod,
+ message: String, event: Event): Unit = {
+ handler handle event
+ log(method, message)
+ }
+
+ def pipeLogsTo(loggers: Array[Logger]): Unit = {
+ TestOutputConsole.synchronized {
+ for {
+ LogElement(method, message) <- logBuffer
+ logger <- loggers
+ } method(logger) {
+ if (logger.ansiCodesSupported) message
+ else removeColors(message)
+ }
+ }
+ }
+
+ def allLogs: List[LogElement] = logBuffer.toList
+
+ private val colorPattern = raw"\033\[\d{1,2}m"
+
+ private def removeColors(message: String): String =
+ message.replaceAll(colorPattern, "")
+
+ private val unEscPat = Pattern.compile("(\\\\\\\\|\\\\n|\\\\r)")
+ private def unescape(message: String): String = {
+ val m = unEscPat.matcher(message)
+ val res = new StringBuffer()
+ while (m.find()) {
+ val repl = m.group() match {
+ case "\\\\" => "\\\\"
+ case "\\n" => "\n"
+ case "\\r" => "\r"
+ }
+ m.appendReplacement(res, repl);
+ }
+ m.appendTail(res);
+ res.toString
+ }
+
+}
+
+object TestOutputConsole {
+ type LogMethod = Logger => (String => Unit)
+ case class LogElement(method: LogMethod, message: String)
+}