Skip to content

Commit 23603fe

Browse files
committed
Add RunnerOrchestration to ParallelTesting trait
1 parent 63c1a6d commit 23603fe

File tree

4 files changed

+149
-87
lines changed

4 files changed

+149
-87
lines changed

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class CompilationTests extends SummaryReport with ParallelTesting {
1515

1616
// Test suite configuration --------------------------------------------------
1717

18-
def maxDuration = 3.seconds
18+
def maxDuration = 180.seconds
1919
def numberOfSlaves = 5
2020
def safeMode = sys.env.get("SAFEMODE").isDefined
2121
def isInteractive = SummaryReport.isInteractive
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package dotty.tools.dotc
2+
package vulpix
3+
4+
import java.io.{
5+
File => JFile,
6+
InputStream, ObjectInputStream,
7+
OutputStream, ObjectOutputStream,
8+
ByteArrayOutputStream, PrintStream
9+
}
10+
import java.lang.reflect.InvocationTargetException
11+
12+
import dotty.tools.dotc.vulpix.Statuses._
13+
14+
object ChildMain {
15+
val realStdin = System.in
16+
val realStderr = System.err
17+
val realStdout = System.out
18+
19+
private def runMain(dir: JFile): Status = {
20+
def renderStackTrace(ex: Throwable): String =
21+
ex.getStackTrace
22+
.takeWhile(_.getMethodName != "invoke0")
23+
.mkString(" ", "\n ", "")
24+
25+
def resetOutDescriptors(): Unit = {
26+
System.setOut(realStdout)
27+
System.setErr(realStderr)
28+
}
29+
30+
import java.net.{ URL, URLClassLoader }
31+
32+
val printStream = new ByteArrayOutputStream
33+
34+
try {
35+
// Do classloading magic and running here:
36+
val ucl = new URLClassLoader(Array(dir.toURI.toURL))
37+
val cls = ucl.loadClass("Test")
38+
val meth = cls.getMethod("main", classOf[Array[String]])
39+
40+
try {
41+
val ps = new PrintStream(printStream)
42+
System.setOut(ps)
43+
System.setErr(ps)
44+
Console.withOut(printStream) {
45+
Console.withErr(printStream) {
46+
// invoke main with "jvm" as arg
47+
meth.invoke(null, Array("jvm"))
48+
}
49+
}
50+
resetOutDescriptors()
51+
} catch {
52+
case t: Throwable =>
53+
resetOutDescriptors()
54+
throw t
55+
}
56+
new Success(printStream.toString("utf-8"))
57+
}
58+
catch {
59+
case ex: NoSuchMethodException =>
60+
val msg = s"test in '$dir' did not contain method: ${ex.getMessage}"
61+
new Failure(msg, renderStackTrace(ex.getCause))
62+
63+
case ex: ClassNotFoundException =>
64+
val msg = s"test in '$dir' did not contain class: ${ex.getMessage}"
65+
new Failure(msg, renderStackTrace(ex.getCause))
66+
67+
case ex: InvocationTargetException =>
68+
val msg = s"An exception ocurred when running main: ${ex.getCause}"
69+
new Failure(msg, renderStackTrace(ex.getCause))
70+
}
71+
}
72+
73+
def main(args: Array[String]): Unit = {
74+
val stdin = new ObjectInputStream(System.in);
75+
val stdout = new ObjectOutputStream(System.out);
76+
77+
while (true) {
78+
val dir = stdin.readObject().asInstanceOf[JFile]
79+
stdout.writeObject(runMain(dir))
80+
stdout.flush()
81+
}
82+
}
83+
}

compiler/test/dotty/tools/dotc/vulpix/ParallelTesting.scala

Lines changed: 58 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package vulpix
66
import java.io.{ File => JFile }
77
import java.text.SimpleDateFormat
88
import java.util.HashMap
9-
import java.lang.reflect.InvocationTargetException
109
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
1110
import java.nio.file.{ Files, Path, Paths, NoSuchFileException }
1211
import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException }
@@ -24,13 +23,15 @@ import reporting.diagnostic.MessageContainer
2423
import interfaces.Diagnostic.ERROR
2524
import dotc.util.DiffUtil
2625

