Skip to content
This repository
Browse code

testQuick: track previous test status.

  • Loading branch information...
commit 6e0ad08ad3ed4332864f85d4304bcc7a9d572890 1 parent fe75376
Eugene Vigdorchik authored
30 main/Defaults.scala
@@ -64,11 +64,8 @@ object Defaults extends BuildCommon
64 64 logBuffered :== false,
65 65 connectInput :== false,
66 66 cancelable :== false,
67   - cancelable :== false,
68 67 autoScalaLibrary :== true,
69 68 onLoad <<= onLoad ?? idFun[State],
70   - tags in test := Seq(Tags.Test -> 1),
71   - tags in testOnly <<= tags in test,
72 69 onUnload <<= (onUnload ?? idFun[State]),
73 70 onUnload <<= (onUnload, taskTemporaryDirectory) { (f, dir) => s => { try f(s) finally IO.delete(dir) } },
74 71 watchingMessage <<= watchingMessage ?? Watched.defaultWatchingMessage,
@@ -78,8 +75,6 @@ object Defaults extends BuildCommon
78 75 trapExit in run :== true,
79 76 traceLevel in run :== 0,
80 77 traceLevel in runMain :== 0,
81   - logBuffered in testOnly :== true,
82   - logBuffered in test :== true,
83 78 traceLevel in console :== Int.MaxValue,
84 79 traceLevel in consoleProject :== Int.MaxValue,
85 80 autoCompilerPlugins :== true,
@@ -121,7 +116,13 @@ object Defaults extends BuildCommon
121 116 includeFilter in unmanagedResources :== AllPassFilter,
122 117 excludeFilter :== (".*" - ".") || HiddenFileFilter,
123 118 pomIncludeRepository :== Classpaths.defaultRepositoryFilter
124   - ))
  119 + ) ++ {
  120 + val testSettings = for (task <- Seq(test, testOnly, testQuick)) yield Seq[Setting[_]](
  121 + logBuffered in task := true,
  122 + tags in task := Seq(Tags.Test -> 1)
  123 + )
  124 + testSettings.flatten
  125 + })
125 126 def projectCore: Seq[Setting[_]] = Seq(
126 127 name <<= thisProject(_.id),
127 128 logManager <<= extraLoggers(LogManager.defaults),
@@ -285,16 +286,18 @@ object Defaults extends BuildCommon
285 286 definedTestNames <<= definedTests map ( _.map(_.name).distinct) storeAs definedTestNames triggeredBy compile,
286 287 testListeners in GlobalScope :== Nil,
287 288 testOptions in GlobalScope :== Nil,
288   - testExecution in test <<= testExecutionTask(test),
289   - testExecution in testOnly <<= testExecutionTask(testOnly),
290 289 testFilter in testOnly :== (selectedFilter _),
  290 + testFilter in testQuick <<= testQuickFilter,
291 291 executeTests <<= (streams in test, loadedTestFrameworks, testExecution in test, testLoader, definedTests, resolvedScoped, state) flatMap {
292 292 (s, frameworkMap, config, loader, discovered, scoped, st) =>
293 293 implicit val display = Project.showContextKey(st)
294 294 Tests(frameworkMap, loader, discovered, config, noTestsMessage(ScopedKey(scoped.scope, test.key)), s.log)
295 295 },
296 296 test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results) },
297   - testOnly <<= testOnlyTask
  297 + testOnly <<= inputTests(testOnly),
  298 + testQuick <<= inputTests(testQuick)
  299 + ) ++ (
  300 + for (task <- Seq(test, testOnly, testQuick)) yield testExecution in task <<= testExecutionTask(task)
298 301 )
299 302 private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String =
300 303 "No tests to run for " + display(scoped)
@@ -324,7 +327,14 @@ object Defaults extends BuildCommon
324 327 def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] =
325 328 (testOptions in task, parallelExecution in task, tags in task) map { (opts, par, ts) => new Tests.Execution(opts, par, ts) }
326 329
327   - def testOnlyTask: Initialize[InputTask[Unit]] = inputTests(testOnly)
  330 + def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] =
  331 + (compile in test, cacheDirectory) map {
  332 + case (analysis, dir) =>
  333 + val succeeded = new TestResultFilter(dir / "succeeded_tests")
  334 + args => test => selectedFilter(args)(test) && {
  335 + !succeeded(test) // Add recompilation status.
  336 + }
  337 + }
