Fixed scripted test framework to test version of sbt being built. Trying out cross-classloader logger.

git-svn-id: https://simple-build-tool.googlecode.com/svn/trunk@884 d89573ee-9141-11dd-94d4-bdf5e562f29c
This commit is contained in:
dmharrah 2009-07-25 21:43:25 +00:00
parent d0a9e63140
commit 6cbe27ed95
4 changed files with 83 additions and 46 deletions

View File

@ -4,7 +4,9 @@
package sbt.test
import Scripted._
import FileUtilities.{classLocation, sbtJar, scalaCompilerJar, scalaLibraryJar, wrapNull}
import java.io.File
import java.net.URLClassLoader
trait ScalaScripted extends BasicScalaProject with Scripted with MavenStyleScalaPaths
{
@ -16,7 +18,7 @@ trait ScalaScripted extends BasicScalaProject with Scripted with MavenStyleScala
lazy val scriptedOnly = scriptedMethodTask(scriptedDependencies : _*)
override def scriptedClasspath = runClasspath +++ Path.lazyPathFinder { Path.fromFile(FileUtilities.sbtJar) :: Nil }
override def scriptedClasspath = runClasspath +++ Path.lazyPathFinder { Path.fromFile(sbtJar) :: Nil }
}
trait SbtScripted extends ScalaScripted
{
@ -35,32 +37,39 @@ final case class ScriptedTest(group: String, name: String) extends NotNull
trait Scripted extends Project with MultiTaskProject
{
def sbtTests: Path
def scriptedTask(dependencies: ManagedTask*) = compoundTask(scriptedTests(listTests, dependencies : _*))
def scriptedTask(dependencies: ManagedTask*) = dynamic(scriptedTests(listTests, dependencies : _*))
def scriptedMethodTask(dependencies: ManagedTask*) = multiTask(listTests.map(_.toString).toList) { includeFunction =>
scriptedTests(listTests.filter(test => includeFunction(test.toString)), dependencies : _*)
}
def listTests = (new ListTests(sbtTests.asFile, include _, log)).listTests
def scriptedTests(tests: Seq[ScriptedTest], dependencies: ManagedTask*) =
{
// load ScriptedTests using a ClassLoader that loads from the project classpath so that the version
// of sbt being built is tested, not the one doing the building.
val loader = ScriptedLoader(_scriptedClasspath.toArray)
val scriptedClass = Class.forName(ScriptedClassName, true, loader)
val scriptedConstructor = scriptedClass.getConstructor(classOf[File], classOf[ClassLoader])
val rawRunner = scriptedConstructor.newInstance(sbtTests.asFile, loader)
val runner = rawRunner.asInstanceOf[{def scriptedTest(group: String, name: String, log: Logger): Option[String]}]
val localLogger = new LocalLogger(log)
lazy val runner =
{
// load ScriptedTests using a ClassLoader that loads from the project classpath so that the version
// of sbt being built is tested, not the one doing the building.
val filtered = new FilteredLoader(ClassLoader.getSystemClassLoader, Seq("sbt.", "scala.", "ch.epfl.", "org.apache.", "org.jsch."))
val loader = new URLClassLoader(_scriptedClasspath.toArray, filtered)
val scriptedClass = Class.forName(ScriptedClassName, true, loader)
val scriptedConstructor = scriptedClass.getConstructor(classOf[File], classOf[ClassLoader])
val rawRunner = scriptedConstructor.newInstance(sbtTests.asFile, loader)
rawRunner.asInstanceOf[{def scriptedTest(group: String, name: String, log: Reflected.Logger): String}]
}
val startTask = task { None } named("scripted-test-start") dependsOn(dependencies : _*)
def scriptedTest(test: ScriptedTest) = task { runner.scriptedTest(test.group, test.name, log) } named test.toString dependsOn(startTask)
def scriptedTest(test: ScriptedTest) =
task { unwrapOption(runner.scriptedTest(test.group, test.name, localLogger)) } named test.toString dependsOn(startTask)
val testTasks = tests.map(scriptedTest)
task { None } named("scripted-test-complete") dependsOn(testTasks : _*)
}
private def unwrapOption[T](s: T): Option[T] = if(s == null) None else Some(s)
/** The classpath to use for scripted tests. This ensures that the version of sbt being built is the one used for testing.*/
private def _scriptedClasspath =
{
val buildClasspath = FileUtilities.classLocation[Scripted]
val scalacJar = FileUtilities.scalaCompilerJar.toURI.toURL
buildClasspath :: scalacJar :: scriptedClasspath.get.map(_.asURL).toList
val buildClasspath = classLocation[Scripted]
val scalaJars = List(scalaLibraryJar, scalaCompilerJar).map(_.toURI.toURL).toList
buildClasspath :: scalaJars ::: scriptedClasspath.get.map(_.asURL).toList
}
def scriptedClasspath: PathFinder = Path.emptyPathFinder
@ -71,7 +80,7 @@ private[test] object Scripted
{
val ScriptedClassName = "sbt.test.ScriptedTests"
val SbtTestDirectoryName = "sbt-test"
def list(directory: File, filter: java.io.FileFilter) = FileUtilities.wrapNull(directory.listFiles(filter))
def list(directory: File, filter: java.io.FileFilter) = wrapNull(directory.listFiles(filter))
}
private[test] final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) extends NotNull
{

View File

@ -1,29 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package sbt.test
import java.net.URL
object ScriptedLoader
{
def apply(paths: Array[URL]): ClassLoader = new ScriptedLoader(paths)
}
private class ScriptedLoader(paths: Array[URL]) extends LoaderBase(paths, classOf[ScriptedLoader].getClassLoader)
{
private val delegateFor = List("sbt.Logger", "sbt.LogEvent", "sbt.SetLevel", "sbt.Success", "sbt.Log", "sbt.SetTrace", "sbt.Trace", "sbt.ControlEvent")
def doLoadClass(className: String): Class[_] =
{
// Logger needs to be loaded from the version of sbt building the project because we need to pass
// a Logger from that loader into ScriptedTests.
// All other sbt classes should be loaded from the project classpath so that we test those classes with 'scripted'
if(!shouldDelegate(className) && (className.startsWith("sbt.") || className.startsWith("scala.tools.")))
findClass(className)
else
selfLoadClass(className)
}
private def shouldDelegate(className: String) = delegateFor.exists(check => isNestedOrSelf(className, check))
private def isNestedOrSelf(className: String, checkAgainst: String) =
className == checkAgainst || className.startsWith(checkAgainst + "$")
}

View File

@ -0,0 +1,50 @@
package sbt.test
object Reflected
{
type Logger =
{
def enableTrace(flag: Boolean): Unit
def traceEnabled: Boolean
def getLevel: Int
def setLevel(level: Int): Unit
def trace(t: F0[Throwable]): Unit
def success(message: F0[String]): Unit
def log(level: Int, message: F0[String]): Unit
def control(event: Int, message: F0[String]): Unit
}
type F0[T] =
{
def apply(): T
}
}
final class LocalLogger(logger: Logger) extends NotNull
{
import Reflected.F0
def enableTrace(flag: Boolean) = logger.enableTrace(flag)
def traceEnabled = logger.traceEnabled
def getLevel = logger.getLevel.id
def setLevel(level: Int) = logger.setLevel(Level(level))
def trace(t: F0[Throwable]) = logger.trace(t())
def success(message: F0[String]) = logger.success(message())
def log(level: Int, message: F0[String]) = logger.log(Level(level), message())
def control(event: Int, message: F0[String]) = logger.control(ControlEvent(event), message())
}
final class RemoteLogger(logger: Reflected.Logger) extends Logger
{
private final class F0[T](s: => T) extends NotNull { def apply(): T = s }
def getLevel: Level.Value = Level(logger.getLevel)
def setLevel(newLevel: Level.Value) = logger.setLevel(newLevel.id)
def enableTrace(flag: Boolean) = logger.enableTrace(flag)
def traceEnabled = logger.traceEnabled
def trace(t: => Throwable) = logger.trace(new F0(t))
def success(message: => String) = logger.success(new F0(message))
def log(level: Level.Value, message: => String) = logger.log(level.id, new F0(message))
def control(event: ControlEvent.Value, message: => String) = logger.control(event.id, new F0(message))
def logAll(events: Seq[LogEvent]) = events.foreach(log)
}

View File

@ -14,12 +14,19 @@ final class ScriptedTests(testResources: Resources) extends NotNull
val ScriptFilename = "test"
import testResources._
def scriptedTest(group: String, name: String, log: Logger): Option[String] =
readOnlyResourceDirectory(group, name).fold(err => Some(err), testDirectory => scriptedTest(testDirectory, log))
def scriptedTest(testDirectory: File, log: Logger): Option[String] =
private def printClass(c: Class[_]) = println(c.getName + " loader=" +c.getClassLoader + " location=" + FileUtilities.classLocationFile(c))
def scriptedTest(group: String, name: String, logger: Reflected.Logger): String =
{
val log = new RemoteLogger(logger)
val result = readOnlyResourceDirectory(group, name).fold(err => Some(err), testDirectory => scriptedTest(testDirectory, log))
wrapOption(result)
}
private def scriptedTest(testDirectory: File, log: Logger): Option[String] =
{
(for(script <- (new TestScriptParser(testDirectory, log)).parse(new File(testDirectory, ScriptFilename)).right;
u <- withProject(testDirectory, log)(script).right )
yield u).left.toOption
}
private[this] def wrapOption[T >: Null](s: Option[T]): T = s match { case Some(t) => t; case None => null }
}