buffered, separate loggers for each test

This commit is contained in:
Mark Harrah 2011-05-07 22:02:06 -04:00
parent e653517c8d
commit c8fe1a3c1d
5 changed files with 48 additions and 10 deletions

View File

@ -10,6 +10,7 @@ package sbt
import Configurations.{Compile, CompilerPlugin, IntegrationTest, Runtime, Test}
import complete._
import std.TaskExtra._
import org.scalatools.testing.{AnnotatedFingerprint, SubclassFingerprint}
import scala.xml.{Node => XNode,NodeSeq}
import org.apache.ivy.core.module.{descriptor, id}
@ -44,6 +45,9 @@ object Defaults
))
def globalCore: Seq[Setting[_]] = inScope(GlobalScope)(Seq(
pollInterval :== 500,
logBuffered :== false,
logBuffered in testOnly :== true,
logBuffered in test :== true,
autoCompilerPlugins :== true,
internalConfigurationMap :== Configurations.internalMap _,
initialize :== (),
@ -219,9 +223,24 @@ object Defaults
)
def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( Seq(
testListeners <<= (streams, testListeners) map { (s, ls) => TestLogger(s.log) +: ls },
testListeners <<= (streams, resolvedScoped, streamsManager, logBuffered in key, testListeners) map { (s, sco, sm, buff, ls) =>
TestLogger(s.log, testLogger(sm, test in sco.scope), buff) +: ls
},
testOptions <<= (testOptions, testListeners) map { (options, ls) => Tests.Listeners(ls) +: options }
) )
def testLogger(manager: Streams, baseKey: Scoped)(tdef: TestDefinition): Logger =
{
val scope = baseKey.scope
val extra = scope.extra match { case Select(x) => x; case _ => AttributeMap.empty }
val key = ScopedKey(scope.copy(extra = Select(testExtra(extra, tdef))), baseKey.key)
manager(key).log
}
def buffered(log: Logger): Logger = new BufferedLogger(FullLogger(log))
def testExtra(extra: AttributeMap, tdef: TestDefinition): AttributeMap =
{
val mod = tdef.fingerprint match { case f: SubclassFingerprint => f.isModule; case f: AnnotatedFingerprint => f.isModule; case _ => false }
extra.put(name.key, tdef.name).put(isModule, mod)
}
def testOnlyTask =
InputTask( TaskData(definedTests)(testOnlyParser)(Nil) ) { result =>

View File

@ -27,6 +27,7 @@ object Keys
val showTiming = SettingKey[Boolean]("show-timing", "If true, the command success message includes the completion time.")
val timingFormat = SettingKey[java.text.DateFormat]("timing-format", "The format used for displaying the completion time.")
val logManager = SettingKey[LogManager]("log-manager", "The log manager, which creates Loggers for different contexts.")
val logBuffered = SettingKey[Boolean]("log-buffered", "True if logging should be buffered until work completes.")
// Project keys
val projectCommand = AttributeKey[Boolean]("project-command", "Marks Commands that were registered for the current Project.")
@ -154,6 +155,7 @@ object Keys
val testOptions = TaskKey[Seq[TestOption]]("test-options", "Options for running tests.")
val testFrameworks = SettingKey[Seq[TestFramework]]("test-frameworks", "Registered, although not necessarily present, test frameworks.")
val testListeners = TaskKey[Seq[TestReportListener]]("test-listeners", "Defines test listeners.")
val isModule = AttributeKey[Boolean]("is-module", "True if the target is a module.")
// Classpath/Dependency Management Keys
type Classpath = Seq[Attributed[File]]

View File

@ -24,7 +24,7 @@ object LogManager
def withScreenLogger(mk: => AbstractLogger): LogManager = withLoggers(mk)
def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(false)): LogManager =
def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(true)): LogManager =
new LogManager {
def apply(data: Settings[Scope], task: ScopedKey[_], to: PrintWriter): Logger =
defaultLogger(data, task, screen, backed(to))

View File

@ -26,7 +26,7 @@ object TestFrameworks
val SpecsCompat = new TestFramework("sbt.impl.SpecsFramework")
}
class TestFramework(val implClassName: String) extends NotNull
class TestFramework(val implClassName: String)
{
def create(loader: ClassLoader, log: Logger): Option[Framework] =
{
@ -34,7 +34,7 @@ class TestFramework(val implClassName: String) extends NotNull
catch { case e: ClassNotFoundException => log.debug("Framework implementation '" + implClassName + "' not present."); None }
}
}
final class TestDefinition(val name: String, val fingerprint: Fingerprint) extends NotNull
final class TestDefinition(val name: String, val fingerprint: Fingerprint)
{
override def toString = "Test " + name + " : " + TestFramework.toString(fingerprint)
override def equals(t: Any) =
@ -47,8 +47,14 @@ final class TestDefinition(val name: String, val fingerprint: Fingerprint) exten
final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq[TestReportListener], log: Logger)
{
private[this] val delegate = framework.testRunner(loader, listeners.flatMap(_.contentLogger).toArray)
private[this] def run(testDefinition: TestDefinition, handler: EventHandler, args: Array[String]): Unit =
{
val loggers = listeners.flatMap(_.contentLogger(testDefinition))
val delegate = framework.testRunner(loader, loggers.map(_.log).toArray)
try { delegateRun(delegate, testDefinition, handler, args) }
finally { loggers.foreach( _.flush() ) }
}
private[this] def delegateRun(delegate: Runner, testDefinition: TestDefinition, handler: EventHandler, args: Array[String]): Unit =
(testDefinition.fingerprint, delegate) match
{
case (simple: TestFingerprint, _) => delegate.run(testDefinition.name, simple, handler, args)

View File

@ -17,7 +17,7 @@ trait TestReportListener
/** called if test completed */
def endGroup(name: String, result: TestResult.Value)
/** Used by the test framework for logging test results*/
def contentLogger: Option[TLogger] = None
def contentLogger(test: TestDefinition): Option[ContentLogger] = None
}
trait TestsListener extends TestReportListener
@ -52,7 +52,15 @@ object TestEvent
object TestLogger
{
def apply(logger: sbt.Logger): TestLogger = new TestLogger(wrap(logger))
def apply(logger: sbt.Logger, logTest: TestDefinition => sbt.Logger, buffered: Boolean): TestLogger =
new TestLogger(new TestLogging(wrap(logger), tdef => contentLogger(logTest(tdef), buffered)) )
def contentLogger(log: sbt.Logger, buffered: Boolean): ContentLogger =
{
val blog = new BufferedLogger(FullLogger(log))
if(buffered) blog.record()
new ContentLogger(wrap(blog), () => blog.stopQuietly())
}
def wrap(logger: sbt.Logger): TLogger =
new TLogger
{
@ -62,11 +70,14 @@ object TestLogger
def debug(s: String) = log(Level.Debug, s)
def trace(t: Throwable) = logger.trace(t)
private def log(level: Level.Value, s: String) = logger.log(level, s)
def ansiCodesSupported() = logger.ansiCodesSupported
def ansiCodesSupported() = true//logger.ansiCodesSupported
}
}
class TestLogger(val log: TLogger) extends TestsListener
final class TestLogging(val global: TLogger, val logTest: TestDefinition => ContentLogger)
final class ContentLogger(val log: TLogger, val flush: () => Unit)
class TestLogger(val logging: TestLogging) extends TestsListener
{
import logging.{global => log, logTest}
protected var skipped, errors, passed, failures = 0
def startGroup(name: String) {}
@ -106,5 +117,5 @@ class TestLogger(val log: TLogger) extends TestsListener
case TestResult.Failed => log.error("Failed: " + postfix)
}
}
override def contentLogger: Option[TLogger] = Some(log)
override def contentLogger(test: TestDefinition): Option[ContentLogger] = Some(logTest(test))
}