Bill, Mark, Peter, All,
I have code that fixes this (it works on my branch), it's just up to Mark to put it into sbt. Here is the link to the code:
https://scalatest.dev.java.net/source/browse/scalatest/branches/josh-sbt-2/app/src/main/scala/org/scalatest/sbt/ScalaTestRunner.scala?rev=2017&view=markup
For anyone that wants to use sbt with scalatest at this very second, you can do this in two steps:
1) Put ScalaTestRunner in your own source code in the org.scalatest.sbt package.
2) Add this line to your sbt project file:
override def compileClasspath = super.compileClasspath +++ Path.fromFile(FileUtilities.sbtJar)
However, last time I tried this was at least a week back. Hopefully it still works. Here is the code.
package org.scalatest.sbt
import _root_.sbt._
/**The test runner for ScalaTest suites. */
class ScalaTestRunner(val log: Logger, val listeners: Seq[TestReportListener],
val testLoader: ClassLoader) extends BasicTestRunner
{
import _root_.java.lang.reflect.Modifier
def runTest(testClassName: String): Result.Value = {
val testClass = Class.forName(testClassName, true, testLoader).asSubclass(classOf[Suite])
if( isAccessibleSuite(testClass)){
val test = testClass.newInstance
val reporter = new ScalaTestReporter
test.run(None, reporter, new Stopper {}, Filter(), Map.empty, None, new Tracker)
if (reporter.succeeded) Result.Passed else Result.Failed
} else{
Result.Passed
}
}
private val emptyClassArray = new Array[java.lang.Class[T] forSome { type T }](0)
private def isAccessibleSuite(clazz: java.lang.Class[_]): Boolean = {
try {
classOf[Suite].isAssignableFrom(clazz) &&
Modifier.isPublic(clazz.getModifiers) &&
!Modifier.isAbstract(clazz.getModifiers) &&
Modifier.isPublic(clazz.getConstructor(emptyClassArray: _*).getModifiers)
} catch {
case nsme: NoSuchMethodException => false
case se: SecurityException => false
}
}
/**An implementation of Reporter for ScalaTest. */
private class ScalaTestReporter extends org.scalatest.Reporter with NotNull
{
import org.scalatest.events._
var succeeded = true
def apply(event: Event) {
event match {
// why log.info sometimes and fire(MessageEvent...) other times?
case rc: RunCompleted => log.info("Run completed.")
case rs: RunStarting => fire(MessageEvent("Run starting"))
case rs: RunStopped => {succeeded = false; fire(ErrorEvent("Run stopped"))}
case ra: RunAborted => {succeeded = false; fire(ErrorEvent("Run aborted"))}
// this one seems to be working really well
case ts: TestStarting => fire(TypedEvent(ts.testName, "Test Starting", None)(None))
// not sure what to do here at all
case tp: TestPending =>
case tf: TestFailed => {succeeded = false; fire(TypedErrorEvent(tf.testName, "Test Failed", None, tf.throwable)(Some(Result.Failed)))}
// this one also seems to be working really well
case ts: TestSucceeded => fire(TypedEvent(ts.testName, "Test Succeeded", None)(Some(Result.Passed)))
// need to check if there is a reason why this test was ignored
case ti: TestIgnored => fire(IgnoredEvent(ti.testName, Some("test ignored")))
case sc: SuiteCompleted => fire(TypedEvent(sc.suiteName, "Suite Completed", None)(None))
// why not sure Some(Result.Failed) here?
// also, why not say succeeded = false?
// seems like i should do both if the suite is aborted.
case sa: SuiteAborted => fire(TypedErrorEvent(sa.suiteName, "Suite Aborted", Some(sa.message), sa.throwable)(None))
case ss: SuiteStarting => fire(TypedEvent(ss.suiteName, "Suite Starting", None)(None))
// not actually sure if this is what i should do here...info provided is really just...some random extra info provided by a test, like a log statement
case ip: InfoProvided => fire(MessageEvent(ip.message))
}
}
}
}