From 5ce54c68f9130dea5b4d31b3b47bb78d76a57952 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 9 Mar 2017 16:45:23 +0100 Subject: [PATCH 01/39] Add initial compilation functionality to ParallelTesting trait --- .../dotty/tools/dotc/ParallelTesting.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 compiler/test/dotty/tools/dotc/ParallelTesting.scala diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala new file mode 100644 index 000000000000..5df9e2e3df97 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -0,0 +1,41 @@ +package dotty +package tools +package dotc + +import java.io.{ File => JFile } +import scala.io.Source + +import core.Contexts._ +import reporting.{ Reporter, UniqueMessagePositions, HideNonSensicalMessages, MessageRendering } +import reporting.diagnostic.MessageContainer +import interfaces.Diagnostic.ERROR +import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.nio.file.{ Files, Path, Paths } + +trait ParallelTesting { + + private val driver = new Driver { + override def newCompiler(implicit ctx: Context) = new Compiler + } + + private class DaftReporter(suppress: Boolean) + extends Reporter with UniqueMessagePositions with HideNonSensicalMessages + with MessageRendering { + private var _errors: List[MessageContainer] = Nil + def errors = _errors + + override def doReport(m: MessageContainer)(implicit ctx: Context) = { + if (!suppress && m.level == ERROR) { + _errors = m :: _errors + System.err.println(messageAndPos(m.contained, m.pos, diagnosticLevel(m))) + } + } + } + + private def compile(files: Array[JFile], flags: Array[String]): (Array[JFile], List[MessageContainer]) = { + val reporter = new DaftReporter(suppress = false) + driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) + files -> reporter.errors + } + +} From 531c7226d9a765aa1530f4437633365e3e140f0f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 9 Mar 2017 16:47:04 +0100 Subject: [PATCH 02/39] Add interface for unit tests into `ParallelTesting` --- .../dotty/tools/dotc/ParallelTesting.scala | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 5df9e2e3df97..4061517e653b 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -38,4 +38,64 @@ trait ParallelTesting { files -> reporter.errors } + def compileFilesInDir(f: String, flags: Array[String])(implicit outDir: String): Unit = { + val dir = new JFile(f) + require(f.contains("/tests"), "only allowed to run integration tests from `tests` dir using this method") + require(dir.isDirectory && dir.exists, "passed non-directory to `compileFilesInDir`") + require(outDir.last == '/', "please specify an `outDir` with a trailing slash") + + def toCompilerDirFromDir(d: JFile): JFile = { + val targetDir = new JFile(outDir + s"${dir.getName}/${d.getName}") + // create if not exists + targetDir.mkdirs() + d.listFiles.foreach(copyToDir(targetDir, _)) + targetDir + } + def toCompilerDirFromFile(file: JFile): JFile = { + val uniqueSubdir = file.getName.substring(0, file.getName.lastIndexOf('.')) + val targetDir = new JFile(outDir + s"${dir.getName}/$uniqueSubdir") + // create if not exists + targetDir.mkdirs() + // copy file to dir: + copyToDir(targetDir, file) + targetDir + } + def copyToDir(dir: JFile, file: JFile): Unit = { + val target = Paths.get(dir.getAbsolutePath, file.getName) + Files.copy(file.toPath, target, REPLACE_EXISTING).toFile + } + + val (dirs, files) = + dir.listFiles.foldLeft((List.empty[JFile], List.empty[JFile])) { case ((dirs, files), f) => + if (f.isDirectory) (f :: dirs, files) + else (dirs, f :: files) + } + + // Directories in which to compile all containing files with `flags`: + val dirsToCompile = files.map(toCompilerDirFromFile) ++ dirs.map(toCompilerDirFromDir) + + // Progress bar setup + val numberOfTargets = dirsToCompile.length + var targetsCompiled = 0 + val start = System.currentTimeMillis + var errors = 0 + + dirsToCompile.map { dir => + val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + targetsCompiled += 1 + val timestamp = (System.currentTimeMillis - start) / 1000 + val progress = (targetsCompiled.toDouble / numberOfTargets * 40).toInt + print( + s"Compiling tests in $f [" + + ("=" * (math.max(progress - 1, 0))) + + (if (progress > 0) ">" else "") + + (" " * (39 - progress)) + + s"] $targetsCompiled/$numberOfTargets, ${timestamp}s, errors: $errors\r" + ) + val (_, newErrors ) = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath)) + errors += newErrors.length + } + println(s"Compiled tests in $f [========================================] $targetsCompiled/$numberOfTargets, ${(System.currentTimeMillis - start) / 1000}s, errors: $errors ") + + } } From 58e1c659c406705e84322f96b500eecc754f99cd Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 9 Mar 2017 16:47:39 +0100 Subject: [PATCH 03/39] Add java compilation to `ParallelTesting` --- .../dotty/tools/dotc/ParallelTesting.scala | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 4061517e653b..eada2c763012 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -33,6 +33,29 @@ trait ParallelTesting { } private def compile(files: Array[JFile], flags: Array[String]): (Array[JFile], List[MessageContainer]) = { + + 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 compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) { + val scalaLib = findJarFromRuntime("scala-library") + val fullArgs = Array( + "javac", + "-classpath", + s".:$scalaLib" + ) ++ flags.takeRight(2) ++ fs + + assert(Runtime.getRuntime.exec(fullArgs).waitFor() == 0, s"java compilation failed for ${fs.mkString(", ")}") + } + + compileWithJavac(files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath)) + val reporter = new DaftReporter(suppress = false) driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) files -> reporter.errors From 7b75bb24b68c87b41d714f0510fc4fa18b3cfedd Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 10 Mar 2017 13:25:24 +0100 Subject: [PATCH 04/39] Fix i851.java not being valid java --- tests/pos/i851.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos/i851.java b/tests/pos/i851.java index 39e0bf35031a..cfd86ccc445a 100644 --- a/tests/pos/i851.java +++ b/tests/pos/i851.java @@ -5,4 +5,4 @@ interface J extends I { }; } -class C extends J {} \ No newline at end of file +class C implements J {} From 0620c2b2602b44e569135b6b0648b2bd5994b78b Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 9 Mar 2017 17:01:47 +0100 Subject: [PATCH 05/39] Prefix out directory with name of test --- compiler/test/dotty/tools/dotc/ParallelTesting.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index eada2c763012..711fc86a3fde 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -61,7 +61,12 @@ trait ParallelTesting { files -> reporter.errors } - def compileFilesInDir(f: String, flags: Array[String])(implicit outDir: String): Unit = { + def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): Unit = { + // each calling method gets its own unique output directory, in which we + // place the dir being compiled: + val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName + val outDir = outDirectory + callingMethod + "/" + val dir = new JFile(f) require(f.contains("/tests"), "only allowed to run integration tests from `tests` dir using this method") require(dir.isDirectory && dir.exists, "passed non-directory to `compileFilesInDir`") From fc2bdc85381d93dcf7010cf13347569e14efb29c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 10 Mar 2017 13:24:53 +0100 Subject: [PATCH 06/39] Parallelize compilation runs for ParallelTesting infra --- .../dotty/tools/dotc/ParallelTesting.scala | 119 ++++++++++++++---- 1 file changed, 92 insertions(+), 27 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 711fc86a3fde..45e341c333ab 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -11,9 +11,87 @@ import reporting.diagnostic.MessageContainer import interfaces.Diagnostic.ERROR import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.{ Files, Path, Paths } +import java.util.concurrent.{ Executors => JExecutors, TimeUnit } +import scala.util.control.NonFatal trait ParallelTesting { + private abstract class CompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) { + val totalTargets = targetDirs.length + + private[this] var _errors = 0 + def errors: Int = synchronized { _errors } + + private[this] var _targetsCompiled = 0 + private def targetsCompiled: Int = synchronized { _targetsCompiled } + + protected final def completeCompilation(newErrors: Int) = synchronized { + _targetsCompiled += 1 + _errors += newErrors + } + + private def statusRunner: Runnable = new Runnable { + def run(): Unit = { + val start = System.currentTimeMillis + var tCompiled = targetsCompiled + while (tCompiled < totalTargets) { + val timestamp = (System.currentTimeMillis - start) / 1000 + val progress = (tCompiled.toDouble / totalTargets * 40).toInt + print( + s"Compiling tests in $fromDir [" + + ("=" * (math.max(progress - 1, 0))) + + (if (progress > 0) ">" else "") + + (" " * (39 - progress)) + + s"] $tCompiled/$totalTargets, ${timestamp}s, errors: $errors\r" + ) + Thread.sleep(50) + tCompiled = targetsCompiled + } + // println, otherwise no newline and cursor at start of line + println( + s"Compiled tests in $fromDir " + + s"[========================================] $totalTargets/$totalTargets, " + + s"${(System.currentTimeMillis - start) / 1000}s, errors: $errors " + ) + } + } + + protected def compilationRunnable(dir: JFile): Runnable + + private[ParallelTesting] def execute(): this.type = { + assert(_targetsCompiled == 0, "not allowed to re-use a `CompileRun`") + val pool = JExecutors.newWorkStealingPool() + pool.submit(statusRunner) + + targetDirs.foreach { dir => + pool.submit(compilationRunnable(dir)) + } + + pool.shutdown() + pool.awaitTermination(10, TimeUnit.MINUTES) + this + } + } + + private final class PosCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) + extends CompileRun(targetDirs, fromDir, flags) { + protected def compilationRunnable(dir: JFile): Runnable = new Runnable { + def run(): Unit = + try { + val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + val errors = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), false) + completeCompilation(errors.length) + } + catch { + case NonFatal(e) => { + System.err.println(s"\n${e.getMessage}\n") + completeCompilation(1) + throw e + } + } + } + } + private val driver = new Driver { override def newCompiler(implicit ctx: Context) = new Compiler } @@ -32,7 +110,7 @@ trait ParallelTesting { } } - private def compile(files: Array[JFile], flags: Array[String]): (Array[JFile], List[MessageContainer]) = { + private def compile(files: Array[JFile], flags: Array[String], suppressErrors: Boolean): List[MessageContainer] = { def findJarFromRuntime(partialName: String) = { val urls = ClassLoader.getSystemClassLoader.asInstanceOf[java.net.URLClassLoader].getURLs.map(_.getFile.toString) @@ -56,12 +134,20 @@ trait ParallelTesting { compileWithJavac(files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath)) - val reporter = new DaftReporter(suppress = false) + val reporter = new DaftReporter(suppress = suppressErrors) driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) - files -> reporter.errors + reporter.errors } - def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): Unit = { + + class CompilationTest(targetDirs: List[JFile], fromDir: String, flags: Array[String]) { + def pos: Unit = { + val run = new PosCompileRun(targetDirs, fromDir, flags).execute() + assert(run.errors == 0, s"Expected no errors when compiling $fromDir") + } + } + + def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { // each calling method gets its own unique output directory, in which we // place the dir being compiled: val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName @@ -102,28 +188,7 @@ trait ParallelTesting { // Directories in which to compile all containing files with `flags`: val dirsToCompile = files.map(toCompilerDirFromFile) ++ dirs.map(toCompilerDirFromDir) - // Progress bar setup - val numberOfTargets = dirsToCompile.length - var targetsCompiled = 0 - val start = System.currentTimeMillis - var errors = 0 - - dirsToCompile.map { dir => - val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - targetsCompiled += 1 - val timestamp = (System.currentTimeMillis - start) / 1000 - val progress = (targetsCompiled.toDouble / numberOfTargets * 40).toInt - print( - s"Compiling tests in $f [" + - ("=" * (math.max(progress - 1, 0))) + - (if (progress > 0) ">" else "") + - (" " * (39 - progress)) + - s"] $targetsCompiled/$numberOfTargets, ${timestamp}s, errors: $errors\r" - ) - val (_, newErrors ) = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath)) - errors += newErrors.length - } - println(s"Compiled tests in $f [========================================] $targetsCompiled/$numberOfTargets, ${(System.currentTimeMillis - start) / 1000}s, errors: $errors ") - + // Create a CompilationTest and let the user decide whether to execute a pos or a neg test + new CompilationTest(dirsToCompile, f, flags) } } From 57f8d1b1f7962f99cf27501994b1440e369fe597 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Sat, 11 Mar 2017 16:35:52 +0100 Subject: [PATCH 07/39] Add neg testing capability to ParallelTesting --- .../dotty/tools/dotc/ParallelTesting.scala | 47 ++++++++++++++++++- tests/neg/i941.scala | 2 +- tests/neg/instantiateAbstract.scala | 2 +- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 45e341c333ab..8c4750f9f7b1 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -92,6 +92,44 @@ trait ParallelTesting { } } + private final class NegCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) + extends CompileRun(targetDirs, fromDir, flags) { + private[this] var _failed = false + private[this] def fail(): Unit = _failed = true + + def didFail: Boolean = _failed + + protected def compilationRunnable(dir: JFile): Runnable = new Runnable { + def run(): Unit = + try { + val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + + val expectedErrors = dir.listFiles.filter(_.getName.endsWith(".scala")).foldLeft(0) { (acc, file) => + acc + Source.fromFile(file).sliding("// error".length).count(_.mkString == "// error") + } + + val errors = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), true) + val actualErrors = errors.length + + if (expectedErrors != actualErrors) { + System.err.println { + s"\nWrong number of errors encountered when compiling $dir, expected: $expectedErrors, actual: $actualErrors\n" + } + fail() + } + + completeCompilation(actualErrors) + } + catch { + case NonFatal(e) => { + System.err.println(s"\n${e.getMessage}\n") + completeCompilation(1) + throw e + } + } + } + } + private val driver = new Driver { override def newCompiler(implicit ctx: Context) = new Compiler } @@ -103,9 +141,9 @@ trait ParallelTesting { def errors = _errors override def doReport(m: MessageContainer)(implicit ctx: Context) = { - if (!suppress && m.level == ERROR) { + if (m.level == ERROR) { _errors = m :: _errors - System.err.println(messageAndPos(m.contained, m.pos, diagnosticLevel(m))) + if (!suppress) System.err.println(messageAndPos(m.contained, m.pos, diagnosticLevel(m))) } } } @@ -145,6 +183,11 @@ trait ParallelTesting { val run = new PosCompileRun(targetDirs, fromDir, flags).execute() assert(run.errors == 0, s"Expected no errors when compiling $fromDir") } + + def neg: Unit = assert( + !(new NegCompileRun(targetDirs, fromDir, flags).execute().didFail), + s"Wrong number of errors encountered when compiling $fromDir" + ) } def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { diff --git a/tests/neg/i941.scala b/tests/neg/i941.scala index 2643c2546b2c..fc29cc27dc3a 100644 --- a/tests/neg/i941.scala +++ b/tests/neg/i941.scala @@ -1,7 +1,7 @@ object Test { def bar(tl: => String) = { - val x = tl _ //error + val x = tl _ // error val y = x _ // error val s: String = x() // error } diff --git a/tests/neg/instantiateAbstract.scala b/tests/neg/instantiateAbstract.scala index 10eeac64d9e2..a2ff38ef43da 100644 --- a/tests/neg/instantiateAbstract.scala +++ b/tests/neg/instantiateAbstract.scala @@ -15,7 +15,7 @@ object Test { @scala.annotation.Annotation type T = String // error @scala.annotation.Annotation val x = 1 // error - @scala.annotation.Annotation def f = 1 //error + @scala.annotation.Annotation def f = 1 // error (1: @scala.annotation.Annotation) // error From 3acba311aaf831c1b249341142e1308ed1f73050 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 13 Mar 2017 10:42:24 +0100 Subject: [PATCH 08/39] Add support for error annotations in neg tests --- .../dotty/tools/dotc/CompilationTests.scala | 61 +++++++ .../dotty/tools/dotc/ParallelTesting.scala | 169 +++++++++++++----- 2 files changed, 188 insertions(+), 42 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/CompilationTests.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala new file mode 100644 index 000000000000..22f7e6d91975 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -0,0 +1,61 @@ +package dotty +package tools +package dotc + +import org.junit.Test +import java.io.{ File => JFile } + +class CompilationTests extends ParallelTesting { + import CompilationTests.{ defaultOutputDir, defaultOptions } + + @Test def compilePos = + compileFilesInDir("../tests/pos", defaultOptions).pos + + @Test def compileNeg = + compileShallowFilesInDir("../tests/neg", defaultOptions).neg +} + +object CompilationTests { + implicit val defaultOutputDir: String = "../out/" + + private val noCheckOptions = Array( + "-pagewidth", "80" + ) + + private val checkOptions = Array( + "-Yno-deep-subtypes", + "-Yno-double-bindings", + "-Yforce-sbt-phases" + ) + + private 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 +} diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 8c4750f9f7b1..f1363e49fc6d 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -13,6 +13,7 @@ import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.{ Files, Path, Paths } import java.util.concurrent.{ Executors => JExecutors, TimeUnit } import scala.util.control.NonFatal +import java.util.HashMap trait ParallelTesting { @@ -49,8 +50,8 @@ trait ParallelTesting { } // println, otherwise no newline and cursor at start of line println( - s"Compiled tests in $fromDir " + - s"[========================================] $totalTargets/$totalTargets, " + + s"Compiled tests in $fromDir " + + s"[=======================================] $totalTargets/$totalTargets, " + s"${(System.currentTimeMillis - start) / 1000}s, errors: $errors " ) } @@ -79,8 +80,8 @@ trait ParallelTesting { def run(): Unit = try { val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - val errors = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), false) - completeCompilation(errors.length) + val reporter = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), false) + completeCompilation(reporter.errorCount) } catch { case NonFatal(e) => { @@ -104,12 +105,30 @@ trait ParallelTesting { try { val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - val expectedErrors = dir.listFiles.filter(_.getName.endsWith(".scala")).foldLeft(0) { (acc, file) => - acc + Source.fromFile(file).sliding("// error".length).count(_.mkString == "// error") + // 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` + val errorMap = new HashMap[String, Integer]() + var expectedErrors = 0 + dir.listFiles.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) + errorMap.put(file.getAbsolutePath, noposErrors) + + expectedErrors += noposErrors + errors + } } - val errors = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), true) - val actualErrors = errors.length + val reporter = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), true) + val actualErrors = reporter.errorCount if (expectedErrors != actualErrors) { System.err.println { @@ -117,6 +136,45 @@ trait ParallelTesting { } fail() } + else if ( + // Here we check that there is a correpsonding error reported for + // each annotation + !reporter.errors.forall { error => + val fileName = error.pos.source.file.toString + val fileAndRow = s"$fileName:${error.pos.line}" + + val rowErrors = errorMap.get(fileAndRow) + lazy val noposErrors = errorMap.get(fileName) + + if (rowErrors ne null) { + if (rowErrors == 1) errorMap.remove(fileAndRow) + else errorMap.put(fileAndRow, rowErrors - 1) + true + } + else if (noposErrors ne null) { + if (noposErrors == 1) errorMap.remove(fileName) + else errorMap.put(fileName, noposErrors - 1) + true + } + else { + System.err.println { + s"Error reported in ${error.pos}, but no annotation found" + } + false + } + } + ) { + System.err.println { + s"\nErrors found on incorrect row numbers when compiling $dir" + } + fail() + } + else if (!errorMap.isEmpty) { + System.err.println { + s"\nError annotation(s) have {=}: $errorMap" + } + fail() + } completeCompilation(actualErrors) } @@ -148,7 +206,7 @@ trait ParallelTesting { } } - private def compile(files: Array[JFile], flags: Array[String], suppressErrors: Boolean): List[MessageContainer] = { + private def compile(files: Array[JFile], flags: Array[String], suppressErrors: Boolean): DaftReporter = { def findJarFromRuntime(partialName: String) = { val urls = ClassLoader.getSystemClassLoader.asInstanceOf[java.net.URLClassLoader].getURLs.map(_.getFile.toString) @@ -174,7 +232,7 @@ trait ParallelTesting { val reporter = new DaftReporter(suppress = suppressErrors) driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) - reporter.errors + reporter } @@ -190,48 +248,75 @@ trait ParallelTesting { ) } - def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - // each calling method gets its own unique output directory, in which we - // place the dir being compiled: - val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName - val outDir = outDirectory + callingMethod + "/" + private def toCompilerDirFromDir(d: JFile, sourceDir: JFile, outDir: String): JFile = { + val targetDir = new JFile(outDir + s"${sourceDir.getName}/${d.getName}") + // create if not exists + targetDir.mkdirs() + d.listFiles.foreach(copyToDir(targetDir, _)) + targetDir + } + + private def toCompilerDirFromFile(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") + // create if not exists + targetDir.mkdirs() + // copy file to dir: + copyToDir(targetDir, file) + targetDir + } + + private def copyToDir(dir: JFile, file: JFile): Unit = { + val target = Paths.get(dir.getAbsolutePath, file.getName) + Files.copy(file.toPath, target, REPLACE_EXISTING).toFile + } - val dir = new JFile(f) + private def requirements(f: String, sourceDir: JFile, outDir: String): Unit = { require(f.contains("/tests"), "only allowed to run integration tests from `tests` dir using this method") - require(dir.isDirectory && dir.exists, "passed non-directory to `compileFilesInDir`") + require(sourceDir.isDirectory && sourceDir.exists, "passed non-directory to `compileFilesInDir`") require(outDir.last == '/', "please specify an `outDir` with a trailing slash") + } - def toCompilerDirFromDir(d: JFile): JFile = { - val targetDir = new JFile(outDir + s"${dir.getName}/${d.getName}") - // create if not exists - targetDir.mkdirs() - d.listFiles.foreach(copyToDir(targetDir, _)) - targetDir - } - def toCompilerDirFromFile(file: JFile): JFile = { - val uniqueSubdir = file.getName.substring(0, file.getName.lastIndexOf('.')) - val targetDir = new JFile(outDir + s"${dir.getName}/$uniqueSubdir") - // create if not exists - targetDir.mkdirs() - // copy file to dir: - copyToDir(targetDir, file) - targetDir - } - def copyToDir(dir: JFile, file: JFile): Unit = { - val target = Paths.get(dir.getAbsolutePath, file.getName) - Files.copy(file.toPath, target, REPLACE_EXISTING).toFile + 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 (dirs, f :: files) } - val (dirs, files) = - dir.listFiles.foldLeft((List.empty[JFile], List.empty[JFile])) { case ((dirs, files), f) => - if (f.isDirectory) (f :: dirs, files) - else (dirs, f :: files) - } + def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + // each calling method gets its own unique output directory, in which we + // place the dir being compiled: + val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName + val outDir = outDirectory + callingMethod + "/" + val sourceDir = new JFile(f) + requirements(f, sourceDir, outDir) + + val (dirs, files) = compilationTargets(sourceDir) // Directories in which to compile all containing files with `flags`: - val dirsToCompile = files.map(toCompilerDirFromFile) ++ dirs.map(toCompilerDirFromDir) + val dirsToCompile = + files.map(toCompilerDirFromFile(_, sourceDir, outDir)) ++ + dirs.map(toCompilerDirFromDir(_, sourceDir, outDir)) // Create a CompilationTest and let the user decide whether to execute a pos or a neg test new CompilationTest(dirsToCompile, f, flags) } + + def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + // each calling method gets its own unique output directory, in which we + // place the dir being compiled: + val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName + val outDir = outDirectory + callingMethod + "/" + val sourceDir = new JFile(f) + requirements(f, sourceDir, outDir) + + val (_, files) = compilationTargets(sourceDir) + + // Create a CompilationTest and let the user decide whether to execute a pos or a neg test + new CompilationTest( + files.map(toCompilerDirFromFile(_, sourceDir, outDir)), + f, + flags + ) + } } From c52457c7554f5a0648190562e6750067f811844c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 13 Mar 2017 11:50:53 +0100 Subject: [PATCH 09/39] Add ability to compile single files --- .../test/dotty/tools/dotc/ParallelTesting.scala | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index f1363e49fc6d..2ce585a96e31 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -272,7 +272,6 @@ trait ParallelTesting { } private def requirements(f: String, sourceDir: JFile, outDir: String): Unit = { - require(f.contains("/tests"), "only allowed to run integration tests from `tests` dir using this method") require(sourceDir.isDirectory && sourceDir.exists, "passed non-directory to `compileFilesInDir`") require(outDir.last == '/', "please specify an `outDir` with a trailing slash") } @@ -283,6 +282,22 @@ trait ParallelTesting { else (dirs, f :: files) } + def compileFileInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + // each calling method gets its own unique output directory, in which we + // place the dir being compiled: + val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName + val outDir = outDirectory + callingMethod + "/" + val sourceFile = new JFile(f) + val parent = sourceFile.getParentFile + require( + sourceFile.exists && !sourceFile.isDirectory && + (parent ne null) && parent.exists && parent.isDirectory, + s"Source file: $f, didn't exist" + ) + + new CompilationTest(toCompilerDirFromFile(sourceFile, parent, outDir) :: Nil, f, flags) + } + def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { // each calling method gets its own unique output directory, in which we // place the dir being compiled: From cd345b43378a24a75a78d699546a1f8964efc88f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 14 Mar 2017 16:38:33 +0100 Subject: [PATCH 10/39] Add run testing capabilities --- .../dotty/tools/dotc/CompilationTests.scala | 34 ++++- .../dotty/tools/dotc/ParallelTesting.scala | 122 +++++++++++++++--- tests/run/getclass.check | 4 +- tests/run/getclass.scala | 6 +- tests/run/origins.check | 6 - tests/run/origins.flags | 1 - tests/run/origins.scala | 21 --- tests/run/shutdownhooks.check | 3 - tests/run/shutdownhooks.scala | 37 ------ 9 files changed, 142 insertions(+), 92 deletions(-) delete mode 100644 tests/run/origins.check delete mode 100644 tests/run/origins.flags delete mode 100644 tests/run/origins.scala delete mode 100644 tests/run/shutdownhooks.check delete mode 100644 tests/run/shutdownhooks.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 22f7e6d91975..c5c710ab1183 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -6,13 +6,36 @@ import org.junit.Test import java.io.{ File => JFile } class CompilationTests extends ParallelTesting { - import CompilationTests.{ defaultOutputDir, defaultOptions } + import CompilationTests.{ defaultOutputDir, defaultOptions, picklingOptions } + + // Positive tests ------------------------------------------------------------ @Test def compilePos = compileFilesInDir("../tests/pos", defaultOptions).pos + // Negative tests ------------------------------------------------------------ + @Test def compileNeg = compileShallowFilesInDir("../tests/neg", defaultOptions).neg + + // Run tests ----------------------------------------------------------------- + + @Test def runArraycopy = + compileFile("../tests/run/arraycopy.scala", defaultOptions).run + + @Test def runAll = + compileFilesInDir("../tests/run", defaultOptions).run + + // Pickling Tests ------------------------------------------------------------ + + @Test def testPickling = + compileFilesInDir("../tests/pickling", picklingOptions).pos + + @Test def testPicklingAst = + compileFilesInDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions).pos + + @Test def testPicklingInf = + compileFile("../tests/pos/pickleinf.scala", picklingOptions).pos } object CompilationTests { @@ -58,4 +81,13 @@ object CompilationTests { 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" + ) } diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 2ce585a96e31..a7189fff4974 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -9,6 +9,7 @@ import core.Contexts._ import reporting.{ Reporter, UniqueMessagePositions, HideNonSensicalMessages, MessageRendering } import reporting.diagnostic.MessageContainer import interfaces.Diagnostic.ERROR +import java.lang.reflect.InvocationTargetException import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.{ Files, Path, Paths } import java.util.concurrent.{ Executors => JExecutors, TimeUnit } @@ -31,6 +32,10 @@ trait ParallelTesting { _errors += newErrors } + private[this] var _failed = false + final protected[this] def fail(): Unit = synchronized { _failed = true } + def didFail: Boolean = _failed + private def statusRunner: Runnable = new Runnable { def run(): Unit = { val start = System.currentTimeMillis @@ -85,6 +90,9 @@ trait ParallelTesting { } catch { case NonFatal(e) => { + // if an exception is thrown during compilation, the complete test + // run should fail + fail() System.err.println(s"\n${e.getMessage}\n") completeCompilation(1) throw e @@ -93,13 +101,77 @@ trait ParallelTesting { } } - private final class NegCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) + private final class RunCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) extends CompileRun(targetDirs, fromDir, flags) { - private[this] var _failed = false - private[this] def fail(): Unit = _failed = true + private def verifyOutput(checkFile: JFile, dir: JFile) = try { + // Do classloading magic and running here: + import java.net.{ URL, URLClassLoader } + import java.io.ByteArrayOutputStream + val ucl = new URLClassLoader(Array(dir.toURI.toURL)) + val cls = ucl.loadClass("Test") + val meth = cls.getMethod("main", classOf[Array[String]]) + + val printStream = new ByteArrayOutputStream + Console.withOut(printStream) { + meth.invoke(null, Array("jvm")) // partest passes at least "jvm" as an arg + } - def didFail: Boolean = _failed + val outputLines = printStream.toString("utf-8").lines.toArray + val checkLines = Source.fromFile(checkFile).getLines.toArray + + def linesMatch = + outputLines + .zip(checkLines) + .forall { case (x, y) => x == y } + + if (outputLines.length != checkLines.length || !linesMatch) { + System.err.println { + s"\nOutput from run test '$dir' did not match expected, output:\n${outputLines.mkString("\n")}" + } + fail() + } + } + catch { + case _: NoSuchMethodException => + System.err.println(s"\ntest in '$dir' did not contain a main method") + fail() + + case _: ClassNotFoundException => + System.err.println(s"\ntest in '$dir' did was not contained within a `Test` object") + fail() + + case _: InvocationTargetException => + System.err.println(s"\nTest in '$dir' might be using args(X) where X > 0") + fail() + } + protected def compilationRunnable(dir: JFile): Runnable = new Runnable { + def run(): Unit = + try { + val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + val checkFile = dir.listFiles.find(_.getName.endsWith(".check")) + val reporter = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), false) + completeCompilation(reporter.errorCount) + + if (reporter.errorCount == 0 && checkFile.isDefined) verifyOutput(checkFile.get, dir) + else if (reporter.errorCount > 0) { + System.err.println(s"\nCompilation failed for: '$dir'") + fail() + } + } + catch { + case NonFatal(e) => { + System.err.println(s"\n$e\n${e.getMessage}") + completeCompilation(1) + fail() + throw e + } + } + } + } + + private final class NegCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) + extends CompileRun(targetDirs, fromDir, flags) { protected def compilationRunnable(dir: JFile): Runnable = new Runnable { def run(): Unit = try { @@ -188,10 +260,6 @@ trait ParallelTesting { } } - private val driver = new Driver { - override def newCompiler(implicit ctx: Context) = new Compiler - } - private class DaftReporter(suppress: Boolean) extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { @@ -206,7 +274,13 @@ trait ParallelTesting { } } - private def compile(files: Array[JFile], flags: Array[String], suppressErrors: Boolean): DaftReporter = { + private def compile(files0: Array[JFile], flags: Array[String], suppressErrors: Boolean): DaftReporter = { + + 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) @@ -231,11 +305,11 @@ trait ParallelTesting { compileWithJavac(files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath)) val reporter = new DaftReporter(suppress = suppressErrors) + val driver = new Driver { def newCompiler(implicit ctx: Context) = new Compiler } driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) reporter } - class CompilationTest(targetDirs: List[JFile], fromDir: String, flags: Array[String]) { def pos: Unit = { val run = new PosCompileRun(targetDirs, fromDir, flags).execute() @@ -246,6 +320,11 @@ trait ParallelTesting { !(new NegCompileRun(targetDirs, fromDir, flags).execute().didFail), s"Wrong number of errors encountered when compiling $fromDir" ) + + def run: Unit = assert( + !(new RunCompileRun(targetDirs, fromDir, flags).execute().didFail), + s"Run tests failed for test $fromDir" + ) } private def toCompilerDirFromDir(d: JFile, sourceDir: JFile, outDir: String): JFile = { @@ -257,13 +336,19 @@ trait ParallelTesting { } private def toCompilerDirFromFile(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") - // create if not exists - targetDir.mkdirs() - // copy file to dir: - copyToDir(targetDir, file) - targetDir + val uniqueSubdir = file.getName.substring(0, file.getName.lastIndexOf('.')) + val targetDir = new JFile(outDir + s"${sourceDir.getName}/$uniqueSubdir") + if (file.getName.endsWith(".java") || file.getName.endsWith(".scala")) { + // create if not exists + targetDir.mkdirs() + // copy file to dir: + copyToDir(targetDir, file) + + // copy checkfile if it exists + val checkFile = new JFile(file.getAbsolutePath.substring(0, file.getAbsolutePath.lastIndexOf('.')) + ".check") + if (checkFile.exists) copyToDir(targetDir, checkFile) + } + targetDir } private def copyToDir(dir: JFile, file: JFile): Unit = { @@ -279,10 +364,11 @@ trait ParallelTesting { 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 (f.getName.endsWith(".check")) (dirs, files) else (dirs, f :: files) } - def compileFileInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + def compileFile(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { // each calling method gets its own unique output directory, in which we // place the dir being compiled: val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName diff --git a/tests/run/getclass.check b/tests/run/getclass.check index 9d88762f4954..ea73f6127c8f 100644 --- a/tests/run/getclass.check +++ b/tests/run/getclass.check @@ -22,5 +22,5 @@ class [D class [Lscala.collection.immutable.List; Functions: -class Test$$$Lambda$1 -class Test$$$Lambda$2 +class Test$$$Lambda$ +class Test$$$Lambda$ diff --git a/tests/run/getclass.scala b/tests/run/getclass.scala index 7a13a61a2196..b74e1b20211d 100644 --- a/tests/run/getclass.scala +++ b/tests/run/getclass.scala @@ -35,8 +35,8 @@ object Test { println("\nFunctions:") // FunctionN.getClass.toString has form of "class Test$$$Lambda$N/1349414238", - // but number (1349414238) depends on environment - println(f1.getClass.toString.takeWhile(_ != '/')) - println(f2.getClass.toString.takeWhile(_ != '/')) + // but "N/1349414238" depends on environment + println(f1.getClass.toString.take("class Test$$$Lambda$".length)) + println(f2.getClass.toString.take("class Test$$$Lambda$".length)) } } diff --git a/tests/run/origins.check b/tests/run/origins.check deleted file mode 100644 index 54d58296b9b6..000000000000 --- a/tests/run/origins.check +++ /dev/null @@ -1,6 +0,0 @@ - ->> Origins tag 'boop' logged 65 calls from 3 distinguished sources. - - 50 Test$.$anonfun$f3$1(origins.scala:16) - 10 Test$.$anonfun$f2$1(origins.scala:15) - 5 Test$.$anonfun$f1$1(origins.scala:14) diff --git a/tests/run/origins.flags b/tests/run/origins.flags deleted file mode 100644 index 690753d807c1..000000000000 --- a/tests/run/origins.flags +++ /dev/null @@ -1 +0,0 @@ --no-specialization -Ydelambdafy:inline \ No newline at end of file diff --git a/tests/run/origins.scala b/tests/run/origins.scala deleted file mode 100644 index 6529351d3c76..000000000000 --- a/tests/run/origins.scala +++ /dev/null @@ -1,21 +0,0 @@ -import scala.reflect.internal.util.Origins - -package goxbox { - object Socks { - val origins = Origins("boop") - - def boop(x: Int): Int = origins { 5 } - } -} - -object Test { - import goxbox.Socks.boop - - def f1() = 1 to 5 map boop - def f2() = 1 to 10 map boop - def f3() = 1 to 50 map boop - - def main(args: Array[String]): Unit = { - f1() ; f2() ; f3() - } -} diff --git a/tests/run/shutdownhooks.check b/tests/run/shutdownhooks.check deleted file mode 100644 index 29956956e30c..000000000000 --- a/tests/run/shutdownhooks.check +++ /dev/null @@ -1,3 +0,0 @@ -Fooblitzky! -main#shutdown. -Test#shutdown. diff --git a/tests/run/shutdownhooks.scala b/tests/run/shutdownhooks.scala deleted file mode 100644 index 5f512a391ab3..000000000000 --- a/tests/run/shutdownhooks.scala +++ /dev/null @@ -1,37 +0,0 @@ -object Test { - scala.sys.addShutdownHook { - Thread.sleep(1000) - println("Test#shutdown.") - } - - def daemon() = { - val t = new Thread { - override def run(): Unit = { - Thread.sleep(10000) - println("Hallelujah!") // should not see this - } - } - t.setDaemon(true) - t.start() - t - } - - def nonDaemon() = { - val t = new Thread { - override def run(): Unit = { - Thread.sleep(100) - println("Fooblitzky!") - } - } - t.start() - t - } - - def main(args: Array[String]): Unit = { - daemon() - nonDaemon() - scala.sys.addShutdownHook { - println("main#shutdown.") - } - } -} From 0c23687055ddb902dd1802cb48e122cc5b02082e Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 15 Mar 2017 15:17:48 +0100 Subject: [PATCH 11/39] Stylistic changes to `Compiler` and `DPConsoleRunner` --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../test/dotty/partest/DPConsoleRunner.scala | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index efde897cd3e3..0d3fb5821501 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -129,7 +129,7 @@ class Compiler { .setMode(Mode.ImplicitsEnabled) .setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true)) .setFreshNames(new FreshNameCreator.Default) - ctx.initialize()(start) // re-initialize the base context with start + ctx.initialize()(start) // re-initialize the base context with start def addImport(ctx: Context, refFn: () => TermRef) = ctx.fresh.setImportInfo(ImportInfo.rootImport(refFn)(ctx)) (start.setRunInfo(new RunInfo(start)) /: defn.RootImportFns)(addImport) diff --git a/compiler/test/dotty/partest/DPConsoleRunner.scala b/compiler/test/dotty/partest/DPConsoleRunner.scala index aa926efe29dc..3362d7a59343 100644 --- a/compiler/test/dotty/partest/DPConsoleRunner.scala +++ b/compiler/test/dotty/partest/DPConsoleRunner.scala @@ -90,18 +90,18 @@ extends SuiteRunner(testSourcePath, fileManager, updateCheck, failed, javaCmdPat } /** Some tests require a limitation of resources, tests which are compiled - * with one or more of the flags in this list will be run with - * `limitedThreads`. This is necessary because some test flags require a lot - * of memory when running the compiler and may exhaust the available memory - * when run in parallel with too many other tests. - * - * This number could be increased on the CI, but might fail locally if - * scaled too extreme - override with: - * - * ``` - * -Ddotty.tests.limitedThreads=X - * ``` - */ + * with one or more of the flags in this list will be run with + * `limitedThreads`. This is necessary because some test flags require a lot + * of memory when running the compiler and may exhaust the available memory + * when run in parallel with too many other tests. + * + * This number could be increased on the CI, but might fail locally if + * scaled too extreme - override with: + * + * ``` + * -Ddotty.tests.limitedThreads=X + * ``` + */ def limitResourceFlags = List("-Ytest-pickler") private val limitedThreads = sys.props.get("dotty.tests.limitedThreads").getOrElse("2") From 5d7fb14b04a576d33b80430ef76608922061cda9 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 16 Mar 2017 16:34:18 +0100 Subject: [PATCH 12/39] Rewrite testing logic as to not copy files --- .../dotty/tools/dotc/ParallelTesting.scala | 440 +++++++++++------- 1 file changed, 267 insertions(+), 173 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index a7189fff4974..2c9e813e166b 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -18,8 +18,12 @@ import java.util.HashMap trait ParallelTesting { - private abstract class CompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) { - val totalTargets = targetDirs.length + private case class Target(files: Array[JFile], outDir: JFile) { + override def toString() = outDir.toString + } + + private abstract class CompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) { + val totalTargets = targets.length private[this] var _errors = 0 def errors: Int = synchronized { _errors } @@ -44,33 +48,43 @@ trait ParallelTesting { val timestamp = (System.currentTimeMillis - start) / 1000 val progress = (tCompiled.toDouble / totalTargets * 40).toInt print( - s"Compiling tests in $fromDir [" + - ("=" * (math.max(progress - 1, 0))) + + "[" + ("=" * (math.max(progress - 1, 0))) + (if (progress > 0) ">" else "") + (" " * (39 - progress)) + - s"] $tCompiled/$totalTargets, ${timestamp}s, errors: $errors\r" + s"] compiling ($tCompiled/$totalTargets, ${timestamp}s) in '$fromDir'\r" ) Thread.sleep(50) tCompiled = targetsCompiled } // println, otherwise no newline and cursor at start of line println( - s"Compiled tests in $fromDir " + - s"[=======================================] $totalTargets/$totalTargets, " + - s"${(System.currentTimeMillis - start) / 1000}s, errors: $errors " + s"[=======================================] compiled ($totalTargets/$totalTargets, " + + s"${(System.currentTimeMillis - start) / 1000}s) in '$fromDir' errors: $errors" ) } } - protected def compilationRunnable(dir: JFile): Runnable + protected def compileTry(op: => Unit) = + try op catch { + case NonFatal(e) => { + // if an exception is thrown during compilation, the complete test + // run should fail + fail() + e.printStackTrace() + completeCompilation(1) + throw e + } + } + + protected def compilationRunnable(target: Target): Runnable private[ParallelTesting] def execute(): this.type = { assert(_targetsCompiled == 0, "not allowed to re-use a `CompileRun`") val pool = JExecutors.newWorkStealingPool() pool.submit(statusRunner) - targetDirs.foreach { dir => - pool.submit(compilationRunnable(dir)) + targets.foreach { target => + pool.submit(compilationRunnable(target)) } pool.shutdown() @@ -79,30 +93,19 @@ trait ParallelTesting { } } - private final class PosCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) - extends CompileRun(targetDirs, fromDir, flags) { - protected def compilationRunnable(dir: JFile): Runnable = new Runnable { - def run(): Unit = - try { - val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - val reporter = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), false) - completeCompilation(reporter.errorCount) - } - catch { - case NonFatal(e) => { - // if an exception is thrown during compilation, the complete test - // run should fail - fail() - System.err.println(s"\n${e.getMessage}\n") - completeCompilation(1) - throw e - } - } + private final class PosCompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) + extends CompileRun(targets, fromDir, flags, times) { + protected def compilationRunnable(target: Target): Runnable = new Runnable { + def run(): Unit = compileTry { + val sourceFiles = target.files.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + val reporter = compile(sourceFiles, flags, false, times, target.outDir) + completeCompilation(reporter.errorCount) + } } } - private final class RunCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) - extends CompileRun(targetDirs, fromDir, flags) { + private final class RunCompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) + extends CompileRun(targets, fromDir, flags, times) { private def verifyOutput(checkFile: JFile, dir: JFile) = try { // Do classloading magic and running here: import java.net.{ URL, URLClassLoader } @@ -145,118 +148,99 @@ trait ParallelTesting { fail() } - protected def compilationRunnable(dir: JFile): Runnable = new Runnable { - def run(): Unit = - try { - val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - val checkFile = dir.listFiles.find(_.getName.endsWith(".check")) - val reporter = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), false) - completeCompilation(reporter.errorCount) - - if (reporter.errorCount == 0 && checkFile.isDefined) verifyOutput(checkFile.get, dir) - else if (reporter.errorCount > 0) { - System.err.println(s"\nCompilation failed for: '$dir'") - fail() - } - } - catch { - case NonFatal(e) => { - System.err.println(s"\n$e\n${e.getMessage}") - completeCompilation(1) - fail() - throw e - } + protected def compilationRunnable(target: Target): Runnable = new Runnable { + def run(): Unit = compileTry { + val sourceFiles = target.files.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + val checkFile = target.files.find(_.getName.endsWith(".check")) + val reporter = compile(sourceFiles, flags, false, times, target.outDir) + completeCompilation(reporter.errorCount) + + if (reporter.errorCount == 0 && checkFile.isDefined) verifyOutput(checkFile.get, target.outDir) + else if (reporter.errorCount > 0) { + System.err.println(s"\nCompilation failed for: '$target'") + fail() } + } } } - private final class NegCompileRun(targetDirs: List[JFile], fromDir: String, flags: Array[String]) - extends CompileRun(targetDirs, fromDir, flags) { - protected def compilationRunnable(dir: JFile): Runnable = new Runnable { - def run(): Unit = - try { - val sourceFiles = dir.listFiles.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - - // 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` - val errorMap = new HashMap[String, Integer]() - var expectedErrors = 0 - dir.listFiles.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) - errorMap.put(file.getAbsolutePath, noposErrors) - - expectedErrors += noposErrors + errors + private final class NegCompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) + extends CompileRun(targets, fromDir, flags, times) { + protected def compilationRunnable(target: Target): Runnable = new Runnable { + def run(): Unit = compileTry { + val sourceFiles = target.files.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) + + // 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` + val errorMap = new HashMap[String, Integer]() + var expectedErrors = 0 + target.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 } + } - val reporter = compile(sourceFiles, flags ++ Array("-d", dir.getAbsolutePath), true) - val actualErrors = reporter.errorCount + val reporter = compile(sourceFiles, flags, true, times, target.outDir) + val actualErrors = reporter.errorCount - if (expectedErrors != actualErrors) { - System.err.println { - s"\nWrong number of errors encountered when compiling $dir, expected: $expectedErrors, actual: $actualErrors\n" - } - fail() - } - else if ( - // Here we check that there is a correpsonding error reported for - // each annotation - !reporter.errors.forall { error => - val fileName = error.pos.source.file.toString - val fileAndRow = s"$fileName:${error.pos.line}" - - val rowErrors = errorMap.get(fileAndRow) - lazy val noposErrors = errorMap.get(fileName) - - if (rowErrors ne null) { - if (rowErrors == 1) errorMap.remove(fileAndRow) - else errorMap.put(fileAndRow, rowErrors - 1) - true - } - else if (noposErrors ne null) { - if (noposErrors == 1) errorMap.remove(fileName) - else errorMap.put(fileName, noposErrors - 1) - true - } - else { - System.err.println { - s"Error reported in ${error.pos}, but no annotation found" - } - false - } - } - ) { - System.err.println { - s"\nErrors found on incorrect row numbers when compiling $dir" - } - fail() + def missingAnnotations = !reporter.errors.forall { error => + val getter = if (error.pos.exists) { + val fileName = error.pos.source.file.toString + s"$fileName:${error.pos.line}" + + } else "nopos" + + val errors = errorMap.get(getter) + + if (errors ne null) { + if (errors == 1) errorMap.remove(getter) + else errorMap.put(getter, errors - 1) + true } - else if (!errorMap.isEmpty) { + else { System.err.println { - s"\nError annotation(s) have {=}: $errorMap" + s"Error reported in ${error.pos.source}, but no annotation found" } - fail() + false } + } - completeCompilation(actualErrors) + if (expectedErrors != actualErrors) { + System.err.println { + s"\nWrong number of errors encountered when compiling $target, expected: $expectedErrors, actual: $actualErrors\n" + } + fail() } - catch { - case NonFatal(e) => { - System.err.println(s"\n${e.getMessage}\n") - completeCompilation(1) - throw e + else if (missingAnnotations) { + System.err.println { + s"\nErrors found on incorrect row numbers when compiling $target" } + fail() + } + else if (!errorMap.isEmpty) { + System.err.println { + s"\nError annotation(s) have {=}: $errorMap" + } + fail() } + + completeCompilation(actualErrors) + } } } @@ -266,6 +250,18 @@ trait ParallelTesting { private var _errors: List[MessageContainer] = Nil def errors = _errors + private var _summary = new StringBuilder + def echoSummary(msg: String): this.type = { + _summary.append(msg) + this + } + + def printSummary(): this.type = { + val msg = _summary.toString + if (msg.nonEmpty) println(msg) + this + } + override def doReport(m: MessageContainer)(implicit ctx: Context) = { if (m.level == ERROR) { _errors = m :: _errors @@ -274,7 +270,9 @@ trait ParallelTesting { } } - private def compile(files0: Array[JFile], flags: Array[String], suppressErrors: Boolean): DaftReporter = { + private def compile(files0: Array[JFile], flags0: Array[String], suppressErrors: Boolean, times: Int, targetDir: JFile): DaftReporter = { + + val flags = flags0 ++ Array("-d", targetDir.getAbsolutePath) def flattenFiles(f: JFile): Array[JFile] = if (f.isDirectory) f.listFiles.flatMap(flattenFiles) @@ -296,64 +294,122 @@ trait ParallelTesting { val fullArgs = Array( "javac", "-classpath", - s".:$scalaLib" + s".:$scalaLib:${targetDir.getAbsolutePath}" ) ++ flags.takeRight(2) ++ fs - assert(Runtime.getRuntime.exec(fullArgs).waitFor() == 0, s"java compilation failed for ${fs.mkString(", ")}") - } + Runtime.getRuntime.exec(fullArgs).waitFor() == 0 + } else true - compileWithJavac(files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath)) + // First we try to compile the java files in the directory: + val javaFiles = files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath) + val javaCompiledBefore = compileWithJavac(javaFiles) + + // Then we compile the scala files: val reporter = new DaftReporter(suppress = suppressErrors) - val driver = new Driver { def newCompiler(implicit ctx: Context) = new Compiler } + 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)) + + private def echoSummary(rep: Reporter, msg: String)(implicit ctx: Context) = + rep.asInstanceOf[DaftReporter].echoSummary(msg) + + override def doCompile(comp: Compiler, files: List[String])(implicit ctx: Context) = + ntimes(times) { run => + val start = System.nanoTime() + val rep = super.doCompile(comp, files) + echoSummary(rep, s"\ntime run $run: ${(System.nanoTime - start) / 1000000}ms") + } + } + driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) - reporter + + // If the java files failed compilation before, we try again after: + if (!javaCompiledBefore) + assert(compileWithJavac(javaFiles), s"java compilation failed for ${javaFiles.mkString(", ")}") + + if (flags.contains("-verbose")) reporter.printSummary() + else reporter } - class CompilationTest(targetDirs: List[JFile], fromDir: String, flags: Array[String]) { - def pos: Unit = { - val run = new PosCompileRun(targetDirs, fromDir, flags).execute() + class CompilationTest private ( + targets: List[Target], + fromDir: String, + flags: Array[String], + times: Int, + shouldDelete: Boolean + ) { + def this(target: Target, fromDir: String, flags: Array[String]) = + this(List(target), fromDir, flags, 1, true) + + def this(targets: List[Target], fromDir: String, flags: Array[String]) = + this(targets, fromDir, flags, 1, true) + + def pos: this.type = { + val run = new PosCompileRun(targets, fromDir, flags, times).execute() assert(run.errors == 0, s"Expected no errors when compiling $fromDir") + if (shouldDelete) targets.foreach(t => delete(t.outDir)) + this } - def neg: Unit = assert( - !(new NegCompileRun(targetDirs, fromDir, flags).execute().didFail), - s"Wrong number of errors encountered when compiling $fromDir" - ) + def neg: this.type = { + assert( + !(new NegCompileRun(targets, fromDir, flags, times).execute().didFail), + s"Wrong number of errors encountered when compiling $fromDir" + ) + if (shouldDelete) targets.foreach(t => delete(t.outDir)) + this + } - def run: Unit = assert( - !(new RunCompileRun(targetDirs, fromDir, flags).execute().didFail), - s"Run tests failed for test $fromDir" - ) + def run: this.type = { + assert( + !(new RunCompileRun(targets, fromDir, flags, times).execute().didFail), + s"Run tests failed for test $fromDir" + ) + if (shouldDelete) targets.foreach(t => delete(t.outDir)) + this + } + + def times(i: Int): CompilationTest = + new CompilationTest(targets, fromDir, flags, i, shouldDelete) + + def verbose: CompilationTest = + new CompilationTest(targets, fromDir, flags ++ Array("-verbose", "-Ylog-classpath"), times, shouldDelete) + + def keepOutput: CompilationTest = + new CompilationTest(targets, fromDir, flags, times, false) + + def delete(): Unit = targets.foreach(t => delete(t.outDir)) + + def targetDirs: List[JFile] = targets.map(_.outDir) + + private def delete(file: JFile): Unit = { + if (file.isDirectory) file.listFiles.foreach(delete) + Files.delete(file.toPath) + } } private def toCompilerDirFromDir(d: JFile, sourceDir: JFile, outDir: String): JFile = { val targetDir = new JFile(outDir + s"${sourceDir.getName}/${d.getName}") - // create if not exists targetDir.mkdirs() - d.listFiles.foreach(copyToDir(targetDir, _)) targetDir } private def toCompilerDirFromFile(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") - if (file.getName.endsWith(".java") || file.getName.endsWith(".scala")) { - // create if not exists - targetDir.mkdirs() - // copy file to dir: - copyToDir(targetDir, file) - - // copy checkfile if it exists - val checkFile = new JFile(file.getAbsolutePath.substring(0, file.getAbsolutePath.lastIndexOf('.')) + ".check") - if (checkFile.exists) copyToDir(targetDir, checkFile) - } - targetDir + val uniqueSubdir = file.getName.substring(0, file.getName.lastIndexOf('.')) + val targetDir = new JFile(outDir + s"${sourceDir.getName}/$uniqueSubdir") + targetDir.mkdirs() + targetDir } private def copyToDir(dir: JFile, file: JFile): Unit = { val target = Paths.get(dir.getAbsolutePath, file.getName) - Files.copy(file.toPath, target, REPLACE_EXISTING).toFile + Files.copy(file.toPath, target, REPLACE_EXISTING) + if (file.isDirectory) file.listFiles.map(copyToDir(target.toFile, _)) } private def requirements(f: String, sourceDir: JFile, outDir: String): Unit = { @@ -365,6 +421,7 @@ trait ParallelTesting { sourceDir.listFiles.foldLeft((List.empty[JFile], List.empty[JFile])) { case ((dirs, files), f) => if (f.isDirectory) (f :: dirs, files) else if (f.getName.endsWith(".check")) (dirs, files) + else if (f.getName.endsWith(".flags")) (dirs, files) else (dirs, f :: files) } @@ -381,7 +438,45 @@ trait ParallelTesting { s"Source file: $f, didn't exist" ) - new CompilationTest(toCompilerDirFromFile(sourceFile, parent, outDir) :: Nil, f, flags) + val target = Target(Array(sourceFile), toCompilerDirFromFile(sourceFile, parent, outDir)) + new CompilationTest(target, f, flags) + } + + def compileDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + // each calling method gets its own unique output directory, in which we + // place the dir being compiled: + val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName + val outDir = outDirectory + callingMethod + "/" + val sourceDir = new JFile(f) + requirements(f, sourceDir, outDir) + + def flatten(f: JFile): Array[JFile] = + if (f.isDirectory) f.listFiles.flatMap(flatten) + else Array(f) + + // Directories in which to compile all containing files with `flags`: + val targetDir = new JFile(outDir) + targetDir.mkdirs() + + val target = Target(flatten(sourceDir), targetDir) + new CompilationTest(target, f, flags) + } + + def compileList(files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { + // each calling method gets its own unique output directory, in which we + // place the dir being compiled: + val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName + val outDir = outDirectory + callingMethod + "/" + + // 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 = Target(files.map(new JFile(_)).toArray, targetDir) + + // Create a CompilationTest and let the user decide whether to execute a pos or a neg test + new CompilationTest(target, outDir.toString, flags) } def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { @@ -394,13 +489,12 @@ trait ParallelTesting { val (dirs, files) = compilationTargets(sourceDir) - // Directories in which to compile all containing files with `flags`: - val dirsToCompile = - files.map(toCompilerDirFromFile(_, sourceDir, outDir)) ++ - dirs.map(toCompilerDirFromDir(_, sourceDir, outDir)) + val targets = + files.map(f => Target(Array(f), toCompilerDirFromFile(f, sourceDir, outDir))) ++ + dirs.map(dir => Target(dir.listFiles, toCompilerDirFromDir(dir, sourceDir, outDir))) // Create a CompilationTest and let the user decide whether to execute a pos or a neg test - new CompilationTest(dirsToCompile, f, flags) + new CompilationTest(targets, f, flags) } def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { @@ -413,11 +507,11 @@ trait ParallelTesting { val (_, files) = compilationTargets(sourceDir) + val targets = files.map { file => + Target(Array(file), toCompilerDirFromFile(file, sourceDir, outDir)) + } + // Create a CompilationTest and let the user decide whether to execute a pos or a neg test - new CompilationTest( - files.map(toCompilerDirFromFile(_, sourceDir, outDir)), - f, - flags - ) + new CompilationTest(targets, f, flags) } } From 620daf888042d63bb118fb0907e9e11e6a2a5b00 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 16 Mar 2017 16:35:13 +0100 Subject: [PATCH 13/39] Add all tests from `tests.scala` as is to new infra --- .../dotty/tools/dotc/CompilationTests.scala | 372 +++++++++++++++++- 1 file changed, 357 insertions(+), 15 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index c5c710ab1183..e3b8f6234b2d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -6,52 +6,392 @@ import org.junit.Test import java.io.{ File => JFile } class CompilationTests extends ParallelTesting { - import CompilationTests.{ defaultOutputDir, defaultOptions, picklingOptions } + import CompilationTests._ // Positive tests ------------------------------------------------------------ - @Test def compilePos = + @Test def compilePos: Unit = compileFilesInDir("../tests/pos", defaultOptions).pos + @Test def compilePosScala2: Unit = + compileFilesInDir("../tests/pos-scala2", scala2Mode).pos + + @Test def nullarifyPos: Unit = + compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")).pos + + @Test def rewrites: Unit = + compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")).pos + + @Test def t8146aPos: Unit = + compileFile("../tests/pos-special/t8146a.scala", allowDeepSubtypes).pos + + @Test def t5545Pos: Unit = + compileFilesInDir("../tests/pos-special/spec-t5545", defaultOptions).pos + + @Test def utf8encodedPos: Unit = + compileFile("../tests/pos-special/utf8encoded.scala", explicitUTF8).pos + + @Test def utf16encodedPos: Unit = + compileFile("../tests/pos-special/utf16encoded.scala", explicitUTF16).pos + + @Test def compileStdLibPos: Unit = + compileList(StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")).pos + + @Test def compileMixedPos: Unit = compileList( + List( + "../tests/pos/B.scala", + "../scala-scala/src/library/scala/collection/immutable/Seq.scala", + "../scala-scala/src/library/scala/collection/parallel/ParSeq.scala", + "../scala-scala/src/library/scala/package.scala", + "../scala-scala/src/library/scala/collection/GenSeqLike.scala", + "../scala-scala/src/library/scala/collection/SeqLike.scala", + "../scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala" + ), + defaultOptions + ).pos + + @Test def compileIndexedSeqPos: Unit = + compileFile("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala", defaultOptions).pos + + @Test def compileAstPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions).pos + + @Test def compileConfigPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/config", defaultOptions).pos + + @Test def compileCorePos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core", allowDeepSubtypes).pos + + @Test def compileCoreNoCheckPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath).pos + + @Test def compileTransformPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/transform", allowDeepSubtypes).pos + + @Test def compileParsingPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/parsing", defaultOptions).pos + + @Test def compilePrintingPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/printing", defaultOptions).pos + + @Test def compileReportingPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/reporting", defaultOptions).pos + + @Test def compileTyperPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions).pos + + @Test def compileUtilPos: Unit = + compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions).pos + + @Test def compileIoPos: Unit = + compileDir("../compiler/src/dotty/tools/io", defaultOptions).pos + + @Test def labelsPos: Unit = + compileFile("../tests/pos/Labels.scala", defaultOptions).times(2).pos + + @Test def testNonCyclic: Unit = compileList( + List( + "../compiler/src/dotty/tools/dotc/CompilationUnit.scala", + "../compiler/src/dotty/tools/dotc/core/Types.scala", + "../compiler/src/dotty/tools/dotc/ast/Trees.scala" + ), + defaultOptions.and("-Xprompt") + ).times(2).pos + + @Test def issue34Pos: Unit = compileList( + List( + "../compiler/src/dotty/tools/dotc/config/Properties.scala", + "../compiler/src/dotty/tools/dotc/config/PathResolver.scala" + ), + defaultOptions.and("-Xprompt") + ).times(2).pos + + @Test def javaInteropPos: Unit = + compileFilesInDir("../tests/pos-java-interop", defaultOptions).times(2).pos + + // New tests ----------------------------------------------------------------- + + @Test def compileNew: Unit = + compileFilesInDir("../tests/new", defaultOptions).pos + // Negative tests ------------------------------------------------------------ - @Test def compileNeg = + @Test def compileNeg: Unit = compileShallowFilesInDir("../tests/neg", defaultOptions).neg + @Test def typedIdentsNeg: Unit = + compileDir("../tests/neg/typedIdents", defaultOptions).neg + + @Test def typersNeg: Unit = + compileFile("../tests/neg/customArgs/typers.scala", allowDoubleBindings).neg + + @Test def overrideClassNeg: Unit = + compileFile("../tests/neg/customArgs/overrideClass.scala", scala2Mode).neg + + @Test def autoTuplingNeg: Unit = + compileFile("../tests/neg/customArgs/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")).neg + + @Test def i1050Neg: Unit = + compileFile("../tests/neg/customArgs/i1050.scala", defaultOptions.and("-strict")).neg + + @Test def i1240Neg: Unit = + compileFile("../tests/neg/customArgs/i1240.scala", allowDoubleBindings).neg + + @Test def i2002Neg: Unit = + compileFile("../tests/neg/customArgs/i2002.scala", allowDoubleBindings).neg + + @Test def nopredefNeg: Unit = + compileFile("../tests/neg/customArgs/nopredef.scala", defaultOptions.and("-Yno-predef")).neg + + @Test def noimportsNeg: Unit = + compileFile("../tests/neg/customArgs/noimports.scala", defaultOptions.and("-Yno-imports")).neg + + @Test def noimports2Neg: Unit = + compileFile("../tests/neg/customArgs/noimports2.scala", defaultOptions.and("-Yno-imports")).neg + + @Test def t1672bTailcallNeg: Unit = + compileFile("../tests/neg/tailcall/t1672b.scala", defaultOptions).neg + + @Test def t3275TailcallNeg: Unit = + compileFile("../tests/neg/tailcall/t3275.scala", defaultOptions).neg + + @Test def t6574TailcallNeg: Unit = + compileFile("../tests/neg/tailcall/t6574.scala", defaultOptions).neg + + @Test def tailrecTailcallNeg: Unit = + compileFile("../tests/neg/tailcall/tailrec.scala", defaultOptions).neg + + @Test def tailrec2TailcallNeg: Unit = + compileFile("../tests/neg/tailcall/tailrec-2.scala", defaultOptions).neg + + @Test def tailrec3TailcallNeg: Unit = + compileFile("../tests/neg/tailcall/tailrec-3.scala", defaultOptions).neg + // Run tests ----------------------------------------------------------------- - @Test def runArraycopy = + @Test def runAll: Unit = + compileFilesInDir("../tests/run", defaultOptions).run + + @Test def runArraycopy: Unit = compileFile("../tests/run/arraycopy.scala", defaultOptions).run - @Test def runAll = - compileFilesInDir("../tests/run", defaultOptions).run + // Benchmark Tests ----------------------------------------------------------- + + @Test def t2168Pos: Unit = + compileFile("../tests/pos/t2168.scala", defaultOptions).times(2).pos + + @Test def erasurePos: Unit = + compileFile("../tests/pos/erasure.scala", defaultOptions).times(2).pos + + @Test def coderPos: Unit = + compileFile("../tests/pos/Coder.scala", defaultOptions).times(2).pos + + @Test def blockescapesPos: Unit = + compileFile("../tests/pos/blockescapes.scala", defaultOptions).times(2).pos + + @Test def collectionsPos: Unit = + compileFile("../tests/pos/collections.scala", defaultOptions).times(2).pos + + @Test def functions1Pos: Unit = + compileFile("../tests/pos/functions1.scala", defaultOptions).times(2).pos + + @Test def implicits1Pos: Unit = + compileFile("../tests/pos/implicits1.scala", defaultOptions).times(2).pos + + @Test def inferredPos: Unit = + compileFile("../tests/pos/inferred.scala", defaultOptions).times(2).pos + + @Test def patternsPos: Unit = + compileFile("../tests/pos/Patterns.scala", defaultOptions).times(2).pos + + @Test def selftypesPos: Unit = + compileFile("../tests/pos/selftypes.scala", defaultOptions).times(2).pos + + @Test def varargsPos: Unit = + compileFile("../tests/pos/varargs.scala", defaultOptions).times(2).pos + + @Test def varargPatternsPos: Unit = + compileFile("../tests/pos/vararg-pattern.scala", defaultOptions).times(2).pos + + @Test def opassignPos: Unit = + compileFile("../tests/pos/opassign.scala", defaultOptions).times(2).pos + + @Test def typedapplyPos: Unit = + compileFile("../tests/pos/typedapply.scala", defaultOptions).times(2).pos + + @Test def nameddefaultsPos: Unit = + compileFile("../tests/pos/nameddefaults.scala", defaultOptions).times(2).pos + + @Test def desugarPos: Unit = + compileFile("../tests/pos/desugar.scala", defaultOptions).times(2).pos + + @Test def sigsPos: Unit = + compileFile("../tests/pos/sigs.scala", defaultOptions).times(2).pos + + @Test def typersPos: Unit = + compileFile("../tests/pos/typers.scala", defaultOptions).times(2).pos + + @Test def typedIdentsPos: Unit = + compileDir("../tests/pos/typedIdents", defaultOptions).times(2).pos + + @Test def assignmentsPos: Unit = + compileFile("../tests/pos/assignments.scala", defaultOptions).times(2).pos + + @Test def packageobjectPos: Unit = + compileFile("../tests/pos/packageobject.scala", defaultOptions).times(2).pos + + @Test def overloadedPos: Unit = + compileFile("../tests/pos/overloaded.scala", defaultOptions).times(2).pos + + @Test def overridesPos: Unit = + compileFile("../tests/pos/overrides.scala", defaultOptions).times(2).pos + + @Test def javaOverridePos: Unit = + compileDir("../tests/pos/java-override", defaultOptions).times(2).pos + + @Test def templateParentsPos: Unit = + compileFile("../tests/pos/templateParents.scala", defaultOptions).times(2).pos + + @Test def overloadedAccessPos: Unit = + compileFile("../tests/pos/overloadedAccess.scala", defaultOptions).times(2).pos + + @Test def approximateUnionPos: Unit = + compileFile("../tests/pos/approximateUnion.scala", defaultOptions).times(2).pos + + @Test def tailcallPos: Unit = + compileFilesInDir("../tests/pos/tailcall", defaultOptions).times(2).pos + + @Test def valueclassesPos: Unit = + compileShallowFilesInDir("../tests/pos/pos_valueclasses", defaultOptions).times(2).pos + + @Test def subtypingPos: Unit = + compileFile("../tests/pos/subtyping.scala", defaultOptions).times(2).pos + + @Test def packageObjPos: Unit = + compileFile("../tests/pos/i0239.scala", defaultOptions).times(2).pos + + @Test def anonClassSubtypingPos: Unit = + compileFile("../tests/pos/anonClassSubtyping.scala", defaultOptions).times(2).pos + + @Test def extmethodsPos: Unit = + compileFile("../tests/pos/extmethods.scala", defaultOptions).times(2).pos + + @Test def companionsPos: Unit = + compileFile("../tests/pos/companions.scala", defaultOptions).times(2).pos // Pickling Tests ------------------------------------------------------------ - @Test def testPickling = + @Test def testPickling: Unit = compileFilesInDir("../tests/pickling", picklingOptions).pos - @Test def testPicklingAst = - compileFilesInDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions).pos + @Test def testPicklingAst: Unit = + compileDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions).pos - @Test def testPicklingInf = + @Test def testPicklingInf: Unit = compileFile("../tests/pos/pickleinf.scala", picklingOptions).pos + + @Test def tastyNew: Unit = + compileFilesInDir("../tests/new", picklingOptions).pos + + @Test def tastyRuntime: Unit = + compileDir("../library/src/dotty/runtime", picklingOptions).pos + + @Test def tastyTools: Unit = + compileDir("../compiler/src/dotty/tools", picklingOptions).pos + + @Test def tastyBackendJvm: Unit = + compileDir("../compiler/src/dotty/tools/backend/jvm", picklingOptions).pos + + @Test def tastyDotc: Unit = + compileDir("../compiler/src/dotty/tools/dotc", picklingOptions).pos + + @Test def tastyDotcAst: Unit = + compileDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions).pos + + @Test def tastyDotcConfig: Unit = + compileDir("../compiler/src/dotty/tools/dotc/config", picklingOptions).pos + + @Test def tastyCore: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core", picklingOptions).pos + + @Test def tastyClassfile: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core/classfile", picklingOptions).pos + + @Test def dotcTasty: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core/tasty", picklingOptions).pos + + @Test def tastyUnpickleScala2: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core/unpickleScala2", picklingOptions).pos + + @Test def tastyParsing: Unit = + compileDir("../compiler/src/dotty/tools/dotc/parsing", picklingOptions).pos + + @Test def tastyPrinting: Unit = + compileDir("../compiler/src/dotty/tools/dotc/printing", picklingOptions).pos + + @Test def tastyRepl: Unit = + compileDir("../compiler/src/dotty/tools/dotc/repl", picklingOptions).pos + + @Test def tastyRewrite: Unit = + compileDir("../compiler/src/dotty/tools/dotc/rewrite", picklingOptions).pos + + @Test def tastyTransform: Unit = + compileDir("../compiler/src/dotty/tools/dotc/transform", picklingOptions).pos + + @Test def tastyTyper: Unit = + compileDir("../compiler/src/dotty/tools/dotc/typer", picklingOptions).pos + + @Test def tastyUtil: Unit = + compileDir("../compiler/src/dotty/tools/dotc/util", picklingOptions).pos + + @Test def tastyIo: Unit = + compileDir("../compiler/src/dotty/tools/io", picklingOptions).pos + + @Test def tastyTests: Unit = + compileDir("../tests/tasty", picklingOptions).pos + + @Test def tastyBootstrap: Unit = { + def dotty1 = + compileDir("../compiler/src/dotty", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) + def lib = + compileDir("../library/src", defaultOptions) + def dotty2 = + compileDir("../compiler/src/dotty", defaultOptions.and("-priorityclasspath", defaultOutputDir)) + + List(dotty1, lib, dotty2).map(_.keepOutput.pos).foreach(_.delete()) + } + + @Test def dotty = { + def bootedLib = + compileDir("../library/src", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) + def dottyDependsOnBootedLib = + compileDir("../compiler/src/dotty", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) + + List(bootedLib, dottyDependsOnBootedLib).map(_.keepOutput.pos).foreach(_.delete()) + } } object CompilationTests { implicit val defaultOutputDir: String = "../out/" - private val noCheckOptions = Array( - "-pagewidth", "80" + 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" ) - private val checkOptions = Array( + val checkOptions = Array( "-Yno-deep-subtypes", "-Yno-double-bindings", "-Yforce-sbt-phases" ) - private val classPath = { + val classPath = { val paths = Jars.dottyTestDeps map { p => val file = new JFile(p) assert( @@ -83,11 +423,13 @@ object CompilationTests { 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") } From 101d124acda3cc8edf7ea3c55db8b80fb1e1cd05 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 16 Mar 2017 16:37:43 +0100 Subject: [PATCH 14/39] Move REPL diff tests out of `tests.scala` --- compiler/test/dotc/tests.scala | 2 -- .../test/dotty/tools/dotc/CompilerTest.scala | 17 -------------- .../test/dotty/tools/dotc/repl/TestREPL.scala | 23 +++++++++++++++++++ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala index 3ebf7f2c4645..ce8dff056aad 100644 --- a/compiler/test/dotc/tests.scala +++ b/compiler/test/dotc/tests.scala @@ -85,7 +85,6 @@ class tests extends CompilerTest { val negDir = testsDir + "neg/" val runDir = testsDir + "run/" val newDir = testsDir + "new/" - val replDir = testsDir + "repl/" val javaDir = testsDir + "pos-java-interop/" val sourceDir = "./src/" @@ -173,7 +172,6 @@ class tests extends CompilerTest { @Test def pos_utf16 = compileFile(posSpecialDir, "utf16encoded", explicitUTF16) @Test def new_all = compileFiles(newDir, twice) - @Test def repl_all = replFiles(replDir) @Test def neg_all = compileFiles(negDir, verbose = true, compileSubDirs = false) @Test def neg_typedIdents() = compileDir(negDir, "typedIdents") diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala index db12994f4ba4..f35f9f919d15 100644 --- a/compiler/test/dotty/tools/dotc/CompilerTest.scala +++ b/compiler/test/dotty/tools/dotc/CompilerTest.scala @@ -237,23 +237,6 @@ abstract class CompilerTest { } } - def replFile(prefix: String, fileName: String): Unit = { - val path = s"$prefix$fileName" - val f = new PlainFile(path) - val repl = new TestREPL(new String(f.toCharArray)) - repl.process(Array[String]()) - repl.check() - } - - def replFiles(path: String): Unit = { - val dir = Directory(path) - val fileNames = dir.files.toArray.map(_.jfile.getName).filter(_ endsWith ".check") - for (name <- fileNames) { - log(s"testing $path$name") - replFile(path, name) - } - } - // ========== HELPERS ============= private def expectedErrors(filePaths: List[String]): List[ErrorsInFile] = if (filePaths.exists(isNegTest(_))) filePaths.map(getErrors(_)) else Nil diff --git a/compiler/test/dotty/tools/dotc/repl/TestREPL.scala b/compiler/test/dotty/tools/dotc/repl/TestREPL.scala index a38abcbab8c2..131a88ab19ef 100644 --- a/compiler/test/dotty/tools/dotc/repl/TestREPL.scala +++ b/compiler/test/dotty/tools/dotc/repl/TestREPL.scala @@ -5,6 +5,8 @@ package repl import core.Contexts.Context import collection.mutable import java.io.StringWriter +import dotty.tools.io.{ PlainFile, Directory } +import org.junit.Test /** A subclass of REPL used for testing. * It takes a transcript of a REPL session in `script`. The transcript @@ -62,3 +64,24 @@ class TestREPL(script: String) extends REPL { } } } + +class REPLTests { + def replFile(prefix: String, fileName: String): Unit = { + val path = s"$prefix$fileName" + val f = new PlainFile(path) + val repl = new TestREPL(new String(f.toCharArray)) + repl.process(Array[String]()) + repl.check() + } + + def replFiles(path: String): Unit = { + val dir = Directory(path) + val fileNames = dir.files.toArray.map(_.jfile.getName).filter(_ endsWith ".check") + for (name <- fileNames) { + println(s"testing $path$name") + replFile(path, name) + } + } + + @Test def replAll = replFiles("../tests/repl/") +} From c0a76af4f3434c0e6527d56e636b446bfd5598bb Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 16 Mar 2017 16:38:37 +0100 Subject: [PATCH 15/39] Move whitelist test out of `tests.scala` --- compiler/test/dotc/tests.scala | 12 ------------ compiler/test/dotty/tools/StdLibSources.scala | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala index ce8dff056aad..1c80767ee255 100644 --- a/compiler/test/dotc/tests.scala +++ b/compiler/test/dotc/tests.scala @@ -203,18 +203,6 @@ class tests extends CompilerTest { private val stdlibFiles: List[String] = StdLibSources.whitelisted - @Test def checkWBLists = { - val stdlibFilesBlackListed = StdLibSources.blacklisted - - val duplicates = stdlibFilesBlackListed.groupBy(x => x).filter(_._2.size > 1).filter(_._2.size > 1) - val msg = duplicates.map(x => s"'${x._1}' appears ${x._2.size} times").mkString(s"Duplicate entries in ${StdLibSources.blacklistFile}:\n", "\n", "\n") - assertTrue(msg, duplicates.isEmpty) - - val filesNotInStdLib = stdlibFilesBlackListed.toSet -- StdLibSources.all - val msg2 = filesNotInStdLib.map(x => s"'$x'").mkString(s"Entries in ${StdLibSources.blacklistFile} where not found:\n", "\n", "\n") - assertTrue(msg2, filesNotInStdLib.isEmpty) - } - @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: "-Yno-inline" :: scala2mode) @Test def compileMixed = compileLine( """../tests/pos/B.scala diff --git a/compiler/test/dotty/tools/StdLibSources.scala b/compiler/test/dotty/tools/StdLibSources.scala index e3da36f22d5b..24ca9b80beb2 100644 --- a/compiler/test/dotty/tools/StdLibSources.scala +++ b/compiler/test/dotty/tools/StdLibSources.scala @@ -57,3 +57,20 @@ object StdLibSources { .toList } + +class StdLibSources { + import org.junit.Test + import org.junit.Assert._ + + @Test def checkWBLists = { + val stdlibFilesBlackListed = StdLibSources.blacklisted + + val duplicates = stdlibFilesBlackListed.groupBy(x => x).filter(_._2.size > 1).filter(_._2.size > 1) + val msg = duplicates.map(x => s"'${x._1}' appears ${x._2.size} times").mkString(s"Duplicate entries in ${StdLibSources.blacklistFile}:\n", "\n", "\n") + assertTrue(msg, duplicates.isEmpty) + + val filesNotInStdLib = stdlibFilesBlackListed.toSet -- StdLibSources.all + val msg2 = filesNotInStdLib.map(x => s"'$x'").mkString(s"Entries in ${StdLibSources.blacklistFile} where not found:\n", "\n", "\n") + assertTrue(msg2, filesNotInStdLib.isEmpty) + } +} From 404ce89a1b9408e4c3ceb2f90eddbdb776e011c6 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 16 Mar 2017 16:39:39 +0100 Subject: [PATCH 16/39] Remove `tests.scala` --- compiler/test/dotc/tests.scala | 374 ------------------ .../dotty/tools/dotc/CompilationTests.scala | 12 + .../transform/PatmatExhaustivityTest.scala | 10 +- 3 files changed, 18 insertions(+), 378 deletions(-) delete mode 100644 compiler/test/dotc/tests.scala diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala deleted file mode 100644 index 1c80767ee255..000000000000 --- a/compiler/test/dotc/tests.scala +++ /dev/null @@ -1,374 +0,0 @@ -package dotc - -import dotty.Jars -import dotty.tools.dotc.CompilerTest -import dotty.tools.StdLibSources -import org.junit.{Before, Test} -import org.junit.Assert._ - -import java.io.{ File => JFile } -import scala.reflect.io.Directory -import scala.io.Source - -// tests that match regex '(pos|dotc|run|java|compileStdLib)\.*' would be executed as benchmarks. -class tests extends CompilerTest { - - def isRunByJenkins: Boolean = sys.props.isDefinedAt("dotty.jenkins.build") - - val defaultOutputDir = "../out/" - - val noCheckOptions = List( -// "-verbose", -// "-Ylog:frontend", -// "-Xprompt", -// "-explaintypes", -// "-Yshow-suppressed-errors", - "-pagewidth", "120", - "-d", defaultOutputDir - ) - - val checkOptions = List( - "-Yno-deep-subtypes", - "-Yno-double-bindings", - "-Yforce-sbt-phases", - "-color:never" - ) - - 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 (":") - - List("-classpath", paths) - } - - implicit val defaultOptions: List[String] = noCheckOptions ++ { - if (isRunByJenkins) List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") // should be Ycheck:all, but #725 - else List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") - } ++ checkOptions ++ classPath - - val testPickling = List("-Xprint-types", "-Ytest-pickler", "-Ystop-after:pickler", "-Yprintpos") - - val twice = List("#runs", "2") - val staleSymbolError: List[String] = List() - - val allowDeepSubtypes = defaultOptions diff List("-Yno-deep-subtypes") - val allowDoubleBindings = defaultOptions diff List("-Yno-double-bindings") - val scala2mode = List("-language:Scala2") - - val explicitUTF8 = List("-encoding", "UTF8") - val explicitUTF16 = List("-encoding", "UTF16") - - val testsDir = "../tests/" - val posDir = testsDir + "pos/" - val posSpecialDir = testsDir + "pos-special/" - val posScala2Dir = testsDir + "pos-scala2/" - val negDir = testsDir + "neg/" - val runDir = testsDir + "run/" - val newDir = testsDir + "new/" - val javaDir = testsDir + "pos-java-interop/" - - val sourceDir = "./src/" - val dottyDir = sourceDir + "dotty/" - val toolsDir = dottyDir + "tools/" - val backendDir = toolsDir + "backend/" - val dotcDir = toolsDir + "dotc/" - val coreDir = dotcDir + "core/" - val parsingDir = dotcDir + "parsing/" - val dottyReplDir = dotcDir + "repl/" - val typerDir = dotcDir + "typer/" - val libDir = "../library/src/" - - def dottyBootedLib = compileDir(libDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ::: defaultOptions)(allowDeepSubtypes) // note the -deep argument - def dottyDependsOnBootedLib = compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ::: defaultOptions)(allowDeepSubtypes) // note the -deep argument - - @Before def cleanup(): Unit = { - // remove class files from stdlib and tests compilation - Directory(defaultOutputDir + "scala").deleteRecursively() - Directory(defaultOutputDir + "java").deleteRecursively() - } - - @Test def pickle_pickleOK = compileFiles(testsDir + "pickling/", testPickling) -// This directory doesn't exist anymore -// @Test def pickle_pickling = compileDir(coreDir, "pickling", testPickling) - @Test def pickle_ast = compileDir(dotcDir, "ast", testPickling) - @Test def pickle_inf = compileFile(posDir, "pickleinf", testPickling) - - //@Test def pickle_core = compileDir(dotcDir, "core", testPickling, xerrors = 2) // two spurious comparison errors in Types and TypeOps - - @Test def pos_arraycopy = - compileFile(runDir, "arraycopy", List("-Ylog-classpath")) - @Test def pos_t2168_pat = compileFile(posDir, "t2168", twice) - @Test def pos_erasure = compileFile(posDir, "erasure", twice) - @Test def pos_Coder() = compileFile(posDir, "Coder", twice) - @Test def pos_blockescapes() = compileFile(posDir, "blockescapes", twice) - @Test def pos_collections() = compileFile(posDir, "collections", twice) - @Test def pos_functions1() = compileFile(posDir, "functions1", twice) - @Test def pos_implicits1() = compileFile(posDir, "implicits1", twice) - @Test def pos_inferred() = compileFile(posDir, "inferred", twice) - @Test def pos_Patterns() = compileFile(posDir, "Patterns", twice) - @Test def pos_selftypes() = compileFile(posDir, "selftypes", twice) - @Test def pos_varargs() = compileFile(posDir, "varargs", twice) - @Test def pos_vararg_patterns() = compileFile(posDir, "vararg-pattern", twice) - @Test def pos_opassign() = compileFile(posDir, "opassign", twice) - @Test def pos_typedapply() = compileFile(posDir, "typedapply", twice) - @Test def pos_nameddefaults() = compileFile(posDir, "nameddefaults", twice) - @Test def pos_desugar() = compileFile(posDir, "desugar", twice) - @Test def pos_sigs() = compileFile(posDir, "sigs", twice) - @Test def pos_typers() = compileFile(posDir, "typers", twice) - @Test def pos_typedIdents() = compileDir(posDir, "typedIdents", twice) - @Test def pos_assignments() = compileFile(posDir, "assignments", twice) - @Test def pos_packageobject() = compileFile(posDir, "packageobject", twice) - @Test def pos_overloaded() = compileFile(posDir, "overloaded", twice) - @Test def pos_overrides() = compileFile(posDir, "overrides", twice) - @Test def pos_javaOverride() = compileDir(posDir, "java-override", twice) - @Test def pos_templateParents() = compileFile(posDir, "templateParents", twice) - @Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", twice) - @Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", twice) - @Test def pos_tailcall = compileDir(posDir, "tailcall", twice) - @Test def pos_valueclasses = compileFiles(posDir + "pos_valueclasses/", twice) - @Test def pos_nullarify = compileFile(posDir, "nullarify", args = "-Ycheck:nullarify" :: Nil) - @Test def pos_subtyping = compileFile(posDir, "subtyping", twice) - @Test def pos_packageObj = compileFile(posDir, "i0239", twice) - @Test def pos_anonClassSubtyping = compileFile(posDir, "anonClassSubtyping", twice) - @Test def pos_extmethods = compileFile(posDir, "extmethods", twice) - @Test def pos_companions = compileFile(posDir, "companions", twice) - @Test def posVarargsT1625 = compileFiles(posDir + "varargsInMethodsT1625/") - - @Test def pos_all = compileFiles(posDir) // twice omitted to make tests run faster - - @Test def pos_scala2_all = compileFiles(posScala2Dir, scala2mode) - - @Test def rewrites = compileFile(posScala2Dir, "rewrites", "-rewrite" :: scala2mode) - - @Test def pos_t8146a = compileFile(posSpecialDir, "t8146a")(allowDeepSubtypes) - - @Test def pos_t5545 = { - // compile by hand in two batches, since junit lacks the infrastructure to - // compile files in multiple batches according to _1, _2, ... suffixes. - compileFile(posSpecialDir, "spec-t5545/S_1") - compileFile(posSpecialDir, "spec-t5545/S_2") - } - @Test def pos_utf8 = compileFile(posSpecialDir, "utf8encoded", explicitUTF8) - @Test def pos_utf16 = compileFile(posSpecialDir, "utf16encoded", explicitUTF16) - - @Test def new_all = compileFiles(newDir, twice) - - @Test def neg_all = compileFiles(negDir, verbose = true, compileSubDirs = false) - @Test def neg_typedIdents() = compileDir(negDir, "typedIdents") - - @Test def negVarargsT1625 = compileFiles(negDir + "varargsInMethodsT1625/") - - val negCustomArgs = negDir + "customArgs/" - - @Test def neg_typers() = compileFile(negCustomArgs, "typers")(allowDoubleBindings) - @Test def neg_overrideClass = compileFile(negCustomArgs, "overrideClass", scala2mode) - @Test def neg_autoTupling = compileFile(negCustomArgs, "autoTuplingTest", args = "-language:noAutoTupling" :: Nil) - @Test def neg_i1050 = compileFile(negCustomArgs, "i1050", List("-strict")) - @Test def neg_i1240 = compileFile(negCustomArgs, "i1240")(allowDoubleBindings) - @Test def neg_i2002 = compileFile(negCustomArgs, "i2002")(allowDoubleBindings) - - val negTailcallDir = negDir + "tailcall/" - @Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b") - @Test def neg_tailcall_t3275 = compileFile(negTailcallDir, "t3275") - @Test def neg_tailcall_t6574 = compileFile(negTailcallDir, "t6574") - @Test def neg_tailcall = compileFile(negTailcallDir, "tailrec") - @Test def neg_tailcall2 = compileFile(negTailcallDir, "tailrec-2") - @Test def neg_tailcall3 = compileFile(negTailcallDir, "tailrec-3") - - @Test def neg_nopredef = compileFile(negCustomArgs, "nopredef", List("-Yno-predef")) - @Test def neg_noimports = compileFile(negCustomArgs, "noimports", List("-Yno-imports")) - @Test def neg_noimpots2 = compileFile(negCustomArgs, "noimports2", List("-Yno-imports")) - - @Test def run_all = runFiles(runDir) - - private val stdlibFiles: List[String] = StdLibSources.whitelisted - - @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: "-Yno-inline" :: scala2mode) - @Test def compileMixed = compileLine( - """../tests/pos/B.scala - |../scala-scala/src/library/scala/collection/immutable/Seq.scala - |../scala-scala/src/library/scala/collection/parallel/ParSeq.scala - |../scala-scala/src/library/scala/package.scala - |../scala-scala/src/library/scala/collection/GenSeqLike.scala - |../scala-scala/src/library/scala/collection/SeqLike.scala - |../scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala""".stripMargin) - @Test def compileIndexedSeq = compileLine("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala") - @Test def compileParSetLike = compileLine("../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala") - @Test def compileParSetSubset = compileLine( - """../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala - |../scala-scala/src/library/scala/collection/parallel/mutable/ParSet.scala - |../scala-scala/src/library/scala/collection/mutable/SetLike.scala""".stripMargin)(scala2mode ++ defaultOptions) - - @Test def dotty = { - dottyBootedLib - dottyDependsOnBootedLib - } - - @Test def dotc_ast = compileDir(dotcDir, "ast") - @Test def dotc_config = compileDir(dotcDir, "config") - @Test def dotc_core = compileDir(dotcDir, "core")(allowDeepSubtypes)// twice omitted to make tests run faster - @Test def dotc_core_nocheck = compileDir(dotcDir, "core")(noCheckOptions ++ classPath) - -// This directory doesn't exist anymore -// @Test def dotc_core_pickling = compileDir(coreDir, "pickling")(allowDeepSubtypes)// twice omitted to make tests run faster - - @Test def dotc_transform = compileDir(dotcDir, "transform")(allowDeepSubtypes)// twice omitted to make tests run faster - - @Test def dotc_parsing = compileDir(dotcDir, "parsing") // twice omitted to make tests run faster - - @Test def dotc_printing = compileDir(dotcDir, "printing") // twice omitted to make tests run faster - - @Test def dotc_reporting = compileDir(dotcDir, "reporting") // twice omitted to make tests run faster - - @Test def dotc_typer = compileDir(dotcDir, "typer")// twice omitted to make tests run faster - // error: error while loading Checking$$anon$2$, - // class file 'target/scala-2.11/dotty_2.11-0.1.1-SNAPSHOT.jar(dotty/tools/dotc/typer/Checking$$anon$2.class)' - // has location not matching its contents: contains class $anon - - @Test def dotc_util = compileDir(dotcDir, "util") // twice omitted to make tests run faster - - @Test def tools_io = compileDir(toolsDir, "io") // inner class has symbol - - @Test def helloWorld = compileFile(posDir, "HelloWorld") - @Test def labels = compileFile(posDir, "Labels", twice) - //@Test def tools = compileDir(dottyDir, "tools", "-deep" :: Nil)(allowDeepSubtypes) - - @Test def testNonCyclic = compileList("testNonCyclic", List( - dotcDir + "CompilationUnit.scala", - coreDir + "Types.scala", - dotcDir + "ast/Trees.scala" - ), List("-Xprompt") ++ staleSymbolError ++ twice) - - @Test def testIssue_34 = compileList("testIssue_34", List( - dotcDir + "config/Properties.scala", - dotcDir + "config/PathResolver.scala" - ), List(/* "-Ylog:frontend", */ "-Xprompt") ++ staleSymbolError ++ twice) - - @Test def java_all = compileFiles(javaDir, twice) - //@Test def dotc_compilercommand = compileFile(dotcDir + "config/", "CompilerCommand") - - //TASTY tests - @Test def tasty_new_all = compileFiles(newDir, testPickling) - - @Test def tasty_dotty = compileDir(sourceDir, "dotty", testPickling) - - // Disabled because we get stale symbol errors on the SourceFile annotation, which is normal. - // @Test def tasty_annotation_internal = compileDir(s"${dottyDir}annotation/", "internal", testPickling) - - @Test def tasty_runtime = compileDir(s"${libDir}dotty/", "runtime", testPickling) - @Test def tasty_runtime_vc = compileDir(s"${libDir}dotty/runtime/", "vc", testPickling) - - @Test def tasty_tools = compileDir(dottyDir, "tools", testPickling) - - //TODO: issue with ./src/dotty/tools/backend/jvm/DottyBackendInterface.scala - @Test def tasty_backend_jvm = compileList("tasty_backend_jvm", List( - "CollectEntryPoints.scala", "GenBCode.scala", "LabelDefs.scala", - "scalaPrimitives.scala" - ) map (s"${backendDir}jvm/" + _), testPickling) - - //@Test def tasty_backend_sjs = compileDir(s"${backendDir}", "sjs", testPickling) - - @Test def tasty_dotc = compileDir(toolsDir, "dotc", testPickling) - @Test def tasty_dotc_ast = compileDir(dotcDir, "ast", testPickling) - @Test def tasty_dotc_config = compileDir(dotcDir, "config", testPickling) - - //TODO: issue with ./src/dotty/tools/dotc/core/Types.scala - @Test def tasty_core = compileList("tasty_core", List( - "Annotations.scala", "Constants.scala", "Constraint.scala", "ConstraintHandling.scala", - "ConstraintRunInfo.scala", "Contexts.scala", "Decorators.scala", "Definitions.scala", - "DenotTransformers.scala", "Denotations.scala", "Flags.scala", "Hashable.scala", - "NameOps.scala", "Names.scala", "OrderingConstraint.scala", "Periods.scala", - "Phases.scala", "Scopes.scala", "Signature.scala", "StdNames.scala", - "Substituters.scala", "SymDenotations.scala", "SymbolLoaders.scala", "Symbols.scala", - "TypeApplications.scala", "TypeComparer.scala", "TypeErasure.scala", "TypeOps.scala", - "TyperState.scala", "Uniques.scala" - ) map (coreDir + _), testPickling) - - @Test def tasty_classfile = compileDir(coreDir, "classfile", testPickling) - @Test def tasty_tasty = compileDir(coreDir, "tasty", testPickling) - @Test def tasty_unpickleScala2 = compileDir(coreDir, "unpickleScala2", testPickling) - - //TODO: issue with ./src/dotty/tools/dotc/parsing/Parsers.scala - @Test def tasty_dotc_parsing = compileList("tasty_dotc_parsing", List( - "CharArrayReader.scala", "JavaParsers.scala", "JavaScanners.scala", "JavaTokens.scala", - "MarkupParserCommon.scala", "MarkupParsers.scala", "package.scala" ,"Scanners.scala", - "ScriptParsers.scala", "SymbolicXMLBuilder.scala", "Tokens.scala", "Utility.scala" - ) map (parsingDir + _), testPickling) - - @Test def tasty_dotc_printing = compileDir(dotcDir, "printing", testPickling) - - @Test def tasty_dotc_repl = compileDir(dotcDir, "repl", testPickling) - - //@Test def tasty_dotc_reporting = compileDir(dotcDir, "reporting", testPickling) - @Test def tasty_dotc_rewrite = compileDir(dotcDir, "rewrite", testPickling) - - //TODO: issues with LazyVals.scala, PatternMatcher.scala - @Test def tasty_dotc_transform = compileList("tasty_dotc_transform", List( - "AugmentScala2Traits.scala", "CapturedVars.scala", "CheckReentrant.scala", "CheckStatic.scala", - "ClassOf.scala", "CollectEntryPoints.scala", "Constructors.scala", "CrossCastAnd.scala", - "CtxLazy.scala", "ElimByName.scala", "ElimErasedValueType.scala", "ElimRepeated.scala", - "ElimStaticThis.scala", "Erasure.scala", "ExpandPrivate.scala", "ExpandSAMs.scala", - "ExplicitOuter.scala", "ExtensionMethods.scala", "FirstTransform.scala", - "Flatten.scala", "FullParameterization.scala", "FunctionalInterfaces.scala", "GetClass.scala", - "Getters.scala", "InterceptedMethods.scala", "LambdaLift.scala", "LiftTry.scala", "LinkScala2ImplClasses.scala", - "MacroTransform.scala", "Memoize.scala", "Mixin.scala", "MixinOps.scala", "NonLocalReturns.scala", - "NormalizeFlags.scala", "OverridingPairs.scala", "ParamForwarding.scala", "Pickler.scala", "PostTyper.scala", - "ResolveSuper.scala", "RestoreScopes.scala", "SeqLiterals.scala", "Splitter.scala", "SuperAccessors.scala", - "SymUtils.scala", "SyntheticMethods.scala", "TailRec.scala", "TreeChecker.scala", "TreeExtractors.scala", - "TreeGen.scala", "TreeTransform.scala", "TypeTestsCasts.scala", "TypeUtils.scala", "ValueClasses.scala", - "VCElideAllocations.scala", "VCInlineMethods.scala" - ) map (s"${dotcDir}transform/" + _), testPickling) - - //TODO: issue with ./src/dotty/tools/dotc/typer/Namer.scala - @Test def tasty_typer = compileList("tasty_typer", List( - "Applications.scala", "Checking.scala", "ConstFold.scala", "ErrorReporting.scala", - "EtaExpansion.scala", "FrontEnd.scala", "Implicits.scala", "ImportInfo.scala", - "Inferencing.scala", "ProtoTypes.scala", "ReTyper.scala", "RefChecks.scala", - "TypeAssigner.scala", "Typer.scala", "VarianceChecker.scala", "Variances.scala" - ) map (typerDir + _), testPickling) - - @Test def tasty_dotc_util = compileDir(dotcDir, "util", testPickling) - @Test def tasty_tools_io = compileDir(toolsDir, "io", testPickling) - - @Test def tasty_bootstrap = { - val logging = if (false) List("-Ylog-classpath", "-verbose") else Nil - val opt = List("-priorityclasspath", defaultOutputDir) ++ logging - // first compile dotty - compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ++ logging)(allowDeepSubtypes) - - compileDir(libDir, "dotty", "-deep" :: opt) - compileDir(libDir, "scala", "-deep" :: opt) - compileDir(dottyDir, "tools", opt) - compileDir(toolsDir, "dotc", opt) - compileDir(dotcDir, "ast", opt) - compileDir(dotcDir, "config", opt) - compileDir(dotcDir, "parsing", opt) - compileDir(dotcDir, "printing", opt) - compileDir(dotcDir, "repl", opt) - compileDir(dotcDir, "reporting", opt) - compileDir(dotcDir, "rewrite", opt) - compileDir(dotcDir, "transform", opt) - compileDir(dotcDir, "typer", opt) - compileDir(dotcDir, "util", opt) - } -} diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index e3b8f6234b2d..28343056592d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -53,6 +53,18 @@ class CompilationTests extends ParallelTesting { @Test def compileIndexedSeqPos: Unit = compileFile("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala", defaultOptions).pos + @Test def compileParSetLikePos: Unit = + compileFile("../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", defaultOptions).pos + + @Test def compileParSetSubsetPos: Unit = compileList( + List( + "../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", + "../scala-scala/src/library/scala/collection/parallel/mutable/ParSet.scala", + "../scala-scala/src/library/scala/collection/mutable/SetLike.scala" + ), + scala2Mode + ).pos + @Test def compileAstPos: Unit = compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions).pos diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index d9a5d8a386b6..96ab241fd434 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -1,17 +1,19 @@ -package test.transform +package dotty +package tools +package dotc +package transform import java.io._ import scala.io.Source._ import scala.reflect.io.Directory import org.junit.Test -import dotty.tools.dotc.Main -import dotty.tools.dotc.reporting.TestReporter +import reporting.TestReporter class PatmatExhaustivityTest { val testsDir = "../tests/patmat" // stop-after: patmatexhaust-huge.scala crash compiler - val options = List("-color:never", "-Ystop-after:splitter", "-Ycheck-all-patmat") ++ (new dotc.tests).classPath + val options = List("-color:never", "-Ystop-after:splitter", "-Ycheck-all-patmat") ++ CompilationTests.classPath private def compileFile(file: File) = { val stringBuffer = new StringWriter() From f6eb90208177866af339540ee7658c0d11fc3882 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 17 Mar 2017 11:35:15 +0100 Subject: [PATCH 17/39] Kill partest --- .drone.yml | 2 - .drone.yml.sig | 2 +- compiler/test/dotc/comptest.scala | 29 +- compiler/test/dotty/partest/DPConfig.scala | 40 -- .../test/dotty/partest/DPConsoleRunner.scala | 411 ------------ .../test/dotty/partest/DPDirectCompiler.scala | 36 - compiler/test/dotty/tools/StdLibSources.scala | 6 +- .../test/dotty/tools/dotc/CompilerTest.scala | 613 ------------------ .../dotty/tools/dotc/ParallelTesting.scala | 11 +- project/Build.scala | 89 +-- 10 files changed, 34 insertions(+), 1205 deletions(-) delete mode 100644 compiler/test/dotty/partest/DPConfig.scala delete mode 100644 compiler/test/dotty/partest/DPConsoleRunner.scala delete mode 100644 compiler/test/dotty/partest/DPDirectCompiler.scala delete mode 100644 compiler/test/dotty/tools/dotc/CompilerTest.scala diff --git a/.drone.yml b/.drone.yml index 83309ef4ea47..6d49aace5344 100644 --- a/.drone.yml +++ b/.drone.yml @@ -36,5 +36,3 @@ matrix: TEST: - ;test;dotty-bin-tests/test - ;publishLocal;dotty-bootstrapped/test - - partest-only-no-bootstrap --show-diff --verbose - - partest-only --show-diff --verbose diff --git a/.drone.yml.sig b/.drone.yml.sig index 1093928f0526..e6c378c5ba40 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXg0MDk2bSAtSi1YWDpSZXNlcnZlZENvZGVDYWNoZVNpemU9NTEybSAtSi1YWDpNYXhNZXRhc3BhY2VTaXplPTEwMjRtIC1EZG90dHkuZHJvbmUubWVtPTQwOTZtICIke1RFU1R9IgogICAgd2hlbjoKICAgICAgYnJhbmNoOgogICAgICAgIGV4Y2x1ZGU6IGdoLXBhZ2VzCgogIGRvY3VtZW50YXRpb246CiAgICBpbWFnZTogbGFtcGVwZmwvZG90dHk6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBjb21tYW5kczoKICAgICAgLSAuL3Byb2plY3Qvc2NyaXB0cy9nZW5Eb2NzICIke1RFU1R9IiAkQk9UX1BBU1MKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRvdHR5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBzdGF0dXM6IGNoYW5nZWQKCm1hdHJpeDoKICBURVNUOgogICAgLSA7dGVzdDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7cHVibGlzaExvY2FsO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0CiAgICAtIHBhcnRlc3Qtb25seS1uby1ib290c3RyYXAgLS1zaG93LWRpZmYgLS12ZXJib3NlCiAgICAtIHBhcnRlc3Qtb25seSAtLXNob3ctZGlmZiAtLXZlcmJvc2UK.VRqZiSgeE6OumPlEvs4TWfxIHNOEVjR_ZmyBmapxZ-U \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXg0MDk2bSAtSi1YWDpSZXNlcnZlZENvZGVDYWNoZVNpemU9NTEybSAtSi1YWDpNYXhNZXRhc3BhY2VTaXplPTEwMjRtIC1EZG90dHkuZHJvbmUubWVtPTQwOTZtICIke1RFU1R9IgogICAgd2hlbjoKICAgICAgYnJhbmNoOgogICAgICAgIGV4Y2x1ZGU6IGdoLXBhZ2VzCgogIGRvY3VtZW50YXRpb246CiAgICBpbWFnZTogbGFtcGVwZmwvZG90dHk6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBjb21tYW5kczoKICAgICAgLSAuL3Byb2plY3Qvc2NyaXB0cy9nZW5Eb2NzICIke1RFU1R9IiAkQk9UX1BBU1MKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRvdHR5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBzdGF0dXM6IGNoYW5nZWQKCm1hdHJpeDoKICBURVNUOgogICAgLSA7dGVzdDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7cHVibGlzaExvY2FsO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0Cg.jh9DiIPPWc33AN8J2-GRV-PThSGGTFis5HmG9AgOTV8 \ No newline at end of file diff --git a/compiler/test/dotc/comptest.scala b/compiler/test/dotc/comptest.scala index 5ae1823e8cfa..b1a6fa266996 100644 --- a/compiler/test/dotc/comptest.scala +++ b/compiler/test/dotc/comptest.scala @@ -1,24 +1,25 @@ package dotc -import dotty.tools.dotc.CompilerTest +import dotty.tools.dotc.ParallelTesting -object comptest extends CompilerTest { +object comptest extends ParallelTesting { - override val generatePartestFiles = false - val defaultOutputDir: String = "" + implicit val defaultOutputDir: String = "." val posDir = "./tests/pos/" val negDir = "./tests/neg/" val dotcDir = "./src/dotty/" - def main(args: Array[String]) = - compileList("comptest", List( - dotcDir + "tools/dotc/CompilationUnit.scala", - dotcDir + "tools/dotc/core/Types.scala", - dotcDir + "tools/dotc/ast/Trees.scala"), List( - "#runs", "2", - "-Ylog:frontend", - "-Xprompt"))(Nil) - -// compileDir(dotcDir + "tools/dotc/", "printing", List("-Xprompt", "-Ylog:frontend", "#runs", "2", "-uniqid")) + def main(args: Array[String]): Unit = + compileList( + List( + dotcDir + "tools/dotc/CompilationUnit.scala", + dotcDir + "tools/dotc/core/Types.scala", + dotcDir + "tools/dotc/ast/Trees.scala" + ), + Array( + "-Ylog:frontend", + "-Xprompt" + ) + ) } diff --git a/compiler/test/dotty/partest/DPConfig.scala b/compiler/test/dotty/partest/DPConfig.scala deleted file mode 100644 index 5c493f465049..000000000000 --- a/compiler/test/dotty/partest/DPConfig.scala +++ /dev/null @@ -1,40 +0,0 @@ -package dotty.partest - -import scala.collection.JavaConversions._ -import scala.reflect.io.Path -import java.io.File - -import scala.tools.partest.PartestDefaults - - -/** Dotty Partest runs all tests in the provided testDirs located under - * testRoot. There can be several directories with pos resp. neg tests, as - * long as the prefix is pos/neg. - * - * Each testDir can also have a __defaultFlags.flags file, which provides - * compiler flags and is used unless there's a specific flags file (e.g. for - * test pos/A.scala, if there's a pos/A.flags file those flags are used, - * otherwise pos/__defaultFlags.flags are used if the file exists). - */ -object DPConfig { - /** Options used for _running_ the run tests. - * Note that this is different from the options used when _compiling_ tests, - * those are determined by the sbt configuration. - */ - val runJVMOpts = s"-Xms64M -Xmx1024M ${PartestDefaults.javaOpts}" - - val testRoot = (Path("..") / Path("tests") / Path("partest-generated")).toString - val genLog = Path(testRoot) / Path("gen.log") - - lazy val testDirs = { - val root = new File(testRoot) - val dirs = if (!root.exists) Array.empty[String] else root.listFiles.filter(_.isDirectory).map(_.getName) - if (dirs.isEmpty) - throw new Exception("Partest did not detect any generated sources") - dirs - } - - // Tests finish faster when running in parallel, but console output is - // out of order and sometimes the compiler crashes - val runTestsInParallel = true -} diff --git a/compiler/test/dotty/partest/DPConsoleRunner.scala b/compiler/test/dotty/partest/DPConsoleRunner.scala deleted file mode 100644 index 3362d7a59343..000000000000 --- a/compiler/test/dotty/partest/DPConsoleRunner.scala +++ /dev/null @@ -1,411 +0,0 @@ -/* NOTE: Adapted from ScalaJSPartest.scala in - * https://github.com/scala-js/scala-js/ - * TODO make partest configurable */ - -package dotty.partest - -import dotty.tools.FatalError -import scala.reflect.io.AbstractFile -import scala.tools.partest._ -import scala.tools.partest.nest._ -import TestState.{ Pass, Fail, Crash, Uninitialized, Updated } -import ClassPath.{ join, split } -import FileManager.{ compareFiles, compareContents, joinPaths, withTempFile } -import scala.util.matching.Regex -import tools.nsc.io.{ File => NSCFile } -import java.io.{ File, PrintStream, FileOutputStream, PrintWriter, FileWriter } -import java.net.URLClassLoader - -/** Runs dotty partest from the Console, discovering test sources in - * DPConfig.testRoot that have been generated automatically by - * DPPrepJUnitRunner. Use `sbt partest` to run. If additional jars are - * required by some run tests, add them to partestDeps in the sbt Build.scala. - */ -object DPConsoleRunner { - def main(args: Array[String]): Unit = { - // unfortunately sbt runTask passes args as single string - // extra jars for run tests are passed with -dottyJars ... - val jarFinder = """-dottyJars (\d*) (.*)""".r - val (jarList, otherArgs) = args.toList.partition(jarFinder.findFirstIn(_).isDefined) - val (extraJars, moreArgs) = jarList match { - case Nil => sys.error("Error: DPConsoleRunner needs \"-dottyJars *\".") - case jarFinder(nr, jarString) :: Nil => - val jars = jarString.split(" ").toList - val count = nr.toInt - if (jars.length < count) - sys.error("Error: DPConsoleRunner found wrong number of dottyJars: " + jars + ", expected: " + nr) - else (jars.take(count), jars.drop(count)) - case list => sys.error("Error: DPConsoleRunner found several -dottyJars options: " + list) - } - new DPConsoleRunner((otherArgs ::: moreArgs) mkString (" "), extraJars).runPartest - } -} - -// console runner has a suite runner which creates a test runner for each test -class DPConsoleRunner(args: String, extraJars: List[String]) extends ConsoleRunner(args) { - override val suiteRunner = new DPSuiteRunner ( - testSourcePath = optSourcePath getOrElse DPConfig.testRoot, - fileManager = new DottyFileManager(extraJars), - updateCheck = optUpdateCheck, - failed = optFailed, - consoleArgs = args) - - override def run = {} - def runPartest = super.run -} - -class DottyFileManager(extraJars: List[String]) extends FileManager(Nil) { - lazy val extraJarList = extraJars.map(NSCFile(_)) - override lazy val libraryUnderTest = Path(extraJars.find(_.contains("scala-library")).getOrElse("")) - override lazy val reflectUnderTest = Path(extraJars.find(_.contains("scala-reflect")).getOrElse("")) - override lazy val compilerUnderTest = Path(extraJars.find(_.contains("dotty")).getOrElse("")) -} - -class DPSuiteRunner(testSourcePath: String, // relative path, like "files", or "pending" - fileManager: DottyFileManager, - updateCheck: Boolean, - failed: Boolean, - consoleArgs: String, - javaCmdPath: String = PartestDefaults.javaCmd, - javacCmdPath: String = PartestDefaults.javacCmd, - scalacExtraArgs: Seq[String] = Seq.empty, - javaOpts: String = DPConfig.runJVMOpts) -extends SuiteRunner(testSourcePath, fileManager, updateCheck, failed, javaCmdPath, javacCmdPath, scalacExtraArgs, javaOpts) { - - if (!DPConfig.runTestsInParallel) - sys.props("partest.threads") = "1" - - sys.props("partest.root") = "." - - // override to provide Dotty banner - override def banner: String = { - s"""|Welcome to Partest for Dotty! Partest version: ${Properties.versionNumberString} - |Compiler under test: dotty.tools.dotc.Bench or dotty.tools.dotc.Main - |Generated test sources: ${PathSettings.srcDir}${File.separator} - |Test directories: ${DPConfig.testDirs.toList.mkString(", ")} - |Debugging: failed tests have compiler output in test-kind.clog, run output in test-kind.log, class files in test-kind.obj - |Parallel: ${DPConfig.runTestsInParallel} - |Options: (use partest --help for usage information) ${consoleArgs} - """.stripMargin - } - - /** Some tests require a limitation of resources, tests which are compiled - * with one or more of the flags in this list will be run with - * `limitedThreads`. This is necessary because some test flags require a lot - * of memory when running the compiler and may exhaust the available memory - * when run in parallel with too many other tests. - * - * This number could be increased on the CI, but might fail locally if - * scaled too extreme - override with: - * - * ``` - * -Ddotty.tests.limitedThreads=X - * ``` - */ - def limitResourceFlags = List("-Ytest-pickler") - private val limitedThreads = sys.props.get("dotty.tests.limitedThreads").getOrElse("2") - - override def runTestsForFiles(kindFiles: Array[File], kind: String): Array[TestState] = { - val (limitResourceTests, parallelTests) = - kindFiles partition { kindFile => - val flags = kindFile.changeExtension("flags").fileContents - limitResourceFlags.exists(seqFlag => flags.contains(seqFlag)) - } - - val seqResults = - if (!limitResourceTests.isEmpty) { - val savedThreads = sys.props("partest.threads") - sys.props("partest.threads") = { - assert( - savedThreads == null || limitedThreads.toInt <= savedThreads.toInt, - """|Should not use more threads than the default, when the point - |is to limit the amount of resources""".stripMargin - ) - limitedThreads - } - - NestUI.echo(s"## we will run ${limitResourceTests.length} tests using ${PartestDefaults.numThreads} thread(s) in parallel") - val res = super.runTestsForFiles(limitResourceTests, kind) - - if (savedThreads != null) - sys.props("partest.threads") = savedThreads - else - sys.props.remove("partest.threads") - - res - } else Array[TestState]() - - val parResults = - if (!parallelTests.isEmpty) { - NestUI.echo(s"## we will run ${parallelTests.length} tests in parallel using ${PartestDefaults.numThreads} thread(s)") - super.runTestsForFiles(parallelTests, kind) - } else Array[TestState]() - - seqResults ++ parResults - } - - // override for DPTestRunner and redirecting compilation output to test.clog - override def runTest(testFile: File): TestState = { - val runner = new DPTestRunner(testFile, this) - - val state = - try { - runner.run match { - // Append compiler output to transcript if compilation failed, - // printed with --verbose option - case TestState.Fail(f, r@"compilation failed", transcript) => - TestState.Fail(f, r, transcript ++ runner.cLogFile.fileLines.dropWhile(_ == "")) - case res => res - } - } catch { - case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) - } - reportTest(state) - runner.cleanup() - - onFinishTest(testFile, state) - } - - // override NestUI.reportTest because --show-diff doesn't work. The diff used - // seems to add each line to transcript separately, whereas NestUI assumes - // that the diff string was added as one entry in the transcript - def reportTest(state: TestState) = { - import NestUI._ - import NestUI.color._ - - if (isTerse && state.isOk) { - NestUI.reportTest(state) - } else { - echo(statusLine(state)) - if (!state.isOk && isDiffy) { - val differ = bold(red("% ")) + "diff " - state.transcript.dropWhile(s => !(s startsWith differ)) foreach (echo(_)) - // state.transcript find (_ startsWith differ) foreach (echo(_)) // original - } - } - } -} - -class DPTestRunner(testFile: File, suiteRunner: DPSuiteRunner) extends nest.Runner(testFile, suiteRunner) { - val cLogFile = SFile(logFile).changeExtension("clog") - - // override to provide DottyCompiler - override def newCompiler = new dotty.partest.DPDirectCompiler(this) - - // Adapted from nest.Runner#javac because: - // - Our classpath handling is different and we need to pass extraClassPath - // to java to get the scala-library which is required for some java tests - // - The compiler output should be redirected to cLogFile, like the output of - // dotty itself - override def javac(files: List[File]): TestState = { - import fileManager._ - import suiteRunner._ - import FileManager.joinPaths - // compile using command-line javac compiler - val args = Seq( - suiteRunner.javacCmdPath, // FIXME: Dotty deviation just writing "javacCmdPath" doesn't work - "-d", - outDir.getAbsolutePath, - "-classpath", - joinPaths(outDir :: extraClasspath ++ testClassPath) - ) ++ files.map(_.getAbsolutePath) - - pushTranscript(args mkString " ") - - val captured = StreamCapture(runCommand(args, cLogFile)) - if (captured.result) genPass() else { - cLogFile appendAll captured.stderr - cLogFile appendAll captured.stdout - genFail("java compilation failed") - } - } - - // Overriden in order to recursively get all sources that should be handed to - // the compiler. Otherwise only sources in the top dir is compiled - works - // because the compiler is on the classpath. - override def sources(file: File): List[File] = - if (file.isDirectory) - file.listFiles.toList.flatMap { f => - if (f.isDirectory) sources(f) - else if (f.isJavaOrScala) List(f) - else Nil - } - else List(file) - - // Enable me to "fix" the depth issue - remove once completed - //override def compilationRounds(file: File): List[CompileRound] = { - // val srcs = sources(file) match { - // case Nil => - // System.err.println { - // s"""|================================================================================ - // |Warning! You attempted to compile sources from: - // | $file - // |but partest was unable to find any sources - uncomment DPConsoleRunner#sources - // |================================================================================""".stripMargin - // } - // List(new File("./tests/pos/HelloWorld.scala")) // "just compile some crap" - Guillaume - // case xs => - // xs - // } - // (groupedFiles(srcs) map mixedCompileGroup).flatten - //} - - // FIXME: This is copy-pasted from nest.Runner where it is private - // Remove this once https://github.com/scala/scala-partest/pull/61 is merged - /** Runs command redirecting standard out and - * error out to output file. - */ - def runCommand(args: Seq[String], outFile: File): Boolean = { - import scala.sys.process.{ Process, ProcessLogger } - //(Process(args) #> outFile !) == 0 or (Process(args) ! pl) == 0 - val pl = ProcessLogger(outFile) - val nonzero = 17 // rounding down from 17.3 - def run: Int = { - val p = Process(args) run pl - try p.exitValue - catch { - case e: InterruptedException => - NestUI verbose s"Interrupted waiting for command to finish (${args mkString " "})" - p.destroy - nonzero - case t: Throwable => - NestUI verbose s"Exception waiting for command to finish: $t (${args mkString " "})" - p.destroy - throw t - } - finally pl.close() - } - (pl buffer run) == 0 - } - - // override to provide default dotty flags from file in directory - override def flagsForCompilation(sources: List[File]): List[String] = { - val specificFlags = super.flagsForCompilation(sources) - if (specificFlags.isEmpty) defaultFlags - else specificFlags - } - - val defaultFlags = { - val defaultFile = parentFile.listFiles.toList.find(_.getName == "__defaultFlags.flags") - defaultFile.map({ file => - SFile(file).safeSlurp.map({ content => words(content).filter(_.nonEmpty) }).getOrElse(Nil) - }).getOrElse(Nil) - } - - // override to add the check for nr of compilation errors if there's a - // target.nerr file - override def runNegTest() = runInContext { - sealed abstract class NegTestState - // Don't get confused, the neg test passes when compilation fails for at - // least one round (optionally checking the number of compiler errors and - // compiler console output) - case object CompFailed extends NegTestState - // the neg test fails when all rounds return either of these: - case class CompFailedButWrongNErr(expected: String, found: String) extends NegTestState - case object CompFailedButWrongDiff extends NegTestState - case object CompSucceeded extends NegTestState - - def nerrIsOk(reason: String) = { - val nerrFinder = """compilation failed with (\d+) errors""".r - reason match { - case nerrFinder(found) => - SFile(FileOps(testFile) changeExtension "nerr").safeSlurp match { - case Some(exp) if (exp != found) => CompFailedButWrongNErr(exp, found) - case _ => CompFailed - } - case _ => CompFailed - } - } - - // we keep the partest semantics where only one round needs to fail - // compilation, not all - val compFailingRounds = - compilationRounds(testFile) - .map { round => - val ok = round.isOk - setLastState(if (ok) genPass else genFail("compilation failed")) - (round.result, ok) - } - .filter { case (_, ok) => !ok } - - val failureStates = compFailingRounds.map({ case (result, _) => result match { - // or, OK, we'll let you crash the compiler with a FatalError if you supply a check file - case Crash(_, t, _) if !checkFile.canRead || !t.isInstanceOf[FatalError] => CompSucceeded - case Fail(_, reason, _) => if (diffIsOk) nerrIsOk(reason) else CompFailedButWrongDiff - case _ => if (diffIsOk) CompFailed else CompFailedButWrongDiff - }}) - - if (failureStates.exists({ case CompFailed => true; case _ => false })) { - true - } else { - val existsNerr = failureStates.exists({ - case CompFailedButWrongNErr(exp, found) => - nextTestActionFailing(s"wrong number of compilation errors, expected: $exp, found: $found") - true - case _ => - false - }) - - if (existsNerr) false - else { - val existsDiff = failureStates.exists({ - case CompFailedButWrongDiff => - nextTestActionFailing(s"output differs") - true - case _ => - false - }) - if (existsDiff) false - else nextTestActionFailing("expected compilation failure") - } - } - } - - // override to change check file updating to original file, not generated - override def diffIsOk: Boolean = { - // always normalize the log first - normalizeLog() - val diff = currentDiff - // if diff is not empty, is update needed? - val updating: Option[Boolean] = ( - if (diff == "") None - else Some(suiteRunner.updateCheck) - ) - pushTranscript(s"diff $logFile $checkFile") - nextTestAction(updating) { - case Some(true) => - val origCheck = SFile(checkFile.changeExtension("checksrc").fileLines(1)) - NestUI.echo("Updating original checkfile " + origCheck) - origCheck writeAll file2String(logFile) - genUpdated() - case Some(false) => - // Get a word-highlighted diff from git if we can find it - val bestDiff = if (updating.isEmpty) "" else { - if (checkFile.canRead) - gitDiff(logFile, checkFile) getOrElse { - s"diff $logFile $checkFile\n$diff" - } - else diff - } - pushTranscript(bestDiff) - genFail("output differs") - case None => genPass() // redundant default case - } getOrElse true - } - - // override to add dotty and scala jars to classpath - override def extraClasspath = - suiteRunner.fileManager.asInstanceOf[DottyFileManager].extraJarList ::: super.extraClasspath - - - // FIXME: Dotty deviation: error if return type is omitted: - // overriding method cleanup in class Runner of type ()Unit; - // method cleanup of type => Boolean | Unit has incompatible type - - // override to keep class files if failed and delete clog if ok - override def cleanup: Unit = if (lastState.isOk) { - logFile.delete - cLogFile.delete - Directory(outDir).deleteRecursively - } -} diff --git a/compiler/test/dotty/partest/DPDirectCompiler.scala b/compiler/test/dotty/partest/DPDirectCompiler.scala deleted file mode 100644 index 410dac338df2..000000000000 --- a/compiler/test/dotty/partest/DPDirectCompiler.scala +++ /dev/null @@ -1,36 +0,0 @@ -package dotty.partest - -import dotty.tools.dotc.reporting.ConsoleReporter -import scala.tools.partest.{ TestState, nest } -import java.io.{ File, PrintWriter, FileWriter } - - -/* NOTE: Adapted from partest.DirectCompiler */ -class DPDirectCompiler(runner: DPTestRunner) extends nest.DirectCompiler(runner) { - - override def compile(opts0: List[String], sources: List[File]): TestState = { - val clogFWriter = new FileWriter(runner.cLogFile.jfile, true) - val clogWriter = new PrintWriter(clogFWriter, true) - clogWriter.println("\ncompiling " + sources.mkString(" ") + "\noptions: " + opts0.mkString(" ")) - - try { - val processor = - if (opts0.exists(_.startsWith("#"))) dotty.tools.dotc.Bench else dotty.tools.dotc.Main - val clogger = new ConsoleReporter(writer = clogWriter) - val reporter = processor.process((sources.map(_.toString) ::: opts0).toArray, clogger) - if (!reporter.hasErrors) runner.genPass() - else { - clogWriter.println(reporter.summary) - runner.genFail(s"compilation failed with ${reporter.errorCount} errors") - } - } catch { - case t: Throwable => - t.printStackTrace - t.printStackTrace(clogWriter) - runner.genCrash(t) - } finally { - clogFWriter.close - clogWriter.close - } - } -} diff --git a/compiler/test/dotty/tools/StdLibSources.scala b/compiler/test/dotty/tools/StdLibSources.scala index 24ca9b80beb2..0c11210586a3 100644 --- a/compiler/test/dotty/tools/StdLibSources.scala +++ b/compiler/test/dotty/tools/StdLibSources.scala @@ -1,8 +1,9 @@ package dotty.tools import java.io.File - import scala.io.Source +import org.junit.Test +import org.junit.Assert._ object StdLibSources { @@ -59,9 +60,6 @@ object StdLibSources { } class StdLibSources { - import org.junit.Test - import org.junit.Assert._ - @Test def checkWBLists = { val stdlibFilesBlackListed = StdLibSources.blacklisted diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala deleted file mode 100644 index f35f9f919d15..000000000000 --- a/compiler/test/dotty/tools/dotc/CompilerTest.scala +++ /dev/null @@ -1,613 +0,0 @@ -package dotty.tools.dotc - -import repl.TestREPL -import core.Contexts._ -import dotty.partest.DPConfig -import interfaces.Diagnostic.ERROR -import reporting._ -import diagnostic.MessageContainer -import util.SourcePosition -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 - * -.log file with output of failed tests. Partest reads compiler - * flags and the number of errors expected from a neg test from .flags - * and .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 - * .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. - */ -abstract class CompilerTest { - - /** Override with output dir of test so it can be patched. Partest expects - * classes to be in partest-generated/[kind]/[testname]-[kind].obj/ */ - val defaultOutputDir: String - - /** Override to filter out tests that should not be run by partest. */ - def partestableFile(prefix: String, fileName: String, extension: String, args: List[String]) = true - 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.") - compileArgs(cmdLine.split("\n"), Nil) - } - - /** Compiles the given code file. - * - * @param prefix the parent directory (including separator at the end) - * @param fileName the filename, by default without extension - * @param args arguments to the compiler - * @param extension the file extension, .scala by default - * @param defaultOptions more arguments to the compiler - */ - def compileFile(prefix: String, fileName: String, args: List[String] = Nil, extension: String = ".scala", runTest: Boolean = false) - (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") - } - } - } - def runFile(prefix: String, fileName: String, args: List[String] = Nil, extension: String = ".scala") - (implicit defaultOptions: List[String]): Unit = { - compileFile(prefix, fileName, args, extension, true) - } - - def findJarFromRuntime(partialName: String): 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")}""" - ) - } - } - - private def compileWithJavac( - fs: Array[String], - args: Array[String] - )(implicit defaultOptions: List[String]): Boolean = { - val scalaLib = findJarFromRuntime("scala-library") - val fullArgs = Array( - "javac", - "-classpath", - s".:$scalaLib" - ) ++ args ++ defaultOptions.dropWhile("-d" != _).take(2) ++ fs - - Runtime.getRuntime.exec(fullArgs).waitFor() == 0 - } - - /** Compiles the code files in the given directory together. If args starts - * with "-deep", all files in subdirectories (and so on) are included. */ - def compileDir(prefix: String, dirName: String, args: List[String] = Nil, runTest: Boolean = false) - (implicit defaultOptions: List[String]): Unit = { - def computeFilePathsAndExpErrors = { - val dir = Directory(prefix + dirName) - val (files, normArgs) = args match { - case "-deep" :: args1 => (dir.deepFiles, args1) - case _ => (dir.files, args) - } - val (filePaths, javaFilePaths) = files - .toArray.map(_.toString) - .foldLeft((Array.empty[String], Array.empty[String])) { case (acc @ (fp, jfp), name) => - if (name endsWith ".scala") (name +: fp, jfp) - else if (name endsWith ".java") (fp, name +: jfp) - else (fp, jfp) - } - 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") - } - } - } - def runDir(prefix: String, dirName: String, args: List[String] = Nil) - (implicit defaultOptions: List[String]): Unit = - compileDir(prefix, dirName, args, true) - - /** Compiles each source in the directory path separately by calling - * compileFile resp. compileDir. */ - def compileFiles(path: String, args: List[String] = Nil, verbose: Boolean = true, runTest: Boolean = false, - compileSubDirs: Boolean = true)(implicit defaultOptions: List[String]): Unit = { - val dir = Directory(path) - val fileNames = dir.files.toArray.map(_.jfile.getName).filter(name => (name endsWith ".scala") || (name endsWith ".java")) - for (name <- fileNames) { - if (verbose) log(s"testing $path$name") - compileFile(path, name, args, "", runTest) - } - if (compileSubDirs) - for (subdir <- dir.dirs) { - if (verbose) log(s"testing $subdir") - compileDir(path, subdir.jfile.getName, args, runTest) - } - } - def runFiles(path: String, args: List[String] = Nil, verbose: Boolean = true) - (implicit defaultOptions: List[String]): Unit = - compileFiles(path, args, verbose, true) - - /** 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 - } - } - - // ========== HELPERS ============= - - private def expectedErrors(filePaths: List[String]): List[ErrorsInFile] = if (filePaths.exists(isNegTest(_))) filePaths.map(getErrors(_)) else Nil - - private def expectedErrors(filePath: String): List[ErrorsInFile] = expectedErrors(List(filePath)) - - private def isNegTest(testPath: String) = testPath.contains("/neg/") - - private def compileArgs(args: Array[String], expectedErrorsPerFile: List[ErrorsInFile]) - (implicit defaultOptions: List[String]): Unit = { - val allArgs = args ++ defaultOptions - val verbose = allArgs.contains("-verbose") - //println(s"""all args: ${allArgs.mkString("\n")}""") - val processor = if (allArgs.exists(_.startsWith("#"))) Bench else Main - val storeReporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { - private val consoleReporter = new ConsoleReporter() - private val innerStoreReporter = new StoreReporter(consoleReporter) - def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { - if (m.level == ERROR || verbose) { - innerStoreReporter.flush() - consoleReporter.doReport(m) - } - else if (errorCount > 0) consoleReporter.doReport(m) - else innerStoreReporter.doReport(m) - } - } - val reporter = processor.process(allArgs, storeReporter) - - val nerrors = reporter.errorCount - val xerrors = (expectedErrorsPerFile map {_.totalErrors}).sum - def expectedErrorFiles = - expectedErrorsPerFile.collect{ - case er if er.totalErrors > 0 => er.fileName - } - assert(nerrors == xerrors, - s"""Wrong # of errors. Expected: $xerrors, found: $nerrors - |Files with expected errors: $expectedErrorFiles - |errors: - """.stripMargin) - // NEG TEST - if (xerrors > 0) { - val errorLines = reporter.allErrors.map(_.pos) - // reporter didn't record as many errors as its errorCount says - assert(errorLines.length == nerrors, s"Not enough errors recorded.") - - // Some compiler errors have an associated source position. Each error - // needs to correspond to a "// error" marker on that line in the source - // file and vice versa. - // Other compiler errors don't have an associated source position. Their - // number should correspond to the total count of "// nopos-error" - // markers in all files - val (errorsByFile, errorsWithoutPos) = errorLines.groupBy(_.source.file).toList.partition(_._1.toString != "") - - // check errors with source position - val foundErrorsPerFile = errorsByFile.map({ case (fileName, errorList) => - val posErrorLinesToNr = errorList.groupBy(_.line).toList.map({ case (line, list) => (line, list.length) }).sortBy(_._1) - ErrorsInFile(fileName.toString, 0, posErrorLinesToNr) - }) - val expectedErrorsPerFileZeroed = expectedErrorsPerFile.map({ - case ErrorsInFile(fileName, _, posErrorLinesToNr) => - ErrorsInFile(fileName.toString, 0, posErrorLinesToNr) - }) - checkErrorsWithPosition(expectedErrorsPerFileZeroed, foundErrorsPerFile) - - // check errors without source position - val expectedNoPos = expectedErrorsPerFile.map(_.noposErrorNr).sum - val foundNoPos = errorsWithoutPos.map(_._2.length).sum - assert(foundNoPos == expectedNoPos, - s"Wrong # of errors without source position. Expected (all files): $expectedNoPos, found (compiler): $foundNoPos") - } - } - - // ========== NEG TEST HELPERS ============= - - /** Captures the number of nopos-errors in the given file and the number of - * errors with a position, represented as a tuple of source line and number - * of errors on that line. */ - case class ErrorsInFile(fileName: String, noposErrorNr: Int, posErrorLinesToNr: List[(Int, Int)]) { - def totalErrors = noposErrorNr + posErrorLinesToNr.map(_._2).sum - } - - /** Extracts the errors expected for the given neg test file. */ - def getErrors(fileName: String): ErrorsInFile = { - val content = SFile(fileName).slurp - val (line, rest) = content.span(_ != '\n') - - @tailrec - def checkLine(line: String, rest: String, index: Int, noposAcc: Int, posAcc: List[(Int, Int)]): ErrorsInFile = { - val posErrors = "// ?error".r.findAllIn(line).length - val newPosAcc = if (posErrors > 0) (index, posErrors) :: posAcc else posAcc - val newNoPosAcc = noposAcc + "// ?nopos-error".r.findAllIn(line).length - val (newLine, newRest) = rest.span(_ != '\n') - if (newRest.isEmpty) - ErrorsInFile(fileName.toString, newNoPosAcc, newPosAcc.reverse) - else - checkLine(newLine, newRest.tail, index + 1, newNoPosAcc, newPosAcc) // skip leading '\n' - } - - checkLine(line, rest.tail, 0, 0, Nil) // skip leading '\n' - } - - /** Asserts that the expected and found number of errors correspond, and - * otherwise throws an error with the filename, plus optionally a line - * number if available. */ - def errorMsg(fileName: String, lineNumber: Option[Int], exp: Int, found: Int) = { - val i = lineNumber.map({ i => ":" + (i + 1) }).getOrElse("") - assert(found == exp, s"Wrong # of errors for $fileName$i. Expected (file): $exp, found (compiler): $found") - } - - /** Compares the expected with the found errors and creates a nice error - * message if they don't agree. */ - def checkErrorsWithPosition(expected: List[ErrorsInFile], found: List[ErrorsInFile]): Unit = { - // create nice error messages - expected.diff(found) match { - case Nil => // nothing missing - case ErrorsInFile(fileName, _, expectedLines) :: xs => - found.find(_.fileName == fileName) match { - case None => - // expected some errors, but none found for this file - errorMsg(fileName, None, expectedLines.map(_._2).sum, 0) - case Some(ErrorsInFile(_,_,foundLines)) => - // found wrong number/location of markers for this file - compareLines(fileName, expectedLines, foundLines) - } - } - - found.diff(expected) match { - case Nil => // nothing missing - case ErrorsInFile(fileName, _, foundLines) :: xs => - expected.find(_.fileName == fileName) match { - case None => - // found some errors, but none expected for this file - errorMsg(fileName, None, 0, foundLines.map(_._2).sum) - case Some(ErrorsInFile(_,_,expectedLines)) => - // found wrong number/location of markers for this file - compareLines(fileName, expectedLines, foundLines) - } - } - } - - /** Gives an error message for one line where the expected number of errors and - * the number of compiler errors differ. */ - def compareLines(fileName: String, expectedLines: List[(Int, Int)], foundLines: List[(Int, Int)]) = { - expectedLines foreach{ - case (line, expNr) => - foundLines.find(_._1 == line) match { - case Some((_, `expNr`)) => // this line is ok - case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr) - case None => - println(s"expected lines = $expectedLines%, %") - println(s"found lines = $foundLines%, %") - errorMsg(fileName, Some(line), expNr, 0) - } - } - foundLines foreach { - case (line, foundNr) => - expectedLines.find(_._1 == line) match { - case Some((_, `foundNr`)) => // this line is ok - case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr) - case None => errorMsg(fileName, Some(line), 0, foundNr) - } - } - } - - // ========== PARTEST HELPERS ============= - - // In particular, don't copy flags from scalac tests - private val extensionsToCopy = scala.collection.immutable.HashSet("scala", "java") - - /** Determines what kind of test to run. */ - private def testKind(prefixDir: String, runTest: Boolean) = { - if (runTest) "run" - else if (isNegTest(prefixDir)) "neg" - else if (prefixDir.endsWith("run" + JFile.separator)) { - log("WARNING: test is being run as pos test despite being in a run directory. " + - "Use runFile/runDir instead of compileFile/compileDir to do a run test") - "pos" - } else "pos" - } - - /** The three possibilities: no generated sources exist yet, the same sources - * exist already, different sources exist. */ - object Difference extends Enumeration { - type Difference = Value - val NotExists, ExistsSame, ExistsDifferent = Value - } - 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 = { - - @tailrec def copyfile(file: SFile, bytewise: Boolean): Unit = { - if (bytewise) { - val in = file.inputStream() - val out = SFile(dest).outputStream() - val buffer = new Array[Byte](1024) - @tailrec def loop(available: Int):Unit = { - if (available < 0) {()} - else { - out.write(buffer, 0, available) - val read = in.read(buffer) - loop(read) - } - } - loop(0) - in.close() - out.close() - } else { - try { - SFile(dest)(scala.io.Codec.UTF8).writeAll((s"/* !!!!! WARNING: DO NOT MODIFY. Original is at: $file !!!!! */").replace("\\", "/"), file.slurp("UTF-8")) - } catch { - case unmappable: java.nio.charset.MalformedInputException => - copyfile(file, true) //there are bytes that can't be mapped with UTF-8. Bail and just do a straight byte-wise copy without the warning header. - } - } - } - - processFileDir(sourceFile, { sf => - if (extensionsToCopy.contains(sf.extension)) { - dest.parent.jfile.mkdirs - copyfile(sf, false) - } else { - log(s"WARNING: ignoring $sf") - } - }, { sdir => - dest.jfile.mkdirs - sdir.list.foreach(path => recCopyFiles(path, dest / path.name)) - }, Some("DPCompilerTest.recCopyFiles: sourceFile not found: " + sourceFile)) - } - - /** Reads the existing files for the given test source if any. */ - private def getExisting(dest: Path): ExistingFiles = { - val content: Option[Option[String]] = processFileDir(dest, f => try Some(f.slurp("UTF8")) catch {case io: java.io.IOException => Some(io.toString())}, d => Some("")) - if (content.isDefined && content.get.isDefined) { - val flags = (dest changeExtension "flags").toFile.safeSlurp - val nerr = (dest changeExtension "nerr").toFile.safeSlurp - ExistingFiles(content.get, flags, nerr) - } else ExistingFiles() - } - - /** Encapsulates existing generated test files. */ - case class ExistingFiles(genSrc: Option[String] = None, flags: Option[String] = None, nerr: Option[String] = None) { - def isDifferent(sourceFile: JFile, otherFlags: List[String], otherNerr: String): Difference = { - if (!genSrc.isDefined) { - NotExists - } else { - val source = processFileDir(sourceFile, { f => try Some(f.slurp("UTF8")) catch {case _: java.io.IOException => None} }, { d => Some("") }, - Some("DPCompilerTest sourceFile doesn't exist: " + sourceFile)).get - if (source == genSrc) { - nerr match { - case Some(n) if (n != otherNerr) => ExistsDifferent - case None if (otherNerr != "0") => ExistsDifferent - case _ if (flags.map(_ == otherFlags.mkString(" ")).getOrElse(otherFlags.isEmpty)) => ExistsSame - case _ => ExistsDifferent - } - } else ExistsDifferent - } - } - } - - import scala.util.matching.Regex - val nrFinder = """(.*_v)(\d+)""".r - /** Changes the version number suffix in the name (without extension). */ - private def replaceVersion(name: String, nr: Int): Option[String] = { - val nrString = nr.toString - name match { - case nrFinder(prefix, `nrString`) => Some(prefix + (nr + 1)) - case _ if nr != 0 => None - case _ => Some(name + "_v1") - } - } - - /** Returns None if the given path doesn't exist, otherwise returns Some of - * applying either processFile or processDir, depending on what the path - * refers to in the file system. If failMsgOnNone is defined, this function - * asserts that the file exists using the provided message. */ - private def processFileDir[T](input: Path, processFile: SFile => T, processDir: Directory => T, failMsgOnNone: Option[String] = None): Option[T] = { - val res = input.ifFile(f => processFile(f)).orElse(input.ifDirectory(d => processDir(d))) - (failMsgOnNone, res) match { - case (Some(msg), None) => assert(false, msg); None - case _ => res - } - } - - /** 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") -} diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 2c9e813e166b..62bd9fb7bc8a 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -11,7 +11,7 @@ import reporting.diagnostic.MessageContainer import interfaces.Diagnostic.ERROR import java.lang.reflect.InvocationTargetException import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.nio.file.{ Files, Path, Paths } +import java.nio.file.{ Files, Path, Paths, NoSuchFileException } import java.util.concurrent.{ Executors => JExecutors, TimeUnit } import scala.util.control.NonFatal import java.util.HashMap @@ -343,10 +343,10 @@ trait ParallelTesting { times: Int, shouldDelete: Boolean ) { - def this(target: Target, fromDir: String, flags: Array[String]) = + private[ParallelTesting] def this(target: Target, fromDir: String, flags: Array[String]) = this(List(target), fromDir, flags, 1, true) - def this(targets: List[Target], fromDir: String, flags: Array[String]) = + private[ParallelTesting] def this(targets: List[Target], fromDir: String, flags: Array[String]) = this(targets, fromDir, flags, 1, true) def pos: this.type = { @@ -389,7 +389,10 @@ trait ParallelTesting { private def delete(file: JFile): Unit = { if (file.isDirectory) file.listFiles.foreach(delete) - Files.delete(file.toPath) + try Files.delete(file.toPath) + catch { + case _: NoSuchFileException => // already deleted, everything's fine + } } } diff --git a/project/Build.scala b/project/Build.scala index ce560d8e4f62..ae76722485fc 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -131,27 +131,7 @@ object Build { settings( triggeredMessage in ThisBuild := Watched.clearWhenTriggered, - addCommandAlias("run", "dotty-compiler/run") ++ - addCommandAlias( - "partest", - ";publishLocal" + // Non-bootstrapped dotty needs to be published first - ";dotty-compiler-bootstrapped/lockPartestFile" + - ";dotty-compiler-bootstrapped/test:test" + - ";dotty-compiler-bootstrapped/runPartestRunner" - ) ++ - addCommandAlias( - "partest-only", - ";publishLocal" + // Non-bootstrapped dotty needs to be published first - ";dotty-compiler-bootstrapped/lockPartestFile" + - ";dotty-compiler-bootstrapped/test:test-only dotc.tests" + - ";dotty-compiler-bootstrapped/runPartestRunner" - ) ++ - addCommandAlias( - "partest-only-no-bootstrap", - ";dotty-compiler/lockPartestFile" + - ";dotty-compiler/test:test-only dotc.tests" + - ";dotty-compiler/runPartestRunner" - ) + addCommandAlias("run", "dotty-compiler/run") ). settings(publishing) @@ -276,23 +256,20 @@ object Build { com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys.withSource := true, // get libraries onboard - partestDeps := Seq(scalaCompiler, - "org.scala-lang" % "scala-reflect" % scalacVersion, - "org.scala-lang" % "scala-library" % scalacVersion % "test"), - libraryDependencies ++= partestDeps.value, - libraryDependencies ++= Seq("org.scala-lang.modules" %% "scala-xml" % "1.0.1", - "org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test", - "com.novocode" % "junit-interface" % "0.11" % "test"), - resolvers += Resolver.typesafeIvyRepo("releases"), // For org.scala-sbt:interface - libraryDependencies += "org.scala-sbt" % "interface" % sbtVersion.value, + libraryDependencies ++= Seq(scalaCompiler, + "org.scala-sbt" % "interface" % sbtVersion.value, + "org.scala-lang.modules" %% "scala-xml" % "1.0.1", + "com.novocode" % "junit-interface" % "0.11" % "test", + "org.scala-lang" % "scala-reflect" % scalacVersion, + "org.scala-lang" % "scala-library" % scalacVersion % "test"), // enable improved incremental compilation algorithm incOptions := incOptions.value.withNameHashing(true), // For convenience, change the baseDirectory when running the compiler baseDirectory in (Compile, run) := baseDirectory.value / "..", - // .. but not when running partest + // .. but not when running test baseDirectory in (Test, run) := baseDirectory.value, repl := Def.inputTaskDyn { @@ -343,34 +320,6 @@ object Build { TestFrameworks.JUnit, "-a", "-v", "--run-listener=dotty.tools.ContextEscapeDetector" ), - testOptions in Test += Tests.Cleanup({ () => partestLockFile.delete }), - - lockPartestFile := { - // When this file is present, running `test` generates the files for - // partest. Otherwise it just executes the tests directly. - val lockDir = partestLockFile.getParentFile - lockDir.mkdirs - // Cannot have concurrent partests as they write to the same directory. - if (lockDir.list.size > 0) - throw new RuntimeException("ERROR: sbt partest: another partest is already running, pid in lock file: " + lockDir.list.toList.mkString(" ")) - partestLockFile.createNewFile - partestLockFile.deleteOnExit - }, - runPartestRunner := Def.inputTaskDyn { - // Magic! This is both an input task and a dynamic task. Apparently - // command line arguments get passed to the last task in an aliased - // sequence (see partest alias below), so this works. - val args = Def.spaceDelimited("").parsed - val jars = List( - (packageBin in Compile).value.getAbsolutePath, - packageAll.value("dotty-library"), - packageAll.value("dotty-interfaces") - ) ++ getJarPaths(partestDeps.value, ivyPaths.value.ivyHome) - val dottyJars = - s"""-dottyJars ${jars.length + 2} dotty.jar dotty-lib.jar ${jars.mkString(" ")}""" - // Provide the jars required on the classpath of run tests - runTask(Test, "dotty.partest.DPConsoleRunner", dottyJars + " " + args.mkString(" ")) - }.evaluated, /* Add the sources of scalajs-ir. * To guarantee that dotty can bootstrap without depending on a version @@ -454,7 +403,7 @@ object Build { "-Ddotty.tests.classes.compiler=" + pA("dotty-compiler") ) - ("-DpartestParentID=" + pid) :: jars ::: tuning ::: agentOptions ::: ci_build ::: path.toList + jars ::: tuning ::: agentOptions ::: ci_build ::: path.toList } ) @@ -793,26 +742,6 @@ object DottyInjectedPlugin extends AutoPlugin { ) ) - // Partest tasks - lazy val lockPartestFile = TaskKey[Unit]("lockPartestFile", "Creates the lock file at ./tests/locks/partest-.lock") - lazy val partestLockFile = new File("." + File.separator + "tests" + File.separator + "locks" + File.separator + s"partest-$pid.lock") - def pid = java.lang.Long.parseLong(java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split("@")(0)) - - lazy val runPartestRunner = InputKey[Unit]("runPartestRunner", "Runs partest") - - lazy val partestDeps = SettingKey[Seq[ModuleID]]("partestDeps", "Finds jars for partest dependencies") - def getJarPaths(modules: Seq[ModuleID], ivyHome: Option[File]): Seq[String] = ivyHome match { - case Some(home) => - modules.map({ module => - val file = Path(home) / Path("cache") / - Path(module.organization) / Path(module.name) / Path("jars") / - Path(module.name + "-" + module.revision + ".jar") - if (!file.isFile) throw new RuntimeException("ERROR: sbt getJarPaths: dependency jar not found: " + file) - else file.jfile.getAbsolutePath - }) - case None => throw new RuntimeException("ERROR: sbt getJarPaths: ivyHome not defined") - } - // Compile with dotty lazy val compileWithDottySettings = { inConfig(Compile)(inTask(compile)(Defaults.runnerTask) ++ Seq( From 5d36bf9f2ff6bb5339e05a7ca6aac48e90ada174 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 17 Mar 2017 18:51:31 +0100 Subject: [PATCH 18/39] Make tests composable --- .../dotty/tools/dotc/CompilationTests.scala | 497 ++++++------------ .../dotty/tools/dotc/ParallelTesting.scala | 277 +++++++--- 2 files changed, 360 insertions(+), 414 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 28343056592d..8136cb8612ce 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -14,354 +14,185 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("../tests/pos", defaultOptions).pos @Test def compilePosScala2: Unit = - compileFilesInDir("../tests/pos-scala2", scala2Mode).pos - - @Test def nullarifyPos: Unit = - compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")).pos - - @Test def rewrites: Unit = - compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")).pos - - @Test def t8146aPos: Unit = - compileFile("../tests/pos-special/t8146a.scala", allowDeepSubtypes).pos - - @Test def t5545Pos: Unit = - compileFilesInDir("../tests/pos-special/spec-t5545", defaultOptions).pos - - @Test def utf8encodedPos: Unit = - compileFile("../tests/pos-special/utf8encoded.scala", explicitUTF8).pos - - @Test def utf16encodedPos: Unit = - compileFile("../tests/pos-special/utf16encoded.scala", explicitUTF16).pos - - @Test def compileStdLibPos: Unit = - compileList(StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")).pos - - @Test def compileMixedPos: Unit = compileList( - List( - "../tests/pos/B.scala", - "../scala-scala/src/library/scala/collection/immutable/Seq.scala", - "../scala-scala/src/library/scala/collection/parallel/ParSeq.scala", - "../scala-scala/src/library/scala/package.scala", - "../scala-scala/src/library/scala/collection/GenSeqLike.scala", - "../scala-scala/src/library/scala/collection/SeqLike.scala", - "../scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala" - ), - defaultOptions - ).pos - - @Test def compileIndexedSeqPos: Unit = - compileFile("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala", defaultOptions).pos - - @Test def compileParSetLikePos: Unit = - compileFile("../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", defaultOptions).pos - - @Test def compileParSetSubsetPos: Unit = compileList( - List( - "../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", - "../scala-scala/src/library/scala/collection/parallel/mutable/ParSet.scala", - "../scala-scala/src/library/scala/collection/mutable/SetLike.scala" - ), - scala2Mode - ).pos - - @Test def compileAstPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions).pos - - @Test def compileConfigPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/config", defaultOptions).pos - - @Test def compileCorePos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core", allowDeepSubtypes).pos - - @Test def compileCoreNoCheckPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath).pos - - @Test def compileTransformPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/transform", allowDeepSubtypes).pos - - @Test def compileParsingPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/parsing", defaultOptions).pos - - @Test def compilePrintingPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/printing", defaultOptions).pos - - @Test def compileReportingPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/reporting", defaultOptions).pos - - @Test def compileTyperPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions).pos - - @Test def compileUtilPos: Unit = - compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions).pos - - @Test def compileIoPos: Unit = - compileDir("../compiler/src/dotty/tools/io", defaultOptions).pos - - @Test def labelsPos: Unit = - compileFile("../tests/pos/Labels.scala", defaultOptions).times(2).pos - - @Test def testNonCyclic: Unit = compileList( - List( - "../compiler/src/dotty/tools/dotc/CompilationUnit.scala", - "../compiler/src/dotty/tools/dotc/core/Types.scala", - "../compiler/src/dotty/tools/dotc/ast/Trees.scala" - ), - defaultOptions.and("-Xprompt") - ).times(2).pos - - @Test def issue34Pos: Unit = compileList( - List( - "../compiler/src/dotty/tools/dotc/config/Properties.scala", - "../compiler/src/dotty/tools/dotc/config/PathResolver.scala" - ), - defaultOptions.and("-Xprompt") - ).times(2).pos - - @Test def javaInteropPos: Unit = - compileFilesInDir("../tests/pos-java-interop", defaultOptions).times(2).pos + compileFilesInDir("../tests/pos-scala2", scala2Mode).pos() + + @Test def compilePosMixedFlags: Unit = { + compileList(StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")) + + compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")) + + // this guy sucks, he changes the sourcefile itself: + // compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")) + + compileFile("../tests/pos-special/t8146a.scala", allowDeepSubtypes) + + compileFile("../tests/pos-special/utf8encoded.scala", explicitUTF8) + + compileFile("../tests/pos-special/utf16encoded.scala", explicitUTF16) + + compileList( + List( + "../tests/pos/B.scala", + "../scala-scala/src/library/scala/collection/immutable/Seq.scala", + "../scala-scala/src/library/scala/collection/parallel/ParSeq.scala", + "../scala-scala/src/library/scala/package.scala", + "../scala-scala/src/library/scala/collection/GenSeqLike.scala", + "../scala-scala/src/library/scala/collection/SeqLike.scala", + "../scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala" + ), + defaultOptions + ) + + compileFilesInDir("../tests/pos-special/spec-t5545", defaultOptions) + + compileFile("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala", defaultOptions) + + compileFile("../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", defaultOptions) + + compileList( + List( + "../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", + "../scala-scala/src/library/scala/collection/parallel/mutable/ParSet.scala", + "../scala-scala/src/library/scala/collection/mutable/SetLike.scala" + ), + scala2Mode + ) + }.pos() + + @Test def compileCoreNoCheck: Unit = + compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath).pos() + + @Test def compileDotcInternals: Unit = { + compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/config", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/core", allowDeepSubtypes) + + compileDir("../compiler/src/dotty/tools/dotc/transform", allowDeepSubtypes) + + compileDir("../compiler/src/dotty/tools/dotc/parsing", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/printing", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/reporting", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions) + + compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions) + + compileDir("../compiler/src/dotty/tools/io", defaultOptions) + }.pos() + + @Test def posTwice: Unit = { + compileFile("../tests/pos/Labels.scala", defaultOptions) + + compileFilesInDir("../tests/pos-java-interop", defaultOptions) + + compileFile("../tests/pos/t2168.scala", defaultOptions) + + compileFile("../tests/pos/erasure.scala", defaultOptions) + + compileFile("../tests/pos/Coder.scala", defaultOptions) + + compileFile("../tests/pos/blockescapes.scala", defaultOptions) + + compileFile("../tests/pos/collections.scala", defaultOptions) + + compileFile("../tests/pos/functions1.scala", defaultOptions) + + compileFile("../tests/pos/implicits1.scala", defaultOptions) + + compileFile("../tests/pos/inferred.scala", defaultOptions) + + compileFile("../tests/pos/Patterns.scala", defaultOptions) + + compileFile("../tests/pos/selftypes.scala", defaultOptions) + + compileFile("../tests/pos/varargs.scala", defaultOptions) + + compileFile("../tests/pos/vararg-pattern.scala", defaultOptions) + + compileFile("../tests/pos/opassign.scala", defaultOptions) + + compileFile("../tests/pos/typedapply.scala", defaultOptions) + + compileFile("../tests/pos/nameddefaults.scala", defaultOptions) + + compileFile("../tests/pos/desugar.scala", defaultOptions) + + compileFile("../tests/pos/sigs.scala", defaultOptions) + + compileFile("../tests/pos/typers.scala", defaultOptions) + + compileDir("../tests/pos/typedIdents", defaultOptions) + + compileFile("../tests/pos/assignments.scala", defaultOptions) + + compileFile("../tests/pos/packageobject.scala", defaultOptions) + + compileFile("../tests/pos/overloaded.scala", defaultOptions) + + compileFile("../tests/pos/overrides.scala", defaultOptions) + + compileDir("../tests/pos/java-override", defaultOptions) + + compileFile("../tests/pos/templateParents.scala", defaultOptions) + + compileFile("../tests/pos/overloadedAccess.scala", defaultOptions) + + compileFile("../tests/pos/approximateUnion.scala", defaultOptions) + + compileFilesInDir("../tests/pos/tailcall", defaultOptions) + + compileShallowFilesInDir("../tests/pos/pos_valueclasses", defaultOptions) + + compileFile("../tests/pos/subtyping.scala", defaultOptions) + + compileFile("../tests/pos/i0239.scala", defaultOptions) + + compileFile("../tests/pos/anonClassSubtyping.scala", defaultOptions) + + compileFile("../tests/pos/extmethods.scala", defaultOptions) + + compileFile("../tests/pos/companions.scala", defaultOptions) + + compileList( + List( + "../compiler/src/dotty/tools/dotc/CompilationUnit.scala", + "../compiler/src/dotty/tools/dotc/core/Types.scala", + "../compiler/src/dotty/tools/dotc/ast/Trees.scala" + ), + defaultOptions.and("-Xprompt") + ) + + compileList( + "testIssue34", + List( + "../compiler/src/dotty/tools/dotc/config/Properties.scala", + "../compiler/src/dotty/tools/dotc/config/PathResolver.scala" + ), + defaultOptions.and("-Xprompt") + ) + }.times(2).pos() // New tests ----------------------------------------------------------------- @Test def compileNew: Unit = - compileFilesInDir("../tests/new", defaultOptions).pos + compileFilesInDir("../tests/new", defaultOptions).pos() // Negative tests ------------------------------------------------------------ @Test def compileNeg: Unit = - compileShallowFilesInDir("../tests/neg", defaultOptions).neg - - @Test def typedIdentsNeg: Unit = - compileDir("../tests/neg/typedIdents", defaultOptions).neg - - @Test def typersNeg: Unit = - compileFile("../tests/neg/customArgs/typers.scala", allowDoubleBindings).neg - - @Test def overrideClassNeg: Unit = - compileFile("../tests/neg/customArgs/overrideClass.scala", scala2Mode).neg - - @Test def autoTuplingNeg: Unit = - compileFile("../tests/neg/customArgs/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")).neg - - @Test def i1050Neg: Unit = - compileFile("../tests/neg/customArgs/i1050.scala", defaultOptions.and("-strict")).neg - - @Test def i1240Neg: Unit = - compileFile("../tests/neg/customArgs/i1240.scala", allowDoubleBindings).neg - - @Test def i2002Neg: Unit = - compileFile("../tests/neg/customArgs/i2002.scala", allowDoubleBindings).neg - - @Test def nopredefNeg: Unit = - compileFile("../tests/neg/customArgs/nopredef.scala", defaultOptions.and("-Yno-predef")).neg - - @Test def noimportsNeg: Unit = - compileFile("../tests/neg/customArgs/noimports.scala", defaultOptions.and("-Yno-imports")).neg - - @Test def noimports2Neg: Unit = - compileFile("../tests/neg/customArgs/noimports2.scala", defaultOptions.and("-Yno-imports")).neg - - @Test def t1672bTailcallNeg: Unit = - compileFile("../tests/neg/tailcall/t1672b.scala", defaultOptions).neg - - @Test def t3275TailcallNeg: Unit = - compileFile("../tests/neg/tailcall/t3275.scala", defaultOptions).neg - - @Test def t6574TailcallNeg: Unit = - compileFile("../tests/neg/tailcall/t6574.scala", defaultOptions).neg - - @Test def tailrecTailcallNeg: Unit = - compileFile("../tests/neg/tailcall/tailrec.scala", defaultOptions).neg - - @Test def tailrec2TailcallNeg: Unit = - compileFile("../tests/neg/tailcall/tailrec-2.scala", defaultOptions).neg - - @Test def tailrec3TailcallNeg: Unit = - compileFile("../tests/neg/tailcall/tailrec-3.scala", defaultOptions).neg + compileShallowFilesInDir("../tests/neg", defaultOptions).neg() + + @Test def compileNegCustomFlags: Unit = { + compileFile("../tests/neg/customArgs/typers.scala", allowDoubleBindings) + + compileFile("../tests/neg/customArgs/overrideClass.scala", scala2Mode) + + compileFile("../tests/neg/customArgs/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")) + + compileFile("../tests/neg/customArgs/i1050.scala", defaultOptions.and("-strict")) + + compileFile("../tests/neg/customArgs/i1240.scala", allowDoubleBindings) + + compileFile("../tests/neg/customArgs/i2002.scala", allowDoubleBindings) + + compileFile("../tests/neg/customArgs/nopredef.scala", defaultOptions.and("-Yno-predef")) + + compileFile("../tests/neg/customArgs/noimports.scala", defaultOptions.and("-Yno-imports")) + + compileFile("../tests/neg/customArgs/noimports2.scala", defaultOptions.and("-Yno-imports")) + + compileFile("../tests/neg/tailcall/t1672b.scala", defaultOptions) + + compileFile("../tests/neg/tailcall/t3275.scala", defaultOptions) + + compileFile("../tests/neg/tailcall/t6574.scala", defaultOptions) + + compileFile("../tests/neg/tailcall/tailrec.scala", defaultOptions) + + compileFile("../tests/neg/tailcall/tailrec-2.scala", defaultOptions) + + compileFile("../tests/neg/tailcall/tailrec-3.scala", defaultOptions) + + compileDir("../tests/neg/typedIdents", defaultOptions) + }.neg() // Run tests ----------------------------------------------------------------- @Test def runAll: Unit = - compileFilesInDir("../tests/run", defaultOptions).run - - @Test def runArraycopy: Unit = - compileFile("../tests/run/arraycopy.scala", defaultOptions).run - - // Benchmark Tests ----------------------------------------------------------- - - @Test def t2168Pos: Unit = - compileFile("../tests/pos/t2168.scala", defaultOptions).times(2).pos - - @Test def erasurePos: Unit = - compileFile("../tests/pos/erasure.scala", defaultOptions).times(2).pos - - @Test def coderPos: Unit = - compileFile("../tests/pos/Coder.scala", defaultOptions).times(2).pos - - @Test def blockescapesPos: Unit = - compileFile("../tests/pos/blockescapes.scala", defaultOptions).times(2).pos - - @Test def collectionsPos: Unit = - compileFile("../tests/pos/collections.scala", defaultOptions).times(2).pos - - @Test def functions1Pos: Unit = - compileFile("../tests/pos/functions1.scala", defaultOptions).times(2).pos - - @Test def implicits1Pos: Unit = - compileFile("../tests/pos/implicits1.scala", defaultOptions).times(2).pos - - @Test def inferredPos: Unit = - compileFile("../tests/pos/inferred.scala", defaultOptions).times(2).pos - - @Test def patternsPos: Unit = - compileFile("../tests/pos/Patterns.scala", defaultOptions).times(2).pos - - @Test def selftypesPos: Unit = - compileFile("../tests/pos/selftypes.scala", defaultOptions).times(2).pos - - @Test def varargsPos: Unit = - compileFile("../tests/pos/varargs.scala", defaultOptions).times(2).pos - - @Test def varargPatternsPos: Unit = - compileFile("../tests/pos/vararg-pattern.scala", defaultOptions).times(2).pos - - @Test def opassignPos: Unit = - compileFile("../tests/pos/opassign.scala", defaultOptions).times(2).pos - - @Test def typedapplyPos: Unit = - compileFile("../tests/pos/typedapply.scala", defaultOptions).times(2).pos - - @Test def nameddefaultsPos: Unit = - compileFile("../tests/pos/nameddefaults.scala", defaultOptions).times(2).pos - - @Test def desugarPos: Unit = - compileFile("../tests/pos/desugar.scala", defaultOptions).times(2).pos - - @Test def sigsPos: Unit = - compileFile("../tests/pos/sigs.scala", defaultOptions).times(2).pos - - @Test def typersPos: Unit = - compileFile("../tests/pos/typers.scala", defaultOptions).times(2).pos - - @Test def typedIdentsPos: Unit = - compileDir("../tests/pos/typedIdents", defaultOptions).times(2).pos - - @Test def assignmentsPos: Unit = - compileFile("../tests/pos/assignments.scala", defaultOptions).times(2).pos - - @Test def packageobjectPos: Unit = - compileFile("../tests/pos/packageobject.scala", defaultOptions).times(2).pos - - @Test def overloadedPos: Unit = - compileFile("../tests/pos/overloaded.scala", defaultOptions).times(2).pos - - @Test def overridesPos: Unit = - compileFile("../tests/pos/overrides.scala", defaultOptions).times(2).pos - - @Test def javaOverridePos: Unit = - compileDir("../tests/pos/java-override", defaultOptions).times(2).pos - - @Test def templateParentsPos: Unit = - compileFile("../tests/pos/templateParents.scala", defaultOptions).times(2).pos - - @Test def overloadedAccessPos: Unit = - compileFile("../tests/pos/overloadedAccess.scala", defaultOptions).times(2).pos - - @Test def approximateUnionPos: Unit = - compileFile("../tests/pos/approximateUnion.scala", defaultOptions).times(2).pos - - @Test def tailcallPos: Unit = - compileFilesInDir("../tests/pos/tailcall", defaultOptions).times(2).pos - - @Test def valueclassesPos: Unit = - compileShallowFilesInDir("../tests/pos/pos_valueclasses", defaultOptions).times(2).pos - - @Test def subtypingPos: Unit = - compileFile("../tests/pos/subtyping.scala", defaultOptions).times(2).pos - - @Test def packageObjPos: Unit = - compileFile("../tests/pos/i0239.scala", defaultOptions).times(2).pos - - @Test def anonClassSubtypingPos: Unit = - compileFile("../tests/pos/anonClassSubtyping.scala", defaultOptions).times(2).pos - - @Test def extmethodsPos: Unit = - compileFile("../tests/pos/extmethods.scala", defaultOptions).times(2).pos - - @Test def companionsPos: Unit = - compileFile("../tests/pos/companions.scala", defaultOptions).times(2).pos + compileFilesInDir("../tests/run", defaultOptions).run() // Pickling Tests ------------------------------------------------------------ - @Test def testPickling: Unit = - compileFilesInDir("../tests/pickling", picklingOptions).pos - - @Test def testPicklingAst: Unit = - compileDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions).pos - - @Test def testPicklingInf: Unit = - compileFile("../tests/pos/pickleinf.scala", picklingOptions).pos - - @Test def tastyNew: Unit = - compileFilesInDir("../tests/new", picklingOptions).pos - - @Test def tastyRuntime: Unit = - compileDir("../library/src/dotty/runtime", picklingOptions).pos - - @Test def tastyTools: Unit = - compileDir("../compiler/src/dotty/tools", picklingOptions).pos - - @Test def tastyBackendJvm: Unit = - compileDir("../compiler/src/dotty/tools/backend/jvm", picklingOptions).pos - - @Test def tastyDotc: Unit = - compileDir("../compiler/src/dotty/tools/dotc", picklingOptions).pos - - @Test def tastyDotcAst: Unit = - compileDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions).pos - - @Test def tastyDotcConfig: Unit = - compileDir("../compiler/src/dotty/tools/dotc/config", picklingOptions).pos - - @Test def tastyCore: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core", picklingOptions).pos - - @Test def tastyClassfile: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core/classfile", picklingOptions).pos - - @Test def dotcTasty: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core/tasty", picklingOptions).pos - - @Test def tastyUnpickleScala2: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core/unpickleScala2", picklingOptions).pos - - @Test def tastyParsing: Unit = - compileDir("../compiler/src/dotty/tools/dotc/parsing", picklingOptions).pos - - @Test def tastyPrinting: Unit = - compileDir("../compiler/src/dotty/tools/dotc/printing", picklingOptions).pos - - @Test def tastyRepl: Unit = - compileDir("../compiler/src/dotty/tools/dotc/repl", picklingOptions).pos - - @Test def tastyRewrite: Unit = - compileDir("../compiler/src/dotty/tools/dotc/rewrite", picklingOptions).pos - - @Test def tastyTransform: Unit = - compileDir("../compiler/src/dotty/tools/dotc/transform", picklingOptions).pos - - @Test def tastyTyper: Unit = - compileDir("../compiler/src/dotty/tools/dotc/typer", picklingOptions).pos - - @Test def tastyUtil: Unit = - compileDir("../compiler/src/dotty/tools/dotc/util", picklingOptions).pos - - @Test def tastyIo: Unit = - compileDir("../compiler/src/dotty/tools/io", picklingOptions).pos - - @Test def tastyTests: Unit = - compileDir("../tests/tasty", picklingOptions).pos - + @Test def testPickling1: Unit = { + compileFilesInDir("../tests/new", picklingOptions) + + compileFilesInDir("../tests/pickling", picklingOptions) + + compileDir("../library/src/dotty/runtime", picklingOptions) + + compileDir("../compiler/src/dotty/tools/backend/jvm", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/ast", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/core", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/config", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/parsing", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/printing", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/repl", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/rewrite", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/transform", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/typer", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/util", picklingOptions) + + compileDir("../compiler/src/dotty/tools/io", picklingOptions) + + compileFile("../tests/pos/pickleinf.scala", picklingOptions) + }.limitThreads(4).pos() + + @Test def testPickling2: Unit = { + compileDir("../compiler/src/dotty/tools/dotc/core/classfile", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/core/tasty", picklingOptions) + + compileDir("../compiler/src/dotty/tools/dotc/core/unpickleScala2", picklingOptions) + }.limitThreads(4).pos() + + @Test def testPickling3: Unit = { + compileDir("../compiler/src/dotty/tools", picklingOptions) + }.limitThreads(4).pos() + + @Test def testPickling4: Unit = { + compileDir("../compiler/src/dotty/tools/dotc", picklingOptions) + }.limitThreads(4).pos() + + /** The purpose of this test is two-fold, being able to compile dotty + * bootstrapped, and making sure that TASTY can link against a compiled + * version of Dotty + */ @Test def tastyBootstrap: Unit = { def dotty1 = compileDir("../compiler/src/dotty", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 62bd9fb7bc8a..cdafed36f87a 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -14,15 +14,54 @@ import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.{ Files, Path, Paths, NoSuchFileException } import java.util.concurrent.{ Executors => JExecutors, TimeUnit } import scala.util.control.NonFatal +import scala.util.Try import java.util.HashMap trait ParallelTesting { - private case class Target(files: Array[JFile], outDir: JFile) { + private sealed trait Target { self => + def outDir: JFile + def flags: Array[String] + + def withFlags(newFlags: Array[String]) = self match { + case self: ConcurrentCompilationTarget => + self.copy(flags = newFlags) + case self: SeparateCompilationTarget => + self.copy(flags = newFlags) + } + } + private final case class ConcurrentCompilationTarget( + files: Array[JFile], + flags: Array[String], + outDir: JFile + ) extends Target { override def toString() = outDir.toString } + private final case class SeparateCompilationTarget( + dir: JFile, + flags: Array[String], + outDir: JFile + ) extends Target { + + def compilationUnits: 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(isCompilable)) + } - private abstract class CompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) { + private abstract class CompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) { val totalTargets = targets.length private[this] var _errors = 0 @@ -51,20 +90,20 @@ trait ParallelTesting { "[" + ("=" * (math.max(progress - 1, 0))) + (if (progress > 0) ">" else "") + (" " * (39 - progress)) + - s"] compiling ($tCompiled/$totalTargets, ${timestamp}s) in '$fromDir'\r" + s"] compiling ($tCompiled/$totalTargets, ${timestamp}s)\r" ) - Thread.sleep(50) + Thread.sleep(100) tCompiled = targetsCompiled } // println, otherwise no newline and cursor at start of line println( s"[=======================================] compiled ($totalTargets/$totalTargets, " + - s"${(System.currentTimeMillis - start) / 1000}s) in '$fromDir' errors: $errors" + s"${(System.currentTimeMillis - start) / 1000}s) " ) } } - protected def compileTry(op: => Unit) = + protected def compileTry(op: => Unit): Unit = try op catch { case NonFatal(e) => { // if an exception is thrown during compilation, the complete test @@ -80,7 +119,11 @@ trait ParallelTesting { private[ParallelTesting] def execute(): this.type = { assert(_targetsCompiled == 0, "not allowed to re-use a `CompileRun`") - val pool = JExecutors.newWorkStealingPool() + val pool = threadLimit match { + case Some(i) => JExecutors.newWorkStealingPool(i) + case None => JExecutors.newWorkStealingPool() + } + pool.submit(statusRunner) targets.foreach { target => @@ -93,19 +136,35 @@ trait ParallelTesting { } } - private final class PosCompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) - extends CompileRun(targets, fromDir, flags, times) { + @inline private final def isCompilable(f: JFile): Boolean = { + val name = f.getName + name.endsWith(".scala") || name.endsWith(".java") + } + + private final class PosCompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) + extends CompileRun(targets, times, threadLimit) { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { - val sourceFiles = target.files.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - val reporter = compile(sourceFiles, flags, false, times, target.outDir) - completeCompilation(reporter.errorCount) + target match { + case ConcurrentCompilationTarget(files, flags, outDir) => { + val sourceFiles = files.filter(isCompilable) + val reporter = compile(sourceFiles, flags, false, times, outDir) + completeCompilation(reporter.errorCount) + } + + case target @ SeparateCompilationTarget(dir, flags, outDir) => { + val compilationUnits = target.compilationUnits + val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, false, times, outDir)) + completeCompilation(reporters.foldLeft(0)(_ + _.errorCount)) + } + } + } } } - private final class RunCompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) - extends CompileRun(targets, fromDir, flags, times) { + private final class RunCompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) + extends CompileRun(targets, times, threadLimit) { private def verifyOutput(checkFile: JFile, dir: JFile) = try { // Do classloading magic and running here: import java.net.{ URL, URLClassLoader } @@ -150,13 +209,31 @@ trait ParallelTesting { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { - val sourceFiles = target.files.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - val checkFile = target.files.find(_.getName.endsWith(".check")) - val reporter = compile(sourceFiles, flags, false, times, target.outDir) - completeCompilation(reporter.errorCount) + val (errorCount, hasCheckFile, doVerify) = target match { + case ConcurrentCompilationTarget(files, flags, outDir) => { + val sourceFiles = files.filter(isCompilable) + val checkFile = files.find(_.getName.endsWith(".check")) + val reporter = compile(sourceFiles, flags, false, times, outDir) + + completeCompilation(reporter.errorCount) + (reporter.errorCount, checkFile.isDefined, () => verifyOutput(checkFile.get, outDir)) + } + + case target @ SeparateCompilationTarget(dir, flags, outDir) => { + val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + ".check") + val errorCount = + target + .compilationUnits + .map(files => compile(files.filter(isCompilable), flags, false, times, outDir)) + .foldLeft(0)(_ + _.errorCount) + + completeCompilation(errorCount) + (errorCount, checkFile.exists, () => verifyOutput(checkFile, outDir)) + } + } - if (reporter.errorCount == 0 && checkFile.isDefined) verifyOutput(checkFile.get, target.outDir) - else if (reporter.errorCount > 0) { + if (errorCount == 0 && hasCheckFile) doVerify() + else if (errorCount > 0) { System.err.println(s"\nCompilation failed for: '$target'") fail() } @@ -164,41 +241,40 @@ trait ParallelTesting { } } - private final class NegCompileRun(targets: List[Target], fromDir: String, flags: Array[String], times: Int) - extends CompileRun(targets, fromDir, flags, times) { + private final class NegCompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) + extends CompileRun(targets, times, threadLimit) { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { - val sourceFiles = target.files.filter(f => f.getName.endsWith(".scala") || f.getName.endsWith(".java")) - // 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` - val errorMap = new HashMap[String, Integer]() - var expectedErrors = 0 - target.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) + def errorMapAndExpected(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 } - - expectedErrors += noposErrors + errors } - } - val reporter = compile(sourceFiles, flags, true, times, target.outDir) - val actualErrors = reporter.errorCount + (errorMap, expectedErrors) + } - def missingAnnotations = !reporter.errors.forall { error => + def getMissingAnnotations(errorMap: HashMap[String, Integer], reporterErrors: List[MessageContainer]) = !reporterErrors.forall { error => val getter = if (error.pos.exists) { val fileName = error.pos.source.file.toString s"$fileName:${error.pos.line}" @@ -220,13 +296,33 @@ trait ParallelTesting { } } + val (expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = target match { + case ConcurrentCompilationTarget(files, flags, outDir) => { + val sourceFiles = files.filter(isCompilable) + val (errorMap, expectedErrors) = errorMapAndExpected(sourceFiles) + val reporter = compile(sourceFiles, flags, true, times, outDir) + val actualErrors = reporter.errorCount + + (expectedErrors, actualErrors, () => getMissingAnnotations(errorMap, reporter.errors), errorMap) + } + + case target @ SeparateCompilationTarget(dir, flags, outDir) => { + val compilationUnits = target.compilationUnits + val (errorMap, expectedErrors) = errorMapAndExpected(compilationUnits.toArray.flatten) + val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, true, times, outDir)) + val actualErrors = reporters.foldLeft(0)(_ + _.errorCount) + val errors = reporters.flatMap(_.errors) + (expectedErrors, actualErrors, () => getMissingAnnotations(errorMap, errors), errorMap) + } + } + if (expectedErrors != actualErrors) { System.err.println { s"\nWrong number of errors encountered when compiling $target, expected: $expectedErrors, actual: $actualErrors\n" } fail() } - else if (missingAnnotations) { + else if (hasMissingAnnotations()) { System.err.println { s"\nErrors found on incorrect row numbers when compiling $target" } @@ -289,6 +385,11 @@ trait ParallelTesting { } } + def addOutDir(xs: Array[String]): Array[String] = { + val (beforeCp, cp :: cpArg :: rest) = xs.toList.span(_ != "-classpath") + (beforeCp ++ (cp :: (cpArg + s":${targetDir.getAbsolutePath}") :: rest)).toArray + } + def compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) { val scalaLib = findJarFromRuntime("scala-library") val fullArgs = Array( @@ -326,7 +427,7 @@ trait ParallelTesting { } } - driver.process(flags ++ files.map(_.getAbsolutePath), reporter = reporter) + driver.process(addOutDir(flags) ++ files.map(_.getAbsolutePath), reporter = reporter) // If the java files failed compilation before, we try again after: if (!javaCompiledBefore) @@ -336,52 +437,62 @@ trait ParallelTesting { else reporter } - class CompilationTest private ( - targets: List[Target], - fromDir: String, - flags: Array[String], - times: Int, - shouldDelete: Boolean + final class CompilationTest private ( + private[ParallelTesting] val targets: List[Target], + private[ParallelTesting] val times: Int, + private[ParallelTesting] val shouldDelete: Boolean, + private[ParallelTesting] val threadLimit: Option[Int] ) { - private[ParallelTesting] def this(target: Target, fromDir: String, flags: Array[String]) = - this(List(target), fromDir, flags, 1, true) + private[ParallelTesting] def this(target: Target) = + this(List(target), 1, true, None) - private[ParallelTesting] def this(targets: List[Target], fromDir: String, flags: Array[String]) = - this(targets, fromDir, flags, 1, true) + private[ParallelTesting] def this(targets: List[Target]) = + this(targets, 1, true, None) + + 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") + new CompilationTest(targets ++ other.targets, times, shouldDelete, threadLimit) + } - def pos: this.type = { - val run = new PosCompileRun(targets, fromDir, flags, times).execute() - assert(run.errors == 0, s"Expected no errors when compiling $fromDir") + def pos(): this.type = { + val runErrors = new PosCompileRun(targets, times, threadLimit).execute().errors + assert(runErrors == 0, s"Expected no errors when compiling") if (shouldDelete) targets.foreach(t => delete(t.outDir)) this } - def neg: this.type = { + def neg(): this.type = { assert( - !(new NegCompileRun(targets, fromDir, flags, times).execute().didFail), - s"Wrong number of errors encountered when compiling $fromDir" + !(new NegCompileRun(targets, times, threadLimit).execute().didFail), + s"Wrong number of errors encountered when compiling" ) if (shouldDelete) targets.foreach(t => delete(t.outDir)) this } - def run: this.type = { - assert( - !(new RunCompileRun(targets, fromDir, flags, times).execute().didFail), - s"Run tests failed for test $fromDir" - ) + def run(): this.type = { + val didFail = new RunCompileRun(targets, times, threadLimit).execute().didFail + assert(!didFail, s"Run tests failed") if (shouldDelete) targets.foreach(t => delete(t.outDir)) this } def times(i: Int): CompilationTest = - new CompilationTest(targets, fromDir, flags, i, shouldDelete) + new CompilationTest(targets, i, shouldDelete, threadLimit) - def verbose: CompilationTest = - new CompilationTest(targets, fromDir, flags ++ Array("-verbose", "-Ylog-classpath"), times, shouldDelete) + def verbose: CompilationTest = new CompilationTest( + targets.map(t => t.withFlags(t.flags ++ Array("-verbose", "-Ylog-classpath"))), + times, + shouldDelete, + threadLimit + ) def keepOutput: CompilationTest = - new CompilationTest(targets, fromDir, flags, times, false) + new CompilationTest(targets, times, false, threadLimit) + + def limitThreads(i: Int) = + new CompilationTest(targets, times, shouldDelete, Some(i)) def delete(): Unit = targets.foreach(t => delete(t.outDir)) @@ -441,8 +552,12 @@ trait ParallelTesting { s"Source file: $f, didn't exist" ) - val target = Target(Array(sourceFile), toCompilerDirFromFile(sourceFile, parent, outDir)) - new CompilationTest(target, f, flags) + val target = ConcurrentCompilationTarget( + Array(sourceFile), + flags, + toCompilerDirFromFile(sourceFile, parent, outDir) + ) + new CompilationTest(target) } def compileDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { @@ -461,8 +576,8 @@ trait ParallelTesting { val targetDir = new JFile(outDir) targetDir.mkdirs() - val target = Target(flatten(sourceDir), targetDir) - new CompilationTest(target, f, flags) + val target = ConcurrentCompilationTarget(flatten(sourceDir), flags, targetDir) + new CompilationTest(target) } def compileList(files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { @@ -476,10 +591,10 @@ trait ParallelTesting { targetDir.mkdirs() assert(targetDir.exists, s"couldn't create target directory: $targetDir") - val target = Target(files.map(new JFile(_)).toArray, targetDir) + val target = ConcurrentCompilationTarget(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, outDir.toString, flags) + new CompilationTest(target) } def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { @@ -493,11 +608,11 @@ trait ParallelTesting { val (dirs, files) = compilationTargets(sourceDir) val targets = - files.map(f => Target(Array(f), toCompilerDirFromFile(f, sourceDir, outDir))) ++ - dirs.map(dir => Target(dir.listFiles, toCompilerDirFromDir(dir, sourceDir, outDir))) + files.map(f => ConcurrentCompilationTarget(Array(f), flags, toCompilerDirFromFile(f, sourceDir, outDir))) ++ + dirs.map(dir => SeparateCompilationTarget(dir, flags, toCompilerDirFromDir(dir, sourceDir, outDir))) // Create a CompilationTest and let the user decide whether to execute a pos or a neg test - new CompilationTest(targets, f, flags) + new CompilationTest(targets) } def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { @@ -511,10 +626,10 @@ trait ParallelTesting { val (_, files) = compilationTargets(sourceDir) val targets = files.map { file => - Target(Array(file), toCompilerDirFromFile(file, sourceDir, outDir)) + ConcurrentCompilationTarget(Array(file), flags, toCompilerDirFromFile(file, sourceDir, outDir)) } // Create a CompilationTest and let the user decide whether to execute a pos or a neg test - new CompilationTest(targets, f, flags) + new CompilationTest(targets) } } From 40627780e12fa1b08d57f298e8a95fcde57f0601 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 20 Mar 2017 15:34:46 +0100 Subject: [PATCH 19/39] Fix reflective method lookup to work for both scalac & dotty --- .../dotty/tools/dotc/ParallelTesting.scala | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index cdafed36f87a..884fd155ec19 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -15,6 +15,7 @@ import java.nio.file.{ Files, Path, Paths, NoSuchFileException } import java.util.concurrent.{ Executors => JExecutors, TimeUnit } import scala.util.control.NonFatal import scala.util.Try +import scala.collection.mutable import java.util.HashMap trait ParallelTesting { @@ -539,13 +540,31 @@ trait ParallelTesting { else (dirs, f :: files) } + 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 } + } + .take(6).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") + } + } + def compileFile(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - // each calling method gets its own unique output directory, in which we - // place the dir being compiled: - val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName - val outDir = outDirectory + callingMethod + "/" val sourceFile = new JFile(f) val parent = sourceFile.getParentFile + val outDir = + outDirectory + getCallingMethod + "/" + + sourceFile.getName.substring(0, sourceFile.getName.lastIndexOf('.')) + "/" + require( sourceFile.exists && !sourceFile.isDirectory && (parent ne null) && parent.exists && parent.isDirectory, @@ -561,10 +580,7 @@ trait ParallelTesting { } def compileDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - // each calling method gets its own unique output directory, in which we - // place the dir being compiled: - val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName - val outDir = outDirectory + callingMethod + "/" + val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) requirements(f, sourceDir, outDir) @@ -573,7 +589,7 @@ trait ParallelTesting { else Array(f) // Directories in which to compile all containing files with `flags`: - val targetDir = new JFile(outDir) + val targetDir = new JFile(outDir + "/" + sourceDir.getName + "/") targetDir.mkdirs() val target = ConcurrentCompilationTarget(flatten(sourceDir), flags, targetDir) @@ -581,10 +597,7 @@ trait ParallelTesting { } def compileList(files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { - // each calling method gets its own unique output directory, in which we - // place the dir being compiled: - val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName - val outDir = outDirectory + callingMethod + "/" + val outDir = outDirectory + getCallingMethod + "/" + testName + "/" // Directories in which to compile all containing files with `flags`: val targetDir = new JFile(outDir) @@ -598,10 +611,7 @@ trait ParallelTesting { } def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - // each calling method gets its own unique output directory, in which we - // place the dir being compiled: - val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName - val outDir = outDirectory + callingMethod + "/" + val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) requirements(f, sourceDir, outDir) @@ -616,10 +626,7 @@ trait ParallelTesting { } def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - // each calling method gets its own unique output directory, in which we - // place the dir being compiled: - val callingMethod = Thread.currentThread.getStackTrace.apply(3).getMethodName - val outDir = outDirectory + callingMethod + "/" + val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) requirements(f, sourceDir, outDir) From d2e50d92be4038bf0ec07a61caff7959b8f2a583 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 20 Mar 2017 15:40:13 +0100 Subject: [PATCH 20/39] Make compileList take test name arg --- compiler/test/dotc/comptest.scala | 1 + compiler/test/dotty/tools/dotc/CompilationTests.scala | 10 +++++++--- compiler/test/dotty/tools/dotc/ParallelTesting.scala | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/test/dotc/comptest.scala b/compiler/test/dotc/comptest.scala index b1a6fa266996..03e3d859e575 100644 --- a/compiler/test/dotc/comptest.scala +++ b/compiler/test/dotc/comptest.scala @@ -12,6 +12,7 @@ object comptest extends ParallelTesting { def main(args: Array[String]): Unit = compileList( + "comptest", List( dotcDir + "tools/dotc/CompilationUnit.scala", dotcDir + "tools/dotc/core/Types.scala", diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 8136cb8612ce..3283e11ef68d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -10,14 +10,15 @@ class CompilationTests extends ParallelTesting { // Positive tests ------------------------------------------------------------ - @Test def compilePos: Unit = - compileFilesInDir("../tests/pos", defaultOptions).pos + @Test def compilePos: Unit = { + compileList("compileStdLib", StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")) + + compileFilesInDir("../tests/pos", defaultOptions) + }.pos() @Test def compilePosScala2: Unit = compileFilesInDir("../tests/pos-scala2", scala2Mode).pos() @Test def compilePosMixedFlags: Unit = { - compileList(StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")) + compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")) + // this guy sucks, he changes the sourcefile itself: // compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")) + @@ -25,6 +26,7 @@ class CompilationTests extends ParallelTesting { compileFile("../tests/pos-special/utf8encoded.scala", explicitUTF8) + compileFile("../tests/pos-special/utf16encoded.scala", explicitUTF16) + compileList( + "compileMixed", List( "../tests/pos/B.scala", "../scala-scala/src/library/scala/collection/immutable/Seq.scala", @@ -40,6 +42,7 @@ class CompilationTests extends ParallelTesting { compileFile("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala", defaultOptions) + compileFile("../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", defaultOptions) + compileList( + "parSetSubset", List( "../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala", "../scala-scala/src/library/scala/collection/parallel/mutable/ParSet.scala", @@ -103,6 +106,7 @@ class CompilationTests extends ParallelTesting { compileFile("../tests/pos/extmethods.scala", defaultOptions) + compileFile("../tests/pos/companions.scala", defaultOptions) + compileList( + "testNonCyclic", List( "../compiler/src/dotty/tools/dotc/CompilationUnit.scala", "../compiler/src/dotty/tools/dotc/core/Types.scala", diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 884fd155ec19..0cb6e8d7cff1 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -596,7 +596,7 @@ trait ParallelTesting { new CompilationTest(target) } - def compileList(files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { + def compileList(testName: String, files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + getCallingMethod + "/" + testName + "/" // Directories in which to compile all containing files with `flags`: From 0df3f618b2540ec0c87d16ef911f2f77bbe1b327 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 20 Mar 2017 15:44:10 +0100 Subject: [PATCH 21/39] Support copyToDir for tests that mutate original source --- .../dotty/tools/dotc/CompilationTests.scala | 3 +-- .../dotty/tools/dotc/ParallelTesting.scala | 23 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 3283e11ef68d..6ce5fe95ad20 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -20,8 +20,7 @@ class CompilationTests extends ParallelTesting { @Test def compilePosMixedFlags: Unit = { compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")) + - // this guy sucks, he changes the sourcefile itself: - // compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")) + + compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")).copyToTarget() + compileFile("../tests/pos-special/t8146a.scala", allowDeepSubtypes) + compileFile("../tests/pos-special/utf8encoded.scala", explicitUTF8) + compileFile("../tests/pos-special/utf16encoded.scala", explicitUTF16) + diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 0cb6e8d7cff1..c9b9a8f64b75 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -479,6 +479,23 @@ trait ParallelTesting { this } + 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 + } + + def copyToTarget(): CompilationTest = new CompilationTest ( + targets.map { + case target @ ConcurrentCompilationTarget(files, _, outDir) => + target.copy(files = files.map(copyToDir(outDir,_))) + case target @ SeparateCompilationTarget(dir, _, outDir) => + target.copy(dir = copyToDir(outDir, dir)) + }, + times, shouldDelete, threadLimit + ) + def times(i: Int): CompilationTest = new CompilationTest(targets, i, shouldDelete, threadLimit) @@ -521,12 +538,6 @@ trait ParallelTesting { targetDir } - private def copyToDir(dir: JFile, file: JFile): Unit = { - val target = Paths.get(dir.getAbsolutePath, file.getName) - Files.copy(file.toPath, target, REPLACE_EXISTING) - if (file.isDirectory) file.listFiles.map(copyToDir(target.toFile, _)) - } - private def requirements(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") From 8c02da6858615dacfbcc09c2431e0945fce6d730 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 20 Mar 2017 15:44:46 +0100 Subject: [PATCH 22/39] Fix tasty bootstrap --- .../dotty/tools/dotc/CompilationTests.scala | 47 +++++++++++++------ .../dotty/tools/dotc/ParallelTesting.scala | 31 +++++++----- out/.keep | 0 3 files changed, 52 insertions(+), 26 deletions(-) delete mode 100644 out/.keep diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6ce5fe95ad20..e7ecd342a585 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -197,23 +197,42 @@ class CompilationTests extends ParallelTesting { * version of Dotty */ @Test def tastyBootstrap: Unit = { - def dotty1 = - compileDir("../compiler/src/dotty", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) - def lib = - compileDir("../library/src", defaultOptions) - def dotty2 = - compileDir("../compiler/src/dotty", defaultOptions.and("-priorityclasspath", defaultOutputDir)) + val opt = Array( + "-classpath", + // compile with bootstrapped library on cp: + defaultOutputDir + "lib$1/src/:" + + // as well as bootstrapped compiler: + defaultOutputDir + "dotty1$1/dotty/:" + + Jars.dottyInterfaces + ) - List(dotty1, lib, dotty2).map(_.keepOutput.pos).foreach(_.delete()) - } + def lib = + compileDir("../library/src", + allowDeepSubtypes.and("-Ycheck-reentrant", "-strict", "-priorityclasspath", defaultOutputDir)) - @Test def dotty = { - def bootedLib = - compileDir("../library/src", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) - def dottyDependsOnBootedLib = - compileDir("../compiler/src/dotty", allowDeepSubtypes.and("-Ycheck-reentrant", "-strict")) + def dotty1 = + compileDir("../compiler/src/dotty", opt) - List(bootedLib, dottyDependsOnBootedLib).map(_.keepOutput.pos).foreach(_.delete()) + def dotty2 = + compileShallowFilesInDir("../compiler/src/dotty", opt) + + { + lib.keepOutput :: dotty1.keepOutput :: { + dotty2 + + compileShallowFilesInDir("../compiler/src/dotty/tools", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/ast", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/config", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/parsing", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/printing", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/repl", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/reporting", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/rewrite", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/transform", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/typer", opt) + + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/util", opt) + } :: Nil + }.map(_.pos()).foreach(_.delete()) } } diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index c9b9a8f64b75..d89a8940b13f 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -24,13 +24,16 @@ trait ParallelTesting { def outDir: JFile def flags: Array[String] - def withFlags(newFlags: Array[String]) = self match { - case self: ConcurrentCompilationTarget => - self.copy(flags = newFlags) - case self: SeparateCompilationTarget => - self.copy(flags = newFlags) - } + def withFlags(newFlags: Array[String]) = + if (!flags.containsSlice(newFlags)) self match { + case self: ConcurrentCompilationTarget => + self.copy(flags = newFlags) + case self: SeparateCompilationTarget => + self.copy(flags = newFlags) + } + else self } + private final case class ConcurrentCompilationTarget( files: Array[JFile], flags: Array[String], @@ -387,8 +390,12 @@ trait ParallelTesting { } def addOutDir(xs: Array[String]): Array[String] = { - val (beforeCp, cp :: cpArg :: rest) = xs.toList.span(_ != "-classpath") - (beforeCp ++ (cp :: (cpArg + s":${targetDir.getAbsolutePath}") :: rest)).toArray + 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) { @@ -538,7 +545,7 @@ trait ParallelTesting { targetDir } - private def requirements(f: String, sourceDir: JFile, outDir: String): Unit = { + 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") } @@ -593,7 +600,7 @@ trait ParallelTesting { def compileDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) - requirements(f, sourceDir, outDir) + checkRequirements(f, sourceDir, outDir) def flatten(f: JFile): Array[JFile] = if (f.isDirectory) f.listFiles.flatMap(flatten) @@ -624,7 +631,7 @@ trait ParallelTesting { def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) - requirements(f, sourceDir, outDir) + checkRequirements(f, sourceDir, outDir) val (dirs, files) = compilationTargets(sourceDir) @@ -639,7 +646,7 @@ trait ParallelTesting { def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) - requirements(f, sourceDir, outDir) + checkRequirements(f, sourceDir, outDir) val (_, files) = compilationTargets(sourceDir) diff --git a/out/.keep b/out/.keep deleted file mode 100644 index e69de29bb2d1..000000000000 From 52488dae47d7c17dbac36c5d1d985e2a81ebe9de Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 20 Mar 2017 17:05:50 +0100 Subject: [PATCH 23/39] Disable interactive mode in CI --- compiler/test/dotc/comptest.scala | 2 ++ compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 ++ compiler/test/dotty/tools/dotc/ParallelTesting.scala | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotc/comptest.scala b/compiler/test/dotc/comptest.scala index 03e3d859e575..c2c88abb3c5b 100644 --- a/compiler/test/dotc/comptest.scala +++ b/compiler/test/dotc/comptest.scala @@ -4,6 +4,8 @@ import dotty.tools.dotc.ParallelTesting object comptest extends ParallelTesting { + def interactive: Boolean = true + implicit val defaultOutputDir: String = "." val posDir = "./tests/pos/" diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index e7ecd342a585..8df415a377f3 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -8,6 +8,8 @@ import java.io.{ File => JFile } class CompilationTests extends ParallelTesting { import CompilationTests._ + def interactive: Boolean = !sys.env.contains("DRONE") + // Positive tests ------------------------------------------------------------ @Test def compilePos: Unit = { diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index d89a8940b13f..54f8effcdb68 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -20,6 +20,8 @@ import java.util.HashMap trait ParallelTesting { + def interactive: Boolean + private sealed trait Target { self => def outDir: JFile def flags: Array[String] @@ -128,7 +130,7 @@ trait ParallelTesting { case None => JExecutors.newWorkStealingPool() } - pool.submit(statusRunner) + if (interactive) pool.submit(statusRunner) targets.foreach { target => pool.submit(compilationRunnable(target)) From 5bd99fc8c862bed33f0be0f7c05e1037bebf05a1 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 21 Mar 2017 11:09:22 +0100 Subject: [PATCH 24/39] Remove verbose prints in dottydoc tests --- doc-tool/test/DottyDocTest.scala | 40 +++++++++++++++++++++++++-- doc-tool/test/WhitelistedStdLib.scala | 8 ++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/doc-tool/test/DottyDocTest.scala b/doc-tool/test/DottyDocTest.scala index 4202dca73941..9e7b70c8f868 100644 --- a/doc-tool/test/DottyDocTest.scala +++ b/doc-tool/test/DottyDocTest.scala @@ -9,8 +9,13 @@ import dotc.typer.FrontEnd import dottydoc.core.{ DocASTPhase, ContextDottydoc } import model.Package import dotty.tools.dottydoc.util.syntax._ +import dotc.reporting.{ StoreReporter, MessageRendering } +import dotc.interfaces.Diagnostic.ERROR +import org.junit.Assert.fail -trait DottyDocTest { +import java.io.{ BufferedWriter, OutputStreamWriter } + +trait DottyDocTest extends MessageRendering { dotty.tools.dotc.parsing.Scanners // initialize keywords implicit val ctx: FreshContext = { @@ -26,6 +31,7 @@ trait DottyDocTest { ctx.settings.classpath, dotty.Jars.dottyLib ) + ctx.setReporter(new StoreReporter(ctx.reporter)) base.initialize()(ctx) ctx } @@ -36,17 +42,47 @@ trait DottyDocTest { def phaseName = "assertionPhase" override def run(implicit ctx: Context): Unit = assertion(ctx.docbase.packages) + if (ctx.reporter.hasErrors) { + System.err.println("reporter had errors:") + ctx.reporter.removeBufferedMessages.foreach { msg => + System.err.println { + messageAndPos(msg.contained, msg.pos, diagnosticLevel(msg)) + } + } + } }) :: Nil override def phases = super.phases ++ assertionPhase } + private def callingMethod: String = + Thread.currentThread.getStackTrace.find { + _.getMethodName match { + case "checkSource" | "callingMethod" | "getStackTrace" | "currentThread" => + false + case _ => + true + } + } + .map(_.getMethodName) + .getOrElse { + throw new IllegalStateException("couldn't get calling method via reflection") + } + + private def sourceFileFromString(name: String, contents: String): SourceFile = { + val virtualFile = new scala.reflect.io.VirtualFile(name) + val writer = new BufferedWriter(new OutputStreamWriter(virtualFile.output, "UTF-8")) + writer.write(contents) + writer.close() + new SourceFile(virtualFile, scala.io.Codec.UTF8) + } + def checkSource(source: String)(assertion: Map[String, Package] => Unit): Unit = { val c = compilerWithChecker(assertion) c.rootContext(ctx) val run = c.newRun - run.compile(source) + run.compileSources(sourceFileFromString(callingMethod, source) :: Nil) } def checkFiles(sources: List[String])(assertion: Map[String, Package] => Unit): Unit = { diff --git a/doc-tool/test/WhitelistedStdLib.scala b/doc-tool/test/WhitelistedStdLib.scala index 9092d1dedca7..d59457605a28 100644 --- a/doc-tool/test/WhitelistedStdLib.scala +++ b/doc-tool/test/WhitelistedStdLib.scala @@ -6,18 +6,16 @@ import org.junit.Assert._ class TestWhitelistedCollections extends DottyDocTest { - @Test def arrayHasDocumentation = + @Test def arrayAndImmutableHasDocumentation = checkFiles(TestWhitelistedCollections.files) { packages => val array = packages("scala") .children.find(_.path.mkString(".") == "scala.Array") .get - assert(array.comment.get.body.length > 0) - } + assert(array.comment.get.body.length > 0, + "scala.Array didn't have any documentation") - @Test def traitImmutableHasDocumentation = - checkFiles(TestWhitelistedCollections.files) { packages => val imm = packages("scala") .children.find(_.path.mkString(".") == "scala.Immutable") From a172e45ea3392f6fa526f64cb0e2947755f9a0c2 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 21 Mar 2017 15:26:37 +0100 Subject: [PATCH 25/39] Throw on timeout --- compiler/test/dotty/tools/dotc/ParallelTesting.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 54f8effcdb68..1652dd9c4e75 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -12,7 +12,7 @@ import interfaces.Diagnostic.ERROR 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 } +import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException } import scala.util.control.NonFatal import scala.util.Try import scala.collection.mutable @@ -137,7 +137,8 @@ trait ParallelTesting { } pool.shutdown() - pool.awaitTermination(10, TimeUnit.MINUTES) + if (!pool.awaitTermination(10, TimeUnit.MINUTES)) + throw new TimeoutException("Compiling targets timed out") this } } From 4e8eb320a882124630fb50d53d3d8c3721d9bd4e Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 22 Mar 2017 12:47:27 +0100 Subject: [PATCH 26/39] Consolidate test reporters in `TestReporter` always dumping log file --- .../dotc/reporting/ConsoleReporter.scala | 3 - .../dotty/tools/dotc/reporting/Reporter.scala | 2 +- .../dotty/tools/dotc/CompilationTests.scala | 3 +- .../dotty/tools/dotc/ParallelTesting.scala | 52 +++------ .../tools/dotc/reporting/TestReporter.scala | 107 +++++++++++++++--- .../transform/PatmatExhaustivityTest.scala | 4 +- 6 files changed, 108 insertions(+), 63 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 95f46899562a..9942b9ab9822 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -17,9 +17,6 @@ class ConsoleReporter( import MessageContainer._ - /** maximal number of error messages to be printed */ - protected def ErrorLimit = 100 - /** Prints the message. */ def printMessage(msg: String): Unit = { writer.print(msg + "\n"); writer.flush() } diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index b2c7abec9357..77ea1884dbcf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -63,7 +63,7 @@ trait Reporting { this: Context => |This can be achieved by adding the import clause 'import $fqname' |or by setting the compiler option -language:$feature. |See the Scala docs for value $fqname for a discussion - |why the feature $req be explicitly enabled.""" + |why the feature $req be explicitly enabled.""".stripMargin } } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 8df415a377f3..7f0f84049673 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -249,7 +249,8 @@ object CompilationTests { } val noCheckOptions = Array( - "-pagewidth", "120" + "-pagewidth", "120", + "-color:never" ) val checkOptions = Array( diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 1652dd9c4e75..5c9718ce2eb6 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -3,20 +3,22 @@ package tools package dotc import java.io.{ File => JFile } -import scala.io.Source - -import core.Contexts._ -import reporting.{ Reporter, UniqueMessagePositions, HideNonSensicalMessages, MessageRendering } -import reporting.diagnostic.MessageContainer -import interfaces.Diagnostic.ERROR +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 java.util.HashMap + +import core.Contexts._ +import reporting.{ Reporter, TestReporter } +import reporting.diagnostic.MessageContainer +import interfaces.Diagnostic.ERROR trait ParallelTesting { @@ -281,7 +283,7 @@ trait ParallelTesting { (errorMap, expectedErrors) } - def getMissingAnnotations(errorMap: HashMap[String, Integer], reporterErrors: List[MessageContainer]) = !reporterErrors.forall { error => + def getMissingAnnotations(errorMap: HashMap[String, Integer], reporterErrors: Iterator[MessageContainer]) = !reporterErrors.forall { error => val getter = if (error.pos.exists) { val fileName = error.pos.source.file.toString s"$fileName:${error.pos.line}" @@ -318,7 +320,7 @@ trait ParallelTesting { val (errorMap, expectedErrors) = errorMapAndExpected(compilationUnits.toArray.flatten) val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, true, times, outDir)) val actualErrors = reporters.foldLeft(0)(_ + _.errorCount) - val errors = reporters.flatMap(_.errors) + val errors = reporters.iterator.flatMap(_.errors) (expectedErrors, actualErrors, () => getMissingAnnotations(errorMap, errors), errorMap) } } @@ -347,33 +349,7 @@ trait ParallelTesting { } } - private class DaftReporter(suppress: Boolean) - extends Reporter with UniqueMessagePositions with HideNonSensicalMessages - with MessageRendering { - private var _errors: List[MessageContainer] = Nil - def errors = _errors - - private var _summary = new StringBuilder - def echoSummary(msg: String): this.type = { - _summary.append(msg) - this - } - - def printSummary(): this.type = { - val msg = _summary.toString - if (msg.nonEmpty) println(msg) - this - } - - override def doReport(m: MessageContainer)(implicit ctx: Context) = { - if (m.level == ERROR) { - _errors = m :: _errors - if (!suppress) System.err.println(messageAndPos(m.contained, m.pos, diagnosticLevel(m))) - } - } - } - - private def compile(files0: Array[JFile], flags0: Array[String], suppressErrors: Boolean, times: Int, targetDir: JFile): DaftReporter = { + private def compile(files0: Array[JFile], flags0: Array[String], suppressErrors: Boolean, times: Int, targetDir: JFile): TestReporter = { val flags = flags0 ++ Array("-d", targetDir.getAbsolutePath) @@ -418,7 +394,7 @@ trait ParallelTesting { val javaCompiledBefore = compileWithJavac(javaFiles) // Then we compile the scala files: - val reporter = new DaftReporter(suppress = suppressErrors) + val reporter = TestReporter.parallelReporter(logLevel = if (suppressErrors) ERROR + 1 else ERROR) val driver = if (times == 1) new Driver { def newCompiler(implicit ctx: Context) = new Compiler } else new Driver { @@ -428,7 +404,7 @@ trait ParallelTesting { (emptyReporter /: (1 to n)) ((_, i) => op(i)) private def echoSummary(rep: Reporter, msg: String)(implicit ctx: Context) = - rep.asInstanceOf[DaftReporter].echoSummary(msg) + rep.asInstanceOf[TestReporter].echoSummary(msg) override def doCompile(comp: Compiler, files: List[String])(implicit ctx: Context) = ntimes(times) { run => diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 169908c4f950..4fc7e5dfe93e 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -2,35 +2,61 @@ package dotty.tools package dotc package reporting +import java.io.{ PrintWriter, File => JFile, FileOutputStream } +import java.text.SimpleDateFormat +import java.util.Date + import scala.collection.mutable + import util.SourcePosition import core.Contexts._ import Reporter._ -import java.io.PrintWriter import diagnostic.{ Message, MessageContainer, NoExplanation } import diagnostic.messages._ +import interfaces.Diagnostic.{ ERROR, WARNING, INFO } -class TestReporter(writer: PrintWriter) extends Reporter -with UniqueMessagePositions with HideNonSensicalMessages { - +class TestReporter protected (outWriter: PrintWriter, protected val filePrintln: String => Unit, logLevel: Int) extends Reporter +with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { import MessageContainer._ - /** maximal number of error messages to be printed */ - protected def ErrorLimit = 100 + protected final val _errorBuf = mutable.ArrayBuffer.empty[MessageContainer] + final def errors: Iterator[MessageContainer] = _errorBuf.iterator - def printPos(pos: SourcePosition): Unit = + final def inlineInfo(pos: SourcePosition): String = if (pos.exists) { - if (pos.outer.exists) { - writer.println(s"\ninlined at ${pos.outer}:\n") - printPos(pos.outer) - } + if (pos.outer.exists) + s"\ninlined at ${pos.outer}:\n" + inlineInfo(pos.outer) + else "" + } + else "" + + final def printSummary(): this.type = { + val msg = _summary.toString + if (msg.nonEmpty) { + outWriter.println(msg) + filePrintln(msg) } + this + } + + private var _summary = new StringBuilder + final def echoSummary(msg: String): this.type = { + _summary.append(msg) + this + } /** Prints the message with the given position indication. */ - def printMessageAndPos(msg: String, pos: SourcePosition)(implicit ctx: Context): Unit = { - val posStr = s"${pos.line + 1}: " - writer.println(posStr + msg) - printPos(pos) + def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = { + val msg = messageAndPos(m.contained, m.pos, diagnosticLevel(m)) + val extraInfo = inlineInfo(m.pos) + + if (m.level >= logLevel) { + outWriter.println(msg) + if (extraInfo.nonEmpty) outWriter.println(extraInfo) + } + + filePrintln(msg) + if (extraInfo.nonEmpty) filePrintln(extraInfo) } override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { @@ -41,11 +67,56 @@ with UniqueMessagePositions with HideNonSensicalMessages { } m match { - case m: Error => - printMessageAndPos(m.contained.kind + extra, m.pos) + case m: Error => { + _errorBuf.append(m) + printMessageAndPos(m, extra) + } case w: Warning => - printMessageAndPos(w.contained.kind + extra, w.pos) + printMessageAndPos(w, extra) case _ => } } } + +object TestReporter { + private[this] val logWriter = { + val df = new SimpleDateFormat("yyyy-MM-dd-HH:mm") + val timestamp = df.format(new Date) + new PrintWriter(new FileOutputStream(new JFile(s"../tests-$timestamp.log"), true)) + } + + def parallelReporter(logLevel: Int): TestReporter = new TestReporter( + new PrintWriter(Console.err, true), + str => logWriter.synchronized { + logWriter.println(str) + logWriter.flush() + }, + logLevel + ) + + def reporter(logLevel: Int): TestReporter = new TestReporter( + new PrintWriter(Console.err, true), + logWriter.println, + logLevel + ) + + def simplifiedReporter(writer: PrintWriter): TestReporter = new TestReporter( + writer, + logWriter.println, + 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) + + writer.println(msg) + filePrintln(msg) + + if (extraInfo.nonEmpty) { + writer.println(extraInfo) + filePrintln(extraInfo) + } + } + } +} diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 96ab241fd434..eff86e6e704f 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -17,7 +17,7 @@ class PatmatExhaustivityTest { private def compileFile(file: File) = { val stringBuffer = new StringWriter() - val reporter = new TestReporter(new PrintWriter(stringBuffer)) + val reporter = TestReporter.simplifiedReporter(new PrintWriter(stringBuffer)) try { Main.process((file.getPath::options).toArray, reporter, null) @@ -40,7 +40,7 @@ class PatmatExhaustivityTest { /** A single test with multiple files grouped in a folder */ private def compileDir(file: File) = { val stringBuffer = new StringWriter() - val reporter = new TestReporter(new PrintWriter(stringBuffer)) + val reporter = TestReporter.simplifiedReporter(new PrintWriter(stringBuffer)) val files = Directory(file.getPath).list.toList .filter(f => f.extension == "scala" || f.extension == "java" ) From f2608946e38958b854f0178aa1b84f31d41ff399 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 22 Mar 2017 17:26:04 +0100 Subject: [PATCH 27/39] Add summary report to pos tests and run tests --- .../dotty/tools/dotc/ParallelTesting.scala | 360 ++++++++++++------ .../tools/dotc/reporting/TestReporter.scala | 37 +- 2 files changed, 264 insertions(+), 133 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 5c9718ce2eb6..654022bbde47 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -19,6 +19,7 @@ import core.Contexts._ import reporting.{ Reporter, TestReporter } import reporting.diagnostic.MessageContainer import interfaces.Diagnostic.ERROR +import dotc.util.DiffUtil trait ParallelTesting { @@ -36,6 +37,50 @@ trait ParallelTesting { self.copy(flags = newFlags) } else self + + def buildInstructions(errors: Int, warnings: Int): String = { + val sb = new StringBuilder + val maxLen = 80 + var lineLen = 0 + + sb.append(s"\n\nTest compiled with $errors error(s) and $warnings warning(s), the test can be reproduced by running:") + 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 ConcurrentCompilationTarget(files, _, _) => { + files.map(_.getAbsolutePath).foreach { path => + sb.append("\\\n ") + sb.append(path) + sb += ' ' + } + sb.toString + "\n\n" + } + case self: SeparateCompilationTarget => { + val command = sb.toString + val fsb = new StringBuilder(command) + self.compilationUnits.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" + } + } + } } private final case class ConcurrentCompilationTarget( @@ -69,7 +114,11 @@ trait ParallelTesting { .toList.sortBy(_._1).map(_._2.filter(isCompilable)) } - private abstract class CompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) { + private abstract class Test(targets: List[Target], times: Int, threadLimit: Option[Int]) { + + /** Actual compilation run logic, the test behaviour is defined here */ + protected def compilationRunnable(target: Target): Runnable + val totalTargets = targets.length private[this] var _errors = 0 @@ -78,15 +127,33 @@ trait ParallelTesting { private[this] var _targetsCompiled = 0 private def targetsCompiled: Int = synchronized { _targetsCompiled } - protected final def completeCompilation(newErrors: Int) = synchronized { + protected final def completeCompilation(errors: Int) = synchronized { _targetsCompiled += 1 - _errors += newErrors + _errors += errors } private[this] var _failed = false - final protected[this] def fail(): Unit = synchronized { _failed = true } + protected[this] final def fail(): Unit = synchronized { _failed = true } def didFail: Boolean = _failed + private[this] val failureInstructions = mutable.ArrayBuffer.empty[String] + protected final def addFailureInstruction(ins: String): Unit = + synchronized { failureInstructions.append(ins) } + + private[this] val failedCompilationTargets = mutable.ArrayBuffer.empty[String] + protected final def addFailedCompilationTarget(target: String): Unit = + synchronized { failedCompilationTargets.append(target) } + + protected final def failTarget(target: Target) = + target match { + case ConcurrentCompilationTarget(files, _, _) => + files.map(_.getAbsolutePath).foreach(addFailedCompilationTarget) + fail() + case SeparateCompilationTarget(dir, _, _) => + addFailedCompilationTarget(dir.getAbsolutePath) + fail() + } + private def statusRunner: Runnable = new Runnable { def run(): Unit = { val start = System.currentTimeMillis @@ -123,7 +190,78 @@ trait ParallelTesting { } } - protected def compilationRunnable(target: Target): Runnable + 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 + + + // First we try to compile the java files in the directory: + val javaFiles = files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath) + val javaCompiledBefore = compileWithJavac(javaFiles) + + // Then we compile the scala files: + val reporter = TestReporter.parallelReporter(this, logLevel = if (suppressErrors) 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) + + // If the java files failed compilation before, we try again after: + if (!javaCompiledBefore) + assert(compileWithJavac(javaFiles), s"java compilation failed for ${javaFiles.mkString(", ")}") + + reporter + } private[ParallelTesting] def execute(): this.type = { assert(_targetsCompiled == 0, "not allowed to re-use a `CompileRun`") @@ -141,6 +279,19 @@ trait ParallelTesting { pool.shutdown() if (!pool.awaitTermination(10, TimeUnit.MINUTES)) throw new TimeoutException("Compiling targets timed out") + + if (didFail) { + System.err.println { + """| + |================================================================================ + |Test Report + |================================================================================ + |Failing tests:""".stripMargin + } + failedCompilationTargets.toArray.sorted.foreach(System.err.println) + failureInstructions.iterator.foreach(System.err.println) + } + this } } @@ -150,21 +301,43 @@ trait ParallelTesting { name.endsWith(".scala") || name.endsWith(".java") } - private final class PosCompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) - extends CompileRun(targets, times, threadLimit) { + private final class PosTest(targets: List[Target], times: Int, threadLimit: Option[Int]) + extends Test(targets, times, threadLimit) { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { target match { case ConcurrentCompilationTarget(files, flags, outDir) => { val sourceFiles = files.filter(isCompilable) - val reporter = compile(sourceFiles, flags, false, times, outDir) + val reporter = compile(sourceFiles, flags, false, outDir) completeCompilation(reporter.errorCount) + + if (reporter.errorCount > 0) { + fail() + val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) + addFailureInstruction(errorMsg) + failTarget(target) + reporter.echo(errorMsg) + reporter.flushToFile() + } } case target @ SeparateCompilationTarget(dir, flags, outDir) => { val compilationUnits = target.compilationUnits - val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, false, times, outDir)) - completeCompilation(reporters.foldLeft(0)(_ + _.errorCount)) + val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, false, outDir)) + val errorCount = reporters.foldLeft(0) { (acc, reporter) => + if (reporter.errorCount > 0) { + val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) + addFailureInstruction(errorMsg) + reporter.echo(errorMsg) + reporter.flushToFile() + } + + acc + reporter.errorCount + } + + completeCompilation(errorCount) + + if (errorCount > 0) failTarget(target) } } @@ -172,9 +345,9 @@ trait ParallelTesting { } } - private final class RunCompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) - extends CompileRun(targets, times, threadLimit) { - private def verifyOutput(checkFile: JFile, dir: JFile) = try { + private final class RunTest(targets: List[Target], times: Int, threadLimit: Option[Int]) + extends Test(targets, times, threadLimit) { + private def verifyOutput(checkFile: JFile, dir: JFile, target: Target, warnings: Int) = try { // Do classloading magic and running here: import java.net.{ URL, URLClassLoader } import java.io.ByteArrayOutputStream @@ -196,10 +369,20 @@ trait ParallelTesting { .forall { case (x, y) => x == y } if (outputLines.length != checkLines.length || !linesMatch) { - System.err.println { - s"\nOutput from run test '$dir' did not match expected, output:\n${outputLines.mkString("\n")}" - } - fail() + // Print diff to files and summary: + val diff = outputLines.zip(checkLines).map { case (act, exp) => + DiffUtil.mkColoredCodeDiff(exp, act, true) + }.mkString("\n") + val msg = s"\nOutput from run test '$checkFile' did not match expected, output:\n$diff\n" + System.err.println(msg) + addFailureInstruction(msg) + + // Print build instructions to file and summary: + val buildInstr = target.buildInstructions(0, warnings) + addFailureInstruction(buildInstr) + + // Fail target: + failTarget(target) } } catch { @@ -218,40 +401,70 @@ trait ParallelTesting { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { - val (errorCount, hasCheckFile, doVerify) = target match { + val (errorCount, warningCount, hasCheckFile, doVerify) = target match { case ConcurrentCompilationTarget(files, flags, outDir) => { val sourceFiles = files.filter(isCompilable) - val checkFile = files.find(_.getName.endsWith(".check")) - val reporter = compile(sourceFiles, flags, false, times, 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(sourceFiles, flags, false, outDir) + + if (reporter.errorCount > 0) { + fail() + val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) + addFailureInstruction(errorMsg) + failTarget(target) + reporter.echo(errorMsg) + reporter.flushToFile() + } completeCompilation(reporter.errorCount) - (reporter.errorCount, checkFile.isDefined, () => verifyOutput(checkFile.get, outDir)) + (reporter.errorCount, reporter.warningCount, checkFile.isDefined, () => verifyOutput(checkFile.get, outDir, target, reporter.warningCount)) } case target @ SeparateCompilationTarget(dir, flags, outDir) => { val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + ".check") - val errorCount = + val (errorCount, warningCount) = target .compilationUnits - .map(files => compile(files.filter(isCompilable), flags, false, times, outDir)) - .foldLeft(0)(_ + _.errorCount) + .map(files => compile(files.filter(isCompilable), flags, false, outDir)) + .foldLeft((0,0)) { case ((errors, warnings), reporter) => + if (reporter.errorCount > 0) { + val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) + addFailureInstruction(errorMsg) + reporter.echo(errorMsg) + reporter.flushToFile() + } + + (errors + reporter.errorCount, warnings + reporter.warningCount) + } + + if (errorCount > 0) fail() completeCompilation(errorCount) - (errorCount, checkFile.exists, () => verifyOutput(checkFile, outDir)) + (errorCount, warningCount, checkFile.exists, () => verifyOutput(checkFile, outDir, target, warningCount)) } } if (errorCount == 0 && hasCheckFile) doVerify() else if (errorCount > 0) { System.err.println(s"\nCompilation failed for: '$target'") - fail() + val buildInstr = target.buildInstructions(errorCount, warningCount) + addFailureInstruction(buildInstr) + failTarget(target) } } } } - private final class NegCompileRun(targets: List[Target], times: Int, threadLimit: Option[Int]) - extends CompileRun(targets, times, threadLimit) { + private final class NegTest(targets: List[Target], times: Int, threadLimit: Option[Int]) + extends Test(targets, times, threadLimit) { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { // In neg-tests we allow two types of error annotations, @@ -309,7 +522,7 @@ trait ParallelTesting { case ConcurrentCompilationTarget(files, flags, outDir) => { val sourceFiles = files.filter(isCompilable) val (errorMap, expectedErrors) = errorMapAndExpected(sourceFiles) - val reporter = compile(sourceFiles, flags, true, times, outDir) + val reporter = compile(sourceFiles, flags, true, outDir) val actualErrors = reporter.errorCount (expectedErrors, actualErrors, () => getMissingAnnotations(errorMap, reporter.errors), errorMap) @@ -318,7 +531,7 @@ trait ParallelTesting { case target @ SeparateCompilationTarget(dir, flags, outDir) => { val compilationUnits = target.compilationUnits val (errorMap, expectedErrors) = errorMapAndExpected(compilationUnits.toArray.flatten) - val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, true, times, outDir)) + val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, true, outDir)) val actualErrors = reporters.foldLeft(0)(_ + _.errorCount) val errors = reporters.iterator.flatMap(_.errors) (expectedErrors, actualErrors, () => getMissingAnnotations(errorMap, errors), errorMap) @@ -329,19 +542,19 @@ trait ParallelTesting { System.err.println { s"\nWrong number of errors encountered when compiling $target, expected: $expectedErrors, actual: $actualErrors\n" } - fail() + failTarget(target) } else if (hasMissingAnnotations()) { System.err.println { s"\nErrors found on incorrect row numbers when compiling $target" } - fail() + failTarget(target) } else if (!errorMap.isEmpty) { System.err.println { s"\nError annotation(s) have {=}: $errorMap" } - fail() + failTarget(target) } completeCompilation(actualErrors) @@ -349,81 +562,6 @@ trait ParallelTesting { } } - private def compile(files0: Array[JFile], flags0: Array[String], suppressErrors: Boolean, times: Int, 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") - val fullArgs = Array( - "javac", - "-classpath", - s".:$scalaLib:${targetDir.getAbsolutePath}" - ) ++ flags.takeRight(2) ++ fs - - Runtime.getRuntime.exec(fullArgs).waitFor() == 0 - } else true - - - // First we try to compile the java files in the directory: - val javaFiles = files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath) - val javaCompiledBefore = compileWithJavac(javaFiles) - - // Then we compile the scala files: - val reporter = TestReporter.parallelReporter(logLevel = if (suppressErrors) 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)) - - private def echoSummary(rep: Reporter, msg: String)(implicit ctx: Context) = - rep.asInstanceOf[TestReporter].echoSummary(msg) - - override def doCompile(comp: Compiler, files: List[String])(implicit ctx: Context) = - ntimes(times) { run => - val start = System.nanoTime() - val rep = super.doCompile(comp, files) - echoSummary(rep, s"\ntime run $run: ${(System.nanoTime - start) / 1000000}ms") - } - } - - driver.process(addOutDir(flags) ++ files.map(_.getAbsolutePath), reporter = reporter) - - // If the java files failed compilation before, we try again after: - if (!javaCompiledBefore) - assert(compileWithJavac(javaFiles), s"java compilation failed for ${javaFiles.mkString(", ")}") - - if (flags.contains("-verbose")) reporter.printSummary() - else reporter - } - final class CompilationTest private ( private[ParallelTesting] val targets: List[Target], private[ParallelTesting] val times: Int, @@ -443,7 +581,7 @@ trait ParallelTesting { } def pos(): this.type = { - val runErrors = new PosCompileRun(targets, times, threadLimit).execute().errors + val runErrors = new PosTest(targets, times, threadLimit).execute().errors assert(runErrors == 0, s"Expected no errors when compiling") if (shouldDelete) targets.foreach(t => delete(t.outDir)) this @@ -451,7 +589,7 @@ trait ParallelTesting { def neg(): this.type = { assert( - !(new NegCompileRun(targets, times, threadLimit).execute().didFail), + !(new NegTest(targets, times, threadLimit).execute().didFail), s"Wrong number of errors encountered when compiling" ) if (shouldDelete) targets.foreach(t => delete(t.outDir)) @@ -459,7 +597,7 @@ trait ParallelTesting { } def run(): this.type = { - val didFail = new RunCompileRun(targets, times, threadLimit).execute().didFail + val didFail = new RunTest(targets, times, threadLimit).execute().didFail assert(!didFail, s"Run tests failed") if (shouldDelete) targets.foreach(t => delete(t.outDir)) this diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 4fc7e5dfe93e..fc6ac7d1f734 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -15,13 +15,18 @@ import diagnostic.{ Message, MessageContainer, NoExplanation } import diagnostic.messages._ import interfaces.Diagnostic.{ ERROR, WARNING, INFO } -class TestReporter protected (outWriter: PrintWriter, protected val filePrintln: String => Unit, logLevel: Int) extends Reporter -with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { +class TestReporter protected (outWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int) +extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { import MessageContainer._ protected final val _errorBuf = mutable.ArrayBuffer.empty[MessageContainer] final def errors: Iterator[MessageContainer] = _errorBuf.iterator + protected final val _messageBuf = mutable.ArrayBuffer.empty[String] + + final def flushToFile(): Unit = + _messageBuf.iterator.foreach(filePrintln) + final def inlineInfo(pos: SourcePosition): String = if (pos.exists) { if (pos.outer.exists) @@ -30,20 +35,8 @@ with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { } else "" - final def printSummary(): this.type = { - val msg = _summary.toString - if (msg.nonEmpty) { - outWriter.println(msg) - filePrintln(msg) - } - this - } - - private var _summary = new StringBuilder - final def echoSummary(msg: String): this.type = { - _summary.append(msg) - this - } + def echo(msg: String) = + _messageBuf.append(msg) /** Prints the message with the given position indication. */ def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = { @@ -55,8 +48,8 @@ with UniqueMessagePositions with HideNonSensicalMessages with MessageRendering { if (extraInfo.nonEmpty) outWriter.println(extraInfo) } - filePrintln(msg) - if (extraInfo.nonEmpty) filePrintln(extraInfo) + _messageBuf.append(msg) + if (extraInfo.nonEmpty) _messageBuf.append(extraInfo) } override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { @@ -85,9 +78,9 @@ object TestReporter { new PrintWriter(new FileOutputStream(new JFile(s"../tests-$timestamp.log"), true)) } - def parallelReporter(logLevel: Int): TestReporter = new TestReporter( + def parallelReporter(caller: AnyRef, logLevel: Int): TestReporter = new TestReporter( new PrintWriter(Console.err, true), - str => logWriter.synchronized { + str => caller.synchronized { logWriter.println(str) logWriter.flush() }, @@ -111,11 +104,11 @@ object TestReporter { val extraInfo = inlineInfo(m.pos) writer.println(msg) - filePrintln(msg) + _messageBuf.append(msg) if (extraInfo.nonEmpty) { writer.println(extraInfo) - filePrintln(extraInfo) + _messageBuf.append(extraInfo) } } } From 41b3641aa89fa6121f6b89807d5a030b6f382d0c Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 23 Mar 2017 14:09:48 +0100 Subject: [PATCH 28/39] Add testing of new test framework --- .../dotty/tools/dotc/ParallelTestTests.scala | 51 +++++++ .../dotty/tools/dotc/ParallelTesting.scala | 130 +++++++++++------- tests/partest-test/negAnnotWrongLine.scala | 3 + tests/partest-test/negMissingAnnot.scala | 1 + tests/partest-test/negNoPositionAnnots.scala | 5 + tests/partest-test/negTooManyAnnots.scala | 3 + tests/partest-test/posFail1Error.scala | 3 + tests/partest-test/runDiffOutput1.check | 5 + tests/partest-test/runDiffOutput1.scala | 9 ++ tests/partest-test/runWrongOutput1.check | 6 + tests/partest-test/runWrongOutput1.scala | 9 ++ tests/partest-test/runWrongOutput2.check | 3 + tests/partest-test/runWrongOutput2.scala | 9 ++ 13 files changed, 188 insertions(+), 49 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/ParallelTestTests.scala create mode 100644 tests/partest-test/negAnnotWrongLine.scala create mode 100644 tests/partest-test/negMissingAnnot.scala create mode 100644 tests/partest-test/negNoPositionAnnots.scala create mode 100644 tests/partest-test/negTooManyAnnots.scala create mode 100644 tests/partest-test/posFail1Error.scala create mode 100644 tests/partest-test/runDiffOutput1.check create mode 100644 tests/partest-test/runDiffOutput1.scala create mode 100644 tests/partest-test/runWrongOutput1.check create mode 100644 tests/partest-test/runWrongOutput1.scala create mode 100644 tests/partest-test/runWrongOutput2.check create mode 100644 tests/partest-test/runWrongOutput2.scala diff --git a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala new file mode 100644 index 000000000000..572f63410981 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala @@ -0,0 +1,51 @@ +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 interactive: Boolean = !sys.env.contains("DRONE") + + @Test def missingFile: Unit = + try { + compileFile("../tests/partest-test/i-dont-exist.scala", defaultOptions).expectFailure.neg() + 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.pos() + + @Test def negMissingAnnot: Unit = + compileFile("../tests/partest-test/negMissingAnnot.scala", defaultOptions).expectFailure.neg() + + @Test def negAnnotWrongLine: Unit = + compileFile("../tests/partest-test/negAnnotWrongLine.scala", defaultOptions).expectFailure.neg() + + @Test def negTooManyAnnots: Unit = + compileFile("../tests/partest-test/negTooManyAnnots.scala", defaultOptions).expectFailure.neg() + + @Test def negNoPositionAnnot: Unit = + compileFile("../tests/partest-test/negNoPositionAnnots.scala", defaultOptions).expectFailure.neg() + + @Test def runCompileFail: Unit = + compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.run() + + @Test def runWrongOutput1: Unit = + compileFile("../tests/partest-test/runWrongOutput1.scala", defaultOptions).expectFailure.run() + + @Test def runWrongOutput2: Unit = + compileFile("../tests/partest-test/runWrongOutput2.scala", defaultOptions).expectFailure.run() + + @Test def runDiffOutput1: Unit = + compileFile("../tests/partest-test/runDiffOutput1.scala", defaultOptions).expectFailure.run() +} diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 654022bbde47..b4452299b5a6 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -29,14 +29,16 @@ trait ParallelTesting { def outDir: JFile def flags: Array[String] - def withFlags(newFlags: Array[String]) = + def withFlags(newFlags0: String*) = { + val newFlags = newFlags0.toArray if (!flags.containsSlice(newFlags)) self match { case self: ConcurrentCompilationTarget => - self.copy(flags = newFlags) + self.copy(flags = flags ++ newFlags) case self: SeparateCompilationTarget => - self.copy(flags = newFlags) + self.copy(flags = flags ++ newFlags) } else self + } def buildInstructions(errors: Int, warnings: Int): String = { val sb = new StringBuilder @@ -114,7 +116,7 @@ trait ParallelTesting { .toList.sortBy(_._1).map(_._2.filter(isCompilable)) } - private abstract class Test(targets: List[Target], times: Int, threadLimit: Option[Int]) { + private abstract class Test(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) { /** Actual compilation run logic, the test behaviour is defined here */ protected def compilationRunnable(target: Target): Runnable @@ -154,6 +156,9 @@ trait ParallelTesting { fail() } + protected def echo(msg: String): Unit = + if (!suppressAllOutput) System.err.println(msg) + private def statusRunner: Runnable = new Runnable { def run(): Unit = { val start = System.currentTimeMillis @@ -235,7 +240,8 @@ trait ParallelTesting { val javaCompiledBefore = compileWithJavac(javaFiles) // Then we compile the scala files: - val reporter = TestReporter.parallelReporter(this, logLevel = if (suppressErrors) ERROR + 1 else ERROR) + val reporter = TestReporter.parallelReporter(this, 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 { @@ -270,7 +276,7 @@ trait ParallelTesting { case None => JExecutors.newWorkStealingPool() } - if (interactive) pool.submit(statusRunner) + if (interactive && !suppressAllOutput) pool.submit(statusRunner) targets.foreach { target => pool.submit(compilationRunnable(target)) @@ -281,15 +287,15 @@ trait ParallelTesting { throw new TimeoutException("Compiling targets timed out") if (didFail) { - System.err.println { + echo { """| |================================================================================ |Test Report |================================================================================ |Failing tests:""".stripMargin } - failedCompilationTargets.toArray.sorted.foreach(System.err.println) - failureInstructions.iterator.foreach(System.err.println) + failedCompilationTargets.toArray.sorted.foreach(echo) + failureInstructions.iterator.foreach(echo) } this @@ -301,8 +307,8 @@ trait ParallelTesting { name.endsWith(".scala") || name.endsWith(".java") } - private final class PosTest(targets: List[Target], times: Int, threadLimit: Option[Int]) - extends Test(targets, times, threadLimit) { + private final class PosTest(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) + extends Test(targets, times, threadLimit, suppressAllOutput) { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { target match { @@ -345,8 +351,8 @@ trait ParallelTesting { } } - private final class RunTest(targets: List[Target], times: Int, threadLimit: Option[Int]) - extends Test(targets, times, threadLimit) { + private final class RunTest(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) + extends Test(targets, times, threadLimit, suppressAllOutput) { private def verifyOutput(checkFile: JFile, dir: JFile, target: Target, warnings: Int) = try { // Do classloading magic and running here: import java.net.{ URL, URLClassLoader } @@ -374,7 +380,7 @@ trait ParallelTesting { DiffUtil.mkColoredCodeDiff(exp, act, true) }.mkString("\n") val msg = s"\nOutput from run test '$checkFile' did not match expected, output:\n$diff\n" - System.err.println(msg) + echo(msg) addFailureInstruction(msg) // Print build instructions to file and summary: @@ -387,15 +393,15 @@ trait ParallelTesting { } catch { case _: NoSuchMethodException => - System.err.println(s"\ntest in '$dir' did not contain a main method") + echo(s"\ntest in '$dir' did not contain a main method") fail() case _: ClassNotFoundException => - System.err.println(s"\ntest in '$dir' did was not contained within a `Test` object") + echo(s"\ntest in '$dir' did was not contained within a `Test` object") fail() case _: InvocationTargetException => - System.err.println(s"\nTest in '$dir' might be using args(X) where X > 0") + echo(s"\nTest in '$dir' might be using args(X) where X > 0") fail() } @@ -454,7 +460,7 @@ trait ParallelTesting { if (errorCount == 0 && hasCheckFile) doVerify() else if (errorCount > 0) { - System.err.println(s"\nCompilation failed for: '$target'") + echo(s"\nCompilation failed for: '$target'") val buildInstr = target.buildInstructions(errorCount, warningCount) addFailureInstruction(buildInstr) failTarget(target) @@ -463,8 +469,8 @@ trait ParallelTesting { } } - private final class NegTest(targets: List[Target], times: Int, threadLimit: Option[Int]) - extends Test(targets, times, threadLimit) { + private final class NegTest(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) + extends Test(targets, times, threadLimit, suppressAllOutput) { protected def compilationRunnable(target: Target): Runnable = new Runnable { def run(): Unit = compileTry { // In neg-tests we allow two types of error annotations, @@ -511,7 +517,7 @@ trait ParallelTesting { true } else { - System.err.println { + echo { s"Error reported in ${error.pos.source}, but no annotation found" } false @@ -539,19 +545,19 @@ trait ParallelTesting { } if (expectedErrors != actualErrors) { - System.err.println { + echo { s"\nWrong number of errors encountered when compiling $target, expected: $expectedErrors, actual: $actualErrors\n" } failTarget(target) } else if (hasMissingAnnotations()) { - System.err.println { + echo { s"\nErrors found on incorrect row numbers when compiling $target" } failTarget(target) } else if (!errorMap.isEmpty) { - System.err.println { + echo { s"\nError annotation(s) have {=}: $errorMap" } failTarget(target) @@ -566,39 +572,64 @@ trait ParallelTesting { private[ParallelTesting] val targets: List[Target], private[ParallelTesting] val times: Int, private[ParallelTesting] val shouldDelete: Boolean, - private[ParallelTesting] val threadLimit: Option[Int] + private[ParallelTesting] val threadLimit: Option[Int], + private[ParallelTesting] val shouldFail: Boolean ) { + import org.junit.Assert.fail + private[ParallelTesting] def this(target: Target) = - this(List(target), 1, true, None) + this(List(target), 1, true, None, false) private[ParallelTesting] def this(targets: List[Target]) = - this(targets, 1, true, None) + this(targets, 1, true, None, false) 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") - new CompilationTest(targets ++ other.targets, times, shouldDelete, threadLimit) + require(other.shouldFail == shouldFail, "can't combine tests that have different expectations on outcome") + new CompilationTest(targets ++ other.targets, times, shouldDelete, threadLimit, shouldFail) } def pos(): this.type = { - val runErrors = new PosTest(targets, times, threadLimit).execute().errors - assert(runErrors == 0, s"Expected no errors when compiling") - if (shouldDelete) targets.foreach(t => delete(t.outDir)) - this + val test = new PosTest(targets, times, threadLimit, shouldFail).execute() + + if (!shouldFail && test.didFail) { + fail(s"Expected no errors when compiling, but found: ${test.errors}") + } + else if (shouldFail && !test.didFail) { + fail("Pos test should have failed, but didn't") + } + + cleanup() } def neg(): this.type = { - assert( - !(new NegTest(targets, times, threadLimit).execute().didFail), - s"Wrong number of errors encountered when compiling" - ) - if (shouldDelete) targets.foreach(t => delete(t.outDir)) - this + val test = new NegTest(targets, times, threadLimit, shouldFail).execute() + + 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() } def run(): this.type = { - val didFail = new RunTest(targets, times, threadLimit).execute().didFail - assert(!didFail, s"Run tests failed") + val test = new RunTest(targets, times, threadLimit, shouldFail).execute() + + 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() + } + + private def cleanup(): this.type = { if (shouldDelete) targets.foreach(t => delete(t.outDir)) this } @@ -617,24 +648,25 @@ trait ParallelTesting { case target @ SeparateCompilationTarget(dir, _, outDir) => target.copy(dir = copyToDir(outDir, dir)) }, - times, shouldDelete, threadLimit + times, shouldDelete, threadLimit, shouldFail ) def times(i: Int): CompilationTest = - new CompilationTest(targets, i, shouldDelete, threadLimit) + new CompilationTest(targets, i, shouldDelete, threadLimit, shouldFail) def verbose: CompilationTest = new CompilationTest( - targets.map(t => t.withFlags(t.flags ++ Array("-verbose", "-Ylog-classpath"))), - times, - shouldDelete, - threadLimit + targets.map(t => t.withFlags("-verbose", "-Ylog-classpath")), + times, shouldDelete, threadLimit, shouldFail ) def keepOutput: CompilationTest = - new CompilationTest(targets, times, false, threadLimit) + new CompilationTest(targets, times, false, threadLimit, shouldFail) + + def limitThreads(i: Int): CompilationTest = + new CompilationTest(targets, times, shouldDelete, Some(i), shouldFail) - def limitThreads(i: Int) = - new CompilationTest(targets, times, shouldDelete, Some(i)) + def expectFailure: CompilationTest = + new CompilationTest(targets, times, shouldDelete, threadLimit, true) def delete(): Unit = targets.foreach(t => delete(t.outDir)) diff --git a/tests/partest-test/negAnnotWrongLine.scala b/tests/partest-test/negAnnotWrongLine.scala new file mode 100644 index 000000000000..06ba0a2c3102 --- /dev/null +++ b/tests/partest-test/negAnnotWrongLine.scala @@ -0,0 +1,3 @@ +object Foo { // error + def bar: Int = "LOL" +} diff --git a/tests/partest-test/negMissingAnnot.scala b/tests/partest-test/negMissingAnnot.scala new file mode 100644 index 000000000000..acffb37a3bf5 --- /dev/null +++ b/tests/partest-test/negMissingAnnot.scala @@ -0,0 +1 @@ +class Foo extends Bar diff --git a/tests/partest-test/negNoPositionAnnots.scala b/tests/partest-test/negNoPositionAnnots.scala new file mode 100644 index 000000000000..7f3ce7e340ff --- /dev/null +++ b/tests/partest-test/negNoPositionAnnots.scala @@ -0,0 +1,5 @@ +object Foo { + def bar: Int = "LOL" + + // nopos-error +} diff --git a/tests/partest-test/negTooManyAnnots.scala b/tests/partest-test/negTooManyAnnots.scala new file mode 100644 index 000000000000..5dbd2fd754ed --- /dev/null +++ b/tests/partest-test/negTooManyAnnots.scala @@ -0,0 +1,3 @@ +object Test { + def foo: Int = "LOL" // error // error +} diff --git a/tests/partest-test/posFail1Error.scala b/tests/partest-test/posFail1Error.scala new file mode 100644 index 000000000000..c8b565498d74 --- /dev/null +++ b/tests/partest-test/posFail1Error.scala @@ -0,0 +1,3 @@ +object Test extends Bar { + def main(args: Array[String]): Unit = () +} diff --git a/tests/partest-test/runDiffOutput1.check b/tests/partest-test/runDiffOutput1.check new file mode 100644 index 000000000000..6234030de62e --- /dev/null +++ b/tests/partest-test/runDiffOutput1.check @@ -0,0 +1,5 @@ +1 +2 +4 +4 +5 diff --git a/tests/partest-test/runDiffOutput1.scala b/tests/partest-test/runDiffOutput1.scala new file mode 100644 index 000000000000..32cf6f5b63a9 --- /dev/null +++ b/tests/partest-test/runDiffOutput1.scala @@ -0,0 +1,9 @@ +object Test { + def main(args: Array[String]): Unit = { + println(1) + println(2) + println(3) + println(4) + println(5) + } +} diff --git a/tests/partest-test/runWrongOutput1.check b/tests/partest-test/runWrongOutput1.check new file mode 100644 index 000000000000..b414108e81e5 --- /dev/null +++ b/tests/partest-test/runWrongOutput1.check @@ -0,0 +1,6 @@ +1 +2 +3 +4 +5 +6 diff --git a/tests/partest-test/runWrongOutput1.scala b/tests/partest-test/runWrongOutput1.scala new file mode 100644 index 000000000000..32cf6f5b63a9 --- /dev/null +++ b/tests/partest-test/runWrongOutput1.scala @@ -0,0 +1,9 @@ +object Test { + def main(args: Array[String]): Unit = { + println(1) + println(2) + println(3) + println(4) + println(5) + } +} diff --git a/tests/partest-test/runWrongOutput2.check b/tests/partest-test/runWrongOutput2.check new file mode 100644 index 000000000000..01e79c32a8c9 --- /dev/null +++ b/tests/partest-test/runWrongOutput2.check @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/tests/partest-test/runWrongOutput2.scala b/tests/partest-test/runWrongOutput2.scala new file mode 100644 index 000000000000..32cf6f5b63a9 --- /dev/null +++ b/tests/partest-test/runWrongOutput2.scala @@ -0,0 +1,9 @@ +object Test { + def main(args: Array[String]): Unit = { + println(1) + println(2) + println(3) + println(4) + println(5) + } +} From 1ab7c038e7daed1f05aafa000a284d76ddacb381 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 23 Mar 2017 14:16:55 +0100 Subject: [PATCH 29/39] Move varargs tests to top-level pos and neg --- .../allParamsAreVarArgs.scala => t1625.scala} | 0 tests/neg/t1625b.scala | 3 +++ .../classConstructorVarArgs.scala => t1625c.scala} | 0 .../curriedNegExample.scala => t1625d.scala} | 0 .../firstParamIsVarArgs.scala => t1625e.scala} | 0 .../varargsInMethodsT1625/caseClassConstructorVarArgs.scala | 3 --- .../curriedPosExample.scala => t1625.scala} | 0 .../onlyOneVarArgs.scala => t1625b.scala} | 0 8 files changed, 3 insertions(+), 3 deletions(-) rename tests/neg/{varargsInMethodsT1625/allParamsAreVarArgs.scala => t1625.scala} (100%) create mode 100644 tests/neg/t1625b.scala rename tests/neg/{varargsInMethodsT1625/classConstructorVarArgs.scala => t1625c.scala} (100%) rename tests/neg/{varargsInMethodsT1625/curriedNegExample.scala => t1625d.scala} (100%) rename tests/neg/{varargsInMethodsT1625/firstParamIsVarArgs.scala => t1625e.scala} (100%) delete mode 100644 tests/neg/varargsInMethodsT1625/caseClassConstructorVarArgs.scala rename tests/pos/{varargsInMethodsT1625/curriedPosExample.scala => t1625.scala} (100%) rename tests/pos/{varargsInMethodsT1625/onlyOneVarArgs.scala => t1625b.scala} (100%) diff --git a/tests/neg/varargsInMethodsT1625/allParamsAreVarArgs.scala b/tests/neg/t1625.scala similarity index 100% rename from tests/neg/varargsInMethodsT1625/allParamsAreVarArgs.scala rename to tests/neg/t1625.scala diff --git a/tests/neg/t1625b.scala b/tests/neg/t1625b.scala new file mode 100644 index 000000000000..f2c544f424c3 --- /dev/null +++ b/tests/neg/t1625b.scala @@ -0,0 +1,3 @@ +object T5 { + case class Abc(x: String*, c: String*) // error // error: varargs parameter must come last AND found: String* required: String +} diff --git a/tests/neg/varargsInMethodsT1625/classConstructorVarArgs.scala b/tests/neg/t1625c.scala similarity index 100% rename from tests/neg/varargsInMethodsT1625/classConstructorVarArgs.scala rename to tests/neg/t1625c.scala diff --git a/tests/neg/varargsInMethodsT1625/curriedNegExample.scala b/tests/neg/t1625d.scala similarity index 100% rename from tests/neg/varargsInMethodsT1625/curriedNegExample.scala rename to tests/neg/t1625d.scala diff --git a/tests/neg/varargsInMethodsT1625/firstParamIsVarArgs.scala b/tests/neg/t1625e.scala similarity index 100% rename from tests/neg/varargsInMethodsT1625/firstParamIsVarArgs.scala rename to tests/neg/t1625e.scala diff --git a/tests/neg/varargsInMethodsT1625/caseClassConstructorVarArgs.scala b/tests/neg/varargsInMethodsT1625/caseClassConstructorVarArgs.scala deleted file mode 100644 index 8f8a4fcf66e8..000000000000 --- a/tests/neg/varargsInMethodsT1625/caseClassConstructorVarArgs.scala +++ /dev/null @@ -1,3 +0,0 @@ -object T5 { - case class Abc(x: String*, c: String*) // error //error: varargs parameter must come last AND found: String* required: String -} \ No newline at end of file diff --git a/tests/pos/varargsInMethodsT1625/curriedPosExample.scala b/tests/pos/t1625.scala similarity index 100% rename from tests/pos/varargsInMethodsT1625/curriedPosExample.scala rename to tests/pos/t1625.scala diff --git a/tests/pos/varargsInMethodsT1625/onlyOneVarArgs.scala b/tests/pos/t1625b.scala similarity index 100% rename from tests/pos/varargsInMethodsT1625/onlyOneVarArgs.scala rename to tests/pos/t1625b.scala From f7e3b7002d1eefbeaac3970be4ac729843d6a939 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 27 Mar 2017 14:11:23 +0200 Subject: [PATCH 30/39] Restore partest and enable it to run alongside new partest --- .drone.yml | 4 + .drone.yml.sig | 2 +- compiler/test/dotc/tests.scala | 374 +++++++++++ compiler/test/dotty/partest/DPConfig.scala | 40 ++ .../test/dotty/partest/DPConsoleRunner.scala | 411 ++++++++++++ .../test/dotty/partest/DPDirectCompiler.scala | 36 + .../dotty/tools/dotc/CompilationTests.scala | 4 + .../test/dotty/tools/dotc/CompilerTest.scala | 613 ++++++++++++++++++ project/Build.scala | 87 ++- 9 files changed, 1568 insertions(+), 3 deletions(-) create mode 100644 compiler/test/dotc/tests.scala create mode 100644 compiler/test/dotty/partest/DPConfig.scala create mode 100644 compiler/test/dotty/partest/DPConsoleRunner.scala create mode 100644 compiler/test/dotty/partest/DPDirectCompiler.scala create mode 100644 compiler/test/dotty/tools/dotc/CompilerTest.scala diff --git a/.drone.yml b/.drone.yml index 6d49aace5344..eb36e65d80e2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -36,3 +36,7 @@ matrix: TEST: - ;test;dotty-bin-tests/test - ;publishLocal;dotty-bootstrapped/test + - partest-only-no-bootstrap --show-diff --verbose + - partest-only --show-diff --verbose + - ;set testOptions in LocalProject("dotty-compiler") := Seq() ;dotty-compiler/testOnly dotty.tools.dotc.CompilationTests + - ;publishLocal ;set testOptions in LocalProject("dotty-compiler-bootstrapped") := Seq() ;dotty-bootstrapped/testOnly dotty.tools.dotc.CompilationTests diff --git a/.drone.yml.sig b/.drone.yml.sig index e6c378c5ba40..b823296fa975 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXg0MDk2bSAtSi1YWDpSZXNlcnZlZENvZGVDYWNoZVNpemU9NTEybSAtSi1YWDpNYXhNZXRhc3BhY2VTaXplPTEwMjRtIC1EZG90dHkuZHJvbmUubWVtPTQwOTZtICIke1RFU1R9IgogICAgd2hlbjoKICAgICAgYnJhbmNoOgogICAgICAgIGV4Y2x1ZGU6IGdoLXBhZ2VzCgogIGRvY3VtZW50YXRpb246CiAgICBpbWFnZTogbGFtcGVwZmwvZG90dHk6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBjb21tYW5kczoKICAgICAgLSAuL3Byb2plY3Qvc2NyaXB0cy9nZW5Eb2NzICIke1RFU1R9IiAkQk9UX1BBU1MKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRvdHR5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBzdGF0dXM6IGNoYW5nZWQKCm1hdHJpeDoKICBURVNUOgogICAgLSA7dGVzdDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7cHVibGlzaExvY2FsO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0Cg.jh9DiIPPWc33AN8J2-GRV-PThSGGTFis5HmG9AgOTV8 \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXg0MDk2bSAtSi1YWDpSZXNlcnZlZENvZGVDYWNoZVNpemU9NTEybSAtSi1YWDpNYXhNZXRhc3BhY2VTaXplPTEwMjRtIC1EZG90dHkuZHJvbmUubWVtPTQwOTZtICIke1RFU1R9IgogICAgd2hlbjoKICAgICAgYnJhbmNoOgogICAgICAgIGV4Y2x1ZGU6IGdoLXBhZ2VzCgogIGRvY3VtZW50YXRpb246CiAgICBpbWFnZTogbGFtcGVwZmwvZG90dHk6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBjb21tYW5kczoKICAgICAgLSAuL3Byb2plY3Qvc2NyaXB0cy9nZW5Eb2NzICIke1RFU1R9IiAkQk9UX1BBU1MKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRvdHR5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBzdGF0dXM6IGNoYW5nZWQKCm1hdHJpeDoKICBURVNUOgogICAgLSA7dGVzdDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7cHVibGlzaExvY2FsO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0CiAgICAtIHBhcnRlc3Qtb25seS1uby1ib290c3RyYXAgLS1zaG93LWRpZmYgLS12ZXJib3NlCiAgICAtIHBhcnRlc3Qtb25seSAtLXNob3ctZGlmZiAtLXZlcmJvc2UKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyIikgOj0gU2VxKCkgO2RvdHR5LWNvbXBpbGVyL3Rlc3RPbmx5IGRvdHR5LnRvb2xzLmRvdGMuQ29tcGlsYXRpb25UZXN0cwogICAgLSA7cHVibGlzaExvY2FsIDtzZXQgdGVzdE9wdGlvbnMgaW4gTG9jYWxQcm9qZWN0KCJkb3R0eS1jb21waWxlci1ib290c3RyYXBwZWQiKSA6PSBTZXEoKSA7ZG90dHktYm9vdHN0cmFwcGVkL3Rlc3RPbmx5IGRvdHR5LnRvb2xzLmRvdGMuQ29tcGlsYXRpb25UZXN0cwo.qsDrUBsZtyXeEeRXf9CnC0Rh5FF0lZpKCgf2iZvPckE \ No newline at end of file diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala new file mode 100644 index 000000000000..1c80767ee255 --- /dev/null +++ b/compiler/test/dotc/tests.scala @@ -0,0 +1,374 @@ +package dotc + +import dotty.Jars +import dotty.tools.dotc.CompilerTest +import dotty.tools.StdLibSources +import org.junit.{Before, Test} +import org.junit.Assert._ + +import java.io.{ File => JFile } +import scala.reflect.io.Directory +import scala.io.Source + +// tests that match regex '(pos|dotc|run|java|compileStdLib)\.*' would be executed as benchmarks. +class tests extends CompilerTest { + + def isRunByJenkins: Boolean = sys.props.isDefinedAt("dotty.jenkins.build") + + val defaultOutputDir = "../out/" + + val noCheckOptions = List( +// "-verbose", +// "-Ylog:frontend", +// "-Xprompt", +// "-explaintypes", +// "-Yshow-suppressed-errors", + "-pagewidth", "120", + "-d", defaultOutputDir + ) + + val checkOptions = List( + "-Yno-deep-subtypes", + "-Yno-double-bindings", + "-Yforce-sbt-phases", + "-color:never" + ) + + 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 (":") + + List("-classpath", paths) + } + + implicit val defaultOptions: List[String] = noCheckOptions ++ { + if (isRunByJenkins) List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") // should be Ycheck:all, but #725 + else List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") + } ++ checkOptions ++ classPath + + val testPickling = List("-Xprint-types", "-Ytest-pickler", "-Ystop-after:pickler", "-Yprintpos") + + val twice = List("#runs", "2") + val staleSymbolError: List[String] = List() + + val allowDeepSubtypes = defaultOptions diff List("-Yno-deep-subtypes") + val allowDoubleBindings = defaultOptions diff List("-Yno-double-bindings") + val scala2mode = List("-language:Scala2") + + val explicitUTF8 = List("-encoding", "UTF8") + val explicitUTF16 = List("-encoding", "UTF16") + + val testsDir = "../tests/" + val posDir = testsDir + "pos/" + val posSpecialDir = testsDir + "pos-special/" + val posScala2Dir = testsDir + "pos-scala2/" + val negDir = testsDir + "neg/" + val runDir = testsDir + "run/" + val newDir = testsDir + "new/" + val javaDir = testsDir + "pos-java-interop/" + + val sourceDir = "./src/" + val dottyDir = sourceDir + "dotty/" + val toolsDir = dottyDir + "tools/" + val backendDir = toolsDir + "backend/" + val dotcDir = toolsDir + "dotc/" + val coreDir = dotcDir + "core/" + val parsingDir = dotcDir + "parsing/" + val dottyReplDir = dotcDir + "repl/" + val typerDir = dotcDir + "typer/" + val libDir = "../library/src/" + + def dottyBootedLib = compileDir(libDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ::: defaultOptions)(allowDeepSubtypes) // note the -deep argument + def dottyDependsOnBootedLib = compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ::: defaultOptions)(allowDeepSubtypes) // note the -deep argument + + @Before def cleanup(): Unit = { + // remove class files from stdlib and tests compilation + Directory(defaultOutputDir + "scala").deleteRecursively() + Directory(defaultOutputDir + "java").deleteRecursively() + } + + @Test def pickle_pickleOK = compileFiles(testsDir + "pickling/", testPickling) +// This directory doesn't exist anymore +// @Test def pickle_pickling = compileDir(coreDir, "pickling", testPickling) + @Test def pickle_ast = compileDir(dotcDir, "ast", testPickling) + @Test def pickle_inf = compileFile(posDir, "pickleinf", testPickling) + + //@Test def pickle_core = compileDir(dotcDir, "core", testPickling, xerrors = 2) // two spurious comparison errors in Types and TypeOps + + @Test def pos_arraycopy = + compileFile(runDir, "arraycopy", List("-Ylog-classpath")) + @Test def pos_t2168_pat = compileFile(posDir, "t2168", twice) + @Test def pos_erasure = compileFile(posDir, "erasure", twice) + @Test def pos_Coder() = compileFile(posDir, "Coder", twice) + @Test def pos_blockescapes() = compileFile(posDir, "blockescapes", twice) + @Test def pos_collections() = compileFile(posDir, "collections", twice) + @Test def pos_functions1() = compileFile(posDir, "functions1", twice) + @Test def pos_implicits1() = compileFile(posDir, "implicits1", twice) + @Test def pos_inferred() = compileFile(posDir, "inferred", twice) + @Test def pos_Patterns() = compileFile(posDir, "Patterns", twice) + @Test def pos_selftypes() = compileFile(posDir, "selftypes", twice) + @Test def pos_varargs() = compileFile(posDir, "varargs", twice) + @Test def pos_vararg_patterns() = compileFile(posDir, "vararg-pattern", twice) + @Test def pos_opassign() = compileFile(posDir, "opassign", twice) + @Test def pos_typedapply() = compileFile(posDir, "typedapply", twice) + @Test def pos_nameddefaults() = compileFile(posDir, "nameddefaults", twice) + @Test def pos_desugar() = compileFile(posDir, "desugar", twice) + @Test def pos_sigs() = compileFile(posDir, "sigs", twice) + @Test def pos_typers() = compileFile(posDir, "typers", twice) + @Test def pos_typedIdents() = compileDir(posDir, "typedIdents", twice) + @Test def pos_assignments() = compileFile(posDir, "assignments", twice) + @Test def pos_packageobject() = compileFile(posDir, "packageobject", twice) + @Test def pos_overloaded() = compileFile(posDir, "overloaded", twice) + @Test def pos_overrides() = compileFile(posDir, "overrides", twice) + @Test def pos_javaOverride() = compileDir(posDir, "java-override", twice) + @Test def pos_templateParents() = compileFile(posDir, "templateParents", twice) + @Test def pos_overloadedAccess = compileFile(posDir, "overloadedAccess", twice) + @Test def pos_approximateUnion = compileFile(posDir, "approximateUnion", twice) + @Test def pos_tailcall = compileDir(posDir, "tailcall", twice) + @Test def pos_valueclasses = compileFiles(posDir + "pos_valueclasses/", twice) + @Test def pos_nullarify = compileFile(posDir, "nullarify", args = "-Ycheck:nullarify" :: Nil) + @Test def pos_subtyping = compileFile(posDir, "subtyping", twice) + @Test def pos_packageObj = compileFile(posDir, "i0239", twice) + @Test def pos_anonClassSubtyping = compileFile(posDir, "anonClassSubtyping", twice) + @Test def pos_extmethods = compileFile(posDir, "extmethods", twice) + @Test def pos_companions = compileFile(posDir, "companions", twice) + @Test def posVarargsT1625 = compileFiles(posDir + "varargsInMethodsT1625/") + + @Test def pos_all = compileFiles(posDir) // twice omitted to make tests run faster + + @Test def pos_scala2_all = compileFiles(posScala2Dir, scala2mode) + + @Test def rewrites = compileFile(posScala2Dir, "rewrites", "-rewrite" :: scala2mode) + + @Test def pos_t8146a = compileFile(posSpecialDir, "t8146a")(allowDeepSubtypes) + + @Test def pos_t5545 = { + // compile by hand in two batches, since junit lacks the infrastructure to + // compile files in multiple batches according to _1, _2, ... suffixes. + compileFile(posSpecialDir, "spec-t5545/S_1") + compileFile(posSpecialDir, "spec-t5545/S_2") + } + @Test def pos_utf8 = compileFile(posSpecialDir, "utf8encoded", explicitUTF8) + @Test def pos_utf16 = compileFile(posSpecialDir, "utf16encoded", explicitUTF16) + + @Test def new_all = compileFiles(newDir, twice) + + @Test def neg_all = compileFiles(negDir, verbose = true, compileSubDirs = false) + @Test def neg_typedIdents() = compileDir(negDir, "typedIdents") + + @Test def negVarargsT1625 = compileFiles(negDir + "varargsInMethodsT1625/") + + val negCustomArgs = negDir + "customArgs/" + + @Test def neg_typers() = compileFile(negCustomArgs, "typers")(allowDoubleBindings) + @Test def neg_overrideClass = compileFile(negCustomArgs, "overrideClass", scala2mode) + @Test def neg_autoTupling = compileFile(negCustomArgs, "autoTuplingTest", args = "-language:noAutoTupling" :: Nil) + @Test def neg_i1050 = compileFile(negCustomArgs, "i1050", List("-strict")) + @Test def neg_i1240 = compileFile(negCustomArgs, "i1240")(allowDoubleBindings) + @Test def neg_i2002 = compileFile(negCustomArgs, "i2002")(allowDoubleBindings) + + val negTailcallDir = negDir + "tailcall/" + @Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b") + @Test def neg_tailcall_t3275 = compileFile(negTailcallDir, "t3275") + @Test def neg_tailcall_t6574 = compileFile(negTailcallDir, "t6574") + @Test def neg_tailcall = compileFile(negTailcallDir, "tailrec") + @Test def neg_tailcall2 = compileFile(negTailcallDir, "tailrec-2") + @Test def neg_tailcall3 = compileFile(negTailcallDir, "tailrec-3") + + @Test def neg_nopredef = compileFile(negCustomArgs, "nopredef", List("-Yno-predef")) + @Test def neg_noimports = compileFile(negCustomArgs, "noimports", List("-Yno-imports")) + @Test def neg_noimpots2 = compileFile(negCustomArgs, "noimports2", List("-Yno-imports")) + + @Test def run_all = runFiles(runDir) + + private val stdlibFiles: List[String] = StdLibSources.whitelisted + + @Test def compileStdLib = compileList("compileStdLib", stdlibFiles, "-migration" :: "-Yno-inline" :: scala2mode) + @Test def compileMixed = compileLine( + """../tests/pos/B.scala + |../scala-scala/src/library/scala/collection/immutable/Seq.scala + |../scala-scala/src/library/scala/collection/parallel/ParSeq.scala + |../scala-scala/src/library/scala/package.scala + |../scala-scala/src/library/scala/collection/GenSeqLike.scala + |../scala-scala/src/library/scala/collection/SeqLike.scala + |../scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala""".stripMargin) + @Test def compileIndexedSeq = compileLine("../scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala") + @Test def compileParSetLike = compileLine("../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala") + @Test def compileParSetSubset = compileLine( + """../scala-scala/src/library/scala/collection/parallel/mutable/ParSetLike.scala + |../scala-scala/src/library/scala/collection/parallel/mutable/ParSet.scala + |../scala-scala/src/library/scala/collection/mutable/SetLike.scala""".stripMargin)(scala2mode ++ defaultOptions) + + @Test def dotty = { + dottyBootedLib + dottyDependsOnBootedLib + } + + @Test def dotc_ast = compileDir(dotcDir, "ast") + @Test def dotc_config = compileDir(dotcDir, "config") + @Test def dotc_core = compileDir(dotcDir, "core")(allowDeepSubtypes)// twice omitted to make tests run faster + @Test def dotc_core_nocheck = compileDir(dotcDir, "core")(noCheckOptions ++ classPath) + +// This directory doesn't exist anymore +// @Test def dotc_core_pickling = compileDir(coreDir, "pickling")(allowDeepSubtypes)// twice omitted to make tests run faster + + @Test def dotc_transform = compileDir(dotcDir, "transform")(allowDeepSubtypes)// twice omitted to make tests run faster + + @Test def dotc_parsing = compileDir(dotcDir, "parsing") // twice omitted to make tests run faster + + @Test def dotc_printing = compileDir(dotcDir, "printing") // twice omitted to make tests run faster + + @Test def dotc_reporting = compileDir(dotcDir, "reporting") // twice omitted to make tests run faster + + @Test def dotc_typer = compileDir(dotcDir, "typer")// twice omitted to make tests run faster + // error: error while loading Checking$$anon$2$, + // class file 'target/scala-2.11/dotty_2.11-0.1.1-SNAPSHOT.jar(dotty/tools/dotc/typer/Checking$$anon$2.class)' + // has location not matching its contents: contains class $anon + + @Test def dotc_util = compileDir(dotcDir, "util") // twice omitted to make tests run faster + + @Test def tools_io = compileDir(toolsDir, "io") // inner class has symbol + + @Test def helloWorld = compileFile(posDir, "HelloWorld") + @Test def labels = compileFile(posDir, "Labels", twice) + //@Test def tools = compileDir(dottyDir, "tools", "-deep" :: Nil)(allowDeepSubtypes) + + @Test def testNonCyclic = compileList("testNonCyclic", List( + dotcDir + "CompilationUnit.scala", + coreDir + "Types.scala", + dotcDir + "ast/Trees.scala" + ), List("-Xprompt") ++ staleSymbolError ++ twice) + + @Test def testIssue_34 = compileList("testIssue_34", List( + dotcDir + "config/Properties.scala", + dotcDir + "config/PathResolver.scala" + ), List(/* "-Ylog:frontend", */ "-Xprompt") ++ staleSymbolError ++ twice) + + @Test def java_all = compileFiles(javaDir, twice) + //@Test def dotc_compilercommand = compileFile(dotcDir + "config/", "CompilerCommand") + + //TASTY tests + @Test def tasty_new_all = compileFiles(newDir, testPickling) + + @Test def tasty_dotty = compileDir(sourceDir, "dotty", testPickling) + + // Disabled because we get stale symbol errors on the SourceFile annotation, which is normal. + // @Test def tasty_annotation_internal = compileDir(s"${dottyDir}annotation/", "internal", testPickling) + + @Test def tasty_runtime = compileDir(s"${libDir}dotty/", "runtime", testPickling) + @Test def tasty_runtime_vc = compileDir(s"${libDir}dotty/runtime/", "vc", testPickling) + + @Test def tasty_tools = compileDir(dottyDir, "tools", testPickling) + + //TODO: issue with ./src/dotty/tools/backend/jvm/DottyBackendInterface.scala + @Test def tasty_backend_jvm = compileList("tasty_backend_jvm", List( + "CollectEntryPoints.scala", "GenBCode.scala", "LabelDefs.scala", + "scalaPrimitives.scala" + ) map (s"${backendDir}jvm/" + _), testPickling) + + //@Test def tasty_backend_sjs = compileDir(s"${backendDir}", "sjs", testPickling) + + @Test def tasty_dotc = compileDir(toolsDir, "dotc", testPickling) + @Test def tasty_dotc_ast = compileDir(dotcDir, "ast", testPickling) + @Test def tasty_dotc_config = compileDir(dotcDir, "config", testPickling) + + //TODO: issue with ./src/dotty/tools/dotc/core/Types.scala + @Test def tasty_core = compileList("tasty_core", List( + "Annotations.scala", "Constants.scala", "Constraint.scala", "ConstraintHandling.scala", + "ConstraintRunInfo.scala", "Contexts.scala", "Decorators.scala", "Definitions.scala", + "DenotTransformers.scala", "Denotations.scala", "Flags.scala", "Hashable.scala", + "NameOps.scala", "Names.scala", "OrderingConstraint.scala", "Periods.scala", + "Phases.scala", "Scopes.scala", "Signature.scala", "StdNames.scala", + "Substituters.scala", "SymDenotations.scala", "SymbolLoaders.scala", "Symbols.scala", + "TypeApplications.scala", "TypeComparer.scala", "TypeErasure.scala", "TypeOps.scala", + "TyperState.scala", "Uniques.scala" + ) map (coreDir + _), testPickling) + + @Test def tasty_classfile = compileDir(coreDir, "classfile", testPickling) + @Test def tasty_tasty = compileDir(coreDir, "tasty", testPickling) + @Test def tasty_unpickleScala2 = compileDir(coreDir, "unpickleScala2", testPickling) + + //TODO: issue with ./src/dotty/tools/dotc/parsing/Parsers.scala + @Test def tasty_dotc_parsing = compileList("tasty_dotc_parsing", List( + "CharArrayReader.scala", "JavaParsers.scala", "JavaScanners.scala", "JavaTokens.scala", + "MarkupParserCommon.scala", "MarkupParsers.scala", "package.scala" ,"Scanners.scala", + "ScriptParsers.scala", "SymbolicXMLBuilder.scala", "Tokens.scala", "Utility.scala" + ) map (parsingDir + _), testPickling) + + @Test def tasty_dotc_printing = compileDir(dotcDir, "printing", testPickling) + + @Test def tasty_dotc_repl = compileDir(dotcDir, "repl", testPickling) + + //@Test def tasty_dotc_reporting = compileDir(dotcDir, "reporting", testPickling) + @Test def tasty_dotc_rewrite = compileDir(dotcDir, "rewrite", testPickling) + + //TODO: issues with LazyVals.scala, PatternMatcher.scala + @Test def tasty_dotc_transform = compileList("tasty_dotc_transform", List( + "AugmentScala2Traits.scala", "CapturedVars.scala", "CheckReentrant.scala", "CheckStatic.scala", + "ClassOf.scala", "CollectEntryPoints.scala", "Constructors.scala", "CrossCastAnd.scala", + "CtxLazy.scala", "ElimByName.scala", "ElimErasedValueType.scala", "ElimRepeated.scala", + "ElimStaticThis.scala", "Erasure.scala", "ExpandPrivate.scala", "ExpandSAMs.scala", + "ExplicitOuter.scala", "ExtensionMethods.scala", "FirstTransform.scala", + "Flatten.scala", "FullParameterization.scala", "FunctionalInterfaces.scala", "GetClass.scala", + "Getters.scala", "InterceptedMethods.scala", "LambdaLift.scala", "LiftTry.scala", "LinkScala2ImplClasses.scala", + "MacroTransform.scala", "Memoize.scala", "Mixin.scala", "MixinOps.scala", "NonLocalReturns.scala", + "NormalizeFlags.scala", "OverridingPairs.scala", "ParamForwarding.scala", "Pickler.scala", "PostTyper.scala", + "ResolveSuper.scala", "RestoreScopes.scala", "SeqLiterals.scala", "Splitter.scala", "SuperAccessors.scala", + "SymUtils.scala", "SyntheticMethods.scala", "TailRec.scala", "TreeChecker.scala", "TreeExtractors.scala", + "TreeGen.scala", "TreeTransform.scala", "TypeTestsCasts.scala", "TypeUtils.scala", "ValueClasses.scala", + "VCElideAllocations.scala", "VCInlineMethods.scala" + ) map (s"${dotcDir}transform/" + _), testPickling) + + //TODO: issue with ./src/dotty/tools/dotc/typer/Namer.scala + @Test def tasty_typer = compileList("tasty_typer", List( + "Applications.scala", "Checking.scala", "ConstFold.scala", "ErrorReporting.scala", + "EtaExpansion.scala", "FrontEnd.scala", "Implicits.scala", "ImportInfo.scala", + "Inferencing.scala", "ProtoTypes.scala", "ReTyper.scala", "RefChecks.scala", + "TypeAssigner.scala", "Typer.scala", "VarianceChecker.scala", "Variances.scala" + ) map (typerDir + _), testPickling) + + @Test def tasty_dotc_util = compileDir(dotcDir, "util", testPickling) + @Test def tasty_tools_io = compileDir(toolsDir, "io", testPickling) + + @Test def tasty_bootstrap = { + val logging = if (false) List("-Ylog-classpath", "-verbose") else Nil + val opt = List("-priorityclasspath", defaultOutputDir) ++ logging + // first compile dotty + compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ++ logging)(allowDeepSubtypes) + + compileDir(libDir, "dotty", "-deep" :: opt) + compileDir(libDir, "scala", "-deep" :: opt) + compileDir(dottyDir, "tools", opt) + compileDir(toolsDir, "dotc", opt) + compileDir(dotcDir, "ast", opt) + compileDir(dotcDir, "config", opt) + compileDir(dotcDir, "parsing", opt) + compileDir(dotcDir, "printing", opt) + compileDir(dotcDir, "repl", opt) + compileDir(dotcDir, "reporting", opt) + compileDir(dotcDir, "rewrite", opt) + compileDir(dotcDir, "transform", opt) + compileDir(dotcDir, "typer", opt) + compileDir(dotcDir, "util", opt) + } +} diff --git a/compiler/test/dotty/partest/DPConfig.scala b/compiler/test/dotty/partest/DPConfig.scala new file mode 100644 index 000000000000..5c493f465049 --- /dev/null +++ b/compiler/test/dotty/partest/DPConfig.scala @@ -0,0 +1,40 @@ +package dotty.partest + +import scala.collection.JavaConversions._ +import scala.reflect.io.Path +import java.io.File + +import scala.tools.partest.PartestDefaults + + +/** Dotty Partest runs all tests in the provided testDirs located under + * testRoot. There can be several directories with pos resp. neg tests, as + * long as the prefix is pos/neg. + * + * Each testDir can also have a __defaultFlags.flags file, which provides + * compiler flags and is used unless there's a specific flags file (e.g. for + * test pos/A.scala, if there's a pos/A.flags file those flags are used, + * otherwise pos/__defaultFlags.flags are used if the file exists). + */ +object DPConfig { + /** Options used for _running_ the run tests. + * Note that this is different from the options used when _compiling_ tests, + * those are determined by the sbt configuration. + */ + val runJVMOpts = s"-Xms64M -Xmx1024M ${PartestDefaults.javaOpts}" + + val testRoot = (Path("..") / Path("tests") / Path("partest-generated")).toString + val genLog = Path(testRoot) / Path("gen.log") + + lazy val testDirs = { + val root = new File(testRoot) + val dirs = if (!root.exists) Array.empty[String] else root.listFiles.filter(_.isDirectory).map(_.getName) + if (dirs.isEmpty) + throw new Exception("Partest did not detect any generated sources") + dirs + } + + // Tests finish faster when running in parallel, but console output is + // out of order and sometimes the compiler crashes + val runTestsInParallel = true +} diff --git a/compiler/test/dotty/partest/DPConsoleRunner.scala b/compiler/test/dotty/partest/DPConsoleRunner.scala new file mode 100644 index 000000000000..3362d7a59343 --- /dev/null +++ b/compiler/test/dotty/partest/DPConsoleRunner.scala @@ -0,0 +1,411 @@ +/* NOTE: Adapted from ScalaJSPartest.scala in + * https://github.com/scala-js/scala-js/ + * TODO make partest configurable */ + +package dotty.partest + +import dotty.tools.FatalError +import scala.reflect.io.AbstractFile +import scala.tools.partest._ +import scala.tools.partest.nest._ +import TestState.{ Pass, Fail, Crash, Uninitialized, Updated } +import ClassPath.{ join, split } +import FileManager.{ compareFiles, compareContents, joinPaths, withTempFile } +import scala.util.matching.Regex +import tools.nsc.io.{ File => NSCFile } +import java.io.{ File, PrintStream, FileOutputStream, PrintWriter, FileWriter } +import java.net.URLClassLoader + +/** Runs dotty partest from the Console, discovering test sources in + * DPConfig.testRoot that have been generated automatically by + * DPPrepJUnitRunner. Use `sbt partest` to run. If additional jars are + * required by some run tests, add them to partestDeps in the sbt Build.scala. + */ +object DPConsoleRunner { + def main(args: Array[String]): Unit = { + // unfortunately sbt runTask passes args as single string + // extra jars for run tests are passed with -dottyJars ... + val jarFinder = """-dottyJars (\d*) (.*)""".r + val (jarList, otherArgs) = args.toList.partition(jarFinder.findFirstIn(_).isDefined) + val (extraJars, moreArgs) = jarList match { + case Nil => sys.error("Error: DPConsoleRunner needs \"-dottyJars *\".") + case jarFinder(nr, jarString) :: Nil => + val jars = jarString.split(" ").toList + val count = nr.toInt + if (jars.length < count) + sys.error("Error: DPConsoleRunner found wrong number of dottyJars: " + jars + ", expected: " + nr) + else (jars.take(count), jars.drop(count)) + case list => sys.error("Error: DPConsoleRunner found several -dottyJars options: " + list) + } + new DPConsoleRunner((otherArgs ::: moreArgs) mkString (" "), extraJars).runPartest + } +} + +// console runner has a suite runner which creates a test runner for each test +class DPConsoleRunner(args: String, extraJars: List[String]) extends ConsoleRunner(args) { + override val suiteRunner = new DPSuiteRunner ( + testSourcePath = optSourcePath getOrElse DPConfig.testRoot, + fileManager = new DottyFileManager(extraJars), + updateCheck = optUpdateCheck, + failed = optFailed, + consoleArgs = args) + + override def run = {} + def runPartest = super.run +} + +class DottyFileManager(extraJars: List[String]) extends FileManager(Nil) { + lazy val extraJarList = extraJars.map(NSCFile(_)) + override lazy val libraryUnderTest = Path(extraJars.find(_.contains("scala-library")).getOrElse("")) + override lazy val reflectUnderTest = Path(extraJars.find(_.contains("scala-reflect")).getOrElse("")) + override lazy val compilerUnderTest = Path(extraJars.find(_.contains("dotty")).getOrElse("")) +} + +class DPSuiteRunner(testSourcePath: String, // relative path, like "files", or "pending" + fileManager: DottyFileManager, + updateCheck: Boolean, + failed: Boolean, + consoleArgs: String, + javaCmdPath: String = PartestDefaults.javaCmd, + javacCmdPath: String = PartestDefaults.javacCmd, + scalacExtraArgs: Seq[String] = Seq.empty, + javaOpts: String = DPConfig.runJVMOpts) +extends SuiteRunner(testSourcePath, fileManager, updateCheck, failed, javaCmdPath, javacCmdPath, scalacExtraArgs, javaOpts) { + + if (!DPConfig.runTestsInParallel) + sys.props("partest.threads") = "1" + + sys.props("partest.root") = "." + + // override to provide Dotty banner + override def banner: String = { + s"""|Welcome to Partest for Dotty! Partest version: ${Properties.versionNumberString} + |Compiler under test: dotty.tools.dotc.Bench or dotty.tools.dotc.Main + |Generated test sources: ${PathSettings.srcDir}${File.separator} + |Test directories: ${DPConfig.testDirs.toList.mkString(", ")} + |Debugging: failed tests have compiler output in test-kind.clog, run output in test-kind.log, class files in test-kind.obj + |Parallel: ${DPConfig.runTestsInParallel} + |Options: (use partest --help for usage information) ${consoleArgs} + """.stripMargin + } + + /** Some tests require a limitation of resources, tests which are compiled + * with one or more of the flags in this list will be run with + * `limitedThreads`. This is necessary because some test flags require a lot + * of memory when running the compiler and may exhaust the available memory + * when run in parallel with too many other tests. + * + * This number could be increased on the CI, but might fail locally if + * scaled too extreme - override with: + * + * ``` + * -Ddotty.tests.limitedThreads=X + * ``` + */ + def limitResourceFlags = List("-Ytest-pickler") + private val limitedThreads = sys.props.get("dotty.tests.limitedThreads").getOrElse("2") + + override def runTestsForFiles(kindFiles: Array[File], kind: String): Array[TestState] = { + val (limitResourceTests, parallelTests) = + kindFiles partition { kindFile => + val flags = kindFile.changeExtension("flags").fileContents + limitResourceFlags.exists(seqFlag => flags.contains(seqFlag)) + } + + val seqResults = + if (!limitResourceTests.isEmpty) { + val savedThreads = sys.props("partest.threads") + sys.props("partest.threads") = { + assert( + savedThreads == null || limitedThreads.toInt <= savedThreads.toInt, + """|Should not use more threads than the default, when the point + |is to limit the amount of resources""".stripMargin + ) + limitedThreads + } + + NestUI.echo(s"## we will run ${limitResourceTests.length} tests using ${PartestDefaults.numThreads} thread(s) in parallel") + val res = super.runTestsForFiles(limitResourceTests, kind) + + if (savedThreads != null) + sys.props("partest.threads") = savedThreads + else + sys.props.remove("partest.threads") + + res + } else Array[TestState]() + + val parResults = + if (!parallelTests.isEmpty) { + NestUI.echo(s"## we will run ${parallelTests.length} tests in parallel using ${PartestDefaults.numThreads} thread(s)") + super.runTestsForFiles(parallelTests, kind) + } else Array[TestState]() + + seqResults ++ parResults + } + + // override for DPTestRunner and redirecting compilation output to test.clog + override def runTest(testFile: File): TestState = { + val runner = new DPTestRunner(testFile, this) + + val state = + try { + runner.run match { + // Append compiler output to transcript if compilation failed, + // printed with --verbose option + case TestState.Fail(f, r@"compilation failed", transcript) => + TestState.Fail(f, r, transcript ++ runner.cLogFile.fileLines.dropWhile(_ == "")) + case res => res + } + } catch { + case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) + } + reportTest(state) + runner.cleanup() + + onFinishTest(testFile, state) + } + + // override NestUI.reportTest because --show-diff doesn't work. The diff used + // seems to add each line to transcript separately, whereas NestUI assumes + // that the diff string was added as one entry in the transcript + def reportTest(state: TestState) = { + import NestUI._ + import NestUI.color._ + + if (isTerse && state.isOk) { + NestUI.reportTest(state) + } else { + echo(statusLine(state)) + if (!state.isOk && isDiffy) { + val differ = bold(red("% ")) + "diff " + state.transcript.dropWhile(s => !(s startsWith differ)) foreach (echo(_)) + // state.transcript find (_ startsWith differ) foreach (echo(_)) // original + } + } + } +} + +class DPTestRunner(testFile: File, suiteRunner: DPSuiteRunner) extends nest.Runner(testFile, suiteRunner) { + val cLogFile = SFile(logFile).changeExtension("clog") + + // override to provide DottyCompiler + override def newCompiler = new dotty.partest.DPDirectCompiler(this) + + // Adapted from nest.Runner#javac because: + // - Our classpath handling is different and we need to pass extraClassPath + // to java to get the scala-library which is required for some java tests + // - The compiler output should be redirected to cLogFile, like the output of + // dotty itself + override def javac(files: List[File]): TestState = { + import fileManager._ + import suiteRunner._ + import FileManager.joinPaths + // compile using command-line javac compiler + val args = Seq( + suiteRunner.javacCmdPath, // FIXME: Dotty deviation just writing "javacCmdPath" doesn't work + "-d", + outDir.getAbsolutePath, + "-classpath", + joinPaths(outDir :: extraClasspath ++ testClassPath) + ) ++ files.map(_.getAbsolutePath) + + pushTranscript(args mkString " ") + + val captured = StreamCapture(runCommand(args, cLogFile)) + if (captured.result) genPass() else { + cLogFile appendAll captured.stderr + cLogFile appendAll captured.stdout + genFail("java compilation failed") + } + } + + // Overriden in order to recursively get all sources that should be handed to + // the compiler. Otherwise only sources in the top dir is compiled - works + // because the compiler is on the classpath. + override def sources(file: File): List[File] = + if (file.isDirectory) + file.listFiles.toList.flatMap { f => + if (f.isDirectory) sources(f) + else if (f.isJavaOrScala) List(f) + else Nil + } + else List(file) + + // Enable me to "fix" the depth issue - remove once completed + //override def compilationRounds(file: File): List[CompileRound] = { + // val srcs = sources(file) match { + // case Nil => + // System.err.println { + // s"""|================================================================================ + // |Warning! You attempted to compile sources from: + // | $file + // |but partest was unable to find any sources - uncomment DPConsoleRunner#sources + // |================================================================================""".stripMargin + // } + // List(new File("./tests/pos/HelloWorld.scala")) // "just compile some crap" - Guillaume + // case xs => + // xs + // } + // (groupedFiles(srcs) map mixedCompileGroup).flatten + //} + + // FIXME: This is copy-pasted from nest.Runner where it is private + // Remove this once https://github.com/scala/scala-partest/pull/61 is merged + /** Runs command redirecting standard out and + * error out to output file. + */ + def runCommand(args: Seq[String], outFile: File): Boolean = { + import scala.sys.process.{ Process, ProcessLogger } + //(Process(args) #> outFile !) == 0 or (Process(args) ! pl) == 0 + val pl = ProcessLogger(outFile) + val nonzero = 17 // rounding down from 17.3 + def run: Int = { + val p = Process(args) run pl + try p.exitValue + catch { + case e: InterruptedException => + NestUI verbose s"Interrupted waiting for command to finish (${args mkString " "})" + p.destroy + nonzero + case t: Throwable => + NestUI verbose s"Exception waiting for command to finish: $t (${args mkString " "})" + p.destroy + throw t + } + finally pl.close() + } + (pl buffer run) == 0 + } + + // override to provide default dotty flags from file in directory + override def flagsForCompilation(sources: List[File]): List[String] = { + val specificFlags = super.flagsForCompilation(sources) + if (specificFlags.isEmpty) defaultFlags + else specificFlags + } + + val defaultFlags = { + val defaultFile = parentFile.listFiles.toList.find(_.getName == "__defaultFlags.flags") + defaultFile.map({ file => + SFile(file).safeSlurp.map({ content => words(content).filter(_.nonEmpty) }).getOrElse(Nil) + }).getOrElse(Nil) + } + + // override to add the check for nr of compilation errors if there's a + // target.nerr file + override def runNegTest() = runInContext { + sealed abstract class NegTestState + // Don't get confused, the neg test passes when compilation fails for at + // least one round (optionally checking the number of compiler errors and + // compiler console output) + case object CompFailed extends NegTestState + // the neg test fails when all rounds return either of these: + case class CompFailedButWrongNErr(expected: String, found: String) extends NegTestState + case object CompFailedButWrongDiff extends NegTestState + case object CompSucceeded extends NegTestState + + def nerrIsOk(reason: String) = { + val nerrFinder = """compilation failed with (\d+) errors""".r + reason match { + case nerrFinder(found) => + SFile(FileOps(testFile) changeExtension "nerr").safeSlurp match { + case Some(exp) if (exp != found) => CompFailedButWrongNErr(exp, found) + case _ => CompFailed + } + case _ => CompFailed + } + } + + // we keep the partest semantics where only one round needs to fail + // compilation, not all + val compFailingRounds = + compilationRounds(testFile) + .map { round => + val ok = round.isOk + setLastState(if (ok) genPass else genFail("compilation failed")) + (round.result, ok) + } + .filter { case (_, ok) => !ok } + + val failureStates = compFailingRounds.map({ case (result, _) => result match { + // or, OK, we'll let you crash the compiler with a FatalError if you supply a check file + case Crash(_, t, _) if !checkFile.canRead || !t.isInstanceOf[FatalError] => CompSucceeded + case Fail(_, reason, _) => if (diffIsOk) nerrIsOk(reason) else CompFailedButWrongDiff + case _ => if (diffIsOk) CompFailed else CompFailedButWrongDiff + }}) + + if (failureStates.exists({ case CompFailed => true; case _ => false })) { + true + } else { + val existsNerr = failureStates.exists({ + case CompFailedButWrongNErr(exp, found) => + nextTestActionFailing(s"wrong number of compilation errors, expected: $exp, found: $found") + true + case _ => + false + }) + + if (existsNerr) false + else { + val existsDiff = failureStates.exists({ + case CompFailedButWrongDiff => + nextTestActionFailing(s"output differs") + true + case _ => + false + }) + if (existsDiff) false + else nextTestActionFailing("expected compilation failure") + } + } + } + + // override to change check file updating to original file, not generated + override def diffIsOk: Boolean = { + // always normalize the log first + normalizeLog() + val diff = currentDiff + // if diff is not empty, is update needed? + val updating: Option[Boolean] = ( + if (diff == "") None + else Some(suiteRunner.updateCheck) + ) + pushTranscript(s"diff $logFile $checkFile") + nextTestAction(updating) { + case Some(true) => + val origCheck = SFile(checkFile.changeExtension("checksrc").fileLines(1)) + NestUI.echo("Updating original checkfile " + origCheck) + origCheck writeAll file2String(logFile) + genUpdated() + case Some(false) => + // Get a word-highlighted diff from git if we can find it + val bestDiff = if (updating.isEmpty) "" else { + if (checkFile.canRead) + gitDiff(logFile, checkFile) getOrElse { + s"diff $logFile $checkFile\n$diff" + } + else diff + } + pushTranscript(bestDiff) + genFail("output differs") + case None => genPass() // redundant default case + } getOrElse true + } + + // override to add dotty and scala jars to classpath + override def extraClasspath = + suiteRunner.fileManager.asInstanceOf[DottyFileManager].extraJarList ::: super.extraClasspath + + + // FIXME: Dotty deviation: error if return type is omitted: + // overriding method cleanup in class Runner of type ()Unit; + // method cleanup of type => Boolean | Unit has incompatible type + + // override to keep class files if failed and delete clog if ok + override def cleanup: Unit = if (lastState.isOk) { + logFile.delete + cLogFile.delete + Directory(outDir).deleteRecursively + } +} diff --git a/compiler/test/dotty/partest/DPDirectCompiler.scala b/compiler/test/dotty/partest/DPDirectCompiler.scala new file mode 100644 index 000000000000..410dac338df2 --- /dev/null +++ b/compiler/test/dotty/partest/DPDirectCompiler.scala @@ -0,0 +1,36 @@ +package dotty.partest + +import dotty.tools.dotc.reporting.ConsoleReporter +import scala.tools.partest.{ TestState, nest } +import java.io.{ File, PrintWriter, FileWriter } + + +/* NOTE: Adapted from partest.DirectCompiler */ +class DPDirectCompiler(runner: DPTestRunner) extends nest.DirectCompiler(runner) { + + override def compile(opts0: List[String], sources: List[File]): TestState = { + val clogFWriter = new FileWriter(runner.cLogFile.jfile, true) + val clogWriter = new PrintWriter(clogFWriter, true) + clogWriter.println("\ncompiling " + sources.mkString(" ") + "\noptions: " + opts0.mkString(" ")) + + try { + val processor = + if (opts0.exists(_.startsWith("#"))) dotty.tools.dotc.Bench else dotty.tools.dotc.Main + val clogger = new ConsoleReporter(writer = clogWriter) + val reporter = processor.process((sources.map(_.toString) ::: opts0).toArray, clogger) + if (!reporter.hasErrors) runner.genPass() + else { + clogWriter.println(reporter.summary) + runner.genFail(s"compilation failed with ${reporter.errorCount} errors") + } + } catch { + case t: Throwable => + t.printStackTrace + t.printStackTrace(clogWriter) + runner.genCrash(t) + } finally { + clogFWriter.close + clogWriter.close + } + } +} diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 7f0f84049673..83713748cda5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -5,6 +5,10 @@ package dotc import org.junit.Test import java.io.{ File => JFile } +import org.junit.experimental.categories.Category + + +@Category(Array(classOf[ParallelTesting])) class CompilationTests extends ParallelTesting { import CompilationTests._ diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala new file mode 100644 index 000000000000..f35f9f919d15 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/CompilerTest.scala @@ -0,0 +1,613 @@ +package dotty.tools.dotc + +import repl.TestREPL +import core.Contexts._ +import dotty.partest.DPConfig +import interfaces.Diagnostic.ERROR +import reporting._ +import diagnostic.MessageContainer +import util.SourcePosition +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 + * -.log file with output of failed tests. Partest reads compiler + * flags and the number of errors expected from a neg test from .flags + * and .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 + * .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. + */ +abstract class CompilerTest { + + /** Override with output dir of test so it can be patched. Partest expects + * classes to be in partest-generated/[kind]/[testname]-[kind].obj/ */ + val defaultOutputDir: String + + /** Override to filter out tests that should not be run by partest. */ + def partestableFile(prefix: String, fileName: String, extension: String, args: List[String]) = true + 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.") + compileArgs(cmdLine.split("\n"), Nil) + } + + /** Compiles the given code file. + * + * @param prefix the parent directory (including separator at the end) + * @param fileName the filename, by default without extension + * @param args arguments to the compiler + * @param extension the file extension, .scala by default + * @param defaultOptions more arguments to the compiler + */ + def compileFile(prefix: String, fileName: String, args: List[String] = Nil, extension: String = ".scala", runTest: Boolean = false) + (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") + } + } + } + def runFile(prefix: String, fileName: String, args: List[String] = Nil, extension: String = ".scala") + (implicit defaultOptions: List[String]): Unit = { + compileFile(prefix, fileName, args, extension, true) + } + + def findJarFromRuntime(partialName: String): 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")}""" + ) + } + } + + private def compileWithJavac( + fs: Array[String], + args: Array[String] + )(implicit defaultOptions: List[String]): Boolean = { + val scalaLib = findJarFromRuntime("scala-library") + val fullArgs = Array( + "javac", + "-classpath", + s".:$scalaLib" + ) ++ args ++ defaultOptions.dropWhile("-d" != _).take(2) ++ fs + + Runtime.getRuntime.exec(fullArgs).waitFor() == 0 + } + + /** Compiles the code files in the given directory together. If args starts + * with "-deep", all files in subdirectories (and so on) are included. */ + def compileDir(prefix: String, dirName: String, args: List[String] = Nil, runTest: Boolean = false) + (implicit defaultOptions: List[String]): Unit = { + def computeFilePathsAndExpErrors = { + val dir = Directory(prefix + dirName) + val (files, normArgs) = args match { + case "-deep" :: args1 => (dir.deepFiles, args1) + case _ => (dir.files, args) + } + val (filePaths, javaFilePaths) = files + .toArray.map(_.toString) + .foldLeft((Array.empty[String], Array.empty[String])) { case (acc @ (fp, jfp), name) => + if (name endsWith ".scala") (name +: fp, jfp) + else if (name endsWith ".java") (fp, name +: jfp) + else (fp, jfp) + } + 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") + } + } + } + def runDir(prefix: String, dirName: String, args: List[String] = Nil) + (implicit defaultOptions: List[String]): Unit = + compileDir(prefix, dirName, args, true) + + /** Compiles each source in the directory path separately by calling + * compileFile resp. compileDir. */ + def compileFiles(path: String, args: List[String] = Nil, verbose: Boolean = true, runTest: Boolean = false, + compileSubDirs: Boolean = true)(implicit defaultOptions: List[String]): Unit = { + val dir = Directory(path) + val fileNames = dir.files.toArray.map(_.jfile.getName).filter(name => (name endsWith ".scala") || (name endsWith ".java")) + for (name <- fileNames) { + if (verbose) log(s"testing $path$name") + compileFile(path, name, args, "", runTest) + } + if (compileSubDirs) + for (subdir <- dir.dirs) { + if (verbose) log(s"testing $subdir") + compileDir(path, subdir.jfile.getName, args, runTest) + } + } + def runFiles(path: String, args: List[String] = Nil, verbose: Boolean = true) + (implicit defaultOptions: List[String]): Unit = + compileFiles(path, args, verbose, true) + + /** 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 + } + } + + // ========== HELPERS ============= + + private def expectedErrors(filePaths: List[String]): List[ErrorsInFile] = if (filePaths.exists(isNegTest(_))) filePaths.map(getErrors(_)) else Nil + + private def expectedErrors(filePath: String): List[ErrorsInFile] = expectedErrors(List(filePath)) + + private def isNegTest(testPath: String) = testPath.contains("/neg/") + + private def compileArgs(args: Array[String], expectedErrorsPerFile: List[ErrorsInFile]) + (implicit defaultOptions: List[String]): Unit = { + val allArgs = args ++ defaultOptions + val verbose = allArgs.contains("-verbose") + //println(s"""all args: ${allArgs.mkString("\n")}""") + val processor = if (allArgs.exists(_.startsWith("#"))) Bench else Main + val storeReporter = new Reporter with UniqueMessagePositions with HideNonSensicalMessages { + private val consoleReporter = new ConsoleReporter() + private val innerStoreReporter = new StoreReporter(consoleReporter) + def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { + if (m.level == ERROR || verbose) { + innerStoreReporter.flush() + consoleReporter.doReport(m) + } + else if (errorCount > 0) consoleReporter.doReport(m) + else innerStoreReporter.doReport(m) + } + } + val reporter = processor.process(allArgs, storeReporter) + + val nerrors = reporter.errorCount + val xerrors = (expectedErrorsPerFile map {_.totalErrors}).sum + def expectedErrorFiles = + expectedErrorsPerFile.collect{ + case er if er.totalErrors > 0 => er.fileName + } + assert(nerrors == xerrors, + s"""Wrong # of errors. Expected: $xerrors, found: $nerrors + |Files with expected errors: $expectedErrorFiles + |errors: + """.stripMargin) + // NEG TEST + if (xerrors > 0) { + val errorLines = reporter.allErrors.map(_.pos) + // reporter didn't record as many errors as its errorCount says + assert(errorLines.length == nerrors, s"Not enough errors recorded.") + + // Some compiler errors have an associated source position. Each error + // needs to correspond to a "// error" marker on that line in the source + // file and vice versa. + // Other compiler errors don't have an associated source position. Their + // number should correspond to the total count of "// nopos-error" + // markers in all files + val (errorsByFile, errorsWithoutPos) = errorLines.groupBy(_.source.file).toList.partition(_._1.toString != "") + + // check errors with source position + val foundErrorsPerFile = errorsByFile.map({ case (fileName, errorList) => + val posErrorLinesToNr = errorList.groupBy(_.line).toList.map({ case (line, list) => (line, list.length) }).sortBy(_._1) + ErrorsInFile(fileName.toString, 0, posErrorLinesToNr) + }) + val expectedErrorsPerFileZeroed = expectedErrorsPerFile.map({ + case ErrorsInFile(fileName, _, posErrorLinesToNr) => + ErrorsInFile(fileName.toString, 0, posErrorLinesToNr) + }) + checkErrorsWithPosition(expectedErrorsPerFileZeroed, foundErrorsPerFile) + + // check errors without source position + val expectedNoPos = expectedErrorsPerFile.map(_.noposErrorNr).sum + val foundNoPos = errorsWithoutPos.map(_._2.length).sum + assert(foundNoPos == expectedNoPos, + s"Wrong # of errors without source position. Expected (all files): $expectedNoPos, found (compiler): $foundNoPos") + } + } + + // ========== NEG TEST HELPERS ============= + + /** Captures the number of nopos-errors in the given file and the number of + * errors with a position, represented as a tuple of source line and number + * of errors on that line. */ + case class ErrorsInFile(fileName: String, noposErrorNr: Int, posErrorLinesToNr: List[(Int, Int)]) { + def totalErrors = noposErrorNr + posErrorLinesToNr.map(_._2).sum + } + + /** Extracts the errors expected for the given neg test file. */ + def getErrors(fileName: String): ErrorsInFile = { + val content = SFile(fileName).slurp + val (line, rest) = content.span(_ != '\n') + + @tailrec + def checkLine(line: String, rest: String, index: Int, noposAcc: Int, posAcc: List[(Int, Int)]): ErrorsInFile = { + val posErrors = "// ?error".r.findAllIn(line).length + val newPosAcc = if (posErrors > 0) (index, posErrors) :: posAcc else posAcc + val newNoPosAcc = noposAcc + "// ?nopos-error".r.findAllIn(line).length + val (newLine, newRest) = rest.span(_ != '\n') + if (newRest.isEmpty) + ErrorsInFile(fileName.toString, newNoPosAcc, newPosAcc.reverse) + else + checkLine(newLine, newRest.tail, index + 1, newNoPosAcc, newPosAcc) // skip leading '\n' + } + + checkLine(line, rest.tail, 0, 0, Nil) // skip leading '\n' + } + + /** Asserts that the expected and found number of errors correspond, and + * otherwise throws an error with the filename, plus optionally a line + * number if available. */ + def errorMsg(fileName: String, lineNumber: Option[Int], exp: Int, found: Int) = { + val i = lineNumber.map({ i => ":" + (i + 1) }).getOrElse("") + assert(found == exp, s"Wrong # of errors for $fileName$i. Expected (file): $exp, found (compiler): $found") + } + + /** Compares the expected with the found errors and creates a nice error + * message if they don't agree. */ + def checkErrorsWithPosition(expected: List[ErrorsInFile], found: List[ErrorsInFile]): Unit = { + // create nice error messages + expected.diff(found) match { + case Nil => // nothing missing + case ErrorsInFile(fileName, _, expectedLines) :: xs => + found.find(_.fileName == fileName) match { + case None => + // expected some errors, but none found for this file + errorMsg(fileName, None, expectedLines.map(_._2).sum, 0) + case Some(ErrorsInFile(_,_,foundLines)) => + // found wrong number/location of markers for this file + compareLines(fileName, expectedLines, foundLines) + } + } + + found.diff(expected) match { + case Nil => // nothing missing + case ErrorsInFile(fileName, _, foundLines) :: xs => + expected.find(_.fileName == fileName) match { + case None => + // found some errors, but none expected for this file + errorMsg(fileName, None, 0, foundLines.map(_._2).sum) + case Some(ErrorsInFile(_,_,expectedLines)) => + // found wrong number/location of markers for this file + compareLines(fileName, expectedLines, foundLines) + } + } + } + + /** Gives an error message for one line where the expected number of errors and + * the number of compiler errors differ. */ + def compareLines(fileName: String, expectedLines: List[(Int, Int)], foundLines: List[(Int, Int)]) = { + expectedLines foreach{ + case (line, expNr) => + foundLines.find(_._1 == line) match { + case Some((_, `expNr`)) => // this line is ok + case Some((_, foundNr)) => errorMsg(fileName, Some(line), expNr, foundNr) + case None => + println(s"expected lines = $expectedLines%, %") + println(s"found lines = $foundLines%, %") + errorMsg(fileName, Some(line), expNr, 0) + } + } + foundLines foreach { + case (line, foundNr) => + expectedLines.find(_._1 == line) match { + case Some((_, `foundNr`)) => // this line is ok + case Some((_, expNr)) => errorMsg(fileName, Some(line), expNr, foundNr) + case None => errorMsg(fileName, Some(line), 0, foundNr) + } + } + } + + // ========== PARTEST HELPERS ============= + + // In particular, don't copy flags from scalac tests + private val extensionsToCopy = scala.collection.immutable.HashSet("scala", "java") + + /** Determines what kind of test to run. */ + private def testKind(prefixDir: String, runTest: Boolean) = { + if (runTest) "run" + else if (isNegTest(prefixDir)) "neg" + else if (prefixDir.endsWith("run" + JFile.separator)) { + log("WARNING: test is being run as pos test despite being in a run directory. " + + "Use runFile/runDir instead of compileFile/compileDir to do a run test") + "pos" + } else "pos" + } + + /** The three possibilities: no generated sources exist yet, the same sources + * exist already, different sources exist. */ + object Difference extends Enumeration { + type Difference = Value + val NotExists, ExistsSame, ExistsDifferent = Value + } + 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 = { + + @tailrec def copyfile(file: SFile, bytewise: Boolean): Unit = { + if (bytewise) { + val in = file.inputStream() + val out = SFile(dest).outputStream() + val buffer = new Array[Byte](1024) + @tailrec def loop(available: Int):Unit = { + if (available < 0) {()} + else { + out.write(buffer, 0, available) + val read = in.read(buffer) + loop(read) + } + } + loop(0) + in.close() + out.close() + } else { + try { + SFile(dest)(scala.io.Codec.UTF8).writeAll((s"/* !!!!! WARNING: DO NOT MODIFY. Original is at: $file !!!!! */").replace("\\", "/"), file.slurp("UTF-8")) + } catch { + case unmappable: java.nio.charset.MalformedInputException => + copyfile(file, true) //there are bytes that can't be mapped with UTF-8. Bail and just do a straight byte-wise copy without the warning header. + } + } + } + + processFileDir(sourceFile, { sf => + if (extensionsToCopy.contains(sf.extension)) { + dest.parent.jfile.mkdirs + copyfile(sf, false) + } else { + log(s"WARNING: ignoring $sf") + } + }, { sdir => + dest.jfile.mkdirs + sdir.list.foreach(path => recCopyFiles(path, dest / path.name)) + }, Some("DPCompilerTest.recCopyFiles: sourceFile not found: " + sourceFile)) + } + + /** Reads the existing files for the given test source if any. */ + private def getExisting(dest: Path): ExistingFiles = { + val content: Option[Option[String]] = processFileDir(dest, f => try Some(f.slurp("UTF8")) catch {case io: java.io.IOException => Some(io.toString())}, d => Some("")) + if (content.isDefined && content.get.isDefined) { + val flags = (dest changeExtension "flags").toFile.safeSlurp + val nerr = (dest changeExtension "nerr").toFile.safeSlurp + ExistingFiles(content.get, flags, nerr) + } else ExistingFiles() + } + + /** Encapsulates existing generated test files. */ + case class ExistingFiles(genSrc: Option[String] = None, flags: Option[String] = None, nerr: Option[String] = None) { + def isDifferent(sourceFile: JFile, otherFlags: List[String], otherNerr: String): Difference = { + if (!genSrc.isDefined) { + NotExists + } else { + val source = processFileDir(sourceFile, { f => try Some(f.slurp("UTF8")) catch {case _: java.io.IOException => None} }, { d => Some("") }, + Some("DPCompilerTest sourceFile doesn't exist: " + sourceFile)).get + if (source == genSrc) { + nerr match { + case Some(n) if (n != otherNerr) => ExistsDifferent + case None if (otherNerr != "0") => ExistsDifferent + case _ if (flags.map(_ == otherFlags.mkString(" ")).getOrElse(otherFlags.isEmpty)) => ExistsSame + case _ => ExistsDifferent + } + } else ExistsDifferent + } + } + } + + import scala.util.matching.Regex + val nrFinder = """(.*_v)(\d+)""".r + /** Changes the version number suffix in the name (without extension). */ + private def replaceVersion(name: String, nr: Int): Option[String] = { + val nrString = nr.toString + name match { + case nrFinder(prefix, `nrString`) => Some(prefix + (nr + 1)) + case _ if nr != 0 => None + case _ => Some(name + "_v1") + } + } + + /** Returns None if the given path doesn't exist, otherwise returns Some of + * applying either processFile or processDir, depending on what the path + * refers to in the file system. If failMsgOnNone is defined, this function + * asserts that the file exists using the provided message. */ + private def processFileDir[T](input: Path, processFile: SFile => T, processDir: Directory => T, failMsgOnNone: Option[String] = None): Option[T] = { + val res = input.ifFile(f => processFile(f)).orElse(input.ifDirectory(d => processDir(d))) + (failMsgOnNone, res) match { + case (Some(msg), None) => assert(false, msg); None + case _ => res + } + } + + /** 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") +} diff --git a/project/Build.scala b/project/Build.scala index ae76722485fc..baebaedf5269 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -131,7 +131,27 @@ object Build { settings( triggeredMessage in ThisBuild := Watched.clearWhenTriggered, - addCommandAlias("run", "dotty-compiler/run") + addCommandAlias("run", "dotty-compiler/run") ++ + addCommandAlias( + "partest", + ";publishLocal" + // Non-bootstrapped dotty needs to be published first + ";dotty-compiler-bootstrapped/lockPartestFile" + + ";dotty-compiler-bootstrapped/test:test" + + ";dotty-compiler-bootstrapped/runPartestRunner" + ) ++ + addCommandAlias( + "partest-only", + ";publishLocal" + // Non-bootstrapped dotty needs to be published first + ";dotty-compiler-bootstrapped/lockPartestFile" + + ";dotty-compiler-bootstrapped/test:test-only dotc.tests" + + ";dotty-compiler-bootstrapped/runPartestRunner" + ) ++ + addCommandAlias( + "partest-only-no-bootstrap", + ";dotty-compiler/lockPartestFile" + + ";dotty-compiler/test:test-only dotc.tests" + + ";dotty-compiler/runPartestRunner" + ) ). settings(publishing) @@ -264,6 +284,44 @@ object Build { "org.scala-lang" % "scala-reflect" % scalacVersion, "org.scala-lang" % "scala-library" % scalacVersion % "test"), + // start partest specific settings: + libraryDependencies += "org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test", + testOptions in Test += Tests.Cleanup({ () => partestLockFile.delete }), + // this option is needed so that partest doesn't run + testOptions += Tests.Argument(TestFrameworks.JUnit, "--exclude-categories=dotty.tools.dotc.ParallelTesting"), + partestDeps := Seq( + scalaCompiler, + "org.scala-lang" % "scala-reflect" % scalacVersion, + "org.scala-lang" % "scala-library" % scalacVersion % "test" + ), + lockPartestFile := { + // When this file is present, running `test` generates the files for + // partest. Otherwise it just executes the tests directly. + val lockDir = partestLockFile.getParentFile + lockDir.mkdirs + // Cannot have concurrent partests as they write to the same directory. + if (lockDir.list.size > 0) + throw new RuntimeException("ERROR: sbt partest: another partest is already running, pid in lock file: " + lockDir.list.toList.mkString(" ")) + partestLockFile.createNewFile + partestLockFile.deleteOnExit + }, + runPartestRunner := Def.inputTaskDyn { + // Magic! This is both an input task and a dynamic task. Apparently + // command line arguments get passed to the last task in an aliased + // sequence (see partest alias below), so this works. + val args = Def.spaceDelimited("").parsed + val jars = List( + (packageBin in Compile).value.getAbsolutePath, + packageAll.value("dotty-library"), + packageAll.value("dotty-interfaces") + ) ++ getJarPaths(partestDeps.value, ivyPaths.value.ivyHome) + val dottyJars = + s"""-dottyJars ${jars.length + 2} dotty.jar dotty-lib.jar ${jars.mkString(" ")}""" + // Provide the jars required on the classpath of run tests + runTask(Test, "dotty.partest.DPConsoleRunner", dottyJars + " " + args.mkString(" ")) + }.evaluated, + // end partest specific settings + // enable improved incremental compilation algorithm incOptions := incOptions.value.withNameHashing(true), @@ -403,10 +461,35 @@ object Build { "-Ddotty.tests.classes.compiler=" + pA("dotty-compiler") ) - jars ::: tuning ::: agentOptions ::: ci_build ::: path.toList + ("-DpartestParentID=" + pid) :: jars ::: tuning ::: agentOptions ::: ci_build ::: path.toList } ) + // Partest tasks + lazy val partestDeps = + SettingKey[Seq[ModuleID]]("partestDeps", "Finds jars for partest dependencies") + lazy val runPartestRunner = + InputKey[Unit]("runPartestRunner", "Runs partest") + lazy val lockPartestFile = + TaskKey[Unit]("lockPartestFile", "Creates the lock file at ./tests/locks/partest-.lock") + lazy val partestLockFile = + new File("." + File.separator + "tests" + File.separator + "locks" + File.separator + s"partest-$pid.lock") + + def pid = java.lang.Long.parseLong(java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split("@")(0)) + + def getJarPaths(modules: Seq[ModuleID], ivyHome: Option[File]): Seq[String] = ivyHome match { + case Some(home) => + modules.map({ module => + val file = Path(home) / Path("cache") / + Path(module.organization) / Path(module.name) / Path("jars") / + Path(module.name + "-" + module.revision + ".jar") + if (!file.isFile) throw new RuntimeException("ERROR: sbt getJarPaths: dependency jar not found: " + file) + else file.jfile.getAbsolutePath + }) + case None => throw new RuntimeException("ERROR: sbt getJarPaths: ivyHome not defined") + } + // end partest tasks + lazy val `dotty-compiler` = project.in(file("compiler")). dependsOn(`dotty-interfaces`). dependsOn(`dotty-library`). From fd37852c0499cf0ca3dcc71333e2ab1f638d8f68 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 27 Mar 2017 16:37:45 +0200 Subject: [PATCH 31/39] Add test filtering via `filterTest ` --- .drone.yml | 14 ++-- .drone.yml.sig | 2 +- compiler/test/dotc/comptest.scala | 1 + .../dotty/tools/dotc/CompilationTests.scala | 3 +- .../dotty/tools/dotc/ParallelTestTests.scala | 2 + .../dotty/tools/dotc/ParallelTesting.scala | 66 ++++++++++++------- project/Build.scala | 12 +++- 7 files changed, 68 insertions(+), 32 deletions(-) diff --git a/.drone.yml b/.drone.yml index eb36e65d80e2..520c4049efd3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,7 +6,7 @@ pipeline: - ln -s /var/cache/drone/scala-scala scala-scala - ln -s /var/cache/drone/ivy2 "$HOME/.ivy2" - ./scripts/update-scala-library - - sbt -J-Xmx4096m -J-XX:ReservedCodeCacheSize=512m -J-XX:MaxMetaspaceSize=1024m -Ddotty.drone.mem=4096m "${TEST}" + - sbt -J-Xmx24G -J-XX:ReservedCodeCacheSize=512m -J-XX:MaxMetaspaceSize=4G -Ddotty.drone.mem=4096m "${TEST}" when: branch: exclude: gh-pages @@ -34,9 +34,9 @@ pipeline: matrix: TEST: - - ;test;dotty-bin-tests/test - - ;publishLocal;dotty-bootstrapped/test - - partest-only-no-bootstrap --show-diff --verbose - - partest-only --show-diff --verbose - - ;set testOptions in LocalProject("dotty-compiler") := Seq() ;dotty-compiler/testOnly dotty.tools.dotc.CompilationTests - - ;publishLocal ;set testOptions in LocalProject("dotty-compiler-bootstrapped") := Seq() ;dotty-bootstrapped/testOnly dotty.tools.dotc.CompilationTests + - ;set testOptions in LocalProject("dotty-compiler") += Tests.Argument(TestFrameworks.JUnit, "--exclude-categories=dotty.tools.dotc.ParallelTesting") ;test ;dotty-bin-tests/test + - ;set testOptions in LocalProject("dotty-compiler-bootstrapped") += Tests.Argument(TestFrameworks.JUnit, "--exclude-categories=dotty.tools.dotc.ParallelTesting") ;publishLocal ;dotty-bootstrapped/test + - ;set testOptions in LocalProject("dotty-compiler") += Tests.Argument(TestFrameworks.JUnit, "--exclude-categories=dotty.tools.dotc.ParallelTesting") ;partest-only-no-bootstrap --show-diff --verbose + - ;set testOptions in LocalProject("dotty-compiler-bootstrapped") += Tests.Argument(TestFrameworks.JUnit, "--exclude-categories=dotty.tools.dotc.ParallelTesting") ;partest-only --show-diff --verbose + - ;dotty-compiler/testOnly dotty.tools.dotc.CompilationTests + - ;publishLocal ;dotty-bootstrapped/testOnly dotty.tools.dotc.CompilationTests diff --git a/.drone.yml.sig b/.drone.yml.sig index b823296fa975..329cd5b7ca63 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXg0MDk2bSAtSi1YWDpSZXNlcnZlZENvZGVDYWNoZVNpemU9NTEybSAtSi1YWDpNYXhNZXRhc3BhY2VTaXplPTEwMjRtIC1EZG90dHkuZHJvbmUubWVtPTQwOTZtICIke1RFU1R9IgogICAgd2hlbjoKICAgICAgYnJhbmNoOgogICAgICAgIGV4Y2x1ZGU6IGdoLXBhZ2VzCgogIGRvY3VtZW50YXRpb246CiAgICBpbWFnZTogbGFtcGVwZmwvZG90dHk6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBjb21tYW5kczoKICAgICAgLSAuL3Byb2plY3Qvc2NyaXB0cy9nZW5Eb2NzICIke1RFU1R9IiAkQk9UX1BBU1MKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRvdHR5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBzdGF0dXM6IGNoYW5nZWQKCm1hdHJpeDoKICBURVNUOgogICAgLSA7dGVzdDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7cHVibGlzaExvY2FsO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0CiAgICAtIHBhcnRlc3Qtb25seS1uby1ib290c3RyYXAgLS1zaG93LWRpZmYgLS12ZXJib3NlCiAgICAtIHBhcnRlc3Qtb25seSAtLXNob3ctZGlmZiAtLXZlcmJvc2UKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyIikgOj0gU2VxKCkgO2RvdHR5LWNvbXBpbGVyL3Rlc3RPbmx5IGRvdHR5LnRvb2xzLmRvdGMuQ29tcGlsYXRpb25UZXN0cwogICAgLSA7cHVibGlzaExvY2FsIDtzZXQgdGVzdE9wdGlvbnMgaW4gTG9jYWxQcm9qZWN0KCJkb3R0eS1jb21waWxlci1ib290c3RyYXBwZWQiKSA6PSBTZXEoKSA7ZG90dHktYm9vdHN0cmFwcGVkL3Rlc3RPbmx5IGRvdHR5LnRvb2xzLmRvdGMuQ29tcGlsYXRpb25UZXN0cwo.qsDrUBsZtyXeEeRXf9CnC0Rh5FF0lZpKCgf2iZvPckE \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXgyNEcgLUotWFg6UmVzZXJ2ZWRDb2RlQ2FjaGVTaXplPTUxMm0gLUotWFg6TWF4TWV0YXNwYWNlU2l6ZT00RyAtRGRvdHR5LmRyb25lLm1lbT00MDk2bSAiJHtURVNUfSIKICAgIHdoZW46CiAgICAgIGJyYW5jaDoKICAgICAgICBleGNsdWRlOiBnaC1wYWdlcwoKICBkb2N1bWVudGF0aW9uOgogICAgaW1hZ2U6IGxhbXBlcGZsL2RvdHR5OmxhdGVzdAogICAgcHVsbDogdHJ1ZQogICAgY29tbWFuZHM6CiAgICAgIC0gLi9wcm9qZWN0L3NjcmlwdHMvZ2VuRG9jcyAiJHtURVNUfSIgJEJPVF9QQVNTCiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgoKICBnaXR0ZXI6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCiAgICAgIHN0YXR1czogY2hhbmdlZAoKICBzbGFjazoKICAgIGltYWdlOiBwbHVnaW5zL3NsYWNrCiAgICBjaGFubmVsOiBkb3R0eQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgptYXRyaXg6CiAgVEVTVDoKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyIikgKz0gVGVzdHMuQXJndW1lbnQoVGVzdEZyYW1ld29ya3MuSlVuaXQsICItLWV4Y2x1ZGUtY2F0ZWdvcmllcz1kb3R0eS50b29scy5kb3RjLlBhcmFsbGVsVGVzdGluZyIpIDt0ZXN0IDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7c2V0IHRlc3RPcHRpb25zIGluIExvY2FsUHJvamVjdCgiZG90dHktY29tcGlsZXItYm9vdHN0cmFwcGVkIikgKz0gVGVzdHMuQXJndW1lbnQoVGVzdEZyYW1ld29ya3MuSlVuaXQsICItLWV4Y2x1ZGUtY2F0ZWdvcmllcz1kb3R0eS50b29scy5kb3RjLlBhcmFsbGVsVGVzdGluZyIpIDtwdWJsaXNoTG9jYWwgO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0CiAgICAtIDtzZXQgdGVzdE9wdGlvbnMgaW4gTG9jYWxQcm9qZWN0KCJkb3R0eS1jb21waWxlciIpICs9IFRlc3RzLkFyZ3VtZW50KFRlc3RGcmFtZXdvcmtzLkpVbml0LCAiLS1leGNsdWRlLWNhdGVnb3JpZXM9ZG90dHkudG9vbHMuZG90Yy5QYXJhbGxlbFRlc3RpbmciKSA7cGFydGVzdC1vbmx5LW5vLWJvb3RzdHJhcCAtLXNob3ctZGlmZiAtLXZlcmJvc2UKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyLWJvb3RzdHJhcHBlZCIpICs9IFRlc3RzLkFyZ3VtZW50KFRlc3RGcmFtZXdvcmtzLkpVbml0LCAiLS1leGNsdWRlLWNhdGVnb3JpZXM9ZG90dHkudG9vbHMuZG90Yy5QYXJhbGxlbFRlc3RpbmciKSA7cGFydGVzdC1vbmx5IC0tc2hvdy1kaWZmIC0tdmVyYm9zZQogICAgLSA7ZG90dHktY29tcGlsZXIvdGVzdE9ubHkgZG90dHkudG9vbHMuZG90Yy5Db21waWxhdGlvblRlc3RzCiAgICAtIDtwdWJsaXNoTG9jYWwgO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0T25seSBkb3R0eS50b29scy5kb3RjLkNvbXBpbGF0aW9uVGVzdHMK.IAe3X-XmMyf3-GnA1ENr-D3t_YM_28CAhHX6lP0EB6E \ No newline at end of file diff --git a/compiler/test/dotc/comptest.scala b/compiler/test/dotc/comptest.scala index c2c88abb3c5b..279660eefb58 100644 --- a/compiler/test/dotc/comptest.scala +++ b/compiler/test/dotc/comptest.scala @@ -5,6 +5,7 @@ import dotty.tools.dotc.ParallelTesting object comptest extends ParallelTesting { def interactive: Boolean = true + def regex: Option[String] = None implicit val defaultOutputDir: String = "." diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 83713748cda5..93ddab0570ef 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -7,13 +7,14 @@ import java.io.{ File => JFile } import org.junit.experimental.categories.Category - @Category(Array(classOf[ParallelTesting])) class CompilationTests extends ParallelTesting { import CompilationTests._ def interactive: Boolean = !sys.env.contains("DRONE") + def regex: Option[String] = sys.props.get("dotty.partest.filter") + // Positive tests ------------------------------------------------------------ @Test def compilePos: Unit = { diff --git a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala index 572f63410981..a22df0ace587 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala @@ -12,6 +12,8 @@ class ParallelTestTests extends ParallelTesting { def interactive: Boolean = !sys.env.contains("DRONE") + def regex: Option[String] = None + @Test def missingFile: Unit = try { compileFile("../tests/partest-test/i-dont-exist.scala", defaultOptions).expectFailure.neg() diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index b4452299b5a6..2123e76571a7 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -14,6 +14,7 @@ import scala.io.Source import scala.util.control.NonFatal import scala.util.Try import scala.collection.mutable +import scala.util.matching.Regex import core.Contexts._ import reporting.{ Reporter, TestReporter } @@ -25,6 +26,10 @@ trait ParallelTesting { def interactive: Boolean + def regex: Option[String] + + private lazy val filter: Option[Regex] = regex.map(str => new Regex(str)) + private sealed trait Target { self => def outDir: JFile def flags: Array[String] @@ -121,7 +126,16 @@ trait ParallelTesting { /** Actual compilation run logic, the test behaviour is defined here */ protected def compilationRunnable(target: Target): Runnable - val totalTargets = targets.length + private val allTargets = + if (!filter.isDefined) targets + else targets.filter { + case ConcurrentCompilationTarget(files, _, _) => + files.exists(file => filter.get.findFirstIn(file.getAbsolutePath).isDefined) + case SeparateCompilationTarget(dir, _, _) => + filter.get.findFirstIn(dir.getAbsolutePath).isDefined + } + + val totalTargets = allTargets.length private[this] var _errors = 0 def errors: Int = synchronized { _errors } @@ -271,31 +285,39 @@ trait ParallelTesting { private[ParallelTesting] def execute(): this.type = { assert(_targetsCompiled == 0, "not allowed to re-use a `CompileRun`") - val pool = threadLimit match { - case Some(i) => JExecutors.newWorkStealingPool(i) - case None => JExecutors.newWorkStealingPool() - } - if (interactive && !suppressAllOutput) pool.submit(statusRunner) + if (allTargets.nonEmpty) { + val pool = threadLimit match { + case Some(i) => JExecutors.newWorkStealingPool(i) + case None => JExecutors.newWorkStealingPool() + } - targets.foreach { target => - pool.submit(compilationRunnable(target)) - } + if (interactive && !suppressAllOutput) pool.submit(statusRunner) - pool.shutdown() - if (!pool.awaitTermination(10, TimeUnit.MINUTES)) - throw new TimeoutException("Compiling targets timed out") - - if (didFail) { - echo { - """| - |================================================================================ - |Test Report - |================================================================================ - |Failing tests:""".stripMargin + allTargets.foreach { target => + pool.submit(compilationRunnable(target)) } - failedCompilationTargets.toArray.sorted.foreach(echo) - failureInstructions.iterator.foreach(echo) + + pool.shutdown() + if (!pool.awaitTermination(10, TimeUnit.MINUTES)) + throw new TimeoutException("Compiling targets timed out") + + if (didFail) { + echo { + """| + |================================================================================ + |Test Report + |================================================================================ + |Failing tests:""".stripMargin + } + failedCompilationTargets.toArray.sorted.foreach(echo) + failureInstructions.iterator.foreach(echo) + } + } + else echo { + regex + .map(r => s"""No files matched regex "$r" in test""") + .getOrElse("No tests available under target - erroneous test?") } this diff --git a/project/Build.scala b/project/Build.scala index baebaedf5269..bb02416cc642 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -47,6 +47,9 @@ object Build { // Spawns a repl with the correct classpath lazy val repl = inputKey[Unit]("run the REPL with correct classpath") + // Run tests with filter + lazy val filterTest = inputKey[Unit]("runs integration test with the supplied filter") + // Used to compile files similar to ./bin/dotc script lazy val dotc = inputKey[Unit]("run the compiler using the correct classpath, or the user supplied classpath") @@ -288,7 +291,6 @@ object Build { libraryDependencies += "org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test", testOptions in Test += Tests.Cleanup({ () => partestLockFile.delete }), // this option is needed so that partest doesn't run - testOptions += Tests.Argument(TestFrameworks.JUnit, "--exclude-categories=dotty.tools.dotc.ParallelTesting"), partestDeps := Seq( scalaCompiler, "org.scala-lang" % "scala-reflect" % scalacVersion, @@ -338,6 +340,14 @@ object Build { ) }.evaluated, + filterTest := Def.inputTaskDyn { + val args: Seq[String] = spaceDelimited("").parsed + testOptions := Seq() + (testOnly in Test).toTask( + " dotty.tools.dotc.CompilationTests -- -Ddotty.partest.filter=" + args.head + ) + }.evaluated, + // Override run to be able to run compiled classfiles dotr := { val args: Seq[String] = spaceDelimited("").parsed From 0b65aade84f5ea10eb246bf7e88d92aa95493dca Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 28 Mar 2017 11:40:52 +0200 Subject: [PATCH 32/39] Add documentation to ParallelTesting --- .../dotty/tools/dotc/ParallelTesting.scala | 249 +++++++++++++++++- 1 file changed, 247 insertions(+), 2 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 2123e76571a7..a223747b23dd 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -22,18 +22,39 @@ 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 { + /** If the running environment supports an interactive terminal, each `Test` + * will be run with a progress bar and real time feedback + */ def interactive: Boolean + /** A regex denoting which tests to run, please note that this gets turned + * into an actual regular expression - and as such should be valid. + * + * E.g: `*`is not a regular expression, but `.*` is. + */ def regex: Option[String] + /** The `regex` string gets turned into an actual regular expression used for + * filtering tests + */ private lazy val filter: Option[Regex] = regex.map(str => new Regex(str)) + /** A compilation target whose files or directory of files is to be compiled + * in a specific way defined by the `Test` + */ private sealed trait Target { self => def outDir: JFile def flags: Array[String] + /** 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 { @@ -45,6 +66,7 @@ trait ParallelTesting { 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 @@ -90,6 +112,9 @@ trait ParallelTesting { } } + /** A group of files that may all be compiled together, with the same flags + * and output directory + */ private final case class ConcurrentCompilationTarget( files: Array[JFile], flags: Array[String], @@ -97,12 +122,19 @@ trait ParallelTesting { ) extends Target { override def toString() = outDir.toString } + + /** A target whose files will be compiled separately according to their + * suffix `_X` + */ private final case class SeparateCompilationTarget( dir: JFile, flags: Array[String], outDir: JFile ) extends Target { + /** Get the files grouped by `_X` as a list of groups, files missing this + * suffix will be put into the same group + */ def compilationUnits: List[Array[JFile]] = dir .listFiles @@ -121,11 +153,15 @@ trait ParallelTesting { .toList.sortBy(_._1).map(_._2.filter(isCompilable)) } + /** Each `Test` takes the `targets` and performs the compilation and assertions + * according to the implementing class "neg", "run" or "pos". + */ private abstract class Test(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) { /** Actual compilation run logic, the test behaviour is defined here */ protected def compilationRunnable(target: Target): Runnable + /** All targets left after filtering out */ private val allTargets = if (!filter.isDefined) targets else targets.filter { @@ -135,6 +171,7 @@ trait ParallelTesting { filter.get.findFirstIn(dir.getAbsolutePath).isDefined } + /** Total amount of targets being compiled by this test */ val totalTargets = allTargets.length private[this] var _errors = 0 @@ -143,23 +180,28 @@ trait ParallelTesting { private[this] var _targetsCompiled = 0 private def targetsCompiled: Int = synchronized { _targetsCompiled } + /** Complete the current compilation with the amount of errors encountered */ protected final def completeCompilation(errors: Int) = synchronized { _targetsCompiled += 1 _errors += errors } private[this] var _failed = false + /** Fail the current test */ protected[this] final def fail(): Unit = synchronized { _failed = true } def didFail: Boolean = _failed + /** Instructions on how to reproduce failed target compilations */ private[this] val failureInstructions = mutable.ArrayBuffer.empty[String] protected final def addFailureInstruction(ins: String): Unit = synchronized { failureInstructions.append(ins) } + /** The targets that failed according to the implementing subclass */ private[this] val failedCompilationTargets = mutable.ArrayBuffer.empty[String] protected final def addFailedCompilationTarget(target: String): Unit = synchronized { failedCompilationTargets.append(target) } + /** Fails the current target, and makes sure it gets logged */ protected final def failTarget(target: Target) = target match { case ConcurrentCompilationTarget(files, _, _) => @@ -170,9 +212,11 @@ trait ParallelTesting { fail() } + /** Prints to `System.err` if we're not suppressing all output */ protected def echo(msg: String): Unit = if (!suppressAllOutput) System.err.println(msg) + /** A single `Runnable` that prints a progress bar for the curent `Test` */ private def statusRunner: Runnable = new Runnable { def run(): Unit = { val start = System.currentTimeMillis @@ -197,6 +241,9 @@ trait ParallelTesting { } } + /** Wrapper function to make sure that the compiler itself did not crash - + * if it did, the test should automatically fail. + */ protected def compileTry(op: => Unit): Unit = try op catch { case NonFatal(e) => { @@ -590,6 +637,110 @@ trait ParallelTesting { } } + /** 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 `Target`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[Target], private[ParallelTesting] val times: Int, @@ -605,6 +756,20 @@ trait ParallelTesting { private[ParallelTesting] def this(targets: List[Target]) = 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()` / `neg()`/ `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") @@ -612,6 +777,10 @@ trait ParallelTesting { 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 pos(): this.type = { val test = new PosTest(targets, times, threadLimit, shouldFail).execute() @@ -625,6 +794,10 @@ trait ParallelTesting { 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 neg(): this.type = { val test = new NegTest(targets, times, threadLimit, shouldFail).execute() @@ -638,6 +811,11 @@ trait ParallelTesting { 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 run(): this.type = { val test = new RunTest(targets, times, threadLimit, shouldFail).execute() @@ -651,11 +829,15 @@ trait ParallelTesting { cleanup() } + /** Deletes output directories and files */ private def cleanup(): this.type = { if (shouldDelete) targets.foreach(t => delete(t.outDir)) 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) @@ -663,6 +845,10 @@ trait ParallelTesting { 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 @ ConcurrentCompilationTarget(files, _, outDir) => @@ -673,27 +859,45 @@ trait ParallelTesting { 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)) - def targetDirs: List[JFile] = targets.map(_.outDir) - private def delete(file: JFile): Unit = { if (file.isDirectory) file.listFiles.foreach(delete) try Files.delete(file.toPath) @@ -703,12 +907,14 @@ trait ParallelTesting { } } + /** Create out directory for directory `d` */ private def toCompilerDirFromDir(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 toCompilerDirFromFile(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") @@ -716,11 +922,13 @@ trait ParallelTesting { 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) @@ -729,6 +937,13 @@ trait ParallelTesting { else (dirs, f :: 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 @@ -747,6 +962,7 @@ trait ParallelTesting { } } + /** 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 sourceFile = new JFile(f) val parent = sourceFile.getParentFile @@ -768,6 +984,10 @@ trait ParallelTesting { 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`. + */ def compileDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) @@ -785,6 +1005,10 @@ trait ParallelTesting { 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 outDir = outDirectory + getCallingMethod + "/" + testName + "/" @@ -799,6 +1023,23 @@ trait ParallelTesting { 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 outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) @@ -814,6 +1055,10 @@ trait ParallelTesting { 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 outDir = outDirectory + getCallingMethod + "/" val sourceDir = new JFile(f) From 7d8303df1091930a0ae1b5f7f5fe5d7e970c7d2f Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 28 Mar 2017 14:59:13 +0200 Subject: [PATCH 33/39] Address reviewer feedback on #2125 --- compiler/test/dotc/comptest.scala | 4 +- .../dotty/tools/dotc/CompilationTests.scala | 40 +- .../dotty/tools/dotc/ParallelTestTests.scala | 26 +- .../dotty/tools/dotc/ParallelTesting.scala | 411 +++++++++--------- .../tools/dotc/reporting/TestReporter.scala | 4 +- 5 files changed, 234 insertions(+), 251 deletions(-) diff --git a/compiler/test/dotc/comptest.scala b/compiler/test/dotc/comptest.scala index 279660eefb58..dce002c81522 100644 --- a/compiler/test/dotc/comptest.scala +++ b/compiler/test/dotc/comptest.scala @@ -4,8 +4,8 @@ import dotty.tools.dotc.ParallelTesting object comptest extends ParallelTesting { - def interactive: Boolean = true - def regex: Option[String] = None + def isInteractive = true + def testFilter = None implicit val defaultOutputDir: String = "." diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 93ddab0570ef..74688c24d0fb 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -4,26 +4,27 @@ package dotc import org.junit.Test import java.io.{ File => JFile } - import org.junit.experimental.categories.Category +import scala.util.matching.Regex + @Category(Array(classOf[ParallelTesting])) class CompilationTests extends ParallelTesting { import CompilationTests._ - def interactive: Boolean = !sys.env.contains("DRONE") + def isInteractive: Boolean = !sys.env.contains("DRONE") - def regex: Option[String] = sys.props.get("dotty.partest.filter") + def testFilter: Option[Regex] = sys.props.get("dotty.partest.filter").map(r => new Regex(r)) // Positive tests ------------------------------------------------------------ @Test def compilePos: Unit = { compileList("compileStdLib", StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")) + compileFilesInDir("../tests/pos", defaultOptions) - }.pos() + }.checkCompile() @Test def compilePosScala2: Unit = - compileFilesInDir("../tests/pos-scala2", scala2Mode).pos() + compileFilesInDir("../tests/pos-scala2", scala2Mode).checkCompile() @Test def compilePosMixedFlags: Unit = { compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")) + @@ -56,10 +57,10 @@ class CompilationTests extends ParallelTesting { ), scala2Mode ) - }.pos() + }.checkCompile() @Test def compileCoreNoCheck: Unit = - compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath).pos() + compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath).checkCompile() @Test def compileDotcInternals: Unit = { compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions) + @@ -72,7 +73,7 @@ class CompilationTests extends ParallelTesting { compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions) + compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions) + compileDir("../compiler/src/dotty/tools/io", defaultOptions) - }.pos() + }.checkCompile() @Test def posTwice: Unit = { compileFile("../tests/pos/Labels.scala", defaultOptions) + @@ -128,17 +129,17 @@ class CompilationTests extends ParallelTesting { ), defaultOptions.and("-Xprompt") ) - }.times(2).pos() + }.times(2).checkCompile() // New tests ----------------------------------------------------------------- @Test def compileNew: Unit = - compileFilesInDir("../tests/new", defaultOptions).pos() + compileFilesInDir("../tests/new", defaultOptions).checkCompile() // Negative tests ------------------------------------------------------------ @Test def compileNeg: Unit = - compileShallowFilesInDir("../tests/neg", defaultOptions).neg() + compileShallowFilesInDir("../tests/neg", defaultOptions).checkExpectedErrors() @Test def compileNegCustomFlags: Unit = { compileFile("../tests/neg/customArgs/typers.scala", allowDoubleBindings) + @@ -157,14 +158,17 @@ class CompilationTests extends ParallelTesting { compileFile("../tests/neg/tailcall/tailrec-2.scala", defaultOptions) + compileFile("../tests/neg/tailcall/tailrec-3.scala", defaultOptions) + compileDir("../tests/neg/typedIdents", defaultOptions) - }.neg() + }.checkExpectedErrors() // Run tests ----------------------------------------------------------------- @Test def runAll: Unit = - compileFilesInDir("../tests/run", defaultOptions).run() + compileFilesInDir("../tests/run", defaultOptions).checkRuns() // Pickling Tests ------------------------------------------------------------ + // + // Pickling tests are very memory intensive and as such need to be run with a + // lower level of concurrency as to not kill their running VMs @Test def testPickling1: Unit = { compileFilesInDir("../tests/new", picklingOptions) + @@ -183,21 +187,21 @@ class CompilationTests extends ParallelTesting { compileDir("../compiler/src/dotty/tools/dotc/util", picklingOptions) + compileDir("../compiler/src/dotty/tools/io", picklingOptions) + compileFile("../tests/pos/pickleinf.scala", picklingOptions) - }.limitThreads(4).pos() + }.limitThreads(4).checkCompile() @Test def testPickling2: Unit = { compileDir("../compiler/src/dotty/tools/dotc/core/classfile", picklingOptions) + compileDir("../compiler/src/dotty/tools/dotc/core/tasty", picklingOptions) + compileDir("../compiler/src/dotty/tools/dotc/core/unpickleScala2", picklingOptions) - }.limitThreads(4).pos() + }.limitThreads(4).checkCompile() @Test def testPickling3: Unit = { compileDir("../compiler/src/dotty/tools", picklingOptions) - }.limitThreads(4).pos() + }.limitThreads(4).checkCompile() @Test def testPickling4: Unit = { compileDir("../compiler/src/dotty/tools/dotc", picklingOptions) - }.limitThreads(4).pos() + }.limitThreads(4).checkCompile() /** The purpose of this test is two-fold, being able to compile dotty * bootstrapped, and making sure that TASTY can link against a compiled @@ -239,7 +243,7 @@ class CompilationTests extends ParallelTesting { compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/typer", opt) + compileShallowFilesInDir("../compiler/src/dotty/tools/dotc/util", opt) } :: Nil - }.map(_.pos()).foreach(_.delete()) + }.map(_.checkCompile()).foreach(_.delete()) } } diff --git a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala index a22df0ace587..30ec88455255 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala @@ -10,13 +10,12 @@ import scala.util.control.NonFatal class ParallelTestTests extends ParallelTesting { import CompilationTests._ - def interactive: Boolean = !sys.env.contains("DRONE") - - def regex: Option[String] = None + def isInteractive = !sys.env.contains("DRONE") + def testFilter = None @Test def missingFile: Unit = try { - compileFile("../tests/partest-test/i-dont-exist.scala", defaultOptions).expectFailure.neg() + compileFile("../tests/partest-test/i-dont-exist.scala", defaultOptions).expectFailure.checkExpectedErrors() fail("didn't fail properly") } catch { @@ -25,29 +24,28 @@ class ParallelTestTests extends ParallelTesting { } @Test def pos1Error: Unit = - compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.pos() + compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.checkCompile() - @Test def negMissingAnnot: Unit = - compileFile("../tests/partest-test/negMissingAnnot.scala", defaultOptions).expectFailure.neg() + @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.neg() + compileFile("../tests/partest-test/negAnnotWrongLine.scala", defaultOptions).expectFailure.checkExpectedErrors() @Test def negTooManyAnnots: Unit = - compileFile("../tests/partest-test/negTooManyAnnots.scala", defaultOptions).expectFailure.neg() + compileFile("../tests/partest-test/negTooManyAnnots.scala", defaultOptions).expectFailure.checkExpectedErrors() @Test def negNoPositionAnnot: Unit = - compileFile("../tests/partest-test/negNoPositionAnnots.scala", defaultOptions).expectFailure.neg() + compileFile("../tests/partest-test/negNoPositionAnnots.scala", defaultOptions).expectFailure.checkExpectedErrors() @Test def runCompileFail: Unit = - compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.run() + compileFile("../tests/partest-test/posFail1Error.scala", defaultOptions).expectFailure.checkRuns() @Test def runWrongOutput1: Unit = - compileFile("../tests/partest-test/runWrongOutput1.scala", defaultOptions).expectFailure.run() + compileFile("../tests/partest-test/runWrongOutput1.scala", defaultOptions).expectFailure.checkRuns() @Test def runWrongOutput2: Unit = - compileFile("../tests/partest-test/runWrongOutput2.scala", defaultOptions).expectFailure.run() + compileFile("../tests/partest-test/runWrongOutput2.scala", defaultOptions).expectFailure.checkRuns() @Test def runDiffOutput1: Unit = - compileFile("../tests/partest-test/runDiffOutput1.scala", defaultOptions).expectFailure.run() + compileFile("../tests/partest-test/runDiffOutput1.scala", defaultOptions).expectFailure.checkRuns() } diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index a223747b23dd..3cbc2c908ed7 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -30,27 +30,22 @@ import dotc.util.DiffUtil */ trait ParallelTesting { + import ParallelTesting._ + /** If the running environment supports an interactive terminal, each `Test` * will be run with a progress bar and real time feedback */ - def interactive: Boolean - - /** A regex denoting which tests to run, please note that this gets turned - * into an actual regular expression - and as such should be valid. - * - * E.g: `*`is not a regular expression, but `.*` is. - */ - def regex: Option[String] + def isInteractive: Boolean - /** The `regex` string gets turned into an actual regular expression used for - * filtering tests + /** A regex which is used to filter which tests to run, if `None` will run + * all tests */ - private lazy val filter: Option[Regex] = regex.map(str => new Regex(str)) + def testFilter: Option[Regex] - /** A compilation target whose files or directory of files is to be compiled + /** A test source whose files or directory of files is to be compiled * in a specific way defined by the `Test` */ - private sealed trait Target { self => + private sealed trait TestSource { self => def outDir: JFile def flags: Array[String] @@ -58,9 +53,9 @@ trait ParallelTesting { def withFlags(newFlags0: String*) = { val newFlags = newFlags0.toArray if (!flags.containsSlice(newFlags)) self match { - case self: ConcurrentCompilationTarget => + case self: JointCompilationSource => self.copy(flags = flags ++ newFlags) - case self: SeparateCompilationTarget => + case self: SeparateCompilationSource => self.copy(flags = flags ++ newFlags) } else self @@ -85,7 +80,7 @@ trait ParallelTesting { } self match { - case ConcurrentCompilationTarget(files, _, _) => { + case JointCompilationSource(files, _, _) => { files.map(_.getAbsolutePath).foreach { path => sb.append("\\\n ") sb.append(path) @@ -93,10 +88,10 @@ trait ParallelTesting { } sb.toString + "\n\n" } - case self: SeparateCompilationTarget => { + case self: SeparateCompilationSource => { val command = sb.toString val fsb = new StringBuilder(command) - self.compilationUnits.foreach { files => + self.compilationGroups.foreach { files => files.map(_.getPath).foreach { path => fsb.append("\\\n ") lineLen = 8 @@ -115,27 +110,31 @@ trait ParallelTesting { /** A group of files that may all be compiled together, with the same flags * and output directory */ - private final case class ConcurrentCompilationTarget( + private final case class JointCompilationSource( files: Array[JFile], flags: Array[String], outDir: JFile - ) extends Target { + ) extends TestSource { + def sourceFiles: Array[JFile] = files.filter(isSourceFile) + override def toString() = outDir.toString } - /** A target whose files will be compiled separately according to their + /** A test source whose files will be compiled separately according to their * suffix `_X` */ - private final case class SeparateCompilationTarget( + private final case class SeparateCompilationSource( dir: JFile, flags: Array[String], outDir: JFile - ) extends Target { + ) 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 compilationUnits: List[Array[JFile]] = + def compilationGroups: List[Array[JFile]] = dir .listFiles .groupBy { file => @@ -150,40 +149,40 @@ trait ParallelTesting { .toOption .getOrElse("") } - .toList.sortBy(_._1).map(_._2.filter(isCompilable)) + .toList.sortBy(_._1).map(_._2.filter(isSourceFile)) } - /** Each `Test` takes the `targets` and performs the compilation and assertions + /** Each `Test` takes the `testSources` and performs the compilation and assertions * according to the implementing class "neg", "run" or "pos". */ - private abstract class Test(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) { + private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) { /** Actual compilation run logic, the test behaviour is defined here */ - protected def compilationRunnable(target: Target): Runnable - - /** All targets left after filtering out */ - private val allTargets = - if (!filter.isDefined) targets - else targets.filter { - case ConcurrentCompilationTarget(files, _, _) => - files.exists(file => filter.get.findFirstIn(file.getAbsolutePath).isDefined) - case SeparateCompilationTarget(dir, _, _) => - filter.get.findFirstIn(dir.getAbsolutePath).isDefined + 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 targets being compiled by this test */ - val totalTargets = allTargets.length + /** Total amount of test sources being compiled by this test */ + val sourceCount = filteredSources.length - private[this] var _errors = 0 - def errors: Int = synchronized { _errors } + private[this] var _errorCount = 0 + def errorCount: Int = synchronized { _errorCount } - private[this] var _targetsCompiled = 0 - private def targetsCompiled: Int = synchronized { _targetsCompiled } + private[this] var _testSourcesCompiled = 0 + private def testSourcesCompiled : Int = synchronized { _testSourcesCompiled } /** Complete the current compilation with the amount of errors encountered */ - protected final def completeCompilation(errors: Int) = synchronized { - _targetsCompiled += 1 - _errors += errors + protected final def registerCompilation(errors: Int) = synchronized { + _testSourcesCompiled += 1 + _errorCount += errors } private[this] var _failed = false @@ -191,23 +190,32 @@ trait ParallelTesting { protected[this] final def fail(): Unit = synchronized { _failed = true } def didFail: Boolean = _failed - /** Instructions on how to reproduce failed target compilations */ - private[this] val failureInstructions = mutable.ArrayBuffer.empty[String] + protected def echoBuildInstructions(reporter: TestReporter, testSource: TestSource, err: Int, war: Int) = { + val errorMsg = testSource.buildInstructions(reporter.errorCount, reporter.warningCount) + addFailureInstruction(errorMsg) + failTestSource(testSource) + reporter.echo(errorMsg) + reporter.flushToFile() + } + + + /** 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 { failureInstructions.append(ins) } + synchronized { reproduceInstructions.append(ins) } - /** The targets that failed according to the implementing subclass */ - private[this] val failedCompilationTargets = mutable.ArrayBuffer.empty[String] - protected final def addFailedCompilationTarget(target: String): Unit = - synchronized { failedCompilationTargets.append(target) } + /** The test sources that failed according to the implementing subclass */ + private[this] val failedTestSources = mutable.ArrayBuffer.empty[String] + protected final def addFailedCompilationTarget(testSource: String): Unit = + synchronized { failedTestSources.append(testSource) } - /** Fails the current target, and makes sure it gets logged */ - protected final def failTarget(target: Target) = - target match { - case ConcurrentCompilationTarget(files, _, _) => + /** Fails the current `TestSource`, and makes sure it gets logged */ + protected final def failTestSource(testSource: TestSource) = + testSource match { + case JointCompilationSource(files, _, _) => files.map(_.getAbsolutePath).foreach(addFailedCompilationTarget) fail() - case SeparateCompilationTarget(dir, _, _) => + case SeparateCompilationSource(dir, _, _) => addFailedCompilationTarget(dir.getAbsolutePath) fail() } @@ -217,25 +225,25 @@ trait ParallelTesting { if (!suppressAllOutput) System.err.println(msg) /** A single `Runnable` that prints a progress bar for the curent `Test` */ - private def statusRunner: Runnable = new Runnable { + private def createProgressMonitor: Runnable = new Runnable { def run(): Unit = { val start = System.currentTimeMillis - var tCompiled = targetsCompiled - while (tCompiled < totalTargets) { + var tCompiled = testSourcesCompiled + while (tCompiled < sourceCount) { val timestamp = (System.currentTimeMillis - start) / 1000 - val progress = (tCompiled.toDouble / totalTargets * 40).toInt + val progress = (tCompiled.toDouble / sourceCount * 40).toInt print( "[" + ("=" * (math.max(progress - 1, 0))) + (if (progress > 0) ">" else "") + (" " * (39 - progress)) + - s"] compiling ($tCompiled/$totalTargets, ${timestamp}s)\r" + s"] compiling ($tCompiled/$sourceCount, ${timestamp}s)\r" ) Thread.sleep(100) - tCompiled = targetsCompiled + tCompiled = testSourcesCompiled } // println, otherwise no newline and cursor at start of line println( - s"[=======================================] compiled ($totalTargets/$totalTargets, " + + s"[=======================================] compiled ($sourceCount/$sourceCount, " + s"${(System.currentTimeMillis - start) / 1000}s) " ) } @@ -244,14 +252,14 @@ trait ParallelTesting { /** Wrapper function to make sure that the compiler itself did not crash - * if it did, the test should automatically fail. */ - protected def compileTry(op: => Unit): Unit = + protected def tryCompile(op: => Unit): Unit = try op catch { case NonFatal(e) => { // if an exception is thrown during compilation, the complete test // run should fail fail() e.printStackTrace() - completeCompilation(1) + registerCompilation(1) throw e } } @@ -295,12 +303,6 @@ trait ParallelTesting { Runtime.getRuntime.exec(fullArgs).waitFor() == 0 } else true - - // First we try to compile the java files in the directory: - val javaFiles = files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath) - val javaCompiledBefore = compileWithJavac(javaFiles) - - // Then we compile the scala files: val reporter = TestReporter.parallelReporter(this, logLevel = if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) val driver = @@ -323,25 +325,24 @@ trait ParallelTesting { val allArgs = addOutDir(flags) driver.process(allArgs ++ files.map(_.getAbsolutePath), reporter = reporter) - // If the java files failed compilation before, we try again after: - if (!javaCompiledBefore) - assert(compileWithJavac(javaFiles), s"java compilation failed for ${javaFiles.mkString(", ")}") + val javaFiles = files.filter(_.getName.endsWith(".java")).map(_.getAbsolutePath) + assert(compileWithJavac(javaFiles), s"java compilation failed for ${javaFiles.mkString(", ")}") reporter } - private[ParallelTesting] def execute(): this.type = { - assert(_targetsCompiled == 0, "not allowed to re-use a `CompileRun`") + private[ParallelTesting] def executeTestSuite(): this.type = { + assert(_testSourcesCompiled == 0, "not allowed to re-use a `CompileRun`") - if (allTargets.nonEmpty) { + if (filteredSources.nonEmpty) { val pool = threadLimit match { case Some(i) => JExecutors.newWorkStealingPool(i) case None => JExecutors.newWorkStealingPool() } - if (interactive && !suppressAllOutput) pool.submit(statusRunner) + if (isInteractive && !suppressAllOutput) pool.submit(createProgressMonitor) - allTargets.foreach { target => + filteredSources.foreach { target => pool.submit(compilationRunnable(target)) } @@ -357,12 +358,12 @@ trait ParallelTesting { |================================================================================ |Failing tests:""".stripMargin } - failedCompilationTargets.toArray.sorted.foreach(echo) - failureInstructions.iterator.foreach(echo) + failedTestSources.toArray.sorted.foreach(echo) + reproduceInstructions.iterator.foreach(echo) } } else echo { - regex + testFilter .map(r => s"""No files matched regex "$r" in test""") .getOrElse("No tests available under target - erroneous test?") } @@ -371,48 +372,31 @@ trait ParallelTesting { } } - @inline private final def isCompilable(f: JFile): Boolean = { - val name = f.getName - name.endsWith(".scala") || name.endsWith(".java") - } - - private final class PosTest(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) - extends Test(targets, times, threadLimit, suppressAllOutput) { - protected def compilationRunnable(target: Target): Runnable = new Runnable { - def run(): Unit = compileTry { - target match { - case ConcurrentCompilationTarget(files, flags, outDir) => { - val sourceFiles = files.filter(isCompilable) - val reporter = compile(sourceFiles, flags, false, outDir) - completeCompilation(reporter.errorCount) - - if (reporter.errorCount > 0) { - fail() - val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) - addFailureInstruction(errorMsg) - failTarget(target) - reporter.echo(errorMsg) - reporter.flushToFile() - } + 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 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 target @ SeparateCompilationTarget(dir, flags, outDir) => { - val compilationUnits = target.compilationUnits - val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, false, outDir)) + 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) { - val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) - addFailureInstruction(errorMsg) - reporter.echo(errorMsg) - reporter.flushToFile() - } + if (reporter.errorCount > 0) + echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount) acc + reporter.errorCount } - completeCompilation(errorCount) + registerCompilation(errorCount) - if (errorCount > 0) failTarget(target) + if (errorCount > 0) failTestSource(testSource) } } @@ -420,9 +404,9 @@ trait ParallelTesting { } } - private final class RunTest(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) - extends Test(targets, times, threadLimit, suppressAllOutput) { - private def verifyOutput(checkFile: JFile, dir: JFile, target: Target, warnings: Int) = try { + private final class RunTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) + extends Test(testSources, times, threadLimit, suppressAllOutput) { + private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = try { // Do classloading magic and running here: import java.net.{ URL, URLClassLoader } import java.io.ByteArrayOutputStream @@ -453,32 +437,31 @@ trait ParallelTesting { addFailureInstruction(msg) // Print build instructions to file and summary: - val buildInstr = target.buildInstructions(0, warnings) + val buildInstr = testSource.buildInstructions(0, warnings) addFailureInstruction(buildInstr) // Fail target: - failTarget(target) + failTestSource(testSource) } } catch { - case _: NoSuchMethodException => - echo(s"\ntest in '$dir' did not contain a main method") + case ex: NoSuchMethodException => + echo(s"\ntest in '$dir' did not contain method: ${ex.getMessage}") fail() - case _: ClassNotFoundException => - echo(s"\ntest in '$dir' did was not contained within a `Test` object") + case ex: ClassNotFoundException => + echo(s"\ntest in '$dir' did not contain class: ${ex.getMessage}") fail() - case _: InvocationTargetException => - echo(s"\nTest in '$dir' might be using args(X) where X > 0") + case ex: InvocationTargetException => + echo(s"\nInvocationTargetException ocurred. Test in '$dir' might be using args(X) where X > 0") fail() } - protected def compilationRunnable(target: Target): Runnable = new Runnable { - def run(): Unit = compileTry { - val (errorCount, warningCount, hasCheckFile, doVerify) = target match { - case ConcurrentCompilationTarget(files, flags, outDir) => { - val sourceFiles = files.filter(isCompilable) + protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable { + def run(): Unit = tryCompile { + 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 { @@ -488,67 +471,57 @@ trait ParallelTesting { else Nil } }.headOption - val reporter = compile(sourceFiles, flags, false, outDir) - - if (reporter.errorCount > 0) { - fail() - val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) - addFailureInstruction(errorMsg) - failTarget(target) - reporter.echo(errorMsg) - reporter.flushToFile() - } + val reporter = compile(testSource.sourceFiles, flags, false, outDir) - completeCompilation(reporter.errorCount) - (reporter.errorCount, reporter.warningCount, checkFile.isDefined, () => verifyOutput(checkFile.get, outDir, target, reporter.warningCount)) + 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 target @ SeparateCompilationTarget(dir, flags, outDir) => { + case testSource @ SeparateCompilationSource(dir, flags, outDir) => { val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + ".check") val (errorCount, warningCount) = - target - .compilationUnits - .map(files => compile(files.filter(isCompilable), flags, false, outDir)) + testSource + .compilationGroups + .map(compile(_, flags, false, outDir)) .foldLeft((0,0)) { case ((errors, warnings), reporter) => - if (reporter.errorCount > 0) { - val errorMsg = target.buildInstructions(reporter.errorCount, reporter.warningCount) - addFailureInstruction(errorMsg) - reporter.echo(errorMsg) - reporter.flushToFile() - } + if (reporter.errorCount > 0) + echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount) (errors + reporter.errorCount, warnings + reporter.warningCount) } if (errorCount > 0) fail() - completeCompilation(errorCount) - (errorCount, warningCount, checkFile.exists, () => verifyOutput(checkFile, outDir, target, warningCount)) + registerCompilation(errorCount) + (errorCount, warningCount, checkFile.exists, () => verifyOutput(checkFile, outDir, testSource, warningCount)) } } - if (errorCount == 0 && hasCheckFile) doVerify() + if (errorCount == 0 && hasCheckFile) verifier() else if (errorCount > 0) { - echo(s"\nCompilation failed for: '$target'") - val buildInstr = target.buildInstructions(errorCount, warningCount) + echo(s"\nCompilation failed for: '$testSource'") + val buildInstr = testSource.buildInstructions(errorCount, warningCount) addFailureInstruction(buildInstr) - failTarget(target) + failTestSource(testSource) } } } } - private final class NegTest(targets: List[Target], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) - extends Test(targets, times, threadLimit, suppressAllOutput) { - protected def compilationRunnable(target: Target): Runnable = new Runnable { - def run(): Unit = compileTry { + 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 { // 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 errorMapAndExpected(files: Array[JFile]): (HashMap[String, Integer], Int) = { + 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 => @@ -571,18 +544,18 @@ trait ParallelTesting { (errorMap, expectedErrors) } - def getMissingAnnotations(errorMap: HashMap[String, Integer], reporterErrors: Iterator[MessageContainer]) = !reporterErrors.forall { error => - val getter = if (error.pos.exists) { + 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(getter) + val errors = errorMap.get(key) if (errors ne null) { - if (errors == 1) errorMap.remove(getter) - else errorMap.put(getter, errors - 1) + if (errors == 1) errorMap.remove(key) + else errorMap.put(key, errors - 1) true } else { @@ -593,46 +566,46 @@ trait ParallelTesting { } } - val (expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = target match { - case ConcurrentCompilationTarget(files, flags, outDir) => { - val sourceFiles = files.filter(isCompilable) - val (errorMap, expectedErrors) = errorMapAndExpected(sourceFiles) + 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, () => getMissingAnnotations(errorMap, reporter.errors), errorMap) + (expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, reporter.errors), errorMap) } - case target @ SeparateCompilationTarget(dir, flags, outDir) => { - val compilationUnits = target.compilationUnits - val (errorMap, expectedErrors) = errorMapAndExpected(compilationUnits.toArray.flatten) - val reporters = compilationUnits.map(files => compile(files.filter(isCompilable), flags, true, outDir)) + 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, () => getMissingAnnotations(errorMap, errors), errorMap) + (expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, errors), errorMap) } } if (expectedErrors != actualErrors) { echo { - s"\nWrong number of errors encountered when compiling $target, expected: $expectedErrors, actual: $actualErrors\n" + s"\nWrong number of errors encountered when compiling $testSource, expected: $expectedErrors, actual: $actualErrors\n" } - failTarget(target) + failTestSource(testSource) } else if (hasMissingAnnotations()) { echo { - s"\nErrors found on incorrect row numbers when compiling $target" + s"\nErrors found on incorrect row numbers when compiling $testSource" } - failTarget(target) + failTestSource(testSource) } else if (!errorMap.isEmpty) { echo { - s"\nError annotation(s) have {=}: $errorMap" + s"\nExpected error(s) have {=}: $errorMap" } - failTarget(target) + failTestSource(testSource) } - completeCompilation(actualErrors) + registerCompilation(actualErrors) } } } @@ -665,8 +638,9 @@ trait ParallelTesting { * * pos tests * ========= - * Pos tests verify that the compiler is able to compile the given `Target`s - * and that they generate no errors or exceptions during compilation + * 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 * ========= @@ -742,7 +716,7 @@ trait ParallelTesting { * are compatible according to the `require`s in `+`. */ final class CompilationTest private ( - private[ParallelTesting] val targets: List[Target], + private[ParallelTesting] val targets: List[TestSource], private[ParallelTesting] val times: Int, private[ParallelTesting] val shouldDelete: Boolean, private[ParallelTesting] val threadLimit: Option[Int], @@ -750,10 +724,10 @@ trait ParallelTesting { ) { import org.junit.Assert.fail - private[ParallelTesting] def this(target: Target) = + private[ParallelTesting] def this(target: TestSource) = this(List(target), 1, true, None, false) - private[ParallelTesting] def this(targets: List[Target]) = + private[ParallelTesting] def this(targets: List[TestSource]) = this(targets, 1, true, None, false) /** Compose test targets from `this` with `other` @@ -763,7 +737,7 @@ trait ParallelTesting { * * 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()` / `neg()`/ `run()`) will spin up a thread pool with the + * 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. * @@ -781,11 +755,11 @@ trait ParallelTesting { * compilation without generating errors and that they do not crash the * compiler */ - def pos(): this.type = { - val test = new PosTest(targets, times, threadLimit, shouldFail).execute() + 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.errors}") + 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") @@ -798,8 +772,8 @@ trait ParallelTesting { * correct amount of errors at the correct positions. It also makes sure * that none of these tests crash the compiler */ - def neg(): this.type = { - val test = new NegTest(targets, times, threadLimit, shouldFail).execute() + 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") @@ -816,8 +790,8 @@ trait ParallelTesting { * the compiler; it also makes sure that all tests can run with the * expected output */ - def run(): this.type = { - val test = new RunTest(targets, times, threadLimit, shouldFail).execute() + def checkRuns(): this.type = { + val test = new RunTest(targets, times, threadLimit, shouldFail).executeTestSuite() if (!shouldFail && test.didFail) { fail("Run test failed, but should not") @@ -831,7 +805,7 @@ trait ParallelTesting { /** Deletes output directories and files */ private def cleanup(): this.type = { - if (shouldDelete) targets.foreach(t => delete(t.outDir)) + if (shouldDelete) delete() this } @@ -851,9 +825,9 @@ trait ParallelTesting { */ def copyToTarget(): CompilationTest = new CompilationTest ( targets.map { - case target @ ConcurrentCompilationTarget(files, _, outDir) => + case target @ JointCompilationSource(files, _, outDir) => target.copy(files = files.map(copyToDir(outDir,_))) - case target @ SeparateCompilationTarget(dir, _, outDir) => + case target @ SeparateCompilationSource(dir, _, outDir) => target.copy(dir = copyToDir(outDir, dir)) }, times, shouldDelete, threadLimit, shouldFail @@ -908,14 +882,14 @@ trait ParallelTesting { } /** Create out directory for directory `d` */ - private def toCompilerDirFromDir(d: JFile, sourceDir: JFile, outDir: String): JFile = { + 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 toCompilerDirFromFile(file: JFile, sourceDir: JFile, outDir: String): JFile = { + 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() @@ -951,7 +925,7 @@ trait ParallelTesting { if (seen.contains(elem.getMethodName)) false else { seen += elem.getMethodName; true } } - .take(6).find { elem => + .find { elem => val callingClass = Class.forName(elem.getClassName) classOf[ParallelTesting].isAssignableFrom(callingClass) && elem.getFileName != "ParallelTesting.scala" @@ -976,10 +950,10 @@ trait ParallelTesting { s"Source file: $f, didn't exist" ) - val target = ConcurrentCompilationTarget( + val target = JointCompilationSource( Array(sourceFile), flags, - toCompilerDirFromFile(sourceFile, parent, outDir) + createOutputDirsForFile(sourceFile, parent, outDir) ) new CompilationTest(target) } @@ -1001,7 +975,7 @@ trait ParallelTesting { val targetDir = new JFile(outDir + "/" + sourceDir.getName + "/") targetDir.mkdirs() - val target = ConcurrentCompilationTarget(flatten(sourceDir), flags, targetDir) + val target = JointCompilationSource(flatten(sourceDir), flags, targetDir) new CompilationTest(target) } @@ -1017,7 +991,7 @@ trait ParallelTesting { targetDir.mkdirs() assert(targetDir.exists, s"couldn't create target directory: $targetDir") - val target = ConcurrentCompilationTarget(files.map(new JFile(_)).toArray, flags, targetDir) + val target = JointCompilationSource(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) @@ -1048,8 +1022,8 @@ trait ParallelTesting { val (dirs, files) = compilationTargets(sourceDir) val targets = - files.map(f => ConcurrentCompilationTarget(Array(f), flags, toCompilerDirFromFile(f, sourceDir, outDir))) ++ - dirs.map(dir => SeparateCompilationTarget(dir, flags, toCompilerDirFromDir(dir, sourceDir, outDir))) + files.map(f => JointCompilationSource(Array(f), flags, createOutputDirsForFile(f, sourceDir, outDir))) ++ + dirs.map(dir => SeparateCompilationSource(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) @@ -1067,10 +1041,17 @@ trait ParallelTesting { val (_, files) = compilationTargets(sourceDir) val targets = files.map { file => - ConcurrentCompilationTarget(Array(file), flags, toCompilerDirFromFile(file, sourceDir, outDir)) + JointCompilationSource(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 fc6ac7d1f734..b37d9cd0799f 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -78,9 +78,9 @@ object TestReporter { new PrintWriter(new FileOutputStream(new JFile(s"../tests-$timestamp.log"), true)) } - def parallelReporter(caller: AnyRef, logLevel: Int): TestReporter = new TestReporter( + def parallelReporter(lock: AnyRef, logLevel: Int): TestReporter = new TestReporter( new PrintWriter(Console.err, true), - str => caller.synchronized { + str => lock.synchronized { logWriter.println(str) logWriter.flush() }, From af4bb48a3e1f164f4f5fadd0ac0554f8e5e56081 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 28 Mar 2017 16:26:57 +0200 Subject: [PATCH 34/39] Make run tests run even if there isn't a check file --- .../dotty/tools/dotc/ParallelTesting.scala | 84 ++++++++++--------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 3cbc2c908ed7..6fedfe379920 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -406,56 +406,63 @@ trait ParallelTesting { private final class RunTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) extends Test(testSources, times, threadLimit, suppressAllOutput) { - private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = try { + + private def runMain(dir: JFile, testSource: TestSource): Array[String] = { + import java.io.ByteArrayOutputStream + import java.net.{ URL, URLClassLoader } + + val printStream = new ByteArrayOutputStream + try { // Do classloading magic and running here: - import java.net.{ URL, URLClassLoader } - import java.io.ByteArrayOutputStream val ucl = new URLClassLoader(Array(dir.toURI.toURL)) val cls = ucl.loadClass("Test") val meth = cls.getMethod("main", classOf[Array[String]]) - val printStream = new ByteArrayOutputStream Console.withOut(printStream) { meth.invoke(null, Array("jvm")) // partest passes at least "jvm" as an arg } + } + catch { + case ex: NoSuchMethodException => + echo(s"test in '$dir' did not contain method: ${ex.getMessage} ") + failTestSource(testSource) - val outputLines = printStream.toString("utf-8").lines.toArray - val checkLines = Source.fromFile(checkFile).getLines.toArray - - 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.mkColoredCodeDiff(exp, act, true) - }.mkString("\n") - val msg = s"\nOutput from run test '$checkFile' did not match expected, output:\n$diff\n" - echo(msg) - addFailureInstruction(msg) - - // Print build instructions to file and summary: - val buildInstr = testSource.buildInstructions(0, warnings) - addFailureInstruction(buildInstr) + case ex: ClassNotFoundException => + echo(s"test in '$dir' did not contain class: ${ex.getMessage} ") + failTestSource(testSource) - // Fail target: + case ex: InvocationTargetException => + echo(s"An exception ocurred when running main: ${ex.getCause} ") failTestSource(testSource) - } + } + printStream.toString("utf-8").lines.toArray } - catch { - case ex: NoSuchMethodException => - echo(s"\ntest in '$dir' did not contain method: ${ex.getMessage}") - fail() - - case ex: ClassNotFoundException => - echo(s"\ntest in '$dir' did not contain class: ${ex.getMessage}") - fail() - - case ex: InvocationTargetException => - echo(s"\nInvocationTargetException ocurred. Test in '$dir' might be using args(X) where X > 0") - fail() + + private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = { + val outputLines = runMain(dir, testSource) + val checkLines = Source.fromFile(checkFile).getLines.toArray + + 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.mkColoredCodeDiff(exp, act, true) + }.mkString("\n") + val msg = s"\nOutput from run test '$checkFile' did not match expected, output:\n$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 { @@ -501,6 +508,7 @@ trait ParallelTesting { } 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) From c8321d6a42348308feaf586380ef07ae24e420c8 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 28 Mar 2017 16:27:13 +0200 Subject: [PATCH 35/39] Add test to check failure of non-tail recursive --- compiler/test/dotty/tools/dotc/ParallelTestTests.scala | 6 +++++- tests/partest-test/stackOverflow.scala | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/partest-test/stackOverflow.scala diff --git a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala index 30ec88455255..9964be036bad 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTestTests.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTestTests.scala @@ -26,7 +26,8 @@ class ParallelTestTests extends ParallelTesting { @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 negMissingAnnot: Unit = + compileFile("../tests/partest-test/negMissingAnnot.scala", defaultOptions).expectFailure.checkExpectedErrors() @Test def negAnnotWrongLine: Unit = compileFile("../tests/partest-test/negAnnotWrongLine.scala", defaultOptions).expectFailure.checkExpectedErrors() @@ -48,4 +49,7 @@ class ParallelTestTests extends ParallelTesting { @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() } diff --git a/tests/partest-test/stackOverflow.scala b/tests/partest-test/stackOverflow.scala new file mode 100644 index 000000000000..b3132cc19f44 --- /dev/null +++ b/tests/partest-test/stackOverflow.scala @@ -0,0 +1,7 @@ +object Test { + def foo: Int = bar + def bar: Int = foo + + def main(args: Array[String]): Unit = + println(foo) +} From 8dc162cf28a3e2dfa6923730c3304467b12c5b78 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 28 Mar 2017 16:34:23 +0200 Subject: [PATCH 36/39] Preserve stack trace when invoking main in run tests --- compiler/test/dotty/tools/dotc/ParallelTesting.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 6fedfe379920..c01c444b28cd 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -406,8 +406,12 @@ trait ParallelTesting { 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 = + ex.getStackTrace + .takeWhile(_.getMethodName != "invoke0") + .mkString(" ", "\n ", "") + import java.io.ByteArrayOutputStream import java.net.{ URL, URLClassLoader } @@ -424,15 +428,15 @@ trait ParallelTesting { } catch { case ex: NoSuchMethodException => - echo(s"test in '$dir' did not contain method: ${ex.getMessage} ") + 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} ") + 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} ") + echo(s"An exception ocurred when running main: ${ex.getCause}\n${renderStackTrace(ex.getCause)}") failTestSource(testSource) } printStream.toString("utf-8").lines.toArray From b5b6f5ea98628b005b00bfa515d2e13d644d8435 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Tue, 28 Mar 2017 17:23:43 +0200 Subject: [PATCH 37/39] Fix test reporting for exceptions in compiler --- .../dotty/tools/dotc/ParallelTesting.scala | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index c01c444b28cd..016e8770f1ed 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -46,6 +46,7 @@ trait ParallelTesting { * in a specific way defined by the `Test` */ private sealed trait TestSource { self => + def name: String def outDir: JFile def flags: Array[String] @@ -80,7 +81,7 @@ trait ParallelTesting { } self match { - case JointCompilationSource(files, _, _) => { + case JointCompilationSource(_, files, _, _) => { files.map(_.getAbsolutePath).foreach { path => sb.append("\\\n ") sb.append(path) @@ -111,6 +112,7 @@ trait ParallelTesting { * and output directory */ private final case class JointCompilationSource( + name: String, files: Array[JFile], flags: Array[String], outDir: JFile @@ -124,6 +126,7 @@ trait ParallelTesting { * suffix `_X` */ private final case class SeparateCompilationSource( + name: String, dir: JFile, flags: Array[String], outDir: JFile @@ -164,9 +167,9 @@ trait ParallelTesting { private val filteredSources = if (!testFilter.isDefined) testSources else testSources.filter { - case JointCompilationSource(files, _, _) => + case JointCompilationSource(_, files, _, _) => files.exists(file => testFilter.get.findFirstIn(file.getAbsolutePath).isDefined) - case SeparateCompilationSource(dir, _, _) => + case SeparateCompilationSource(_, dir, _, _) => testFilter.get.findFirstIn(dir.getAbsolutePath).isDefined } @@ -206,19 +209,10 @@ trait ParallelTesting { /** The test sources that failed according to the implementing subclass */ private[this] val failedTestSources = mutable.ArrayBuffer.empty[String] - protected final def addFailedCompilationTarget(testSource: String): Unit = - synchronized { failedTestSources.append(testSource) } - - /** Fails the current `TestSource`, and makes sure it gets logged */ - protected final def failTestSource(testSource: TestSource) = - testSource match { - case JointCompilationSource(files, _, _) => - files.map(_.getAbsolutePath).foreach(addFailedCompilationTarget) - fail() - case SeparateCompilationSource(dir, _, _) => - addFailedCompilationTarget(dir.getAbsolutePath) - fail() - } + protected final def failTestSource(testSource: TestSource) = synchronized { + failedTestSources.append(testSource.name) + fail() + } /** Prints to `System.err` if we're not suppressing all output */ protected def echo(msg: String): Unit = @@ -252,12 +246,12 @@ trait ParallelTesting { /** Wrapper function to make sure that the compiler itself did not crash - * if it did, the test should automatically fail. */ - protected def tryCompile(op: => Unit): Unit = + protected def tryCompile(testSource: TestSource)(op: => Unit): Unit = try op catch { case NonFatal(e) => { // if an exception is thrown during compilation, the complete test // run should fail - fail() + failTestSource(testSource) e.printStackTrace() registerCompilation(1) throw e @@ -353,12 +347,17 @@ trait ParallelTesting { if (didFail) { echo { """| + | + | |================================================================================ |Test Report |================================================================================ |Failing tests:""".stripMargin } - failedTestSources.toArray.sorted.foreach(echo) + failedTestSources.toSet.foreach { source: String => + echo(" " + source) + } + echo("") reproduceInstructions.iterator.foreach(echo) } } @@ -375,9 +374,9 @@ trait ParallelTesting { 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 { + def run(): Unit = tryCompile(testSource) { testSource match { - case testSource @ JointCompilationSource(files, flags, outDir) => { + case testSource @ JointCompilationSource(_, files, flags, outDir) => { val reporter = compile(testSource.sourceFiles, flags, false, outDir) registerCompilation(reporter.errorCount) @@ -385,7 +384,7 @@ trait ParallelTesting { echoBuildInstructions(reporter, testSource, reporter.errorCount, reporter.warningCount) } - case testSource @ SeparateCompilationSource(dir, flags, outDir) => { + 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) @@ -470,9 +469,9 @@ trait ParallelTesting { } protected def compilationRunnable(testSource: TestSource): Runnable = new Runnable { - def run(): Unit = tryCompile { + def run(): Unit = tryCompile(testSource) { val (errorCount, warningCount, hasCheckFile, verifier: Function0[Unit]) = testSource match { - case testSource @ JointCompilationSource(files, flags, outDir) => { + case testSource @ JointCompilationSource(_, files, flags, outDir) => { val checkFile = files.flatMap { file => if (file.isDirectory) Nil else { @@ -491,7 +490,7 @@ trait ParallelTesting { (reporter.errorCount, reporter.warningCount, checkFile.isDefined, () => verifyOutput(checkFile.get, outDir, testSource, reporter.warningCount)) } - case testSource @ SeparateCompilationSource(dir, flags, outDir) => { + case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => { val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + ".check") val (errorCount, warningCount) = testSource @@ -526,7 +525,7 @@ trait ParallelTesting { 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 { + 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. @@ -579,7 +578,7 @@ trait ParallelTesting { } val (expectedErrors, actualErrors, hasMissingAnnotations, errorMap) = testSource match { - case testSource @ JointCompilationSource(files, flags, outDir) => { + case testSource @ JointCompilationSource(_, files, flags, outDir) => { val sourceFiles = testSource.sourceFiles val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(sourceFiles) val reporter = compile(sourceFiles, flags, true, outDir) @@ -588,7 +587,7 @@ trait ParallelTesting { (expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, reporter.errors), errorMap) } - case testSource @ SeparateCompilationSource(dir, flags, outDir) => { + 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)) @@ -837,9 +836,9 @@ trait ParallelTesting { */ def copyToTarget(): CompilationTest = new CompilationTest ( targets.map { - case target @ JointCompilationSource(files, _, outDir) => + case target @ JointCompilationSource(_, files, _, outDir) => target.copy(files = files.map(copyToDir(outDir,_))) - case target @ SeparateCompilationSource(dir, _, outDir) => + case target @ SeparateCompilationSource(_, dir, _, outDir) => target.copy(dir = copyToDir(outDir, dir)) }, times, shouldDelete, threadLimit, shouldFail @@ -950,10 +949,11 @@ trait ParallelTesting { /** 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 + getCallingMethod + "/" + + outDirectory + callingMethod + "/" + sourceFile.getName.substring(0, sourceFile.getName.lastIndexOf('.')) + "/" require( @@ -963,6 +963,7 @@ trait ParallelTesting { ) val target = JointCompilationSource( + callingMethod, Array(sourceFile), flags, createOutputDirsForFile(sourceFile, parent, outDir) @@ -975,7 +976,8 @@ trait ParallelTesting { * contained within the directory `f`. */ def compileDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - val outDir = outDirectory + getCallingMethod + "/" + val callingMethod = getCallingMethod + val outDir = outDirectory + callingMethod + "/" val sourceDir = new JFile(f) checkRequirements(f, sourceDir, outDir) @@ -987,7 +989,7 @@ trait ParallelTesting { val targetDir = new JFile(outDir + "/" + sourceDir.getName + "/") targetDir.mkdirs() - val target = JointCompilationSource(flatten(sourceDir), flags, targetDir) + val target = JointCompilationSource(callingMethod, flatten(sourceDir), flags, targetDir) new CompilationTest(target) } @@ -996,14 +998,15 @@ trait ParallelTesting { * dissociated */ def compileList(testName: String, files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { - val outDir = outDirectory + getCallingMethod + "/" + testName + "/" + 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(files.map(new JFile(_)).toArray, flags, 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) @@ -1027,15 +1030,16 @@ trait ParallelTesting { * the same name as the directory (with the file extension `.check`) */ def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - val outDir = outDirectory + getCallingMethod + "/" + 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(Array(f), flags, createOutputDirsForFile(f, sourceDir, outDir))) ++ - dirs.map(dir => SeparateCompilationSource(dir, flags, createOutputDirsForDir(dir, sourceDir, outDir))) + 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) @@ -1046,14 +1050,15 @@ trait ParallelTesting { * tests. */ def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { - val outDir = outDirectory + getCallingMethod + "/" + 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(Array(file), flags, createOutputDirsForFile(file, sourceDir, outDir)) + 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 From 02ebe0f9b18b7dec024b79109ecf984d23c15cd1 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 29 Mar 2017 13:29:28 +0200 Subject: [PATCH 38/39] Make summary report come at the end of test suite --- .../dotty/tools/dotc/CompilationTests.scala | 2 +- .../tools/dotc/ParallelSummaryReport.java | 67 +++++++++++++++++++ .../dotty/tools/dotc/ParallelTesting.scala | 24 ++----- .../tools/dotc/reporting/TestReporter.scala | 12 +++- 4 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/ParallelSummaryReport.java diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 74688c24d0fb..788e30aa38a9 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -9,7 +9,7 @@ import org.junit.experimental.categories.Category import scala.util.matching.Regex @Category(Array(classOf[ParallelTesting])) -class CompilationTests extends ParallelTesting { +class CompilationTests extends ParallelSummaryReport with ParallelTesting { import CompilationTests._ def isInteractive: Boolean = !sys.env.contains("DRONE") diff --git a/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java b/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java new file mode 100644 index 000000000000..9214e7d25f7e --- /dev/null +++ b/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java @@ -0,0 +1,67 @@ +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 { + private static TestReporter rep = TestReporter.reporter(-1); + private static ArrayDeque failedTests = new ArrayDeque<>(); + private static ArrayDeque 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(-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); + + rep.flushToStdErr(); + + rep.echo(""); + + reproduceInstructions + .stream() + .forEach(rep::echo); + + if (failed > 0) rep.flushToFile(); + } +} diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index 016e8770f1ed..30679de9e0e6 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -31,6 +31,7 @@ import dotc.util.DiffUtil trait ParallelTesting { 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 @@ -197,11 +198,8 @@ trait ParallelTesting { val errorMsg = testSource.buildInstructions(reporter.errorCount, reporter.warningCount) addFailureInstruction(errorMsg) failTestSource(testSource) - reporter.echo(errorMsg) - reporter.flushToFile() } - /** Instructions on how to reproduce failed test source compilations */ private[this] val reproduceInstructions = mutable.ArrayBuffer.empty[String] protected final def addFailureInstruction(ins: String): Unit = @@ -210,7 +208,7 @@ trait ParallelTesting { /** 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) + failedTestSources.append(testSource.name + " failed") fail() } @@ -345,21 +343,11 @@ trait ParallelTesting { throw new TimeoutException("Compiling targets timed out") if (didFail) { - echo { - """| - | - | - |================================================================================ - |Test Report - |================================================================================ - |Failing tests:""".stripMargin - } - failedTestSources.toSet.foreach { source: String => - echo(" " + source) - } - echo("") - reproduceInstructions.iterator.foreach(echo) + reportFailed() + failedTestSources.toSet.foreach(addFailedTest) + reproduceInstructions.iterator.foreach(addReproduceInstruction) } + else reportPassed() } else echo { testFilter diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index b37d9cd0799f..521cf95762f4 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -27,6 +27,9 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M final def flushToFile(): Unit = _messageBuf.iterator.foreach(filePrintln) + final def flushToStdErr(): Unit = + _messageBuf.iterator.foreach(System.err.println) + final def inlineInfo(pos: SourcePosition): String = if (pos.exists) { if (pos.outer.exists) @@ -78,6 +81,11 @@ object TestReporter { new PrintWriter(new FileOutputStream(new JFile(s"../tests-$timestamp.log"), true)) } + def writeToLog(str: String) = { + logWriter.println(str) + logWriter.flush() + } + def parallelReporter(lock: AnyRef, logLevel: Int): TestReporter = new TestReporter( new PrintWriter(Console.err, true), str => lock.synchronized { @@ -89,13 +97,13 @@ object TestReporter { def reporter(logLevel: Int): TestReporter = new TestReporter( new PrintWriter(Console.err, true), - logWriter.println, + writeToLog, logLevel ) def simplifiedReporter(writer: PrintWriter): TestReporter = new TestReporter( writer, - logWriter.println, + writeToLog, WARNING ) { /** Prints the message with the given position indication in a simplified manner */ From 08d75b5b1bcdb7b1051831e7a2282f1fbb896267 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 29 Mar 2017 15:14:01 +0200 Subject: [PATCH 39/39] Revert CI memory changes It is necessary to do this in a new commit because of having to make sure that the signed drone file is current --- .drone.yml | 2 +- .drone.yml.sig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 520c4049efd3..75f7d89de455 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,7 +6,7 @@ pipeline: - ln -s /var/cache/drone/scala-scala scala-scala - ln -s /var/cache/drone/ivy2 "$HOME/.ivy2" - ./scripts/update-scala-library - - sbt -J-Xmx24G -J-XX:ReservedCodeCacheSize=512m -J-XX:MaxMetaspaceSize=4G -Ddotty.drone.mem=4096m "${TEST}" + - sbt -J-Xmx4096m -J-XX:ReservedCodeCacheSize=512m -J-XX:MaxMetaspaceSize=1024m -Ddotty.drone.mem=4096m "${TEST}" when: branch: exclude: gh-pages diff --git a/.drone.yml.sig b/.drone.yml.sig index 329cd5b7ca63..7f5049f14ce8 100644 --- a/.drone.yml.sig +++ b/.drone.yml.sig @@ -1 +1 @@ -eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXgyNEcgLUotWFg6UmVzZXJ2ZWRDb2RlQ2FjaGVTaXplPTUxMm0gLUotWFg6TWF4TWV0YXNwYWNlU2l6ZT00RyAtRGRvdHR5LmRyb25lLm1lbT00MDk2bSAiJHtURVNUfSIKICAgIHdoZW46CiAgICAgIGJyYW5jaDoKICAgICAgICBleGNsdWRlOiBnaC1wYWdlcwoKICBkb2N1bWVudGF0aW9uOgogICAgaW1hZ2U6IGxhbXBlcGZsL2RvdHR5OmxhdGVzdAogICAgcHVsbDogdHJ1ZQogICAgY29tbWFuZHM6CiAgICAgIC0gLi9wcm9qZWN0L3NjcmlwdHMvZ2VuRG9jcyAiJHtURVNUfSIgJEJPVF9QQVNTCiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgoKICBnaXR0ZXI6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCiAgICAgIHN0YXR1czogY2hhbmdlZAoKICBzbGFjazoKICAgIGltYWdlOiBwbHVnaW5zL3NsYWNrCiAgICBjaGFubmVsOiBkb3R0eQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgptYXRyaXg6CiAgVEVTVDoKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyIikgKz0gVGVzdHMuQXJndW1lbnQoVGVzdEZyYW1ld29ya3MuSlVuaXQsICItLWV4Y2x1ZGUtY2F0ZWdvcmllcz1kb3R0eS50b29scy5kb3RjLlBhcmFsbGVsVGVzdGluZyIpIDt0ZXN0IDtkb3R0eS1iaW4tdGVzdHMvdGVzdAogICAgLSA7c2V0IHRlc3RPcHRpb25zIGluIExvY2FsUHJvamVjdCgiZG90dHktY29tcGlsZXItYm9vdHN0cmFwcGVkIikgKz0gVGVzdHMuQXJndW1lbnQoVGVzdEZyYW1ld29ya3MuSlVuaXQsICItLWV4Y2x1ZGUtY2F0ZWdvcmllcz1kb3R0eS50b29scy5kb3RjLlBhcmFsbGVsVGVzdGluZyIpIDtwdWJsaXNoTG9jYWwgO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0CiAgICAtIDtzZXQgdGVzdE9wdGlvbnMgaW4gTG9jYWxQcm9qZWN0KCJkb3R0eS1jb21waWxlciIpICs9IFRlc3RzLkFyZ3VtZW50KFRlc3RGcmFtZXdvcmtzLkpVbml0LCAiLS1leGNsdWRlLWNhdGVnb3JpZXM9ZG90dHkudG9vbHMuZG90Yy5QYXJhbGxlbFRlc3RpbmciKSA7cGFydGVzdC1vbmx5LW5vLWJvb3RzdHJhcCAtLXNob3ctZGlmZiAtLXZlcmJvc2UKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyLWJvb3RzdHJhcHBlZCIpICs9IFRlc3RzLkFyZ3VtZW50KFRlc3RGcmFtZXdvcmtzLkpVbml0LCAiLS1leGNsdWRlLWNhdGVnb3JpZXM9ZG90dHkudG9vbHMuZG90Yy5QYXJhbGxlbFRlc3RpbmciKSA7cGFydGVzdC1vbmx5IC0tc2hvdy1kaWZmIC0tdmVyYm9zZQogICAgLSA7ZG90dHktY29tcGlsZXIvdGVzdE9ubHkgZG90dHkudG9vbHMuZG90Yy5Db21waWxhdGlvblRlc3RzCiAgICAtIDtwdWJsaXNoTG9jYWwgO2RvdHR5LWJvb3RzdHJhcHBlZC90ZXN0T25seSBkb3R0eS50b29scy5kb3RjLkNvbXBpbGF0aW9uVGVzdHMK.IAe3X-XmMyf3-GnA1ENr-D3t_YM_28CAhHX6lP0EB6E \ No newline at end of file +eyJhbGciOiJIUzI1NiJ9.cGlwZWxpbmU6CiAgdGVzdDoKICAgIGltYWdlOiBsYW1wZXBmbC9kb3R0eTpsYXRlc3QKICAgIHB1bGw6IHRydWUKICAgIGNvbW1hbmRzOgogICAgICAtIGxuIC1zIC92YXIvY2FjaGUvZHJvbmUvc2NhbGEtc2NhbGEgc2NhbGEtc2NhbGEKICAgICAgLSBsbiAtcyAvdmFyL2NhY2hlL2Ryb25lL2l2eTIgIiRIT01FLy5pdnkyIgogICAgICAtIC4vc2NyaXB0cy91cGRhdGUtc2NhbGEtbGlicmFyeQogICAgICAtIHNidCAtSi1YbXg0MDk2bSAtSi1YWDpSZXNlcnZlZENvZGVDYWNoZVNpemU9NTEybSAtSi1YWDpNYXhNZXRhc3BhY2VTaXplPTEwMjRtIC1EZG90dHkuZHJvbmUubWVtPTQwOTZtICIke1RFU1R9IgogICAgd2hlbjoKICAgICAgYnJhbmNoOgogICAgICAgIGV4Y2x1ZGU6IGdoLXBhZ2VzCgogIGRvY3VtZW50YXRpb246CiAgICBpbWFnZTogbGFtcGVwZmwvZG90dHk6bGF0ZXN0CiAgICBwdWxsOiB0cnVlCiAgICBjb21tYW5kczoKICAgICAgLSAuL3Byb2plY3Qvc2NyaXB0cy9nZW5Eb2NzICIke1RFU1R9IiAkQk9UX1BBU1MKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCgogIGdpdHRlcjoKICAgIGltYWdlOiBwbHVnaW5zL2dpdHRlcgogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgc3RhdHVzOiBjaGFuZ2VkCgogIHNsYWNrOgogICAgaW1hZ2U6IHBsdWdpbnMvc2xhY2sKICAgIGNoYW5uZWw6IGRvdHR5CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBzdGF0dXM6IGNoYW5nZWQKCm1hdHJpeDoKICBURVNUOgogICAgLSA7c2V0IHRlc3RPcHRpb25zIGluIExvY2FsUHJvamVjdCgiZG90dHktY29tcGlsZXIiKSArPSBUZXN0cy5Bcmd1bWVudChUZXN0RnJhbWV3b3Jrcy5KVW5pdCwgIi0tZXhjbHVkZS1jYXRlZ29yaWVzPWRvdHR5LnRvb2xzLmRvdGMuUGFyYWxsZWxUZXN0aW5nIikgO3Rlc3QgO2RvdHR5LWJpbi10ZXN0cy90ZXN0CiAgICAtIDtzZXQgdGVzdE9wdGlvbnMgaW4gTG9jYWxQcm9qZWN0KCJkb3R0eS1jb21waWxlci1ib290c3RyYXBwZWQiKSArPSBUZXN0cy5Bcmd1bWVudChUZXN0RnJhbWV3b3Jrcy5KVW5pdCwgIi0tZXhjbHVkZS1jYXRlZ29yaWVzPWRvdHR5LnRvb2xzLmRvdGMuUGFyYWxsZWxUZXN0aW5nIikgO3B1Ymxpc2hMb2NhbCA7ZG90dHktYm9vdHN0cmFwcGVkL3Rlc3QKICAgIC0gO3NldCB0ZXN0T3B0aW9ucyBpbiBMb2NhbFByb2plY3QoImRvdHR5LWNvbXBpbGVyIikgKz0gVGVzdHMuQXJndW1lbnQoVGVzdEZyYW1ld29ya3MuSlVuaXQsICItLWV4Y2x1ZGUtY2F0ZWdvcmllcz1kb3R0eS50b29scy5kb3RjLlBhcmFsbGVsVGVzdGluZyIpIDtwYXJ0ZXN0LW9ubHktbm8tYm9vdHN0cmFwIC0tc2hvdy1kaWZmIC0tdmVyYm9zZQogICAgLSA7c2V0IHRlc3RPcHRpb25zIGluIExvY2FsUHJvamVjdCgiZG90dHktY29tcGlsZXItYm9vdHN0cmFwcGVkIikgKz0gVGVzdHMuQXJndW1lbnQoVGVzdEZyYW1ld29ya3MuSlVuaXQsICItLWV4Y2x1ZGUtY2F0ZWdvcmllcz1kb3R0eS50b29scy5kb3RjLlBhcmFsbGVsVGVzdGluZyIpIDtwYXJ0ZXN0LW9ubHkgLS1zaG93LWRpZmYgLS12ZXJib3NlCiAgICAtIDtkb3R0eS1jb21waWxlci90ZXN0T25seSBkb3R0eS50b29scy5kb3RjLkNvbXBpbGF0aW9uVGVzdHMKICAgIC0gO3B1Ymxpc2hMb2NhbCA7ZG90dHktYm9vdHN0cmFwcGVkL3Rlc3RPbmx5IGRvdHR5LnRvb2xzLmRvdGMuQ29tcGlsYXRpb25UZXN0cwo.b9x4iSh27OqWMUT8eR6uiK0OH_eERpnKIaMglF8hKYA \ No newline at end of file