Skip to content

Commit 1e9bc6f

Browse files
committed
Add RunnerOrchestration to ParallelTesting trait
1 parent a61cc0d commit 1e9bc6f

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 }
@@ -23,13 +22,15 @@ import reporting.diagnostic.MessageContainer
2322
import interfaces.Diagnostic.ERROR
2423
import dotc.util.DiffUtil
2524

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

3435
import ParallelTesting._
3536
import SummaryReport._
@@ -224,14 +225,18 @@ trait ParallelTesting { self =>
224225

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

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

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

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

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

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

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

512478
protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable {
513479
def run(): Unit = tryCompile(testSource) {
@@ -552,7 +518,15 @@ trait ParallelTesting { self =>
552518
}
553519

554520
if (errorCount == 0 && hasCheckFile) verifier()
555-
else if (errorCount == 0) runMain(testSource.outDir, testSource)
521+
else if (errorCount == 0) runMain(testSource.outDir) match {
522+
case status: Failure =>
523+
echo(renderFailure(status))
524+
failTestSource(testSource)
525+
case _: Timeout =>
526+
echo("failed because test " + testSource.title + " timed out")
527+
failTestSource(testSource, Some("test timed out"))
528+
case _: Success => // success!
529+
}
556530
else if (errorCount > 0) {
557531
echo(s"\nCompilation failed for: '$testSource'")
558532
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)