testQuick: track previous test status.

This commit is contained in:
Eugene Vigdorchik 2012-03-05 17:17:55 +04:00
parent fe753768d9
commit 6e0ad08ad3
4 changed files with 72 additions and 85 deletions

View File

@ -64,11 +64,8 @@ object Defaults extends BuildCommon
logBuffered :== false,
connectInput :== false,
cancelable :== false,
cancelable :== false,
autoScalaLibrary :== true,
onLoad <<= onLoad ?? idFun[State],
tags in test := Seq(Tags.Test -> 1),
tags in testOnly <<= tags in test,
onUnload <<= (onUnload ?? idFun[State]),
onUnload <<= (onUnload, taskTemporaryDirectory) { (f, dir) => s => { try f(s) finally IO.delete(dir) } },
watchingMessage <<= watchingMessage ?? Watched.defaultWatchingMessage,
@ -78,8 +75,6 @@ object Defaults extends BuildCommon
trapExit in run :== true,
traceLevel in run :== 0,
traceLevel in runMain :== 0,
logBuffered in testOnly :== true,
logBuffered in test :== true,
traceLevel in console :== Int.MaxValue,
traceLevel in consoleProject :== Int.MaxValue,
autoCompilerPlugins :== true,
@ -121,7 +116,13 @@ object Defaults extends BuildCommon
includeFilter in unmanagedResources :== AllPassFilter,
excludeFilter :== (".*" - ".") || HiddenFileFilter,
pomIncludeRepository :== Classpaths.defaultRepositoryFilter
))
) ++ {
val testSettings = for (task <- Seq(test, testOnly, testQuick)) yield Seq[Setting[_]](
logBuffered in task := true,
tags in task := Seq(Tags.Test -> 1)
)
testSettings.flatten
})
def projectCore: Seq[Setting[_]] = Seq(
name <<= thisProject(_.id),
logManager <<= extraLoggers(LogManager.defaults),
@ -285,16 +286,18 @@ object Defaults extends BuildCommon
definedTestNames <<= definedTests map ( _.map(_.name).distinct) storeAs definedTestNames triggeredBy compile,
testListeners in GlobalScope :== Nil,
testOptions in GlobalScope :== Nil,
testExecution in test <<= testExecutionTask(test),
testExecution in testOnly <<= testExecutionTask(testOnly),
testFilter in testOnly :== (selectedFilter _),
testFilter in testQuick <<= testQuickFilter,
executeTests <<= (streams in test, loadedTestFrameworks, testExecution in test, testLoader, definedTests, resolvedScoped, state) flatMap {
(s, frameworkMap, config, loader, discovered, scoped, st) =>
implicit val display = Project.showContextKey(st)
Tests(frameworkMap, loader, discovered, config, noTestsMessage(ScopedKey(scoped.scope, test.key)), s.log)
},
test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) },
testOnly <<= testOnlyTask
testOnly <<= inputTests(testOnly),
testQuick <<= inputTests(testQuick)
) ++ (
for (task <- Seq(test, testOnly, testQuick)) yield testExecution in task <<= testExecutionTask(task)
)
private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String =
"No tests to run for " + display(scoped)
@ -324,7 +327,14 @@ object Defaults extends BuildCommon
def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] =
(testOptions in task, parallelExecution in task, tags in task) map { (opts, par, ts) => new Tests.Execution(opts, par, ts) }
def testOnlyTask: Initialize[InputTask[Unit]] = inputTests(testOnly)
def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] =
(compile in test, cacheDirectory) map {
case (analysis, dir) =>
val succeeded = new TestResultFilter(dir / "succeeded_tests")
args => test => selectedFilter(args)(test) && {
!succeeded(test) // Add recompilation status.
}
}
def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] =
InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result =>

View File

@ -192,6 +192,7 @@ object Keys
val executeTests = TaskKey[Tests.Output]("execute-tests", "Executes all tests, producing a report.")
val test = TaskKey[Unit]("test", "Executes all tests.")
val testOnly = InputKey[Unit]("test-only", "Executes the tests provided as arguments or all tests if no arguments are provided.")
val testQuick = InputKey[Unit]("test-quick", "Executes the tests that either failed before, were not run or whose transitive dependencies changed, among those provided as arguments.")
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.")

