mirror of https://github.com/sbt/sbt.git
merge test-quick reimplementation #393 from vigdorchik/wip_test_quick
This commit is contained in:
commit
4527e9bfc6
|
|
@ -50,7 +50,7 @@ object Defaults extends BuildCommon
|
||||||
def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))(Seq(
|
def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))(Seq(
|
||||||
managedDirectory <<= baseDirectory(_ / "lib_managed")
|
managedDirectory <<= baseDirectory(_ / "lib_managed")
|
||||||
))
|
))
|
||||||
def globalCore: Seq[Setting[_]] = inScope(GlobalScope)(Seq(
|
def globalCore: Seq[Setting[_]] = inScope(GlobalScope)(defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
|
||||||
crossVersion :== CrossVersion.Disabled,
|
crossVersion :== CrossVersion.Disabled,
|
||||||
buildDependencies <<= buildDependencies or Classpaths.constructBuildDependencies,
|
buildDependencies <<= buildDependencies or Classpaths.constructBuildDependencies,
|
||||||
taskTemporaryDirectory := IO.createTemporaryDirectory,
|
taskTemporaryDirectory := IO.createTemporaryDirectory,
|
||||||
|
|
@ -64,11 +64,8 @@ object Defaults extends BuildCommon
|
||||||
logBuffered :== false,
|
logBuffered :== false,
|
||||||
connectInput :== false,
|
connectInput :== false,
|
||||||
cancelable :== false,
|
cancelable :== false,
|
||||||
cancelable :== false,
|
|
||||||
autoScalaLibrary :== true,
|
autoScalaLibrary :== true,
|
||||||
onLoad <<= onLoad ?? idFun[State],
|
onLoad <<= onLoad ?? idFun[State],
|
||||||
tags in test := Seq(Tags.Test -> 1),
|
|
||||||
tags in testOnly <<= tags in test,
|
|
||||||
onUnload <<= (onUnload ?? idFun[State]),
|
onUnload <<= (onUnload ?? idFun[State]),
|
||||||
onUnload <<= (onUnload, taskTemporaryDirectory) { (f, dir) => s => { try f(s) finally IO.delete(dir) } },
|
onUnload <<= (onUnload, taskTemporaryDirectory) { (f, dir) => s => { try f(s) finally IO.delete(dir) } },
|
||||||
watchingMessage <<= watchingMessage ?? Watched.defaultWatchingMessage,
|
watchingMessage <<= watchingMessage ?? Watched.defaultWatchingMessage,
|
||||||
|
|
@ -78,8 +75,6 @@ object Defaults extends BuildCommon
|
||||||
trapExit in run :== true,
|
trapExit in run :== true,
|
||||||
traceLevel in run :== 0,
|
traceLevel in run :== 0,
|
||||||
traceLevel in runMain :== 0,
|
traceLevel in runMain :== 0,
|
||||||
logBuffered in testOnly :== true,
|
|
||||||
logBuffered in test :== true,
|
|
||||||
traceLevel in console :== Int.MaxValue,
|
traceLevel in console :== Int.MaxValue,
|
||||||
traceLevel in consoleProject :== Int.MaxValue,
|
traceLevel in consoleProject :== Int.MaxValue,
|
||||||
autoCompilerPlugins :== true,
|
autoCompilerPlugins :== true,
|
||||||
|
|
@ -122,6 +117,10 @@ object Defaults extends BuildCommon
|
||||||
excludeFilter :== (".*" - ".") || HiddenFileFilter,
|
excludeFilter :== (".*" - ".") || HiddenFileFilter,
|
||||||
pomIncludeRepository :== Classpaths.defaultRepositoryFilter
|
pomIncludeRepository :== Classpaths.defaultRepositoryFilter
|
||||||
))
|
))
|
||||||
|
def defaultTestTasks(key: Scoped): Seq[Setting[_]] = Seq(
|
||||||
|
tags in key := Seq(Tags.Test -> 1),
|
||||||
|
logBuffered in key := true
|
||||||
|
)
|
||||||
def projectCore: Seq[Setting[_]] = Seq(
|
def projectCore: Seq[Setting[_]] = Seq(
|
||||||
name <<= thisProject(_.id),
|
name <<= thisProject(_.id),
|
||||||
logManager <<= extraLoggers(LogManager.defaults),
|
logManager <<= extraLoggers(LogManager.defaults),
|
||||||
|
|
@ -272,7 +271,7 @@ object Defaults extends BuildCommon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val testTasks: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ Seq(
|
lazy val testTasks: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(testQuick) ++ Seq(
|
||||||
testLoader <<= (fullClasspath, scalaInstance, taskTemporaryDirectory) map { (cp, si, temp) => TestFramework.createTestLoader(data(cp), si, IO.createUniqueDirectory(temp)) },
|
testLoader <<= (fullClasspath, scalaInstance, taskTemporaryDirectory) map { (cp, si, temp) => TestFramework.createTestLoader(data(cp), si, IO.createUniqueDirectory(temp)) },
|
||||||
testFrameworks in GlobalScope :== {
|
testFrameworks in GlobalScope :== {
|
||||||
import sbt.TestFrameworks._
|
import sbt.TestFrameworks._
|
||||||
|
|
@ -285,15 +284,16 @@ object Defaults extends BuildCommon
|
||||||
definedTestNames <<= definedTests map ( _.map(_.name).distinct) storeAs definedTestNames triggeredBy compile,
|
definedTestNames <<= definedTests map ( _.map(_.name).distinct) storeAs definedTestNames triggeredBy compile,
|
||||||
testListeners in GlobalScope :== Nil,
|
testListeners in GlobalScope :== Nil,
|
||||||
testOptions in GlobalScope :== Nil,
|
testOptions in GlobalScope :== Nil,
|
||||||
testExecution in test <<= testExecutionTask(test),
|
testFilter in testOnly :== (selectedFilter _),
|
||||||
testExecution in testOnly <<= testExecutionTask(testOnly),
|
testFilter in testQuick <<= testQuickFilter,
|
||||||
executeTests <<= (streams in test, loadedTestFrameworks, testExecution in test, testLoader, definedTests, resolvedScoped, state) flatMap {
|
executeTests <<= (streams in test, loadedTestFrameworks, testExecution in test, testLoader, definedTests, resolvedScoped, state) flatMap {
|
||||||
(s, frameworkMap, config, loader, discovered, scoped, st) =>
|
(s, frameworkMap, config, loader, discovered, scoped, st) =>
|
||||||
implicit val display = Project.showContextKey(st)
|
implicit val display = Project.showContextKey(st)
|
||||||
Tests(frameworkMap, loader, discovered, config, noTestsMessage(ScopedKey(scoped.scope, test.key)), s.log)
|
Tests(frameworkMap, loader, discovered, config, noTestsMessage(ScopedKey(scoped.scope, test.key)), s.log)
|
||||||
},
|
},
|
||||||
test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) },
|
test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) },
|
||||||
testOnly <<= testOnlyTask
|
testOnly <<= inputTests(testOnly),
|
||||||
|
testQuick <<= inputTests(testQuick)
|
||||||
)
|
)
|
||||||
private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String =
|
private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String =
|
||||||
"No tests to run for " + display(scoped)
|
"No tests to run for " + display(scoped)
|
||||||
|
|
@ -301,10 +301,11 @@ object Defaults extends BuildCommon
|
||||||
lazy val TaskGlobal: Scope = ThisScope.copy(task = Global)
|
lazy val TaskGlobal: Scope = ThisScope.copy(task = Global)
|
||||||
lazy val ConfigGlobal: Scope = ThisScope.copy(config = Global)
|
lazy val ConfigGlobal: Scope = ThisScope.copy(config = Global)
|
||||||
def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( Seq(
|
def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( Seq(
|
||||||
testListeners <<= (streams, resolvedScoped, streamsManager, logBuffered, testListeners in TaskGlobal) map { (s, sco, sm, buff, ls) =>
|
testListeners <<= (streams, resolvedScoped, streamsManager, logBuffered, cacheDirectory in test, testListeners in TaskGlobal) map { (s, sco, sm, buff, dir, ls) =>
|
||||||
TestLogger(s.log, testLogger(sm, test in sco.scope), buff) +: ls
|
TestLogger(s.log, testLogger(sm, test in sco.scope), buff) +: new TestStatusReporter(succeededFile(dir)) +: ls
|
||||||
},
|
},
|
||||||
testOptions <<= (testOptions in TaskGlobal, testListeners) map { (options, ls) => Tests.Listeners(ls) +: options }
|
testOptions <<= (testOptions in TaskGlobal, testListeners) map { (options, ls) => Tests.Listeners(ls) +: options },
|
||||||
|
testExecution <<= testExecutionTask(key)
|
||||||
) )
|
) )
|
||||||
def testLogger(manager: Streams, baseKey: Scoped)(tdef: TestDefinition): Logger =
|
def testLogger(manager: Streams, baseKey: Scoped)(tdef: TestDefinition): Logger =
|
||||||
{
|
{
|
||||||
|
|
@ -323,12 +324,39 @@ object Defaults extends BuildCommon
|
||||||
def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] =
|
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) }
|
(testOptions in task, parallelExecution in task, tags in task) map { (opts, par, ts) => new Tests.Execution(opts, par, ts) }
|
||||||
|
|
||||||
def testOnlyTask =
|
def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] =
|
||||||
|
(fullClasspath in test, cacheDirectory) map {
|
||||||
|
(cp, dir) =>
|
||||||
|
val ans = cp.flatMap(_.metadata get Keys.analysis)
|
||||||
|
val succeeded = TestStatus.read(succeededFile(dir))
|
||||||
|
val stamps = collection.mutable.Map.empty[File, Long]
|
||||||
|
def stamp(dep: String): Long = {
|
||||||
|
val stamps = for (a <- ans; f <- a.relations.definesClass(dep)) yield intlStamp(f, a, Set.empty)
|
||||||
|
if (stamps.isEmpty) Long.MinValue else stamps.max
|
||||||
|
}
|
||||||
|
def intlStamp(f: File, analysis: inc.Analysis, s: Set[File]): Long = {
|
||||||
|
if (s contains f) Long.MinValue else
|
||||||
|
stamps.getOrElseUpdate(f, {
|
||||||
|
import analysis.{relations => rel, apis}
|
||||||
|
rel.internalSrcDeps(f).map(intlStamp(_, analysis, s + f)) ++
|
||||||
|
rel.externalDeps(f).map(stamp) +
|
||||||
|
apis.internal(f).compilation.startTime
|
||||||
|
}.max)
|
||||||
|
}
|
||||||
|
args => test => selectedFilter(args)(test) && {
|
||||||
|
succeeded.get(test) match {
|
||||||
|
case None => true
|
||||||
|
case Some(ts) => stamp(test) > ts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def succeededFile(dir: File) = dir / "succeeded_tests"
|
||||||
|
|
||||||
|
def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] =
|
||||||
InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result =>
|
InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result =>
|
||||||
(streams, loadedTestFrameworks, testExecution in testOnly, testLoader, definedTests, resolvedScoped, result, state) flatMap {
|
(streams, loadedTestFrameworks, testFilter in key, testExecution in key, testLoader, definedTests, resolvedScoped, result, state) flatMap {
|
||||||
case (s, frameworks, config, loader, discovered, scoped, (tests, frameworkOptions), st) =>
|
case (s, frameworks, filter, config, loader, discovered, scoped, (tests, frameworkOptions), st) =>
|
||||||
val filter = selectedFilter(tests)
|
val modifiedOpts = Tests.Filter(filter(tests)) +: Tests.Argument(frameworkOptions : _*) +: config.options
|
||||||
val modifiedOpts = Tests.Filter(filter) +: Tests.Argument(frameworkOptions : _*) +: config.options
|
|
||||||
val newConfig = new Tests.Execution(modifiedOpts, config.parallel, config.tags)
|
val newConfig = new Tests.Execution(modifiedOpts, config.parallel, config.tags)
|
||||||
implicit val display = Project.showContextKey(st)
|
implicit val display = Project.showContextKey(st)
|
||||||
Tests(frameworks, loader, discovered, newConfig, noTestsMessage(scoped), s.log) map { results =>
|
Tests(frameworks, loader, discovered, newConfig, noTestsMessage(scoped), s.log) map { results =>
|
||||||
|
|
@ -336,6 +364,8 @@ object Defaults extends BuildCommon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def selectedFilter(args: Seq[String]): String => Boolean =
|
def selectedFilter(args: Seq[String]): String => Boolean =
|
||||||
{
|
{
|
||||||
val filters = args map GlobFilter.apply
|
val filters = args map GlobFilter.apply
|
||||||
|
|
|
||||||
|
|
@ -192,10 +192,12 @@ object Keys
|
||||||
val executeTests = TaskKey[Tests.Output]("execute-tests", "Executes all tests, producing a report.")
|
val executeTests = TaskKey[Tests.Output]("execute-tests", "Executes all tests, producing a report.")
|
||||||
val test = TaskKey[Unit]("test", "Executes all tests.")
|
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 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 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 testFrameworks = SettingKey[Seq[TestFramework]]("test-frameworks", "Registered, although not necessarily present, test frameworks.")
|
||||||
val testListeners = TaskKey[Seq[TestReportListener]]("test-listeners", "Defines test listeners.")
|
val testListeners = TaskKey[Seq[TestReportListener]]("test-listeners", "Defines test listeners.")
|
||||||
val testExecution = TaskKey[Tests.Execution]("test-execution", "Settings controlling test execution")
|
val testExecution = TaskKey[Tests.Execution]("test-execution", "Settings controlling test execution")
|
||||||
|
val testFilter = TaskKey[Seq[String] => String => Boolean]("test-filter", "Filter controlling whether the test is executed")
|
||||||
val isModule = AttributeKey[Boolean]("is-module", "True if the target is a module.")
|
val isModule = AttributeKey[Boolean]("is-module", "True if the target is a module.")
|
||||||
|
|
||||||
// Classpath/Dependency Management Keys
|
// Classpath/Dependency Management Keys
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* 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] 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue