-
Notifications
You must be signed in to change notification settings - Fork 27
Add a mode to reuse JVM for test execution #99
Conversation
The cost of VM startup and JIT is the predominant reason that `partest --run` is slow. We're much better of using one thread on the main JVM to execute these tests. This commit uses such a mode under a non-default option. Unforked test execution requires some care: - we need to check the main JVM is started with the mandated immutable System properties (e.g. file.encoding) - other test-specific system properties (e.g. partest.output) need to be patched into the system property map before each test is executed. - sys.exit calls need to be intercepted with a security manager - stdout / stderr need to be intercepted, both in the Java and Scala console printing APIs. Some tests need to be adapted, in particular those that assume that java.class.path will contain the test classes. Tests can also force usage of the legacy forked mode by adding a .javaopts file with a dummy JVM option. Also improve recording of test durations and flag slow tests in console output.
My branch of adapted/deleted tests is over in https://github.com/retronym/scala/tree/topic/partest-apace With this version of partest, that now completes in 11.5 minutes. |
Seems like JVM reuse is the main win. I've also been experimenting with "Global reuse" which I haven't got working yet, but only seems to offer another 1-2m speedup. I've parked that effort in my |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some small questions/nitpicks. Won't hold up the PR over those.
def withExtraProperties[A](extra: Map[String, String])(action: => A): A = { | ||
val saved = System.getProperties() | ||
val modified = new java.util.Properties() | ||
saved.stringPropertyNames().forEach((k) => modified.setProperty(k, saved.getProperty(k))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
val modified = saved.clone.asInstanceOf[java.util.Properties]
modified.putAll(extra.asJava)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've settled on modified.putAll(saved)
to avoid the cast after cloning.
val savedOut = System.out | ||
val savedErr = System.err | ||
try { | ||
val charset = Charset.defaultCharset() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Just wondering: how do we know System.out
was set to a stream with the encoding returned by Charset.defaultCharset()
? Does it matter? It doesn't look like we can recover the info anyway.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point System.out
will be the default, as the rest of partest hasn't messed with it.
But looking back at this code, I now realise that one other place in the partest is mutating System.{out,err}
:
pushTranscript(args mkString " ")
val captured = StreamCapture(runCommand(args, logFile))
if (captured.result) genPass() else {
logFile appendAll captured.stderr
genFail("java compilation failed")
}
So we have a potential race condition if a Java compilation step happens concurrently with a non-forked test execution step.
That wrapping of Javac looks to be a no-op, we fork a process to start javac, so we should just rely on the ProcessLogger
to redirect its output to a file. I'll add an assertion to that effect and test things out, and hopefully I'll be able to remove that use of StreamCapture
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, we still get Javac compilation errors under --verbose
without that StreamCapture
. Pushed that change.
base = Some(System.nanoTime()) | ||
} | ||
|
||
def stop(): Long = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's add a quick comment to document the unit
nice! supposedly installing a SecurityManager incurs "substantial filesystem performance penalty" (https://github.com/sbt/sbt/blob/v1.1.1/run/src/main/scala/sbt/TrapExit.scala#L425-L432). did you try either using those overrides? alternatively maybe trapping exit can be opt-in by the (I presume) few partests that use sys.exit? (edit: more details in the associated PR sbt/sbt#1153) |
- Comment for Stopwatch - Delete commented out debug code - Refactor properties save/restore - DRY up console scraping
I'm not using the default implementations of class DelegatingSecurityManager(delegate: SecurityManager) extends SecurityManager {
...
override def checkExec(cmd: String): Unit = if (delegate ne null) delegate.checkExec(cmd)
override def checkWrite(file: String): Unit = if (delegate ne null) delegate.checkWrite(file)
override def checkDelete(file: String): Unit = if (delegate ne null) delegate.checkDelete(file)
override def checkRead(file: String): Unit = if (delegate ne null) delegate.checkRead(file)
override def checkRead(file: String, context: scala.Any): Unit = if (delegate ne null) delegate.checkRead(file, context)
|
We fork a process with a process logger that redirects stdout and stderr to a log file, so I can't see why this was ever needed. Tested: ``` Note: test execution will be non-parallel under -Dpartest.exec.in.process % scalac t1459/InheritingPrinter.scala t1459/JavaPrinter.java t1459/ScalaPrinter.scala t1459/Test.java t1459/VarArg.java !! 1 - run/t1459 [compilation failed] % scalac t1459/InheritingPrinter.scala t1459/JavaPrinter.java t1459/ScalaPrinter.scala t1459/Test.java t1459/VarArg.java JavaPrinter.java:1: error: illegal start of type declaration public cla ss JavaPrinter implements VarArg { ^ one error found partest --update-check \ /Users/jz/code/scala/test/files/run/t1459 ```
Add a mode to reuse JVM for test execution
Add a mode to reuse JVM for test execution
val captured = StreamCapture(runCommand(args, logFile)) | ||
if (captured.result) genPass() else { | ||
logFile appendAll captured.stderr | ||
if (runCommand(args, logFile)) genPass() else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer the question in the commit message: there were two tickets, on the first someone comments that process #>
doesn't do stderr. So StreamCapture
came first. The proper fix using process logger came second.
That was exactly five years ago this past US Memorial Day weekend.
I said: In about five years, paulp will levy a commit, "Remove commented code from 2013." I still can't believe it's 2013. It is 2013, right?
You said: I don't recall having to enter 2-0-1-3 on a form this year. Usually I write down the previous year the first few times; after that my brain comes to grips with the annus novus.
It turns out there is no coming to grips.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the nostalgia trip :)
The cost of VM startup and JIT is the predominant reason that
partest --run
is slow. We're much better of using one threadon the main JVM to execute these tests.
This commit uses such a mode under a non-default option.
Unforked test execution requires some care:
immutable System properties (e.g. file.encoding)
need to be patched into the system property map before
each test is executed.
Scala console printing APIs.
Some tests need to be adapted, in particular those that assume
that java.class.path will contain the test classes.
Tests can also force usage of the legacy forked mode by adding
a .javaopts file with a dummy JVM option.
Also improve recording of test durations and flag slow tests in
console output.