import sbt._
import org.scalatools.testing.{Event => TEvent, Result => TResult}
-import java.util.concurrent.atomic.AtomicInteger
-
object SbtTapReporting extends Plugin {
- lazy val tapListener = new SbtTapListener
+ def apply() = new SbtTapListener
}
/**
* the current active group might be one way to go.
*/
class SbtTapListener extends TestsListener {
- var testId = new AtomicInteger(0)
+ var testId = 0
var fileWriter: FileWriter = _
- override def doInit {
- new File("test-results").mkdirs()
-
- fileWriter = new FileWriter("test-results/test.tap")
+ override def doInit = {
+ val filename = scala.util.Properties.envOrElse("SBT_TAP_OUTPUT", "test-results/test.tap")
+ val file = new File(filename)
+ new File(file.getParent).mkdirs
+ fileWriter = new FileWriter(file)
+ writeTap("TAP", "version", 13)
}
- def startGroup(name: String) {}
+ def startGroup(name: String) =
+ writeTapDiag("start", name)
+
+ def endGroup(name: String, result: TestResult.Value) =
+ writeTapDiag("end", name, "with result", result.toString.toLowerCase)
+
+ def endGroup(name: String, t: Throwable) = {
+ writeTapDiag("end", name)
+ writeTapDiag(stackTraceForError(t))
+ }
- def testEvent(event: TestEvent) {
- event.detail.foreach { e: TEvent =>
+ def testEvent(event: TestEvent) = this.synchronized {
+ event.detail.foreach { e: TEvent => testId += 1
+ var modified = false
+ val description = (if (e.testName.contains("#")) {
+ modified = true
+ e.testName.replaceAll("#", "")
+ } else e.testName).replaceAll("\n", "")
e.result match {
- case TResult.Success => writeTapFields("ok", testId.incrementAndGet(), "-", e.testName())
- case TResult.Error | TResult.Failure =>
- writeTapFields("not ok", testId.incrementAndGet(), "-", e.testName())
- // According to the TAP spec, as long as there is any kind of whitespace, this output should belong to the
- // the test that failed and it should get displayed in the UI.
- // TODO:It would be nice if we could report the exact line in the test where this happened.
- writeTapFields(" ", stackTraceForError(e.error()))
+ case TResult.Success =>
+ writeTap("ok", testId, "-", description)
case TResult.Skipped =>
- // it doesn't look like this framework distinguishes between pending and ignored.
- writeTapFields("ok", testId.incrementAndGet(), e.testName(), "#", "skip", e.testName())
+ writeTap("ok", testId, "-", description, "# SKIP")
+ case TResult.Error | TResult.Failure =>
+ writeTap("not ok", testId, "-", description)
+ // TODO: It would be nice if we could report the exact line in the test where this happened.
+ writeTapDiag(stackTraceForError(e.error))
}
+ if (modified) writeTapDiag("warning: hash character(s) removed from test " + testId + " description")
}
}
- override def doComplete(finalResult: TestResult.Value) {
- writeTapFields("1.." + testId.get)
- fileWriter.close()
+ override def doComplete(finalResult: TestResult.Value) = {
+ writeTap("1.." + testId)
+ fileWriter.close
}
- private def writeTapFields(s: Any*) { fileWriter.write(s.mkString("", " ", "\n")) }
+ private def writeTap(s: Any*) = {
+ fileWriter.write(s.mkString("", " ", "\n"))
+ fileWriter.flush
+ }
+
+ private def writeTapDiag(s: Any*) =
+ writeTap("#", s.mkString("", " ", "\n").trim.replaceAll("\\n", "\n# "))
private def stackTraceForError(t: Throwable): String = {
- val sw = new StringWriter()
+ val sw = new StringWriter
val printWriter = new PrintWriter(sw)
t.printStackTrace(printWriter)
sw.toString
}
- def endGroup(name: String, t: Throwable) { }
-
- def endGroup(name: String, result: TestResult.Value) { }
}