aboutsummaryrefslogtreecommitdiff
path: root/compiler/test/dotty/tools/dotc/CompilerTest.scala
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2016-11-02 11:08:28 +0100
committerGuillaume Martres <smarter@ubuntu.com>2016-11-22 01:35:07 +0100
commit8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch)
treea8147561d307af862c295cfc8100d271063bb0dd /compiler/test/dotty/tools/dotc/CompilerTest.scala
parent6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff)
downloaddotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/test/dotty/tools/dotc/CompilerTest.scala')
-rw-r--r--compiler/test/dotty/tools/dotc/CompilerTest.scala623
1 files changed, 623 insertions, 0 deletions
diff --git a/compiler/test/dotty/tools/dotc/CompilerTest.scala b/compiler/test/dotty/tools/dotc/CompilerTest.scala
new file mode 100644
index 000000000..fbec9003a
--- /dev/null
+++ b/compiler/test/dotty/tools/dotc/CompilerTest.scala
@@ -0,0 +1,623 @@
+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
+ * <test>-<kind>.log file with output of failed tests. Partest reads compiler
+ * flags and the number of errors expected from a neg test from <test>.flags
+ * and <test>.nerr files (also generated). The test is in a parent directory
+ * that determines the kind of test:
+ * - pos: checks that compilation succeeds
+ * - neg: checks that compilation fails with the given number of errors
+ * - run: compilation succeeds, partest: test run generates the output in
+ * <test>.check. Run tests always need to be:
+ * object Test { def main(args: Array[String]): Unit = ... }
+ * Classpath jars can be added to partestDeps in the sbt Build.scala.
+ */
+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 jfile = new JFile(file)
+ recCopyFiles(jfile, destDir / jfile.getName)
+ })
+ compileDir(DPConfig.testRoot + JFile.separator, testName, args)
+ destDir.deleteRecursively
+ }
+ }
+
+ 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
+
+ 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
+ //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) {
+ 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 != "<no source>")
+
+ // 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 => 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 = {
+
+ def copyfile(file: SFile, bytewise: Boolean): Unit = {
+ if (bytewise) {
+ val in = file.inputStream()
+ val out = SFile(dest).outputStream()
+ val buffer = new Array[Byte](1024)
+ 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")
+}