328 338
329 339 def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] =
330 340 InputTask( loadForParser(definedTestNames)( (s, i) => testOnlyParser(s, i getOrElse Nil) ) ) { result =>
1  main/Keys.scala
@@ -192,6 +192,7 @@ object Keys
192 192 val executeTests = TaskKey[Tests.Output]("execute-tests", "Executes all tests, producing a report.")
193 193 val test = TaskKey[Unit]("test", "Executes all tests.")
194 194 val testOnly = InputKey[Unit]("test-only", "Executes the tests provided as arguments or all tests if no arguments are provided.")
  195 + 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.")
195 196 val testOptions = TaskKey[Seq[TestOption]]("test-options", "Options for running tests.")
196 197 val testFrameworks = SettingKey[Seq[TestFramework]]("test-frameworks", "Registered, although not necessarily present, test frameworks.")
197 198 val testListeners = TaskKey[Seq[TestReportListener]]("test-listeners", "Defines test listeners.")
51 testing/TestStatusReporter.scala
... ... @@ -0,0 +1,51 @@
  1 +/* sbt -- Simple Build Tool
  2 + * Copyright 2009, 2010, 2011, 2012 Mark Harrah
  3 + */
  4 +package sbt
  5 +
  6 +import java.io.File
  7 +
  8 +import scala.collection.mutable.Map
  9 +
  10 +// Assumes exclusive ownership of the file.
  11 +private[sbt] class TestStatusReporter(f: File) extends TestsListener
  12 +{
  13 + private lazy val succeeded = TestStatus.read(f)
  14 +
  15 + def doInit {}
  16 + def startGroup(name: String) { succeeded remove name }
  17 + def testEvent(event: TestEvent) {}
  18 + def endGroup(name: String, t: Throwable) {}
  19 + def endGroup(name: String, result: TestResult.Value) {
  20 + if(result == TestResult.Passed)
  21 + succeeded(name) = System.currentTimeMillis
  22 + }
  23 + def doComplete(finalResult: TestResult.Value) {
  24 + TestStatus.write(succeeded, "Successful Tests", f)
  25 + }
  26 +}
  27 +
  28 +private[sbt] class TestResultFilter(f: File) extends (String => Boolean) with NotNull
  29 +{
  30 + private lazy val succeeded = TestStatus.read(f)
  31 + def apply(test: String) = succeeded.contains(test)
  32 +}
  33 +
  34 +private object TestStatus
  35 +{
  36 + import java.util.Properties
  37 + def read(f: File): Map[String, Long] =
  38 + {
  39 + import scala.collection.JavaConversions.{enumerationAsScalaIterator, propertiesAsScalaMap}
  40 + val properties = new Properties
  41 + IO.load(properties, f)
  42 + properties map {case (k, v) => (k, v.toLong)}
  43 + }
  44 + def write(map: Map[String, Long], label: String, f: File)
  45 + {
  46 + val properties = new Properties
  47 + for( (test, lastSuccessTime) <- map)
  48 + properties.setProperty(test, lastSuccessTime.toString)
  49 + IO.write(properties, label, f)
  50 + }
  51 +}
75 testing/impl/TestStatusReporter.scala
... ... @@ -1,75 +0,0 @@
1   -/* sbt -- Simple Build Tool
2   - * Copyright 2009 Mark Harrah
3   - */
4   -package sbt.impl
5   -import sbt._
6   -
7   -import java.io.File
8   -import scala.collection.mutable.{HashMap, Map}
9   -
10   -/** Only intended to be used once per instance. */
11   -private[sbt] class TestStatusReporter(path: Path, log: Logger) extends TestsListener
12   -{
13   - private lazy val succeeded: Map[String, Long] = TestStatus.read(path, log)
14   -
15   - def doInit {}
16   - def startGroup(name: String) { succeeded remove name }
17   - def testEvent(event: TestEvent) {}
18   - def endGroup(name: String, t: Throwable) {}
19   - def endGroup(name: String, result: Result.Value)
20   - {
21   - if(result == Result.Passed)
22   - succeeded(name) = System.currentTimeMillis
23   - }
24   - def doComplete(finalResult: Result.Value) { complete() }
25   - def doComplete(t: Throwable) { complete() }
26   -
27   - private def complete()
28   - {
29   - TestStatus.write(succeeded, "Successful Tests", path, log)
30   - }
31   -}
32   -
33   -private[sbt] class TestQuickFilter(testAnalysis: CompileAnalysis, failedOnly: Boolean, path: Path, log: Logger) extends (String => Boolean) with NotNull
34   -{
35   - private lazy val exclude = TestStatus.read(path, log)
36   - private lazy val map = testAnalysis.testSourceMap
37   - def apply(test: String) =
38   - exclude.get(test) match
39   - {
40   - case None => true // include because this test has not been run or did not succeed
41   - case Some(lastSuccessTime) => // succeeded the last time it was run
42   - if(failedOnly)
43   - false // don't include because the last time succeeded
44   - else
45   - testAnalysis.products(map(test)) match
46   - {
47   - case None => true
48   - case Some(products) => products.exists(lastSuccessTime <= _.lastModified) // include if the test is newer than the last run
49   - }
50   - }
51   -}
52   -private object TestStatus
53   -{
54   - import java.util.Properties
55   - def read(path: Path, log: Logger): Map[String, Long] =
56   - {
57   - val map = new HashMap[String, Long]
58   - val properties = new Properties
59   - logError(PropertiesUtilities.load(properties, path, log), "loading", log)
60   - for(test <- PropertiesUtilities.propertyNames(properties))
61   - map.put(test, properties.getProperty(test).toLong)
62   - map
63   - }
64   - def write(map: Map[String, Long], label: String, path: Path, log: Logger)
65   - {
66   - val properties = new Properties
67   - for( (test, lastSuccessTime) <- map)
68   - properties.setProperty(test, lastSuccessTime.toString)
69   - logError(PropertiesUtilities.write(properties, label, path, log), "writing", log)
70   - }
71   - private def logError(result: Option[String], action: String, log: Logger)
72   - {
73   - result.foreach(msg => log.error("Error " + action + " test status: " + msg))
74   - }
75   -}

0 comments on commit 6e0ad08

Please sign in to comment.
Something went wrong with that request. Please try again.