diff options
author | Paul Phillips <paulp@improving.org> | 2010-04-06 22:46:22 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-04-06 22:46:22 +0000 |
commit | d5b8082ce9269f6657d6d3b5467f57fa746f7e76 (patch) | |
tree | 687eb6276929ffc4e061cbced1f226dd232bfaab | |
parent | 2a8667d1cd0649c5567c65b087f08f5d42e2062b (diff) | |
download | scala-d5b8082ce9269f6657d6d3b5467f57fa746f7e76.tar.gz scala-d5b8082ce9269f6657d6d3b5467f57fa746f7e76.tar.bz2 scala-d5b8082ce9269f6657d6d3b5467f57fa746f7e76.zip |
Fixed another partest feature I'd managed to br...
Fixed another partest feature I'd managed to break at the very last
minute. When a test is too slow finishing, there will be messages
identifying the test. It defaults to 90 seconds before the first warning
because I know some machines are slow, but it'd be nice if that was more
like 30. No review.
-rw-r--r-- | src/partest/scala/tools/partest/Alarms.scala | 85 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/Config.scala | 6 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/Dispatcher.scala | 57 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/Entities.scala | 1 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/PartestSpec.scala | 15 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/Universe.scala | 1 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/package.scala | 1 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/util/package.scala | 50 |
8 files changed, 109 insertions, 107 deletions
diff --git a/src/partest/scala/tools/partest/Alarms.scala b/src/partest/scala/tools/partest/Alarms.scala new file mode 100644 index 0000000000..72afc232e5 --- /dev/null +++ b/src/partest/scala/tools/partest/Alarms.scala @@ -0,0 +1,85 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package partest + +import java.util.{ Timer, TimerTask } + +trait Alarms { + self: Universe => + + def interruptMeIn[T](seconds: Int)(body: => T): Option[T] = { + val thisThread = currentThread + val alarm = new SimpleAlarm(seconds * 1000) set thisThread.interrupt() + + try { Some(body) } + catch { case _: InterruptedException => None } + finally { alarm.cancel() ; Thread.interrupted() } + } + + case class AlarmerAction(secs: Int, action: () => Unit) extends Runnable { + override def run() = action() + } + + /** Set any number of alarms up with tuples of the form: + * seconds to alarm -> Function0[Unit] to execute + */ + class Alarmer(alarms: AlarmerAction*) { + import java.util.concurrent._ + + val exec = Executors.newSingleThreadScheduledExecutor() + alarms foreach (x => exec.schedule(x, x.secs, TimeUnit.SECONDS)) + exec.shutdown() + + def cancelAll() = exec.shutdownNow() + } + + class SimpleAlarm(timeout: Long) { + private val alarm = new Timer + + /** Start a timer, running the given body if it goes off. + */ + def set(body: => Unit) = returning(new TimerTask { def run() = body })(alarm.schedule(_, timeout)) + + /** Cancel the timer. + */ + def cancel() = alarm.cancel() + } + + trait TestAlarms { + test: TestEntity => + + private def warning1 = AlarmerAction(testWarning, () => warning( + """|I've been waiting %s seconds for this to complete: + | %s + |It may be stuck, or if not, it should be broken into smaller tests. + |""".stripMargin.format(testWarning, test)) + ) + private def warning2 = AlarmerAction(testWarning * 2, () => warning( + """|Now I've been waiting %s seconds for this to complete: + | %s + |If partest seems hung it would be a good place to look. + |""".stripMargin.format(testWarning * 2, test)) + ) + + def startAlarms(onTimeout: => Unit) = + if (isNoAlarms) new Alarmer() // for alarm debugging + else new Alarmer(Seq(warning1, warning2, AlarmerAction(testTimeout, () => onTimeout)): _*) + } + + // Thread.setDefaultUncaughtExceptionHandler(new UncaughtException) + // class UncaughtException extends Thread.UncaughtExceptionHandler { + // def uncaughtException(t: Thread, e: Throwable) { + // Console.println("Uncaught in %s: %s".format(t, e)) + // } + // } + // + // lazy val logger = File("/tmp/partest.log").bufferedWriter() + // def flog(msg: String) = logger synchronized { + // logger write (msg + "\n") + // logger.flush() + // } +} diff --git a/src/partest/scala/tools/partest/Config.scala b/src/partest/scala/tools/partest/Config.scala index 6be08ae8d2..8023574ae4 100644 --- a/src/partest/scala/tools/partest/Config.scala +++ b/src/partest/scala/tools/partest/Config.scala @@ -22,9 +22,9 @@ trait Config { /** Values related to actors. The timeouts are in seconds. On a dry * run we only allocate one worker so the output isn't interspersed. */ - def workerTimeout = 3600 // 1 hour, seems overly generous - def testWarningSecs = testWarning.toInt // test warning - 90s by default - def testTimeout = testWarningSecs * 10 // test timeout + def workerTimeout = 3600 // 1 hour, probably overly generous + def testTimeout = testTimeout_ flatMap safeToInt getOrElse 900 // test timeout + def testWarning = testWarning_ flatMap safeToInt getOrElse (testTimeout / 10) // test warning def numWorkers = if (isDryRun) 1 else propOrElse("partest.actors", "8").toInt def expectedErrors = propOrElse("partest.errors", "0").toInt def poolSize = (wrapAccessControl(propOrNone("actors.corePoolSize")) getOrElse "16").toInt diff --git a/src/partest/scala/tools/partest/Dispatcher.scala b/src/partest/scala/tools/partest/Dispatcher.scala index e453e27293..2c7d9d6a2f 100644 --- a/src/partest/scala/tools/partest/Dispatcher.scala +++ b/src/partest/scala/tools/partest/Dispatcher.scala @@ -6,12 +6,11 @@ package scala.tools package partest -import util._ import scala.tools.nsc.io._ -import scala.actors.{ Actor, Exit, TIMEOUT } +import scala.actors.{ Actor, TIMEOUT } import scala.actors.Actor._ import scala.collection.immutable -import scala.util.control.ControlThrowable +import scala.util.control.Exception.ultimately /** The machinery for concurrent execution of tests. Each Worker * is given a bundle of tests, which it runs sequentially and then @@ -140,52 +139,16 @@ trait Dispatcher { Actor.loopWhile(testIterator.hasNext) { val parent = self - // pick a test - val test = testIterator.next - - // Sets three alarms: two slowness warnings and a timeout. - def setAlarms() = - new Alarmer( - testWarningSecs -> (() => warning( - """|I've been waiting %d seconds for this to complete: - | "%s" - |Either it's stuck or it is unreasonably slow and should - |be divided into smaller tests. - |""".stripMargin.format(testWarning, test))), - - (testWarningSecs * 2) -> (() => warning( - """|Now I've been waiting %d seconds for this to complete: - | "%s" - |If partest seems hung, let's blame that one. - |""".stripMargin.format(testWarning * 2, test))), - - testTimeout -> (() => parent ! new Timeout(test)) - ) + // pick a test and set some alarms + val test = testIterator.next + val alarmer = test startAlarms (parent ! new Timeout(test)) actor { - /** Debugging alarm issues */ - if (isNoAlarms) { - parent ! TestResult(test, test.isSuccess) - } - else { - // Set alarms, kick it off. Calling isSuccess forces the lazy val - // "process" inside the test, running it. - val alarmer = setAlarms() - def respondWith(res: TestResult) = { - // Cancel the alarms and alert the media. - alarmer.cancelAll() - parent ! res - } - - try respondWith(TestResult(test, test.isSuccess)) - catch { - case x: ControlThrowable => - test.warnAndLogException("Worker caught " + x + ", rethrowing: ", x) - throw x - case x => - test.warnAndLogException("Worker caught " + x + ", failing: ", x) - respondWith(TestResult(test, false)) - } + ultimately(alarmer.cancelAll()) { + // Calling isSuccess forces the lazy val "process" inside the test, running it. + val res = test.isSuccess + // Cancel the alarms and alert the media. + parent ! TestResult(test, res) } } diff --git a/src/partest/scala/tools/partest/Entities.scala b/src/partest/scala/tools/partest/Entities.scala index ad1a396902..ceed145148 100644 --- a/src/partest/scala/tools/partest/Entities.scala +++ b/src/partest/scala/tools/partest/Entities.scala @@ -14,6 +14,7 @@ trait Entities { abstract class TestEntity extends AbsTestEntity with TestContribution with TestHousekeeping + with TestAlarms with EntityLogging with CompilableTest with ScriptableTest diff --git a/src/partest/scala/tools/partest/PartestSpec.scala b/src/partest/scala/tools/partest/PartestSpec.scala index 55d2fdcca5..a1f7d4ef28 100644 --- a/src/partest/scala/tools/partest/PartestSpec.scala +++ b/src/partest/scala/tools/partest/PartestSpec.scala @@ -73,19 +73,20 @@ trait PartestSpec extends CommandLineSpec { val isAnsi = "ansi" / "print output in color" ? heading ("Other options:") - val timeout = "timeout" / "Timeout in seconds" >> ; - val isCleanup = "cleanup" / "delete all stale files and dirs before run" ? - val isNoCleanup = "nocleanup" / "do not delete any logfiles or object dirs" ? - val isStats = "stats" / "collect and print statistics about the tests" ? - val isValidate = "validate" / "examine test filesystem for inconsistencies" ? - val isVersion = "version" / "print version" ? + val timeout_ = "timeout" / "Overall timeout in seconds" |> "14400" + val testWarning_ = "test-warning" / "Test warning in seconds" >> ; // defaults to testTimeout / 10 + val testTimeout_ = "test-timeout" / "Test timeout in seconds" >> ; // defaults to 900 + val isCleanup = "cleanup" / "delete all stale files and dirs before run" ? + val isNoCleanup = "nocleanup" / "do not delete any logfiles or object dirs" ? + val isStats = "stats" / "collect and print statistics about the tests" ? + val isValidate = "validate" / "examine test filesystem for inconsistencies" ? + val isVersion = "version" / "print version" ? // no help for anything below this line - secret options // mostly intended for property configuration. val runsets = "runsets" |> "" val isNoAlarms = ("noalarms" ?) val isInsideAnt = ("is-in-ant" ?) - val testWarning = "test-warning" |> "90" } object PartestSpecReference extends PartestSpec { diff --git a/src/partest/scala/tools/partest/Universe.scala b/src/partest/scala/tools/partest/Universe.scala index 568a155281..557d48fe54 100644 --- a/src/partest/scala/tools/partest/Universe.scala +++ b/src/partest/scala/tools/partest/Universe.scala @@ -26,6 +26,7 @@ abstract class Universe with PartestCompilation with PartestSpec with Config + with Alarms with Actions with Categories { diff --git a/src/partest/scala/tools/partest/package.scala b/src/partest/scala/tools/partest/package.scala index 78dd8d569c..3ef4db7cd8 100644 --- a/src/partest/scala/tools/partest/package.scala +++ b/src/partest/scala/tools/partest/package.scala @@ -18,6 +18,7 @@ package object partest { private[partest] def safeLines(f: File) = safeSlurp(f) split """\r\n|\r|\n""" toList private[partest] def safeArgs(f: File) = toArgs(safeSlurp(f)) + private[partest] def safeToInt(s: String) = try Some(s.toInt) catch { case _: NumberFormatException => None } private[partest] def isJava(f: Path) = f.isFile && (f hasExtension "java") private[partest] def isScala(f: Path) = f.isFile && (f hasExtension "scala") private[partest] def isJavaOrScala(f: Path) = isJava(f) || isScala(f) diff --git a/src/partest/scala/tools/partest/util/package.scala b/src/partest/scala/tools/partest/util/package.scala index d20b0659ec..bc5470ba5d 100644 --- a/src/partest/scala/tools/partest/util/package.scala +++ b/src/partest/scala/tools/partest/util/package.scala @@ -12,14 +12,6 @@ import nsc.io._ /** Misc code still looking for a good home. */ package object util { - def interruptMeIn[T](seconds: Int)(body: => T): Option[T] = { - val thisThread = currentThread - val alarm = new SimpleAlarm(seconds * 1000) set thisThread.interrupt() - - try { Some(body) } - catch { case _: InterruptedException => None } - finally { alarm.cancel() ; Thread.interrupted() } - } def allPropertiesString() = javaHashtableToString(System.getProperties) @@ -67,45 +59,3 @@ package object util { if (result == "No differences") "" else result } } - -package util { - /** Set any number of alarms up with tuples of the form: - * seconds to alarm -> Function0[Unit] to execute - */ - class Alarmer(alarmTimes: (Int, () => Unit)*) { - import java.util.concurrent._ - - val exec = Executors.newSingleThreadScheduledExecutor() - private def sched(secs: Int, f: () => Unit) = - exec.schedule(new Runnable { def run() = f() }, secs, TimeUnit.SECONDS) - - alarmTimes foreach (sched _).tupled - exec.shutdown() - - def cancelAll() = exec.shutdownNow() - } - - class SimpleAlarm(timeout: Long) { - private val alarm = new Timer - /** Start a timer, running the given body if it goes off. - */ - def set(body: => Unit) = returning(new TimerTask { def run() = body })(alarm.schedule(_, timeout)) - - /** Cancel the timer. - */ - def cancel() = alarm.cancel() - } - - // Thread.setDefaultUncaughtExceptionHandler(new UncaughtException) - // class UncaughtException extends Thread.UncaughtExceptionHandler { - // def uncaughtException(t: Thread, e: Throwable) { - // Console.println("Uncaught in %s: %s".format(t, e)) - // } - // } - // - // lazy val logger = File("/tmp/partest.log").bufferedWriter() - // def flog(msg: String) = logger synchronized { - // logger write (msg + "\n") - // logger.flush() - // } -}
\ No newline at end of file |