26+
import vulpix.Statuses._
27+
2728
/** A parallel testing suite whose goal is to integrate nicely with JUnit
2829
*
2930
* This trait can be mixed in to offer parallel testing to compile runs. When
3031
* using this, you should be running your JUnit tests **sequentially**, as the
3132
* test suite itself runs with a high level of concurrency.
3233
*/
33-
trait ParallelTesting { self =>
34+
trait ParallelTesting extends RunnerOrchestration { self =>
3435

3536
import ParallelTesting._
3637
import SummaryReport._
@@ -225,14 +226,18 @@ trait ParallelTesting { self =>
225226

226227
/** The test sources that failed according to the implementing subclass */
227228
private[this] val failedTestSources = mutable.ArrayBuffer.empty[String]
228-
protected final def failTestSource(testSource: TestSource) = synchronized {
229-
failedTestSources.append(testSource.name + " failed")
229+
protected final def failTestSource(testSource: TestSource, reason: Option[String] = None) = synchronized {
230+
val extra = reason.map(" with reason: " + _).getOrElse("")
231+
failedTestSources.append(testSource.name + " failed" + extra)
230232
fail()
231233
}
232234

233235
/** Prints to `System.err` if we're not suppressing all output */
234-
protected def echo(msg: String): Unit =
235-
if (!suppressAllOutput) realStderr.println(msg)
236+
protected def echo(msg: String): Unit = if (!suppressAllOutput) {
237+
// pad right so that output is at least as large as progress bar line
238+
val paddingRight = " " * math.max(0, 80 - msg.length)
239+
realStderr.println(msg + paddingRight)
240+
}
236241

237242
/** A single `Runnable` that prints a progress bar for the curent `Test` */
238243
private def createProgressMonitor: Runnable = new Runnable {
@@ -418,97 +423,58 @@ trait ParallelTesting { self =>
418423
echoBuildInstructions(reporters.head, testSource, errorCount, warningCount)
419424
}
420425
}
421-
422426
}
423427
}
424428
}
425429

426430
private final class RunTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)
427431
extends Test(testSources, times, threadLimit, suppressAllOutput) {
428-
private def runMain(dir: JFile, testSource: TestSource): Array[String] = {
429-
def renderStackTrace(ex: Throwable): String =
430-
ex.getStackTrace
431-
.takeWhile(_.getMethodName != "invoke0")
432-
.mkString(" ", "\n ", "")
433-
434-
import java.io.{ ByteArrayOutputStream, PrintStream }
435-
import java.net.{ URL, URLClassLoader }
436-
437-
val printStream = new ByteArrayOutputStream
438-
439-
try {
440-
// Do classloading magic and running here:
441-
val ucl = new URLClassLoader(Array(dir.toURI.toURL))
442-
val cls = ucl.loadClass("Test")
443-
val meth = cls.getMethod("main", classOf[Array[String]])
444-
445-
synchronized {
446-
try {
447-
val ps = new PrintStream(printStream)
448-
System.setOut(ps)
449-
System.setErr(ps)
450-
Console.withOut(printStream) {
451-
Console.withErr(printStream) {
452-
meth.invoke(null, Array("jvm")) // partest passes at least "jvm" as an arg
453-
}
454-
}
455-
System.setOut(realStdout)
456-
System.setErr(realStderr)
457-
} catch {
458-
case t: Throwable =>
459-
System.setOut(realStdout)
460-
System.setErr(realStderr)
461-
throw t
432+
private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = {
433+
runMain(dir) match {
434+
case success: Success => {
435+
val outputLines = success.output.lines.toArray
436+
val checkLines: Array[String] = Source.fromFile(checkFile).getLines.toArray
437+
val sourceTitle = testSource.title
438+
439+
def linesMatch =
440+
outputLines
441+
.zip(checkLines)
442+
.forall { case (x, y) => x == y }
443+
444+
if (outputLines.length != checkLines.length || !linesMatch) {
445+
// Print diff to files and summary:
446+
val diff = outputLines.zip(checkLines).map { case (act, exp) =>
447+
DiffUtil.mkColoredLineDiff(exp, act)
448+
}.mkString("\n")
449+
450+
val msg =
451+
s"""|Output from '$sourceTitle' did not match check file.
452+
|Diff ('e' is expected, 'a' is actual):
453+
|""".stripMargin + diff + "\n"
454+
echo(msg)
455+
addFailureInstruction(msg)
456+
457+
// Print build instructions to file and summary:
458+
val buildInstr = testSource.buildInstructions(0, warnings)
459+
addFailureInstruction(buildInstr)
460+
461+
// Fail target:
462+
failTestSource(testSource)
462463
}
463464
}
464-
}
465-
catch {
466-
case ex: NoSuchMethodException =>
467-
echo(s"test in '$dir' did not contain method: ${ex.getMessage}\n${renderStackTrace(ex.getCause)}")
468-
failTestSource(testSource)
469465

470-
case ex: ClassNotFoundException =>
471-
echo(s"test in '$dir' did not contain class: ${ex.getMessage}\n${renderStackTrace(ex.getCause)}")
466+
case failure: Failure =>
467+
echo(renderFailure(failure))
472468
failTestSource(testSource)
473469

474-
case ex: InvocationTargetException =>
475-
echo(s"An exception ocurred when running main: ${ex.getCause}\n${renderStackTrace(ex.getCause)}")
476-
failTestSource(testSource)
470+
case _: Timeout =>
471+
echo("failed because test " + testSource.title + " timed out")
472+
failTestSource(testSource, Some("test timed out"))
477473
}
478-
printStream.toString("utf-8").lines.toArray
479474
}
480475

481-
private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = {
482-
val outputLines = runMain(dir, testSource)
483-
val checkLines = Source.fromFile(checkFile).getLines.toArray
484-
val sourceTitle = testSource.title
485-
486-
def linesMatch =
487-
outputLines
488-
.zip(checkLines)
489-
.forall { case (x, y) => x == y }
490-
491-
if (outputLines.length != checkLines.length || !linesMatch) {
492-
// Print diff to files and summary:
493-
val diff = outputLines.zip(checkLines).map { case (act, exp) =>
494-
DiffUtil.mkColoredLineDiff(exp, act)
495-
}.mkString("\n")
496-
497-
val msg =
498-
s"""|Output from '$sourceTitle' did not match check file.
499-
|Diff ('e' is expected, 'a' is actual):
500-
|""".stripMargin + diff + "\n"
501-
echo(msg)
502-
addFailureInstruction(msg)
503-
504-
// Print build instructions to file and summary:
505-
val buildInstr = testSource.buildInstructions(0, warnings)
506-
addFailureInstruction(buildInstr)
507-
508-
// Fail target:
509-
failTestSource(testSource)
510-
}
511-
}
476+
private def renderFailure(failure: Failure): String =
477+
failure.message + "\n" + failure.stacktrace
512478

513479
protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable {
514480
def run(): Unit = tryCompile(testSource) {
@@ -553,7 +519,15 @@ trait ParallelTesting { self =>
553519
}
554520

555521
if (errorCount == 0 && hasCheckFile) verifier()
556-
else if (errorCount == 0) runMain(testSource.outDir, testSource)
522+
else if (errorCount == 0) runMain(testSource.outDir) match {
523+
case status: Failure =>
524+
echo(renderFailure(status))
525+
failTestSource(testSource)
526+
case _: Timeout =>
527+
echo("failed because test " + testSource.title + " timed out")
528+
failTestSource(testSource, Some("test timed out"))
529+
case _: Success => // success!
530+
}
557531
else if (errorCount > 0) {
558532
echo(s"\nCompilation failed for: '$testSource'")
559533
val buildInstr = testSource.buildInstructions(errorCount, warningCount)

compiler/test/dotty/tools/dotc/vulpix/Statuses.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ static class Success implements Status, Serializable {
1212
}
1313

1414
static class Failure implements Status, Serializable {
15-
public final String output;
16-
public Failure(String output) { this.output = output; }
15+
public final String message;
16+
public final String stacktrace;
17+
18+
public Failure(String message, String stacktrace) {
19+
this.message = message;
20+
this.stacktrace = stacktrace;
21+
}
1722
}
1823

1924
static class Timeout implements Status, Serializable {}

0 commit comments

Comments
 (0)