Skip to content

Commit f0cd975

Browse files
Add documentation and optimise performance to the test framework
1 parent c087e62 commit f0cd975

File tree

1 file changed

+65
-33
lines changed

1 file changed

+65
-33
lines changed

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

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import java.util.concurrent.{TimeUnit, TimeoutException, Executors => JExecutors
1212

1313
import scala.collection.mutable
1414
import scala.io.Source
15-
import scala.util.{Random, Try}
15+
import scala.util.{Random, Try, Failure => TryFailure, Success => TrySuccess}
1616
import scala.util.control.NonFatal
1717
import scala.util.matching.Regex
1818

@@ -142,7 +142,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
142142
fromTasty: Boolean = false,
143143
decompilation: Boolean = false
144144
) extends TestSource {
145-
def sourceFiles: Array[JFile] = files.filter(isSourceFile).toArray
145+
def sourceFiles: Array[JFile] = files.filter(isSourceFile)
146146

147147
override def toString() = outDir.toString
148148
}
@@ -180,23 +180,27 @@ trait ParallelTesting extends RunnerOrchestration { self =>
180180
}
181181
.toList.sortBy(_._1).map(_._2.filter(isSourceFile).sorted)
182182

183-
def sourceFiles: Array[JFile] = compilationGroups.flatten.filter(isSourceFile).toArray
183+
def sourceFiles: Array[JFile] = compilationGroups.flatten.toArray
184184
}
185185

186186
private trait CompilationLogic { this: Test =>
187-
val suppressErrors = false
187+
def suppressErrors = false
188188

189-
final def compileTestSource(testSource: TestSource): Either[Throwable, List[TestReporter]] =
189+
/**
190+
* Compiles the test source.
191+
* @return The reporters containing the results of all the compilation runs for this test source.
192+
*/
193+
private final def compileTestSource(testSource: TestSource): Try[List[TestReporter]] =
190194
Try(testSource match {
191195
case testSource @ JointCompilationSource(name, files, flags, outDir, fromTasty, decompilation) =>
192196
val reporter =
193-
if (fromTasty) compileFromTasty( flags, suppressErrors, outDir)
194-
else compile(testSource.sourceFiles, flags, suppressErrors, outDir)
197+
if (fromTasty) compileFromTasty(flags, suppressErrors, outDir)
198+
else compile(testSource.sourceFiles, flags, suppressErrors, outDir)
195199
List(reporter)
196200

197201
case testSource @ SeparateCompilationSource(_, dir, flags, outDir) =>
198202
testSource.compilationGroups.map(files => compile(files, flags, suppressErrors, outDir)) // TODO? only `compile` option?
199-
}).toEither
203+
})
200204