View File

@ -0,0 +1,51 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010, 2011, 2012 Mark Harrah
*/
package sbt
import java.io.File
import scala.collection.mutable.Map
// Assumes exclusive ownership of the file.
private[sbt] class TestStatusReporter(f: File) extends TestsListener
{
private lazy val succeeded = TestStatus.read(f)
def doInit {}
def startGroup(name: String) { succeeded remove name }
def testEvent(event: TestEvent) {}
def endGroup(name: String, t: Throwable) {}
def endGroup(name: String, result: TestResult.Value) {
if(result == TestResult.Passed)
succeeded(name) = System.currentTimeMillis
}
def doComplete(finalResult: TestResult.Value) {
TestStatus.write(succeeded, "Successful Tests", f)
}
}
private[sbt] class TestResultFilter(f: File) extends (String => Boolean) with NotNull
{
private lazy val succeeded = TestStatus.read(f)
def apply(test: String) = succeeded.contains(test)
}
private object TestStatus
{
import java.util.Properties
def read(f: File): Map[String, Long] =
{
import scala.collection.JavaConversions.{enumerationAsScalaIterator, propertiesAsScalaMap}
val properties = new Properties
IO.load(properties, f)
properties map {case (k, v) => (k, v.toLong)}
}
def write(map: Map[String, Long], label: String, f: File)
{
val properties = new Properties
for( (test, lastSuccessTime) <- map)
properties.setProperty(test, lastSuccessTime.toString)
IO.write(properties, label, f)
}
}

View File

@ -1,75 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.impl
import sbt._
import java.io.File
import scala.collection.mutable.{HashMap, Map}
/** Only intended to be used once per instance. */
private[sbt] class TestStatusReporter(path: Path, log: Logger) extends TestsListener
{
private lazy val succeeded: Map[String, Long] = TestStatus.read(path, log)
def doInit {}
def startGroup(name: String) { succeeded remove name }
def testEvent(event: TestEvent) {}
def endGroup(name: String, t: Throwable) {}
def endGroup(name: String, result: Result.Value)
{
if(result == Result.Passed)
succeeded(name) = System.currentTimeMillis
}
def doComplete(finalResult: Result.Value) { complete() }
def doComplete(t: Throwable) { complete() }
private def complete()
{
TestStatus.write(succeeded, "Successful Tests", path, log)
}
}
private[sbt] class TestQuickFilter(testAnalysis: CompileAnalysis, failedOnly: Boolean, path: Path, log: Logger) extends (String => Boolean) with NotNull
{
private lazy val exclude = TestStatus.read(path, log)
private lazy val map = testAnalysis.testSourceMap
def apply(test: String) =
exclude.get(test) match
{
case None => true // include because this test has not been run or did not succeed
case Some(lastSuccessTime) => // succeeded the last time it was run
if(failedOnly)
false // don't include because the last time succeeded
else
testAnalysis.products(map(test)) match
{
case None => true
case Some(products) => products.exists(lastSuccessTime <= _.lastModified) // include if the test is newer than the last run
}
}
}
private object TestStatus
{
import java.util.Properties
def read(path: Path, log: Logger): Map[String, Long] =
{
val map = new HashMap[String, Long]
val properties = new Properties
logError(PropertiesUtilities.load(properties, path, log), "loading", log)
for(test <- PropertiesUtilities.propertyNames(properties))
map.put(test, properties.getProperty(test).toLong)
map
}
def write(map: Map[String, Long], label: String, path: Path, log: Logger)
{
val properties = new Properties
for( (test, lastSuccessTime) <- map)
properties.setProperty(test, lastSuccessTime.toString)
logError(PropertiesUtilities.write(properties, label, path, log), "writing", log)
}
private def logError(result: Option[String], action: String, log: Logger)
{
result.foreach(msg => log.error("Error " + action + " test status: " + msg))
}
}