aboutsummaryrefslogblamecommitdiff
path: root/compiler/test/dotty/tools/dotc/ParallelTesting.scala
blob: 711fc86a3fdee9660bff5dbc40009df70fbcb483 (plain) (tree)


































                                                                                                            






















                                                                                                                       




                                                                              





                                                                                                 


























































                                                                                                                                                                                   
 
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]) = {

    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
  }

  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`")
    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  ")

  }
}