201205
final def countErrorsAndWarnings(reporters: Seq[TestReporter]): (Int, Int) =
202206
reporters.foldLeft((0, 0)) { case ((err, warn), r) => (err + r.errorCount, warn + r.warningCount) }
@@ -205,14 +209,22 @@ trait ParallelTesting extends RunnerOrchestration { self =>
205209
final def countWarnings(reporters: Seq[TestReporter]) = countErrorsAndWarnings(reporters)._2
206210
final def reporterFailed(r: TestReporter) = r.compilerCrashed || r.errorCount > 0
207211

212+
/**
213+
* For a given test source, returns a check file against which the result of the test run
214+
* should be compared. Is used by implementations of this trait.
215+
*/
208216
final def checkFile(testSource: TestSource): Option[JFile] = (testSource match {
209217
case ts: JointCompilationSource =>
210-
ts.files.filter(f => !f.isDirectory).map { f => new JFile(f.getAbsolutePath.replaceFirst("\\.scala$", ".check")) }.headOption
218+
ts.files.collectFirst { case f if !f.isDirectory => new JFile(f.getAbsolutePath.replaceFirst("\\.scala$", ".check")) }
211219

212220
case ts: SeparateCompilationSource =>
213221
Option(new JFile(ts.dir.getAbsolutePath + ".check"))
214222
}).filter(_.exists)
215223

224+
/**
225+
* Checks if the given actual lines are the same as the ones in the check file.
226+
* If not, fails the test.
227+
*/
216228
final def diffTest(testSource: TestSource, checkFile: JFile, actual: List[String]) = {
217229
val expected = Source.fromFile(checkFile, "UTF-8").getLines().toList
218230
for (msg <- diffMessage(testSource.title, actual, expected)) {
@@ -222,6 +234,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
222234
}
223235
}
224236

237+
/** Entry point: runs the test */
225238
final def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { self =>
226239
def checkTestSource(): Unit = tryCompile(testSource) {
227240
val reportersOrCrash = compileTestSource(testSource)
@@ -230,17 +243,34 @@ trait ParallelTesting extends RunnerOrchestration { self =>
230243
}
231244
}
232245

233-
final def onComplete(testSource: TestSource, reportersOrCrash: Either[Throwable, Seq[TestReporter]], logger: LoggedRunnable): Unit =
234-
reportersOrCrash.fold(
235-
exn => onFailure(testSource, Nil, logger, Some(s"Fatal compiler crash when compiling: ${testSource.title}:\n${exn.getMessage}\n${exn.getStackTrace.mkString("\n")}"))
236-
, reporters => testFailed(testSource, reporters).fold
237-
( onSuccess(testSource, reporters, logger ) )
238-
(msg => onFailure(testSource, reporters, logger, Option(msg).filter(_.nonEmpty)) ) )
246+
/** This callback is executed once the compilation of this test source finished */
247+
final def onComplete(testSource: TestSource, reportersOrCrash: Try[Seq[TestReporter]], logger: LoggedRunnable): Unit =
248+
reportersOrCrash match {
249+
case TryFailure(exn) => onFailure(testSource, Nil, logger, Some(s"Fatal compiler crash when compiling: ${testSource.title}:\n${exn.getMessage}\n${exn.getStackTrace.mkString("\n")}"))
250+
case TrySuccess(reporters) => maybeFailureMessage(testSource, reporters) match {
251+
case Some(msg) => onFailure(testSource, reporters, logger, Option(msg).filter(_.nonEmpty))
252+
case None => onSuccess(testSource, reporters, logger)
253+
}
254+
}
239255

240-
def testFailed(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] =
241-
Option(reporters.exists(reporterFailed)).filter(identity).map(_ => s"Compilation failed for: '${testSource.title}'")
256+
/**
257+
* Based on the reporters obtained after the compilation, determines if this test has failed.
258+
* If it has, returns a Some with an error message. Otherwise, returns None.
259+
* As the conditions of failure are different for different test types, this method should be
260+
* overridden by the concrete implementations of this trait.
261+
*/
262+
def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] =
263+
if (reporters.exists(reporterFailed)) Some(s"Compilation failed for: '${testSource.title}'")
264+
else None
242265

266+
/**
267+
* If the test has compiled successfully, this callback will be called. You can still fail the test from this callback.
268+
*/
243269
def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable): Unit = ()
270+
271+
/**
272+
* If the test failed to compile or the compiler crashed, this callback will be called.
273+
*/
244274
def onFailure(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable, message: Option[String]): Unit = {
245275
message.foreach(echo)
246276
reporters.filter(reporterFailed).foreach(logger.logReporterContents)
@@ -623,9 +653,11 @@ trait ParallelTesting extends RunnerOrchestration { self =>
623653

624654
private def verifyOutput(checkFile: Option[JFile], dir: JFile, testSource: TestSource, warnings: Int) = {
625655
if (Properties.testsNoRun) addNoRunWarning()
626-
else runMain(testSource.runClassPath) match {
627-
case Success(_) if !checkFile.isDefined || !checkFile.get.exists =>
628-
case Success(output) => checkFile.foreach(diffTest(testSource, _, output.linesIterator.toList))
656+
else runMain(testSource.runClassPath) match {
657+
case Success(output) => checkFile match {
658+
case Some(file) if file.exists => diffTest(testSource, file, output.linesIterator.toList)
659+
case _ =>
660+
}
629661
case Failure(output) =>
630662
echo(s"Test '${testSource.title}' failed with output:")
631663
echo(output)
@@ -642,20 +674,20 @@ trait ParallelTesting extends RunnerOrchestration { self =>
642674

643675
private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
644676
extends Test(testSources, times, threadLimit, suppressAllOutput) {
645-
override val suppressErrors = true
677+
override def suppressErrors = true
646678

647-
override def testFailed(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = {
648-
val compilerCrashed = reporters.exists(_.compilerCrashed)
649-
val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(testSource.sourceFiles)
650-
val actualErrors = reporters.foldLeft(0)(_ + _.errorCount)
651-
val hasMissingAnnotations = getMissingExpectedErrors(errorMap, reporters.iterator.flatMap(_.errors))
679+
override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = {
680+
def compilerCrashed = reporters.exists(_.compilerCrashed)
681+
lazy val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(testSource.sourceFiles)
682+
lazy val actualErrors = reporters.foldLeft(0)(_ + _.errorCount)
683+
def hasMissingAnnotations = getMissingExpectedErrors(errorMap, reporters.iterator.flatMap(_.errors))
652684

653-
if (compilerCrashed ) Some(s"Compiler crashed when compiling: ${testSource.title}" )
654-
else if (actualErrors == 0 ) Some(s"\nNo errors found when compiling neg test $testSource" )
685+
if (compilerCrashed) Some(s"Compiler crashed when compiling: ${testSource.title}" )
686+
else if (actualErrors == 0) Some(s"\nNo errors found when compiling neg test $testSource" )
655687
else if (expectedErrors != actualErrors) Some(s"\nWrong number of errors encountered when compiling $testSource, expected: $expectedErrors, actual: $actualErrors")
656-
else if (hasMissingAnnotations ) Some(s"\nErrors found on incorrect row numbers when compiling $testSource" )
657-
else if (!errorMap.isEmpty ) Some(s"\nExpected error(s) have {<error position>=<unreported error>}: $errorMap" )
658-
else None
688+
else if (hasMissingAnnotations) Some(s"\nErrors found on incorrect row numbers when compiling $testSource" )
689+
else if (!errorMap.isEmpty) Some(s"\nExpected error(s) have {<error position>=<unreported error>}: $errorMap" )
690+
else None
659691
}
660692

661693
override def onSuccess(testSource: TestSource, reporters: Seq[TestReporter], logger: LoggedRunnable): Unit =
@@ -717,8 +749,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
717749

718750
private final class NoCrashTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting)
719751
extends Test(testSources, times, threadLimit, suppressAllOutput) {
720-
override val suppressErrors = true
721-
override def testFailed(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = None
752+
override def suppressErrors = true
753+
override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = None
722754
}
723755

724756
def diffMessage(sourceTitle: String, outputLines: Seq[String], checkLines: Seq[String]): Option[String] = {

0 commit comments

Comments
 (0)