aboutsummaryrefslogtreecommitdiff
path: root/compiler/test/dotty/tools/dotc
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/test/dotty/tools/dotc')
-rw-r--r--compiler/test/dotty/tools/dotc/CompilationTests.scala86
-rw-r--r--compiler/test/dotty/tools/dotc/CompilerTest.scala231
-rw-r--r--compiler/test/dotty/tools/dotc/ParallelSummaryReport.java73
-rw-r--r--compiler/test/dotty/tools/dotc/ParallelTestTests.scala58
-rw-r--r--compiler/test/dotty/tools/dotc/ParallelTesting.scala1122
-rw-r--r--compiler/test/dotty/tools/dotc/reporting/TestReporter.scala69
-rw-r--r--compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala3
7 files changed, 96 insertions, 1546 deletions
diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala
index 742b93fae..ff50d7238 100644
--- a/compiler/test/dotty/tools/dotc/CompilationTests.scala
+++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala
@@ -2,19 +2,24 @@ package dotty
package tools
package dotc
-import org.junit.Test
-import java.io.{ File => JFile }
-import org.junit.experimental.categories.Category
+import org.junit.{ Test, BeforeClass, AfterClass }
import scala.util.matching.Regex
+import scala.concurrent.duration._
-@Category(Array(classOf[ParallelTesting]))
-class CompilationTests extends ParallelSummaryReport with ParallelTesting {
+import vulpix.{ ParallelTesting, SummaryReport, SummaryReporting, TestConfiguration }
+
+class CompilationTests extends ParallelTesting {
+ import TestConfiguration._
import CompilationTests._
- def isInteractive: Boolean = ParallelSummaryReport.isInteractive
+ // Test suite configuration --------------------------------------------------
- def testFilter: Option[Regex] = sys.props.get("dotty.partest.filter").map(r => new Regex(r))
+ def maxDuration = 180.seconds
+ def numberOfSlaves = 5
+ def safeMode = Properties.testsSafeMode
+ def isInteractive = SummaryReport.isInteractive
+ def testFilter = Properties.testsFilter
// Positive tests ------------------------------------------------------------
@@ -211,9 +216,9 @@ class CompilationTests extends ParallelSummaryReport with ParallelTesting {
val opt = Array(
"-classpath",
// compile with bootstrapped library on cp:
- defaultOutputDir + "lib$1/src/:" +
+ defaultOutputDir + "lib/src/:" +
// as well as bootstrapped compiler:
- defaultOutputDir + "dotty1$1/dotty/:" +
+ defaultOutputDir + "dotty1/dotty/:" +
Jars.dottyInterfaces
)
@@ -248,65 +253,6 @@ class CompilationTests extends ParallelSummaryReport with ParallelTesting {
}
object CompilationTests {
- implicit val defaultOutputDir: String = "../out/"
-
- implicit class RichStringArray(val xs: Array[String]) extends AnyVal {
- def and(args: String*): Array[String] = {
- val argsArr: Array[String] = args.toArray
- xs ++ argsArr
- }
- }
-
- val noCheckOptions = Array(
- "-pagewidth", "120",
- "-color:never"
- )
-
- val checkOptions = Array(
- "-Yno-deep-subtypes",
- "-Yno-double-bindings",
- "-Yforce-sbt-phases"
- )
-
- val classPath = {
- val paths = Jars.dottyTestDeps map { p =>
- val file = new JFile(p)
- assert(
- file.exists,
- s"""|File "$p" couldn't be found. Run `packageAll` from build tool before
- |testing.
- |
- |If running without sbt, test paths need to be setup environment variables:
- |
- | - DOTTY_LIBRARY
- | - DOTTY_COMPILER
- | - DOTTY_INTERFACES
- | - DOTTY_EXTRAS
- |
- |Where these all contain locations, except extras which is a colon
- |separated list of jars.
- |
- |When compiling with eclipse, you need the sbt-interfaces jar, put
- |it in extras."""
- )
- file.getAbsolutePath
- } mkString (":")
-
- Array("-classpath", paths)
- }
-
- private val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef")
-
- val defaultOptions = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath
- val allowDeepSubtypes = defaultOptions diff Array("-Yno-deep-subtypes")
- val allowDoubleBindings = defaultOptions diff Array("-Yno-double-bindings")
- val picklingOptions = defaultOptions ++ Array(
- "-Xprint-types",
- "-Ytest-pickler",
- "-Ystop-after:pickler",
- "-Yprintpos"
- )
- val scala2Mode = defaultOptions ++ Array("-language:Scala2")
- val explicitUTF8 = defaultOptions ++ Array("-encoding", "UTF8")
- val explicitUTF16 = defaultOptions ++ Array("-encoding", "UTF16")
+ implicit val summaryReport: SummaryReporting = new SummaryReport
+ @AfterClass def cleanup(): Unit = summaryReport.echoSummary()
}
diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala
index f35f9f919..c5234ccca 100644
--- a/compiler/test/dotty/tools/dotc/CompilerTest.scala
+++ b/compiler/test/dotty/tools/dotc/CompilerTest.scala
@@ -2,7 +2,6 @@ package dotty.tools.dotc
import repl.TestREPL
import core.Contexts._
-import dotty.partest.DPConfig
import interfaces.Diagnostic.ERROR
import reporting._
import diagnostic.MessageContainer
@@ -11,33 +10,11 @@ import config.CompilerCommand
import dotty.tools.io.PlainFile
import scala.collection.mutable.ListBuffer
import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile }
-import scala.tools.partest.nest.{ FileManager, NestUI }
import scala.annotation.tailrec
import java.io.{ RandomAccessFile, File => JFile }
-/** This class has two modes: it can directly run compiler tests, or it can
- * generate the necessary file structure for partest in the directory
- * DPConfig.testRoot. Both modes are regular JUnit tests. Which mode is used
- * depends on the existence of the tests/locks/partest-ppid.lock file which is
- * created by sbt to trigger partest generation. Sbt will then run partest on
- * the generated sources.
- *
- * Through overriding the partestableXX methods, tests can always be run as
- * JUnit compiler tests. Run tests cannot be run by JUnit, only by partest.
- *
- * A test can either be a file or a directory. Partest will generate a
- * <test>-<kind>.log file with output of failed tests. Partest reads compiler
- * flags and the number of errors expected from a neg test from <test>.flags
- * and <test>.nerr files (also generated). The test is in a parent directory
- * that determines the kind of test:
- * - pos: checks that compilation succeeds
- * - neg: checks that compilation fails with the given number of errors
- * - run: compilation succeeds, partest: test run generates the output in
- * <test>.check. Run tests always need to be:
- * object Test { def main(args: Array[String]): Unit = ... }
- * Classpath jars can be added to partestDeps in the sbt Build.scala.
- */
+/** Legacy compiler tests that run single threaded */
abstract class CompilerTest {
/** Override with output dir of test so it can be patched. Partest expects
@@ -49,32 +26,9 @@ abstract class CompilerTest {
def partestableDir(prefix: String, dirName: String, args: List[String]) = true
def partestableList(testName: String, files: List[String], args: List[String]) = true
- val generatePartestFiles = {
- /* Because we fork in test, the JVM in which this JUnit test runs has a
- * different pid from the one that started the partest. But the forked VM
- * receives the pid of the parent as system property. If the lock file
- * exists, the parent is requesting partest generation. This mechanism
- * allows one sbt instance to run test (JUnit only) and another partest.
- * We cannot run two instances of partest at the same time, because they're
- * writing to the same directories. The sbt lock file generation prevents
- * this.
- */
- val pid = System.getProperty("partestParentID")
- if (pid == null)
- false
- else
- new JFile(".." + JFile.separator + "tests" + JFile.separator + "locks" + JFile.separator + s"partest-$pid.lock").exists
- }
-
- // Delete generated files from previous run and create new log
- val logFile = if (!generatePartestFiles) None else Some(CompilerTest.init)
-
/** Always run with JUnit. */
- def compileLine(cmdLine: String)(implicit defaultOptions: List[String]): Unit = {
- if (generatePartestFiles)
- log("WARNING: compileLine will always run with JUnit, no partest files generated.")
+ def compileLine(cmdLine: String)(implicit defaultOptions: List[String]): Unit =
compileArgs(cmdLine.split("\n"), Nil)
- }
/** Compiles the given code file.
*
@@ -88,36 +42,22 @@ abstract class CompilerTest {
(implicit defaultOptions: List[String]): Unit = {
val filePath = s"$prefix$fileName$extension"
val expErrors = expectedErrors(filePath)
- if (!generatePartestFiles || !partestableFile(prefix, fileName, extension, args ++ defaultOptions)) {
- if (runTest)
- log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$fileName$extension")
- if (args.contains("-rewrite")) {
- val file = new PlainFile(filePath)
- val data = file.toByteArray
- // compile with rewrite
- compileArgs((filePath :: args).toArray, expErrors)
- // compile again, check that file now compiles without -language:Scala2
- val plainArgs = args.filter(arg => arg != "-rewrite" && arg != "-language:Scala2")
- compileFile(prefix, fileName, plainArgs, extension, runTest)
- // restore original test file
- val out = file.output
- out.write(data)
- out.close()
- }
- else compileArgs((filePath :: args).toArray, expErrors)
- } else {
- val kind = testKind(prefix, runTest)
- log(s"generating partest files for test file: $prefix$fileName$extension of kind $kind")
-
- val sourceFile = new JFile(prefix + fileName + extension)
- if (sourceFile.exists) {
- val firstDest = SFile(DPConfig.testRoot + JFile.separator + kind + JFile.separator + fileName + extension)
- val xerrors = expErrors.map(_.totalErrors).sum
- computeDestAndCopyFiles(sourceFile, firstDest, kind, args ++ defaultOptions, xerrors.toString)
- } else {
- throw new java.io.FileNotFoundException(s"Unable to locate test file $prefix$fileName")
- }
+ if (runTest)
+ log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$fileName$extension")
+ if (args.contains("-rewrite")) {
+ val file = new PlainFile(filePath)
+ val data = file.toByteArray
+ // compile with rewrite
+ compileArgs((filePath :: args).toArray, expErrors)
+ // compile again, check that file now compiles without -language:Scala2
+ val plainArgs = args.filter(arg => arg != "-rewrite" && arg != "-language:Scala2")
+ compileFile(prefix, fileName, plainArgs, extension, runTest)
+ // restore original test file
+ val out = file.output
+ out.write(data)
+ out.close()
}
+ else compileArgs((filePath :: args).toArray, expErrors)
}
def runFile(prefix: String, fileName: String, args: List[String] = Nil, extension: String = ".scala")
(implicit defaultOptions: List[String]): Unit = {
@@ -167,33 +107,11 @@ abstract class CompilerTest {
val expErrors = expectedErrors(filePaths.toList)
(filePaths, javaFilePaths, normArgs, expErrors)
}
- if (!generatePartestFiles || !partestableDir(prefix, dirName, args ++ defaultOptions)) {
- if (runTest)
- log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$dirName")
- val (filePaths, javaFilePaths, normArgs, expErrors) = computeFilePathsAndExpErrors
- compileWithJavac(javaFilePaths, Array.empty) // javac needs to run first on dotty-library
- compileArgs(javaFilePaths ++ filePaths ++ normArgs, expErrors)
- } else {
- val (sourceDir, flags, deep) = args match {
- case "-deep" :: args1 => (flattenDir(prefix, dirName), args1 ++ defaultOptions, "deep")
- case _ => (new JFile(prefix + dirName), args ++ defaultOptions, "shallow")
- }
- val kind = testKind(prefix, runTest)
- log(s"generating partest files for test directory ($deep): $prefix$dirName of kind $kind")
-
- if (sourceDir.exists) {
- val firstDest = Directory(DPConfig.testRoot + JFile.separator + kind + JFile.separator + dirName)
- val xerrors = if (isNegTest(prefix)) {
- val (_, _, _, expErrors) = computeFilePathsAndExpErrors
- expErrors.map(_.totalErrors).sum
- } else 0
- computeDestAndCopyFiles(sourceDir, firstDest, kind, flags, xerrors.toString)
- if (deep == "deep")
- Directory(sourceDir).deleteRecursively
- } else {
- throw new java.io.FileNotFoundException(s"Unable to locate test dir $prefix$dirName")
- }
- }
+ if (runTest)
+ log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$dirName")
+ val (filePaths, javaFilePaths, normArgs, expErrors) = computeFilePathsAndExpErrors
+ compileWithJavac(javaFilePaths, Array.empty) // javac needs to run first on dotty-library
+ compileArgs(javaFilePaths ++ filePaths ++ normArgs, expErrors)
}
def runDir(prefix: String, dirName: String, args: List[String] = Nil)
(implicit defaultOptions: List[String]): Unit =
@@ -222,19 +140,8 @@ abstract class CompilerTest {
/** Compiles the given list of code files. */
def compileList(testName: String, files: List[String], args: List[String] = Nil)
(implicit defaultOptions: List[String]): Unit = {
- if (!generatePartestFiles || !partestableList(testName, files, args ++ defaultOptions)) {
- val expErrors = expectedErrors(files)
- compileArgs((files ++ args).toArray, expErrors)
- } else {
- val destDir = Directory(DPConfig.testRoot + JFile.separator + testName)
- files.foreach({ file =>
- val sourceFile = new JFile(file)
- val destFile = destDir / (if (file.startsWith("../")) file.substring(3) else file)
- recCopyFiles(sourceFile, destFile)
- })
- compileDir(DPConfig.testRoot + JFile.separator, testName, args)
- destDir.deleteRecursively
- }
+ val expErrors = expectedErrors(files)
+ compileArgs((files ++ args).toArray, expErrors)
}
// ========== HELPERS =============
@@ -425,60 +332,6 @@ abstract class CompilerTest {
}
import Difference._
- /** The same source might be used for several partest test cases (e.g. with
- * different flags). Detects existing versions and computes the path to be
- * used for this version, e.g. testname_v1 for the first alternative. */
- private def computeDestAndCopyFiles(source: JFile, dest: Path, kind: String, oldFlags: List[String], nerr: String,
- nr: Int = 0, oldOutput: String = defaultOutputDir): Unit = {
-
- val partestOutput = dest.jfile.getParentFile + JFile.separator + dest.stripExtension + "-" + kind + ".obj"
-
- val altOutput =
- source.getParentFile.getAbsolutePath.map(x => if (x == JFile.separatorChar) '_' else x)
-
- val (beforeCp, remaining) = oldFlags
- .map(f => if (f == oldOutput) partestOutput else f)
- .span(_ != "-classpath")
- val flags = beforeCp ++ List("-classpath", (partestOutput :: remaining.drop(1)).mkString(":"))
-
- val difference = getExisting(dest).isDifferent(source, flags, nerr)
- difference match {
- case NotExists => copyFiles(source, dest, partestOutput, flags, nerr, kind)
- case ExistsSame => // nothing else to do
- case ExistsDifferent =>
- val nextDest = dest.parent / (dest match {
- case d: Directory =>
- val newVersion = replaceVersion(d.name, nr).getOrElse(altOutput)
- Directory(newVersion)
- case f =>
- val newVersion = replaceVersion(f.stripExtension, nr).getOrElse(altOutput)
- SFile(newVersion).addExtension(f.extension)
- })
- computeDestAndCopyFiles(source, nextDest, kind, flags, nerr, nr + 1, partestOutput)
- }
- }
-
- /** Copies the test sources. Creates flags, nerr, check and output files. */
- private def copyFiles(sourceFile: Path, dest: Path, partestOutput: String, flags: List[String], nerr: String, kind: String) = {
- recCopyFiles(sourceFile, dest)
-
- new JFile(partestOutput).mkdirs
-
- if (flags.nonEmpty)
- dest.changeExtension("flags").createFile(true).writeAll(flags.mkString(" "))
- if (nerr != "0")
- dest.changeExtension("nerr").createFile(true).writeAll(nerr)
- sourceFile.changeExtension("check").ifFile({ check =>
- if (kind == "run") {
- FileManager.copyFile(check.jfile, dest.changeExtension("check").jfile)
- dest.changeExtension("checksrc").createFile(true).writeAll("check file generated from source:\n" + check.toString)
- } else {
- log(s"WARNING: ignoring $check for test kind $kind")
- }
- })
-
- }
-
/** Recursively copy over source files and directories, excluding extensions
* that aren't in extensionsToCopy. */
private def recCopyFiles(sourceFile: Path, dest: Path): Unit = {
@@ -576,38 +429,6 @@ abstract class CompilerTest {
}
}
- /** Creates a temporary directory and copies all (deep) files over, thus
- * flattening the directory structure. */
- private def flattenDir(prefix: String, dirName: String): JFile = {
- val destDir = Directory(DPConfig.testRoot + JFile.separator + "_temp")
- Directory(prefix + dirName).deepFiles.foreach(source => recCopyFiles(source, destDir / source.name))
- destDir.jfile
- }
-
- /** Write either to console (JUnit) or log file (partest). */
- private def log(msg: String) = logFile.map(_.appendAll(msg + "\n")).getOrElse(println(msg))
-}
-
-object CompilerTest extends App {
-
- /** Deletes generated partest sources from a previous run, recreates
- * directory and returns the freshly created log file. */
- lazy val init: SFile = {
- scala.reflect.io.Directory(DPConfig.testRoot).deleteRecursively
- new JFile(DPConfig.testRoot).mkdirs
- val log = DPConfig.genLog.createFile(true)
- println(s"CompilerTest is generating tests for partest, log: $log")
- log
- }
-
-// val dotcDir = "/Users/odersky/workspace/dotty/src/dotty/"
-
-// new CompilerTest().compileFile(dotcDir + "tools/dotc/", "CompilationUnit")
-// new CompilerTest().compileFile(dotcDir + "tools/dotc/", "Compiler")
-// new CompilerTest().compileFile(dotcDir + "tools/dotc/", "Driver")
-// new CompilerTest().compileFile(dotcDir + "tools/dotc/", "Main")
-// new CompilerTest().compileFile(dotcDir + "tools/dotc/", "Run")
-
-// new CompilerTest().compileDir(dotcDir + "tools/dotc")
- // new CompilerTest().compileFile(dotcDir + "tools/dotc/", "Run")
+ /** Write either to console */
+ private def log(msg: String) = println(msg)
}
diff --git a/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java b/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java
deleted file mode 100644
index 5608b3656..000000000
--- a/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package dotty.tools.dotc;
-
-import org.junit.BeforeClass;
-import org.junit.AfterClass;
-import java.util.ArrayDeque;
-
-import dotty.tools.dotc.reporting.TestReporter;
-import dotty.tools.dotc.reporting.TestReporter$;
-
-/** Note that while `ParallelTesting` runs in parallel, JUnit tests cannot with
- * this class
- */
-public class ParallelSummaryReport {
- public final static boolean isInteractive = !System.getenv().containsKey("DRONE");
-
- private static TestReporter rep = TestReporter.reporter(System.out, -1);
- private static ArrayDeque<String> failedTests = new ArrayDeque<>();
- private static ArrayDeque<String> reproduceInstructions = new ArrayDeque<>();
- private static int passed;
- private static int failed;
-
- public final static void reportFailed() {
- failed++;
- }
-
- public final static void reportPassed() {
- passed++;
- }
-
- public final static void addFailedTest(String msg) {
- failedTests.offer(msg);
- }
-
- public final static void addReproduceInstruction(String msg) {
- reproduceInstructions.offer(msg);
- }
-
- @BeforeClass public final static void setup() {
- rep = TestReporter.reporter(System.out, -1);
- failedTests = new ArrayDeque<>();
- reproduceInstructions = new ArrayDeque<>();
- }
-
- @AfterClass public final static void teardown() {
- rep.echo(
- "\n================================================================================" +
- "\nTest Report" +
- "\n================================================================================" +
- "\n" +
- passed + " passed, " + failed + " failed, " + (passed + failed) + " total" +
- "\n"
- );
-
- failedTests
- .stream()
- .map(x -> " " + x)
- .forEach(rep::echo);
-
- // If we're compiling locally, we don't need reproduce instructions
- if (isInteractive) rep.flushToStdErr();
-
- rep.echo("");
-
- reproduceInstructions
- .stream()
- .forEach(rep::echo);
-
- // If we're on the CI, we want everything
- if (!isInteractive) rep.flushToStdErr();
-
- if (failed > 0) rep.flushToFile();
- }
-}
diff --git a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala
deleted file mode 100644
index cfb108ea7..000000000
--- a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala
+++ /dev/null
@@ -1,58 +0,0 @@
-package dotty
-package tools
-package dotc
-
-import org.junit.Assert._
-import org.junit.Test
-
-import scala.util.control.NonFatal
-
-class ParallelTestTests extends ParallelTesting {
- import CompilationTests._
-
- def isInteractive = !sys.env.contains("DRONE")
- def testFilter = None
-
- @Test def missingFile: Unit =
- try {
- compileFile("../tests/partest-test/i-dont-exist.scala", defaultOptions).expectFailure.checkExpectedErrors()
- fail("didn't fail properly")
- }
- catch {
- case _: IllegalArgumentException => // pass!
- case NonFatal(_) => fail("wrong exception thrown")
- }
-
- @Test def pos1Error: Unit =
- compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.checkCompile()
-
- @Test def negMissingAnnot: Unit =
- compileFile("../tests/partest-test/negMissingAnnot.scala", defaultOptions).expectFailure.checkExpectedErrors()
-
- @Test def negAnnotWrongLine: Unit =
- compileFile("../tests/partest-test/negAnnotWrongLine.scala", defaultOptions).expectFailure.checkExpectedErrors()
-
- @Test def negTooManyAnnots: Unit =
- compileFile("../tests/partest-test/negTooManyAnnots.scala", defaultOptions).expectFailure.checkExpectedErrors()
-
- @Test def negNoPositionAnnot: Unit =
- compileFile("../tests/partest-test/negNoPositionAnnots.scala", defaultOptions).expectFailure.checkExpectedErrors()
-
- @Test def runCompileFail: Unit =
- compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.checkRuns()
-
- @Test def runWrongOutput1: Unit =
- compileFile("../tests/partest-test/runWrongOutput1.scala", defaultOptions).expectFailure.checkRuns()
-
- @Test def runWrongOutput2: Unit =
- compileFile("../tests/partest-test/runWrongOutput2.scala", defaultOptions).expectFailure.checkRuns()
-
- @Test def runDiffOutput1: Unit =
- compileFile("../tests/partest-test/runDiffOutput1.scala", defaultOptions).expectFailure.checkRuns()
-
- @Test def runStackOverflow: Unit =
- compileFile("../tests/partest-test/stackOverflow.scala", defaultOptions).expectFailure.checkRuns()
-
- @Test def runOutRedirects: Unit =
- compileFile("../tests/partest-test/i2147.scala", defaultOptions).expectFailure.checkRuns()
-}
diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala
deleted file mode 100644
index 80c56808b..000000000
--- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala
+++ /dev/null
@@ -1,1122 +0,0 @@
-package dotty
-package tools
-package dotc
-
-import java.io.{ File => JFile }
-import java.text.SimpleDateFormat
-import java.util.HashMap
-import java.lang.reflect.InvocationTargetException
-import java.nio.file.StandardCopyOption.REPLACE_EXISTING
-import java.nio.file.{ Files, Path, Paths, NoSuchFileException }
-import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException }
-
-import scala.io.Source
-import scala.util.control.NonFatal
-import scala.util.Try
-import scala.collection.mutable
-import scala.util.matching.Regex
-import scala.util.Random
-
-import core.Contexts._
-import reporting.{ Reporter, TestReporter }
-import reporting.diagnostic.MessageContainer
-import interfaces.Diagnostic.ERROR
-import dotc.util.DiffUtil
-
-/** A parallel testing suite whose goal is to integrate nicely with JUnit
- *
- * This trait can be mixed in to offer parallel testing to compile runs. When
- * using this, you should be running your JUnit tests **sequentially**, as the
- * test suite itself runs with a high level of concurrency.
- */
-trait ParallelTesting { self =>
-
- import ParallelTesting._
- import ParallelSummaryReport._
-
- /** If the running environment supports an interactive terminal, each `Test`
- * will be run with a progress bar and real time feedback
- */
- def isInteractive: Boolean
-
- /** A regex which is used to filter which tests to run, if `None` will run
- * all tests
- */
- def testFilter: Option[Regex]
-
- /** A test source whose files or directory of files is to be compiled
- * in a specific way defined by the `Test`
- */
- private sealed trait TestSource { self =>
- def name: String
- def outDir: JFile
- def flags: Array[String]
-
-
- def title: String = self match {
- case self: JointCompilationSource =>
- if (self.files.length > 1) name
- else self.files.head.getPath
-
- case self: SeparateCompilationSource =>
- self.dir.getPath
- }
-
- /** Adds the flags specified in `newFlags0` if they do not already exist */
- def withFlags(newFlags0: String*) = {
- val newFlags = newFlags0.toArray
- if (!flags.containsSlice(newFlags)) self match {
- case self: JointCompilationSource =>
- self.copy(flags = flags ++ newFlags)
- case self: SeparateCompilationSource =>
- self.copy(flags = flags ++ newFlags)
- }
- else self
- }
-
- /** Generate the instructions to redo the test from the command line */
- def buildInstructions(errors: Int, warnings: Int): String = {
- val sb = new StringBuilder
- val maxLen = 80
- var lineLen = 0
-
- sb.append(
- s"""|
- |Test '$title' compiled with $errors error(s) and $warnings warning(s),
- |the test can be reproduced by running:""".stripMargin
- )
- sb.append("\n\n./bin/dotc ")
- flags.foreach { arg =>
- if (lineLen > maxLen) {
- sb.append(" \\\n ")
- lineLen = 4
- }
- sb.append(arg)
- lineLen += arg.length
- sb += ' '
- }
-
- self match {
- case JointCompilationSource(_, files, _, _) => {
- files.map(_.getAbsolutePath).foreach { path =>
- sb.append("\\\n ")
- sb.append(path)
- sb += ' '
- }
- sb.toString + "\n\n"
- }
- case self: SeparateCompilationSource => {
- val command = sb.toString
- val fsb = new StringBuilder(command)
- self.compilationGroups.foreach { files =>
- files.map(_.getPath).foreach { path =>
- fsb.append("\\\n ")
- lineLen = 8
- fsb.append(path)
- fsb += ' '
- }
- fsb.append("\n\n")
- fsb.append(command)
- }
- fsb.toString + "\n\n"
- }
- }
- }
- }
-
- /** A group of files that may all be compiled together, with the same flags
- * and output directory
- */
- private final case class JointCompilationSource(
- name: String,
- files: Array[JFile],
- flags: Array[String],
- outDir: JFile
- ) extends TestSource {
- def sourceFiles: Array[JFile] = files.filter(isSourceFile)
-
- override def toString() = outDir.toString
- }
-
- /** A test source whose files will be compiled separately according to their
- * suffix `_X`
- */
- private final case class SeparateCompilationSource(
- name: String,
- dir: JFile,
- flags: Array[String],
- outDir: JFile
- ) extends TestSource {
-
- /** Get the files grouped by `_X` as a list of groups, files missing this
- * suffix will be put into the same group
- *
- * Filters out all none source files
- */
- def compilationGroups: List[Array[JFile]] =
- dir
- .listFiles
- .groupBy { file =>
- val name = file.getName
- Try {
- val potentialNumber = name
- .substring(0, name.lastIndexOf('.'))
- .reverse.takeWhile(_ != '_').reverse
-
- potentialNumber.toInt.toString
- }
- .toOption
- .getOrElse("")
- }
- .toList.sortBy(_._1).map(_._2.filter(isSourceFile))
- }
-
- /** Each `Test` takes the `testSources` and performs the compilation and assertions
- * according to the implementing class "neg", "run" or "pos".
- */
- private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) {
- protected final val realStdout = System.out
- protected final val realStderr = System.err
-
- /** Actual compilation run logic, the test behaviour is defined here */
- protected def compilationRunnable(testSource: TestSource): Runnable
-
- /** All testSources left after filtering out */
- private val filteredSources =
- if (!testFilter.isDefined) testSources
- else testSources.filter {
- case JointCompilationSource(_, files, _, _) =>
- files.exists(file => testFilter.get.findFirstIn(file.getAbsolutePath).isDefined)
- case SeparateCompilationSource(_, dir, _, _) =>
- testFilter.get.findFirstIn(dir.getAbsolutePath).isDefined
- }
-
- /** Total amount of test sources being compiled by this test */
- val sourceCount = filteredSources.length
-
- private[this] var _errorCount = 0
- def errorCount: Int = _errorCount
-
- private[this] var _testSourcesCompiled = 0
- private def testSourcesCompiled: Int = _testSourcesCompiled
-
- /** Complete the current compilation with the amount of errors encountered */
- protected final def registerCompilation(errors: Int) = synchronized {
- _testSourcesCompiled += 1
- _errorCount += errors
- }
-
- private[this] var _failed = false
- /** Fail the current test */
- protected[this] final def fail(): Unit = synchronized { _failed = true }
- def didFail: Boolean = _failed
-
- protected def echoBuildInstructions(reporter: TestReporter, testSource: TestSource, err: Int, war: Int) = {
- val errorMsg = testSource.buildInstructions(reporter.errorCount, reporter.warningCount)
- addFailureInstruction(errorMsg)
- failTestSource(testSource)
- }
-
- /** Instructions on how to reproduce failed test source compilations */
- private[this] val reproduceInstructions = mutable.ArrayBuffer.empty[String]
- protected final def addFailureInstruction(ins: String): Unit =
- synchronized { reproduceInstructions.append(ins) }
-
- /** The test sources that failed according to the implementing subclass */
- private[this] val failedTestSources = mutable.ArrayBuffer.empty[String]
- protected final def failTestSource(testSource: TestSource) = synchronized {
- failedTestSources.append(testSource.name + " failed")
- fail()
- }
-
- /** Prints to `System.err` if we're not suppressing all output */
- protected def echo(msg: String): Unit =
- if (!suppressAllOutput) realStderr.println(msg)
-
- /** A single `Runnable` that prints a progress bar for the curent `Test` */
- private def createProgressMonitor: Runnable = new Runnable {
- def run(): Unit = {
- val start = System.currentTimeMillis
- var tCompiled = testSourcesCompiled
- while (tCompiled < sourceCount) {
- val timestamp = (System.currentTimeMillis - start) / 1000
- val progress = (tCompiled.toDouble / sourceCount * 40).toInt
-
- realStdout.print(
- "[" + ("=" * (math.max(progress - 1, 0))) +
- (if (progress > 0) ">" else "") +
- (" " * (39 - progress)) +
- s"] compiling ($tCompiled/$sourceCount, ${timestamp}s)\r"
- )
-
- Thread.sleep(100)
- tCompiled = testSourcesCompiled
- }
- // println, otherwise no newline and cursor at start of line
- realStdout.println(
- s"[=======================================] compiled ($sourceCount/$sourceCount, " +
- s"${(System.currentTimeMillis - start) / 1000}s) "
- )
- }
- }
-
- /** Wrapper function to make sure that the compiler itself did not crash -
- * if it did, the test should automatically fail.
- */
- protected def tryCompile(testSource: TestSource)(op: => Unit): Unit =
- try {
- if (!isInteractive) realStdout.println(s"Testing ${testSource.title}")
- op
- } catch {
- case NonFatal(e) => {
- // if an exception is thrown during compilation, the complete test
- // run should fail
- failTestSource(testSource)
- e.printStackTrace()
- registerCompilation(1)
- throw e
- }
- }
-
- protected def compile(files0: Array[JFile], flags0: Array[String], suppressErrors: Boolean, targetDir: JFile): TestReporter = {
-
- val flags = flags0 ++ Array("-d", targetDir.getAbsolutePath)
-
- def flattenFiles(f: JFile): Array[JFile] =
- if (f.isDirectory) f.listFiles.flatMap(flattenFiles)
- else Array(f)
-
- val files: Array[JFile] = files0.flatMap(flattenFiles)
-
- def findJarFromRuntime(partialName: String) = {
- val urls = ClassLoader.getSystemClassLoader.asInstanceOf[java.net.URLClassLoader].getURLs.map(_.getFile.toString)
- urls.find(_.contains(partialName)).getOrElse {
- throw new java.io.FileNotFoundException(
- s"""Unable to locate $partialName on classpath:\n${urls.toList.mkString("\n")}"""
- )
- }
- }
-
- def addOutDir(xs: Array[String]): Array[String] = {
- val (beforeCp, cpAndAfter) = xs.toList.span(_ != "-classpath")
- if (cpAndAfter.nonEmpty) {
- val (cp :: cpArg :: rest) = cpAndAfter
- (beforeCp ++ (cp :: (cpArg + s":${targetDir.getAbsolutePath}") :: rest)).toArray
- }
- else (beforeCp ++ ("-classpath" :: targetDir.getAbsolutePath :: Nil)).toArray
- }
-
- def compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) {
- val scalaLib = findJarFromRuntime("scala-library-2.")
- val fullArgs = Array(
- "javac",
- "-classpath",
- s".:$scalaLib:${targetDir.getAbsolutePath}"
- ) ++ flags.takeRight(2) ++ fs
-
- Runtime.getRuntime.exec(fullArgs).waitFor() == 0
- } else true
-
- val reporter =
- TestReporter.reporter(realStdout, logLevel =
- if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR)
-
- val driver =
- if (times == 1) new Driver { def newCompiler(implicit ctx: Context) = new Compiler }
- else new Driver {
- def newCompiler(implicit ctx: Context) = new Compiler
-
- private def ntimes(n: Int)(op: Int => Reporter): Reporter =
- (emptyReporter /: (1 to n)) ((_, i) => op(i))
-
- override def doCompile(comp: Compiler, files: List[String])(implicit ctx: Context) =
- ntimes(times) { run =>
- val start = System.nanoTime()
- val rep = super.doCompile(comp, files)
- ctx.echo(s"\ntime run $run: ${(System.nanoTime - start) / 1000000}ms")
- rep
- }
- }
-
- val allArgs = addOutDir(flags)
- driver.process(allArgs ++ files.map(_.getAbsolutePath), reporter = reporter)
-
- val javaFiles = files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath)
- assert(compileWithJavac(javaFiles), s"java compilation failed for ${javaFiles.mkString(", ")}")
-
- reporter
- }
-
- private[ParallelTesting] def executeTestSuite(): this.type = {
- assert(_testSourcesCompiled == 0, "not allowed to re-use a `CompileRun`")
-
- if (filteredSources.nonEmpty) {
- val pool = threadLimit match {
- case Some(i) => JExecutors.newWorkStealingPool(i)
- case None => JExecutors.newWorkStealingPool()
- }
-
- if (isInteractive && !suppressAllOutput) pool.submit(createProgressMonitor)
-
- filteredSources.foreach { target =>
- pool.submit(compilationRunnable(target))
- }
-
- pool.shutdown()
- if (!pool.awaitTermination(20, TimeUnit.MINUTES)) {
- pool.shutdownNow()
- System.setOut(realStdout)
- System.setErr(realStderr)
- throw new TimeoutException("Compiling targets timed out")
- }
-
- if (didFail) {
- reportFailed()
- failedTestSources.toSet.foreach(addFailedTest)
- reproduceInstructions.iterator.foreach(addReproduceInstruction)
- }
- else reportPassed()
- }
- else echo {
- testFilter
- .map(r => s"""No files matched regex "$r" in test""")
- .getOrElse("No tests available under target - erroneous test?")
- }
-
- this
- }
- }
-
- private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)
- extends Test(testSources, times, threadLimit, suppressAllOutput) {
- protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable {
- def run(): Unit = tryCompile(testSource) {
- testSource match {
- case testSource @ JointCompilationSource(_, files, flags, outDir) => {
- val reporter = compile(testSource.sourceFiles, flags, false, outDir)
- registerCompilation(reporter.errorCount)
-
- if (reporter.errorCount > 0)
- echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount)
- }
-
- case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => {
- val reporters = testSource.compilationGroups.map(files => compile(files, flags, false, outDir))
- val errorCount = reporters.foldLeft(0) { (acc, reporter) =>
- if (reporter.errorCount > 0)
- echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount)
-
- acc + reporter.errorCount
- }
-
- registerCompilation(errorCount)
-
- if (errorCount > 0) failTestSource(testSource)
- }
- }
-
- }
- }
- }
-
- private final class RunTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)
- extends Test(testSources, times, threadLimit, suppressAllOutput) {
- private def runMain(dir: JFile, testSource: TestSource): Array[String] = {
- def renderStackTrace(ex: Throwable): String =
- if (ex == null) ""
- else ex.getStackTrace
- .takeWhile(_.getMethodName != "invoke0")
- .mkString(" ", "\n ", "")
-
- import java.io.{ ByteArrayOutputStream, PrintStream }
- import java.net.{ URL, URLClassLoader }
-
- val printStream = new ByteArrayOutputStream
-
- try {
- // Do classloading magic and running here:
- val ucl = new URLClassLoader(Array(dir.toURI.toURL))
- val cls = ucl.loadClass("Test")
- val meth = cls.getMethod("main", classOf[Array[String]])
-
- synchronized {
- try {
- val ps = new PrintStream(printStream)
- System.setOut(ps)
- System.setErr(ps)
- Console.withOut(printStream) {
- Console.withErr(printStream) {
- meth.invoke(null, Array("jvm")) // partest passes at least "jvm" as an arg
- }
- }
- System.setOut(realStdout)
- System.setErr(realStderr)
- } catch {
- case t: Throwable =>
- System.setOut(realStdout)
- System.setErr(realStderr)
- throw t
- }
- }
- }
- catch {
- case ex: NoSuchMethodException =>
- echo(s"test in '$dir' did not contain method: ${ex.getMessage}\n${renderStackTrace(ex.getCause)}")
- failTestSource(testSource)
-
- case ex: ClassNotFoundException =>
- echo(s"test in '$dir' did not contain class: ${ex.getMessage}\n${renderStackTrace(ex.getCause)}")
- failTestSource(testSource)
-
- case ex: InvocationTargetException =>
- echo(s"An exception ocurred when running main: ${ex.getCause}\n${renderStackTrace(ex.getCause)}")
- failTestSource(testSource)
- }
- printStream.toString("utf-8").lines.toArray
- }
-
- private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = {
- val outputLines = runMain(dir, testSource)
- val checkLines = Source.fromFile(checkFile).getLines.toArray
- val sourceTitle = testSource.title
-
- def linesMatch =
- outputLines
- .zip(checkLines)
- .forall { case (x, y) => x == y }
-
- if (outputLines.length != checkLines.length || !linesMatch) {
- // Print diff to files and summary:
- val diff = outputLines.zip(checkLines).map { case (act, exp) =>
- DiffUtil.mkColoredLineDiff(exp, act)
- }.mkString("\n")
-
- val msg =
- s"""|Output from '$sourceTitle' did not match check file.
- |Diff ('e' is expected, 'a' is actual):
- |""".stripMargin + diff + "\n"
- echo(msg)
- addFailureInstruction(msg)
-
- // Print build instructions to file and summary:
- val buildInstr = testSource.buildInstructions(0, warnings)
- addFailureInstruction(buildInstr)
-
- // Fail target:
- failTestSource(testSource)
- }
- }
-
- protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable {
- def run(): Unit = tryCompile(testSource) {
- val (errorCount, warningCount, hasCheckFile, verifier: Function0[Unit]) = testSource match {
- case testSource @ JointCompilationSource(_, files, flags, outDir) => {
- val checkFile = files.flatMap { file =>
- if (file.isDirectory) Nil
- else {
- val fname = file.getAbsolutePath.reverse.dropWhile(_ != '.').reverse + "check"
- val checkFile = new JFile(fname)
- if (checkFile.exists) List(checkFile)
- else Nil
- }
- }.headOption
- val reporter = compile(testSource.sourceFiles, flags, false, outDir)
-
- if (reporter.errorCount > 0)
- echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount)
-
- registerCompilation(reporter.errorCount)
- (reporter.errorCount, reporter.warningCount, checkFile.isDefined, () => verifyOutput(checkFile.get, outDir, testSource, reporter.warningCount))
- }
-
- case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => {
- val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + ".check")
- val (errorCount, warningCount) =
- testSource
- .compilationGroups
- .map(compile(_, flags, false, outDir))
- .foldLeft((0,0)) { case ((errors, warnings), reporter) =>
- if (reporter.errorCount > 0)
- echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount)
-
- (errors + reporter.errorCount, warnings + reporter.warningCount)
- }
-
- if (errorCount > 0) fail()
-
- registerCompilation(errorCount)
- (errorCount, warningCount, checkFile.exists, () => verifyOutput(checkFile, outDir, testSource, warningCount))
- }
- }
-
- if (errorCount == 0 && hasCheckFile) verifier()
- else if (errorCount == 0) runMain(testSource.outDir, testSource)
- else if (errorCount > 0) {
- echo(s"\nCompilation failed for: '$testSource'")
- val buildInstr = testSource.buildInstructions(errorCount, warningCount)
- addFailureInstruction(buildInstr)
- failTestSource(testSource)
- }
- }
- }
- }
-
- private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)
- extends Test(testSources, times, threadLimit, suppressAllOutput) {
- protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable {
- def run(): Unit = tryCompile(testSource) {
- // In neg-tests we allow two types of error annotations,
- // "nopos-error" which doesn't care about position and "error" which
- // has to be annotated on the correct line number.
- //
- // We collect these in a map `"file:row" -> numberOfErrors`, for
- // nopos errors we save them in `"file" -> numberOfNoPosErrors`
- def getErrorMapAndExpectedCount(files: Array[JFile]): (HashMap[String, Integer], Int) = {
- val errorMap = new HashMap[String, Integer]()
- var expectedErrors = 0
- files.filter(_.getName.endsWith(".scala")).foreach { file =>
- Source.fromFile(file).getLines.zipWithIndex.foreach { case (line, lineNbr) =>
- val errors = line.sliding("// error".length).count(_.mkString == "// error")
- if (errors > 0)
- errorMap.put(s"${file.getAbsolutePath}:${lineNbr}", errors)
-
- val noposErrors = line.sliding("// nopos-error".length).count(_.mkString == "// nopos-error")
- if (noposErrors > 0) {
- val nopos = errorMap.get("nopos")
- val existing: Integer = if (nopos eq null) 0 else nopos
- errorMap.put("nopos", noposErrors + existing)
- }
-
- expectedErrors += noposErrors + errors
- }
- }
-
- (errorMap, expectedErrors)
- }
-
- def getMissingExpectedErrors(errorMap: HashMap[String, Integer], reporterErrors: Iterator[MessageContainer]) = !reporterErrors.forall { error =>
- val key = if (error.pos.exists) {
- val fileName = error.pos.source.file.toString
- s"$fileName:${error.pos.line}"
-
- } else "nopos"
-
- val errors = errorMap.get(key)
-
- if (errors ne null) {
- if (errors == 1) errorMap.remove(key)
- else errorMap.put(key, errors - 1)
- true
- }
- else {
- echo {
- s"Error reported in ${error.pos.source}, but no annotation found"
- }
- false
- }
- }
-
- val (expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = testSource match {
- case testSource @ JointCompilationSource(_, files, flags, outDir) => {
- val sourceFiles = testSource.sourceFiles
- val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(sourceFiles)
- val reporter = compile(sourceFiles, flags, true, outDir)
- val actualErrors = reporter.errorCount
-
- (expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, reporter.errors), errorMap)
- }
-
- case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => {
- val compilationGroups = testSource.compilationGroups
- val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(compilationGroups.toArray.flatten)
- val reporters = compilationGroups.map(compile(_, flags, true, outDir))
- val actualErrors = reporters.foldLeft(0)(_ + _.errorCount)
- val errors = reporters.iterator.flatMap(_.errors)
- (expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, errors), errorMap)
- }
- }
-
- if (expectedErrors != actualErrors) {
- echo {
- s"\nWrong number of errors encountered when compiling $testSource, expected: $expectedErrors, actual: $actualErrors\n"
- }
- failTestSource(testSource)
- }
- else if (hasMissingAnnotations()) {
- echo {
- s"\nErrors found on incorrect row numbers when compiling $testSource"
- }
- failTestSource(testSource)
- }
- else if (!errorMap.isEmpty) {
- echo {
- s"\nExpected error(s) have {<error position>=<unreported error>}: $errorMap"
- }
- failTestSource(testSource)
- }
-
- registerCompilation(actualErrors)
- }
- }
- }
-
- /** The `CompilationTest` is the main interface to `ParallelTesting`, it
- * can be instantiated via one of the following methods:
- *
- * - `compileFile`
- * - `compileDir`
- * - `compileList`
- * - `compileFilesInDir`
- * - `compileShallowFilesInDir`
- *
- * Each compilation test can then be turned into either a "pos", "neg" or
- * "run" test:
- *
- * ```
- * compileFile("../tests/pos/i1103.scala", opts).pos()
- * ```
- *
- * These tests can be customized before calling one of the execution
- * methods, for instance:
- *
- * ```
- * compileFile("../tests/pos/i1103.scala", opts).times(2).verbose.pos()
- * ```
- *
- * Which would compile `i1103.scala` twice with the verbose flag as a "pos"
- * test.
- *
- * pos tests
- * =========
- * Pos tests verify that the compiler is able to compile the given
- * `TestSource`s and that they generate no errors or exceptions during
- * compilation
- *
- * neg tests
- * =========
- * Neg tests are expected to generate a certain amount of errors - but not
- * crash the compiler. In each `.scala` file, you specifiy the line on which
- * the error will be generated, e.g:
- *
- * ```
- * val x: String = 1 // error
- * ```
- *
- * if a line generates multiple errors, you need to annotate it multiple
- * times. For a line that generates two errors:
- *
- * ```
- * val y: String = { val y1: String = 1; 2 } // error // error
- * ```
- *
- * Certain errors have no position, if you need to check these annotate the
- * file anywhere with `// nopos-error`
- *
- * run tests
- * =========
- * Run tests are a superset of pos tests, they both verify compilation and
- * that the compiler does not crash. In addition, run tests verify that the
- * tests are able to run as expected.
- *
- * Run tests need to have the following form:
- *
- * ```
- * object Test {
- * def main(args: Array[String]): Unit = ()
- * }
- * ```
- *
- * This is because the runner instantiates the `Test` class and calls the
- * main method.
- *
- * Other definitions are allowed in the same file, but the file needs to at
- * least have the `Test` object with a `main` method.
- *
- * To verify output you may use `.check` files. These files should share the
- * name of the file or directory that they are testing. For instance:
- *
- * ```none
- * .
- * └── tests
- * ├── i1513.scala
- * └── i1513.check
- * ```
- *
- * If you are testing a directory under separate compilation, you would
- * have:
- *
- * ```none
- * .
- * └── tests
- * ├── myTestDir
- * │ ├── T_1.scala
- * │ ├── T_2.scala
- * │ └── T_3.scala
- * └── myTestDir.check
- * ```
- *
- * In the above example, `i1513.scala` and one of the files `T_X.scala`
- * would contain a `Test` object with a main method.
- *
- * Composing tests
- * ===============
- * Since this is a parallel test suite, it is essential to be able to
- * compose tests to take advantage of the concurrency. This is done using
- * the `+` function. This function will make sure that tests being combined
- * are compatible according to the `require`s in `+`.
- */
- final class CompilationTest private (
- private[ParallelTesting] val targets: List[TestSource],
- private[ParallelTesting] val times: Int,
- private[ParallelTesting] val shouldDelete: Boolean,
- private[ParallelTesting] val threadLimit: Option[Int],
- private[ParallelTesting] val shouldFail: Boolean
- ) {
- import org.junit.Assert.fail
-
- private[ParallelTesting] def this(target: TestSource) =
- this(List(target), 1, true, None, false)
-
- private[ParallelTesting] def this(targets: List[TestSource]) =
- this(targets, 1, true, None, false)
-
- /** Compose test targets from `this` with `other`
- *
- * It does this, only if the two tests are compatible. Otherwise it throws
- * an `IllegalArgumentException`.
- *
- * Grouping tests together like this allows us to take advantage of the
- * concurrency offered by this test suite as each call to an executing
- * method (`pos()` / `checkExpectedErrors()`/ `run()`) will spin up a thread pool with the
- * maximum allowed level of concurrency. Doing this for only a few targets
- * does not yield any real benefit over sequential compilation.
- *
- * As such, each `CompilationTest` should contain as many targets as
- * possible.
- */
- def +(other: CompilationTest) = {
- require(other.times == times, "can't combine tests that are meant to be benchmark compiled")
- require(other.shouldDelete == shouldDelete, "can't combine tests that differ on deleting output")
- require(other.shouldFail == shouldFail, "can't combine tests that have different expectations on outcome")
- new CompilationTest(targets ++ other.targets, times, shouldDelete, threadLimit, shouldFail)
- }
-
- /** Creates a "pos" test run, which makes sure that all tests pass
- * compilation without generating errors and that they do not crash the
- * compiler
- */
- def checkCompile(): this.type = {
- val test = new PosTest(targets, times, threadLimit, shouldFail).executeTestSuite()
-
- if (!shouldFail && test.didFail) {
- fail(s"Expected no errors when compiling, but found: ${test.errorCount}")
- }
- else if (shouldFail && !test.didFail) {
- fail("Pos test should have failed, but didn't")
- }
-
- cleanup()
- }
-
- /** Creates a "neg" test run, which makes sure that each test generates the
- * correct amount of errors at the correct positions. It also makes sure
- * that none of these tests crash the compiler
- */
- def checkExpectedErrors(): this.type = {
- val test = new NegTest(targets, times, threadLimit, shouldFail).executeTestSuite()
-
- if (!shouldFail && test.didFail) {
- fail("Neg test shouldn't have failed, but did")
- }
- else if (shouldFail && !test.didFail) {
- fail("Neg test should have failed, but did not")
- }
-
- cleanup()
- }
-
- /** Creates a "run" test run, which is a superset of "pos". In addition to
- * making sure that all tests pass compilation and that they do not crash
- * the compiler; it also makes sure that all tests can run with the
- * expected output
- */
- def checkRuns(): this.type = {
- val test = new RunTest(targets, times, threadLimit, shouldFail).executeTestSuite()
-
- if (!shouldFail && test.didFail) {
- fail("Run test failed, but should not")
- }
- else if (shouldFail && !test.didFail) {
- fail("Run test should have failed, but did not")
- }
-
- cleanup()
- }
-
- /** Deletes output directories and files */
- private def cleanup(): this.type = {
- if (shouldDelete) delete()
- this
- }
-
- /** Copies `file` to `dir` - taking into account if `file` is a directory,
- * and if so copying recursively
- */
- private def copyToDir(dir: JFile, file: JFile): JFile = {
- val target = Paths.get(dir.getAbsolutePath, file.getName)
- Files.copy(file.toPath, target, REPLACE_EXISTING)
- if (file.isDirectory) file.listFiles.map(copyToDir(target.toFile, _))
- target.toFile
- }
-
- /** Builds a new `CompilationTest` where we have copied the target files to
- * the out directory. This is needed for tests that modify the original
- * source, such as `-rewrite` tests
- */
- def copyToTarget(): CompilationTest = new CompilationTest (
- targets.map {
- case target @ JointCompilationSource(_, files, _, outDir) =>
- target.copy(files = files.map(copyToDir(outDir,_)))
- case target @ SeparateCompilationSource(_, dir, _, outDir) =>
- target.copy(dir = copyToDir(outDir, dir))
- },
- times, shouldDelete, threadLimit, shouldFail
- )
-
- /** Builds a `CompilationTest` which performs the compilation `i` times on
- * each target
- */
- def times(i: Int): CompilationTest =
- new CompilationTest(targets, i, shouldDelete, threadLimit, shouldFail)
-
- /** Builds a `Compilationtest` which passes the verbose flag and logs the
- * classpath
- */
- def verbose: CompilationTest = new CompilationTest(
- targets.map(t => t.withFlags("-verbose", "-Ylog-classpath")),
- times, shouldDelete, threadLimit, shouldFail
- )
-
- /** Builds a `CompilationTest` which keeps the generated output files
- *
- * This is needed for tests like `tastyBootstrap` which relies on first
- * compiling a certain part of the project and then compiling a second
- * part which depends on the first
- */
- def keepOutput: CompilationTest =
- new CompilationTest(targets, times, false, threadLimit, shouldFail)
-
- /** Builds a `CompilationTest` with a limited level of concurrency with
- * maximum `i` threads
- */
- def limitThreads(i: Int): CompilationTest =
- new CompilationTest(targets, times, shouldDelete, Some(i), shouldFail)
-
- /** Builds a `CompilationTest` where the executed test is expected to fail
- *
- * This behaviour is mainly needed for the tests that test the test suite.
- */
- def expectFailure: CompilationTest =
- new CompilationTest(targets, times, shouldDelete, threadLimit, true)
-
- /** Delete all output files generated by this `CompilationTest` */
- def delete(): Unit = targets.foreach(t => delete(t.outDir))
-
- private def delete(file: JFile): Unit = {
- if (file.isDirectory) file.listFiles.foreach(delete)
- try Files.delete(file.toPath)
- catch {
- case _: NoSuchFileException => // already deleted, everything's fine
- }
- }
- }
-
- /** Create out directory for directory `d` */
- private def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = {
- val targetDir = new JFile(outDir + s"${sourceDir.getName}/${d.getName}")
- targetDir.mkdirs()
- targetDir
- }
-
- /** Create out directory for `file` */
- private def createOutputDirsForFile(file: JFile, sourceDir: JFile, outDir: String): JFile = {
- val uniqueSubdir = file.getName.substring(0, file.getName.lastIndexOf('.'))
- val targetDir = new JFile(outDir + s"${sourceDir.getName}/$uniqueSubdir")
- targetDir.mkdirs()
- targetDir
- }
-
- /** Make sure that directory string is as expected */
- private def checkRequirements(f: String, sourceDir: JFile, outDir: String): Unit = {
- require(sourceDir.isDirectory && sourceDir.exists, "passed non-directory to `compileFilesInDir`")
- require(outDir.last == '/', "please specify an `outDir` with a trailing slash")
- }
-
- /** Separates directories from files and returns them as `(dirs, files)` */
- private def compilationTargets(sourceDir: JFile): (List[JFile], List[JFile]) =
- sourceDir.listFiles.foldLeft((List.empty[JFile], List.empty[JFile])) { case ((dirs, files), f) =>
- if (f.isDirectory) (f :: dirs, files)
- else if (isSourceFile(f)) (dirs, f :: files)
- else (dirs, files)
- }
-
- /** Gets the name of the calling method via reflection.
- *
- * It does this in a way that needs to work both with the bootstrapped dotty
- * and the non-bootstrapped version. Since the two compilers generate
- * different bridges, we first need to filter out methods with the same name
- * (bridges) - and then find the `@Test` method in our extending class
- */
- private def getCallingMethod(): String = {
- val seen = mutable.Set.empty[String]
- Thread.currentThread.getStackTrace
- .filter { elem =>
- if (seen.contains(elem.getMethodName)) false
- else { seen += elem.getMethodName; true }
- }
- .find { elem =>
- val callingClass = Class.forName(elem.getClassName)
- classOf[ParallelTesting].isAssignableFrom(callingClass) &&
- elem.getFileName != "ParallelTesting.scala"
- }
- .map(_.getMethodName)
- .getOrElse {
- throw new IllegalStateException("Unable to reflectively find calling method")
- }
- }
-
- /** Compiles a single file from the string path `f` using the supplied flags */
- def compileFile(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = {
- val callingMethod = getCallingMethod
- val sourceFile = new JFile(f)
- val parent = sourceFile.getParentFile
- val outDir =
- outDirectory + callingMethod + "/" +
- sourceFile.getName.substring(0, sourceFile.getName.lastIndexOf('.')) + "/"
-
- require(
- sourceFile.exists && !sourceFile.isDirectory &&
- (parent ne null) && parent.exists && parent.isDirectory,
- s"Source file: $f, didn't exist"
- )
-
- val target = JointCompilationSource(
- callingMethod,
- Array(sourceFile),
- flags,
- createOutputDirsForFile(sourceFile, parent, outDir)
- )
- new CompilationTest(target)
- }
-
- /** Compiles a directory `f` using the supplied `flags`. This method does
- * deep compilation, that is - it compiles all files and subdirectories
- * contained within the directory `f`.
- *
- * By default, files are compiled in alphabetical order. An optional seed
- * can be used for randomization.
- */
- def compileDir(f: String, flags: Array[String], randomOrder: Option[Int] = None)(implicit outDirectory: String): CompilationTest = {
- val callingMethod = getCallingMethod
- val outDir = outDirectory + callingMethod + "/"
- val sourceDir = new JFile(f)
- checkRequirements(f, sourceDir, outDir)
-
- def flatten(f: JFile): Array[JFile] =
- if (f.isDirectory) f.listFiles.flatMap(flatten)
- else Array(f)
-
- // Sort files either alphabetically or randomly using the provided seed:
- val sortedFiles = flatten(sourceDir).sorted
- val randomized = randomOrder match {
- case None => sortedFiles
- case Some(seed) => new Random(seed).shuffle(sortedFiles.toList).toArray
- }
-
- // Directories in which to compile all containing files with `flags`:
- val targetDir = new JFile(outDir + "/" + sourceDir.getName + "/")
- targetDir.mkdirs()
-
- val target = JointCompilationSource(callingMethod, randomized, flags, targetDir)
- new CompilationTest(target)
- }
-
- /** Compiles all `files` together as a single compilation run. It is given a
- * `testName` since files can be in separate directories and or be otherwise
- * dissociated
- */
- def compileList(testName: String, files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = {
- val callingMethod = getCallingMethod
- val outDir = outDirectory + callingMethod + "/" + testName + "/"
-
- // Directories in which to compile all containing files with `flags`:
- val targetDir = new JFile(outDir)
- targetDir.mkdirs()
- assert(targetDir.exists, s"couldn't create target directory: $targetDir")
-
- val target = JointCompilationSource(callingMethod, files.map(new JFile(_)).toArray, flags, targetDir)
-
- // Create a CompilationTest and let the user decide whether to execute a pos or a neg test
- new CompilationTest(target)
- }
-
- /** This function compiles the files and folders contained within directory
- * `f` in a specific way.
- *
- * - Each file is compiled separately as a single compilation run
- * - Each directory is compiled as a `SeparateCompilationTaret`, in this
- * target all files are grouped according to the file suffix `_X` where `X`
- * is a number. These groups are then ordered in ascending order based on
- * the value of `X` and each group is compiled one after the other.
- *
- * For this function to work as expected, we use the same convention for
- * directory layout as the old partest. That is:
- *
- * - Single files can have an associated check-file with the same name (but
- * with file extension `.check`)
- * - Directories can have an associated check-file, where the check file has
- * the same name as the directory (with the file extension `.check`)
- */
- def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = {
- val callingMethod = getCallingMethod
- val outDir = outDirectory + callingMethod + "/"
- val sourceDir = new JFile(f)
- checkRequirements(f, sourceDir, outDir)
-
- val (dirs, files) = compilationTargets(sourceDir)
-
- val targets =
- files.map(f => JointCompilationSource(callingMethod, Array(f), flags, createOutputDirsForFile(f, sourceDir, outDir))) ++
- dirs.map(dir => SeparateCompilationSource(callingMethod, dir, flags, createOutputDirsForDir(dir, sourceDir, outDir)))
-
- // Create a CompilationTest and let the user decide whether to execute a pos or a neg test
- new CompilationTest(targets)
- }
-
- /** This function behaves similar to `compileFilesInDir` but it ignores
- * sub-directories and as such, does **not** perform separate compilation
- * tests.
- */
- def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = {
- val callingMethod = getCallingMethod
- val outDir = outDirectory + callingMethod + "/"
- val sourceDir = new JFile(f)
- checkRequirements(f, sourceDir, outDir)
-
- val (_, files) = compilationTargets(sourceDir)
-
- val targets = files.map { file =>
- JointCompilationSource(callingMethod, Array(file), flags, createOutputDirsForFile(file, sourceDir, outDir))
- }
-
- // Create a CompilationTest and let the user decide whether to execute a pos or a neg test
- new CompilationTest(targets)
- }
-}
-
-object ParallelTesting {
- def isSourceFile(f: JFile): Boolean = {
- val name = f.getName
- name.endsWith(".scala") || name.endsWith(".java")
- }
-}
diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala
index 5641240a7..213181b56 100644
--- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala
+++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala
@@ -23,6 +23,10 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
final def errors: Iterator[MessageContainer] = _errorBuf.iterator
protected final val _messageBuf = mutable.ArrayBuffer.empty[String]
+ final def messages: Iterator[String] = _messageBuf.iterator
+
+ private[this] var _didCrash = false
+ final def compilerCrashed: Boolean = _didCrash
final def flushToFile(): Unit =
_messageBuf
@@ -33,7 +37,6 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
final def flushToStdErr(): Unit =
_messageBuf
.iterator
- .map(_.replaceAll("\u001b\\[.*?m", ""))
.foreach(System.err.println)
final def inlineInfo(pos: SourcePosition): String =
@@ -44,9 +47,17 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
}
else ""
- def echo(msg: String) =
+ def log(msg: String) =
_messageBuf.append(msg)
+ def logStackTrace(thrown: Throwable): Unit = {
+ _didCrash = true
+ val sw = new java.io.StringWriter
+ val pw = new java.io.PrintWriter(sw)
+ thrown.printStackTrace(pw)
+ log(sw.toString)
+ }
+
/** Prints the message with the given position indication. */
def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = {
val msg = messageAndPos(m.contained, m.pos, diagnosticLevel(m))
@@ -73,42 +84,66 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
_errorBuf.append(m)
printMessageAndPos(m, extra)
}
- case w: Warning =>
- printMessageAndPos(w, extra)
- case _ =>
+ case m =>
+ printMessageAndPos(m, extra)
}
}
}
object TestReporter {
- private[this] lazy val logWriter = {
+ private[this] var outFile: JFile = _
+ private[this] var logWriter: PrintWriter = _
+
+ private[this] def initLog() = if (logWriter eq null) {
val df = new SimpleDateFormat("yyyy-MM-dd-HH:mm")
val timestamp = df.format(new Date)
new JFile("../testlogs").mkdirs()
- new PrintWriter(new FileOutputStream(new JFile(s"../testlogs/tests-$timestamp.log"), true))
+ outFile = new JFile(s"../testlogs/tests-$timestamp.log")
+ logWriter = new PrintWriter(new FileOutputStream(outFile, true))
}
- def writeToLog(str: String) = {
+ def logPrintln(str: String) = {
+ initLog()
logWriter.println(str)
logWriter.flush()
}
+ def logPrint(str: String): Unit = {
+ initLog()
+ logWriter.println(str)
+ }
+
+ def logFlush(): Unit =
+ if (logWriter ne null) logWriter.flush()
+
+ def logPath: String = {
+ initLog()
+ outFile.getCanonicalPath
+ }
+
def reporter(ps: PrintStream, logLevel: Int): TestReporter =
- new TestReporter(new PrintWriter(ps, true), writeToLog, logLevel)
+ new TestReporter(new PrintWriter(ps, true), logPrintln, logLevel)
def simplifiedReporter(writer: PrintWriter): TestReporter = {
- val rep = new TestReporter(writer, writeToLog, WARNING) {
+ val rep = new TestReporter(writer, logPrintln, WARNING) {
/** Prints the message with the given position indication in a simplified manner */
override def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = {
- val msg = s"${m.pos.line + 1}: " + m.contained.kind + extra
- val extraInfo = inlineInfo(m.pos)
+ def report() = {
+ val msg = s"${m.pos.line + 1}: " + m.contained.kind + extra
+ val extraInfo = inlineInfo(m.pos)
- writer.println(msg)
- _messageBuf.append(msg)
+ writer.println(msg)
+ _messageBuf.append(msg)
- if (extraInfo.nonEmpty) {
- writer.println(extraInfo)
- _messageBuf.append(extraInfo)
+ if (extraInfo.nonEmpty) {
+ writer.println(extraInfo)
+ _messageBuf.append(extraInfo)
+ }
+ }
+ m match {
+ case m: Error => report()
+ case m: Warning => report()
+ case _ => ()
}
}
}
diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala
index eff86e6e7..1ec4a70a5 100644
--- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala
+++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala
@@ -9,11 +9,12 @@ import scala.io.Source._
import scala.reflect.io.Directory
import org.junit.Test
import reporting.TestReporter
+import vulpix.TestConfiguration
class PatmatExhaustivityTest {
val testsDir = "../tests/patmat"
// stop-after: patmatexhaust-huge.scala crash compiler
- val options = List("-color:never", "-Ystop-after:splitter", "-Ycheck-all-patmat") ++ CompilationTests.classPath
+ val options = List("-color:never", "-Ystop-after:splitter", "-Ycheck-all-patmat") ++ TestConfiguration.classPath
private def compileFile(file: File) = {
val stringBuffer = new StringWriter()