From c8fe1a3c1d32d98a3ecfd84547fffe8db9b83c25 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 7 May 2011 22:02:06 -0400 Subject: [PATCH] buffered, separate loggers for each test --- main/Defaults.scala | 21 ++++++++++++++++++++- main/Keys.scala | 2 ++ main/LogManager.scala | 2 +- testing/TestFramework.scala | 12 +++++++++--- testing/TestReportListener.scala | 21 ++++++++++++++++----- 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index ac1db6e0d..bb3fd322d 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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 => diff --git a/main/Keys.scala b/main/Keys.scala index 6837788cf..5e5d10b81 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -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]] diff --git a/main/LogManager.scala b/main/LogManager.scala index dbdfd87c7..8c48f6322 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -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)) diff --git a/testing/TestFramework.scala b/testing/TestFramework.scala index 6e3bdfe5d..9ca99564c 100644 --- a/testing/TestFramework.scala +++ b/testing/TestFramework.scala @@ -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) diff --git a/testing/TestReportListener.scala b/testing/TestReportListener.scala index 0bf36b7b7..e2f83840c 100644 --- a/testing/TestReportListener.scala +++ b/testing/TestReportListener.scala @@ -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)) }