From fe753768d93ebeaf59c9435059b583a7b2e744d3 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Sun, 4 Mar 2012 11:09:42 +0400 Subject: [PATCH 1/7] Extract testFilter task to substitute in case of test-quick. --- main/Defaults.scala | 28 ++++++++++++++++------------ main/Keys.scala | 1 + 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index fd7e43b73..d62f3e913 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -287,6 +287,7 @@ object Defaults extends BuildCommon testOptions in GlobalScope :== Nil, testExecution in test <<= testExecutionTask(test), testExecution in testOnly <<= testExecutionTask(testOnly), + testFilter in testOnly :== (selectedFilter _), 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) @@ -323,19 +324,22 @@ 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 = - InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result => - (streams, loadedTestFrameworks, testExecution in testOnly, testLoader, definedTests, resolvedScoped, result, state) flatMap { - case (s, frameworks, config, loader, discovered, scoped, (tests, frameworkOptions), st) => - val filter = selectedFilter(tests) - val modifiedOpts = Tests.Filter(filter) +: Tests.Argument(frameworkOptions : _*) +: config.options - val newConfig = new Tests.Execution(modifiedOpts, config.parallel, config.tags) - implicit val display = Project.showContextKey(st) - Tests(frameworks, loader, discovered, newConfig, noTestsMessage(scoped), s.log) map { results => - Tests.showResults(s.log, results) - } + def testOnlyTask: Initialize[InputTask[Unit]] = inputTests(testOnly) + + def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] = + InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result => + (streams, loadedTestFrameworks, testFilter in key, testExecution in key, testLoader, definedTests, resolvedScoped, result, state) flatMap { + case (s, frameworks, filter, config, loader, discovered, scoped, (tests, frameworkOptions), st) => + val modifiedOpts = Tests.Filter(filter(tests)) +: Tests.Argument(frameworkOptions : _*) +: config.options + val newConfig = new Tests.Execution(modifiedOpts, config.parallel, config.tags) + implicit val display = Project.showContextKey(st) + Tests(frameworks, loader, discovered, newConfig, noTestsMessage(scoped), s.log) map { results => + Tests.showResults(s.log, results) + } + } } - } + + def selectedFilter(args: Seq[String]): String => Boolean = { val filters = args map GlobFilter.apply diff --git a/main/Keys.scala b/main/Keys.scala index eeed6e3a9..c5ba0fa9c 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -196,6 +196,7 @@ object Keys 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 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.") // Classpath/Dependency Management Keys From 6e0ad08ad3ed4332864f85d4304bcc7a9d572890 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Mon, 5 Mar 2012 17:17:55 +0400 Subject: [PATCH 2/7] testQuick: track previous test status. --- main/Defaults.scala | 30 +++++++---- main/Keys.scala | 1 + testing/TestStatusReporter.scala | 51 ++++++++++++++++++ testing/impl/TestStatusReporter.scala | 75 --------------------------- 4 files changed, 72 insertions(+), 85 deletions(-) create mode 100644 testing/TestStatusReporter.scala delete mode 100644 testing/impl/TestStatusReporter.scala diff --git a/main/Defaults.scala b/main/Defaults.scala index d62f3e913..ca0e66370 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -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 => diff --git a/main/Keys.scala b/main/Keys.scala index c5ba0fa9c..e235f8b42 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -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.") diff --git a/testing/TestStatusReporter.scala b/testing/TestStatusReporter.scala new file mode 100644 index 000000000..f619d9717 --- /dev/null +++ b/testing/TestStatusReporter.scala @@ -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) + } +} diff --git a/testing/impl/TestStatusReporter.scala b/testing/impl/TestStatusReporter.scala deleted file mode 100644 index 0b83989a7..000000000 --- a/testing/impl/TestStatusReporter.scala +++ /dev/null @@ -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)) - } -} \ No newline at end of file From cef766046db902b960783111e86d4299e6572618 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Mon, 5 Mar 2012 18:37:46 +0400 Subject: [PATCH 3/7] Add forgotten test listener. --- main/Defaults.scala | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index ca0e66370..eb850f5ad 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -116,13 +116,7 @@ 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), @@ -273,7 +267,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)) }, testFrameworks in GlobalScope :== { import sbt.TestFrameworks._ @@ -296,8 +290,6 @@ object Defaults extends BuildCommon test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) }, 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) @@ -305,10 +297,13 @@ object Defaults extends BuildCommon lazy val TaskGlobal: Scope = ThisScope.copy(task = Global) lazy val ConfigGlobal: Scope = ThisScope.copy(config = Global) def testTaskOptions(key: Scoped): Seq[Setting[_]] = inTask(key)( Seq( - testListeners <<= (streams, resolvedScoped, streamsManager, logBuffered, testListeners in TaskGlobal) map { (s, sco, sm, buff, ls) => - TestLogger(s.log, testLogger(sm, test in sco.scope), 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) +: 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), + logBuffered := true, + tags := Seq(Tags.Test -> 1) ) ) def testLogger(manager: Streams, baseKey: Scoped)(tdef: TestDefinition): Logger = { @@ -330,11 +325,12 @@ object Defaults extends BuildCommon def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] = (compile in test, cacheDirectory) map { case (analysis, dir) => - val succeeded = new TestResultFilter(dir / "succeeded_tests") + val succeeded = new TestResultFilter(succeededFile(dir)) args => test => selectedFilter(args)(test) && { !succeeded(test) // Add recompilation status. } } + 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 => From e92ba43b3f257f6dd4ec8c07997f325a5a22edf5 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Wed, 7 Mar 2012 12:28:40 +0400 Subject: [PATCH 4/7] Add API dependency tracking to testQuickFilter. --- main/Defaults.scala | 9 ++++++++- testing/TestStatusReporter.scala | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index eb850f5ad..61a18108f 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -327,7 +327,14 @@ object Defaults extends BuildCommon case (analysis, dir) => val succeeded = new TestResultFilter(succeededFile(dir)) args => test => selectedFilter(args)(test) && { - !succeeded(test) // Add recompilation status. + succeeded(test) match { + case None => true + case Some(ts) => + import analysis.{relations => rel, apis} + rel.definesClass(test) flatMap { + f => (rel.internalSrcDeps(f) map apis.internal) ++ (rel.externalDeps(f) map apis.external) + } exists (_.compilation.startTime > ts) + } } } def succeededFile(dir: File) = dir / "succeeded_tests" diff --git a/testing/TestStatusReporter.scala b/testing/TestStatusReporter.scala index f619d9717..b9e695c42 100644 --- a/testing/TestStatusReporter.scala +++ b/testing/TestStatusReporter.scala @@ -25,10 +25,10 @@ private[sbt] class TestStatusReporter(f: File) extends TestsListener } } -private[sbt] class TestResultFilter(f: File) extends (String => Boolean) with NotNull +private[sbt] class TestResultFilter(f: File) extends NotNull { private lazy val succeeded = TestStatus.read(f) - def apply(test: String) = succeeded.contains(test) + def apply(test: String): Option[Long] = succeeded.get(test) } private object TestStatus From efb9cde9f392b376f293257ee4a37ff3e7044410 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Wed, 7 Mar 2012 13:13:56 +0400 Subject: [PATCH 5/7] Add test file itself to the set to track compilation time. --- main/Defaults.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index 61a18108f..36d8eeaea 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -332,7 +332,7 @@ object Defaults extends BuildCommon case Some(ts) => import analysis.{relations => rel, apis} rel.definesClass(test) flatMap { - f => (rel.internalSrcDeps(f) map apis.internal) ++ (rel.externalDeps(f) map apis.external) + f => ((rel.internalSrcDeps(f) + f) map apis.internal) ++ (rel.externalDeps(f) map apis.external) } exists (_.compilation.startTime > ts) } } From d5bf8cf6d6f22f4b6bc07055c25d2f9642488118 Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Thu, 8 Mar 2012 18:58:44 +0400 Subject: [PATCH 6/7] Transitive compilation dependency tracking. --- main/Defaults.scala | 44 +++++++++++++++++++++----------- testing/TestStatusReporter.scala | 8 +----- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index 36d8eeaea..66001d4d0 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -50,7 +50,7 @@ object Defaults extends BuildCommon def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))(Seq( 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, buildDependencies <<= buildDependencies or Classpaths.constructBuildDependencies, taskTemporaryDirectory := IO.createTemporaryDirectory, @@ -117,6 +117,10 @@ object Defaults extends BuildCommon excludeFilter :== (".*" - ".") || HiddenFileFilter, 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( name <<= thisProject(_.id), logManager <<= extraLoggers(LogManager.defaults), @@ -301,9 +305,7 @@ object Defaults extends BuildCommon 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 }, - testExecution <<= testExecutionTask(key), - logBuffered := true, - tags := Seq(Tags.Test -> 1) + testExecution <<= testExecutionTask(key) ) ) def testLogger(manager: Streams, baseKey: Scoped)(tdef: TestDefinition): Logger = { @@ -323,20 +325,32 @@ object Defaults extends BuildCommon (testOptions in task, parallelExecution in task, tags in task) map { (opts, par, ts) => new Tests.Execution(opts, par, ts) } def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] = - (compile in test, cacheDirectory) map { - case (analysis, dir) => - val succeeded = new TestResultFilter(succeededFile(dir)) - args => test => selectedFilter(args)(test) && { - succeeded(test) match { + (fullClasspath in test, cacheDirectory) map { + (cp, dir) => + val ans = for(e <- cp; an <- e.metadata get Keys.analysis) yield an + 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 = { + 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: Seq[String]) => (test: String) => selectedFilter(args)(test) && { + succeeded.get(test) match { case None => true - case Some(ts) => - import analysis.{relations => rel, apis} - rel.definesClass(test) flatMap { - f => ((rel.internalSrcDeps(f) + f) map apis.internal) ++ (rel.externalDeps(f) map apis.external) - } exists (_.compilation.startTime > ts) + case Some(ts) => stamp(test) > ts } } - } + } dependsOn (compile in test) def succeededFile(dir: File) = dir / "succeeded_tests" def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] = diff --git a/testing/TestStatusReporter.scala b/testing/TestStatusReporter.scala index b9e695c42..bab1d4b71 100644 --- a/testing/TestStatusReporter.scala +++ b/testing/TestStatusReporter.scala @@ -25,13 +25,7 @@ private[sbt] class TestStatusReporter(f: File) extends TestsListener } } -private[sbt] class TestResultFilter(f: File) extends NotNull -{ - private lazy val succeeded = TestStatus.read(f) - def apply(test: String): Option[Long] = succeeded.get(test) -} - -private object TestStatus +private[sbt] object TestStatus { import java.util.Properties def read(f: File): Map[String, Long] = From 40d35a5ced5b7ba4f94cf5e2a28f8e372ad5134b Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Thu, 8 Mar 2012 20:20:17 +0400 Subject: [PATCH 7/7] Change according to review and add a missing recursive dependency check. --- main/Defaults.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) mode change 100644 => 100755 main/Defaults.scala diff --git a/main/Defaults.scala b/main/Defaults.scala old mode 100644 new mode 100755 index 66001d4d0..611cdfcd3 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -327,7 +327,7 @@ object Defaults extends BuildCommon def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] = (fullClasspath in test, cacheDirectory) map { (cp, dir) => - val ans = for(e <- cp; an <- e.metadata get Keys.analysis) yield an + 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 = { @@ -335,22 +335,21 @@ object Defaults extends BuildCommon if (stamps.isEmpty) Long.MinValue else stamps.max } def intlStamp(f: File, analysis: inc.Analysis, s: Set[File]): Long = { - stamps.getOrElseUpdate(f, { - import analysis.{relations => rel, apis} - ( + 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 - }) + }.max) } - (args: Seq[String]) => (test: String) => selectedFilter(args)(test) && { + args => test => selectedFilter(args)(test) && { succeeded.get(test) match { case None => true case Some(ts) => stamp(test) > ts } } - } dependsOn (compile in test) + } def succeededFile(dir: File) = dir / "succeeded_tests" def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] =